UNPKG

6.74 kBJavaScriptView Raw
1/*
2 * Copyright (C) 2016 salesforce.com, inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17"use strict";
18
19var glob = require('glob');
20var path = require('path');
21var fs = require('fs');
22var cli = require('heroku-cli-util');
23var linter = require('eslint').linter;
24var defaultConfig = require('./config');
25var defaultStyle = require('./code-style-rules');
26var objectAssign = require('object-assign');
27var formatter = require('eslint-friendly-formatter');
28
29var SINGLETON_FILE_REGEXP = /(Controller|Renderer|Helper|Provider|Test|Model)\.js$/;
30
31function noop() {}
32
33// this computes the first position after all the comments (multi-line and single-line)
34// from the top of the code
35function afterCommentsPosition(code) {
36 var position = 0;
37 var match;
38 do {
39 // /\*.*?\*/
40 match = code.match(/^(\s*(\/\*([\s\S]*?)\*\/)?\s*)/);
41 if (!match || !match[1]) {
42 match = code.match(/^(\s*\/\/.*\s*)/);
43 }
44 if (match && match[1]) {
45 position += match[1].length;
46 code = code.slice(match[1].length);
47 }
48 } while (match);
49 return position;
50}
51
52function processSingletonCode(code) {
53 // transform `({...})` into `"use strict"; exports = ({...});`
54 var pos = afterCommentsPosition(code);
55 if (code.charAt(pos) === '(') {
56 code = code.slice(0, pos) + '"use strict"; exports = ' + code.slice(pos);
57 pos = code.lastIndexOf(')') + 1;
58 if (code.charAt(pos) !== ';') {
59 code = code.slice(0, pos) + ';' + code.slice(pos);
60 }
61 }
62 return code;
63}
64
65function processFunctionCode(code) {
66 // transform `function () {}` into `"use strict"; exports = function () {};`
67 var pos = afterCommentsPosition(code);
68 if (code.indexOf('function', pos) === pos) {
69 code = code.slice(0, pos) + '"use strict"; exports = ' + code.slice(pos);
70 pos = code.lastIndexOf('}') + 1;
71 if (code.charAt(pos) !== ';') {
72 code = code.slice(0, pos) + ';' + code.slice(pos);
73 }
74 }
75 return code;
76}
77
78function process(src, config, options) {
79 var messages = linter.verify(src, config, {
80 allowInlineConfig: true, // TODO: internal code should be linted with this set to false
81 quiet: true
82 });
83
84 if (!options.verbose) {
85 messages = messages.filter(function (msg) {
86 return msg.severity > 1;
87 });
88 }
89
90 return messages;
91}
92
93module.exports = function (cwd, opts, context) {
94 // No log, debug or warn functions if --json option is passed
95 var log = opts.json ? noop : cli.log;
96 var debug = opts.json ? noop : cli.debug;
97 var warn = opts.json ? noop : cli.warn;
98
99 var ignore = [
100 // these are the things that we know for sure we never want to inspect
101 '**/node_modules/**',
102 '**/jsdoc/**',
103 '**/htdocs/**',
104 '**/invalidTest/**',
105 '**/purposelyInvalid/**',
106 '**/invalidTestData/**',
107 '**/validationTest/**',
108 '**/lintTest/**',
109 '**/target/**',
110 '**/parseError/**',
111 '**/*.junk.js',
112 '**/*_mock?.js'
113 ].concat(opts.ignore ? [opts.ignore] : []);
114
115 var globOptions = {
116 silent: true,
117 cwd: cwd,
118 nodir: true,
119 realpath: true,
120 ignore: ignore
121 };
122
123 var patterns = [opts.files || '**/*.js'];
124
125 var config = {};
126 objectAssign(config, defaultConfig);
127 config.rules = objectAssign({}, defaultConfig.rules);
128
129 if (opts.config) {
130 log('Applying custom rules from ' + opts.config);
131 var customStyle = require(path.join(context.cwd, opts.config));
132 if (customStyle && customStyle.rules) {
133 Object.keys(customStyle.rules).forEach(function (name) {
134 if (defaultStyle.rules.hasOwnProperty(name)) {
135 config.rules[name] = customStyle.rules[name];
136 debug(' -> Rule: ' + name + ' is now set to ' + JSON.stringify(config.rules[name]));
137 } else {
138 debug(' -> Ignoring non-style rule: ' + name);
139 }
140 });
141 }
142 }
143
144 // Using local debug function for non-json text
145 debug('Search for "' + patterns.join('" or "') + '" in folder "' + cwd + '"');
146 debug(' -> Ignoring: ' + ignore.join(','));
147
148 var files = [];
149 // for blt-like structures we look for aura structures
150 // and we search from there to narrow down the search.
151 var folders = glob.sync('**/*.{app,cmp,lib}', globOptions);
152 folders = folders.map(function (v) {
153 return path.dirname(v);
154 });
155 folders = folders.filter(function (v, i) {
156 return folders.indexOf(v) === i;
157 });
158
159 folders.forEach(function (folder) {
160 globOptions.cwd = folder;
161 patterns.forEach(function (pattern) {
162 files = files.concat(glob.sync(pattern, globOptions));
163 });
164 });
165
166 // deduping...
167 files = files.filter(function (v, i) {
168 return files.indexOf(v) === i;
169 });
170
171 if (files.length) {
172 log('Found ' + files.length + ' matching files.\n----------------');
173
174 var output = [];
175 files.forEach(function (file) {
176 var source = fs.readFileSync(file, 'utf8');
177
178 // in some cases, we need to massage the source before linting it
179 if (SINGLETON_FILE_REGEXP.test(file)) {
180 source = processSingletonCode(source);
181 } else {
182 source = processFunctionCode(source);
183 }
184
185 var messages = process(source, config, opts);
186 if (messages) {
187 if (opts.json) {
188 output.push({
189 file: file,
190 result: messages
191 })
192 } else {
193 log('FILE: ' + file + ':');
194 log(formatter([{
195 messages: messages,
196 filePath: '<input>'
197 }]).replace(/<input>/g, 'Line'));
198 }
199 }
200 });
201
202 // printout JSON string to STDOUT
203 if (opts.json) {
204 cli.log(JSON.stringify(output, null, 4));
205 }
206 } else {
207 warn('Did not find matching files.');
208 }
209
210}