1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | "use strict";
|
8 |
|
9 | const espree = require("espree");
|
10 | const estraverse = require("estraverse");
|
11 | const path = require("path");
|
12 | const fs = require("fs");
|
13 | const ini = require("ini-parser");
|
14 |
|
15 | var gModules = null;
|
16 | var gRootDir = null;
|
17 | var directoryManifests = new Map();
|
18 |
|
19 | const callExpressionDefinitions = [
|
20 | /^loader\.lazyGetter\(this, "(\w+)"/,
|
21 | /^loader\.lazyImporter\(this, "(\w+)"/,
|
22 | /^loader\.lazyServiceGetter\(this, "(\w+)"/,
|
23 | /^loader\.lazyRequireGetter\(this, "(\w+)"/,
|
24 | /^XPCOMUtils\.defineLazyGetter\(this, "(\w+)"/,
|
25 | /^XPCOMUtils\.defineLazyModuleGetter\(this, "(\w+)"/,
|
26 | /^ChromeUtils\.defineModuleGetter\(this, "(\w+)"/,
|
27 | /^XPCOMUtils\.defineLazyPreferenceGetter\(this, "(\w+)"/,
|
28 | /^XPCOMUtils\.defineLazyScriptGetter\(this, "(\w+)"/,
|
29 | /^XPCOMUtils\.defineLazyServiceGetter\(this, "(\w+)"/,
|
30 | /^XPCOMUtils\.defineConstant\(this, "(\w+)"/,
|
31 | /^DevToolsUtils\.defineLazyModuleGetter\(this, "(\w+)"/,
|
32 | /^DevToolsUtils\.defineLazyGetter\(this, "(\w+)"/,
|
33 | /^Object\.defineProperty\(this, "(\w+)"/,
|
34 | /^Reflect\.defineProperty\(this, "(\w+)"/,
|
35 | /^this\.__defineGetter__\("(\w+)"/
|
36 | ];
|
37 |
|
38 | const callExpressionMultiDefinitions = [
|
39 | "XPCOMUtils.defineLazyModuleGetters(this,",
|
40 | "XPCOMUtils.defineLazyServiceGetters(this,"
|
41 | ];
|
42 |
|
43 | const imports = [
|
44 | /^(?:Cu|Components\.utils|ChromeUtils)\.import\(".*\/((.*?)\.jsm?)"(?:, this)?\)/
|
45 | ];
|
46 |
|
47 | const workerImportFilenameMatch = /(.*\/)*(.*?\.jsm?)/;
|
48 |
|
49 | module.exports = {
|
50 | get modulesGlobalData() {
|
51 | if (!gModules) {
|
52 | if (this.isMozillaCentralBased()) {
|
53 | gModules = require(path.join(this.rootDir, "tools", "lint", "eslint", "modules.json"));
|
54 | } else {
|
55 | gModules = require("./modules.json");
|
56 | }
|
57 | }
|
58 |
|
59 | return gModules;
|
60 | },
|
61 |
|
62 | |
63 |
|
64 |
|
65 |
|
66 |
|
67 |
|
68 |
|
69 |
|
70 |
|
71 |
|
72 | getAST(sourceText) {
|
73 |
|
74 |
|
75 | var config = this.getPermissiveConfig();
|
76 |
|
77 | return espree.parse(sourceText, config);
|
78 | },
|
79 |
|
80 | |
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 | getASTSource(node, context) {
|
90 | switch (node.type) {
|
91 | case "MemberExpression":
|
92 | if (node.computed) {
|
93 | let filename = context && context.getFilename();
|
94 | throw new Error(`getASTSource unsupported computed MemberExpression in ${filename}`);
|
95 | }
|
96 | return this.getASTSource(node.object) + "." +
|
97 | this.getASTSource(node.property);
|
98 | case "ThisExpression":
|
99 | return "this";
|
100 | case "Identifier":
|
101 | return node.name;
|
102 | case "Literal":
|
103 | return JSON.stringify(node.value);
|
104 | case "CallExpression":
|
105 | var args = node.arguments.map(a => this.getASTSource(a)).join(", ");
|
106 | return this.getASTSource(node.callee) + "(" + args + ")";
|
107 | case "ObjectExpression":
|
108 | return "{}";
|
109 | case "ExpressionStatement":
|
110 | return this.getASTSource(node.expression) + ";";
|
111 | case "FunctionExpression":
|
112 | return "function() {}";
|
113 | case "ArrayExpression":
|
114 | return "[" + node.elements.map(this.getASTSource, this).join(",") + "]";
|
115 | case "ArrowFunctionExpression":
|
116 | return "() => {}";
|
117 | case "AssignmentExpression":
|
118 | return this.getASTSource(node.left) + " = " +
|
119 | this.getASTSource(node.right);
|
120 | default:
|
121 | throw new Error("getASTSource unsupported node type: " + node.type);
|
122 | }
|
123 | },
|
124 |
|
125 | |
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 | walkAST(ast, listener) {
|
137 | let parents = [];
|
138 |
|
139 | estraverse.traverse(ast, {
|
140 | enter(node, parent) {
|
141 | listener(node.type, node, parents);
|
142 |
|
143 | parents.push(node);
|
144 | },
|
145 |
|
146 | leave(node, parent) {
|
147 | if (parents.length == 0) {
|
148 | throw new Error("Left more nodes than entered.");
|
149 | }
|
150 | parents.pop();
|
151 | }
|
152 | });
|
153 | if (parents.length) {
|
154 | throw new Error("Entered more nodes than left.");
|
155 | }
|
156 | },
|
157 |
|
158 | |
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 | convertWorkerExpressionToGlobals(node, isGlobal, dirname) {
|
175 | var getGlobalsForFile = require("./globals").getGlobalsForFile;
|
176 |
|
177 | let globalModules = this.modulesGlobalData;
|
178 |
|
179 | let results = [];
|
180 | let expr = node.expression;
|
181 |
|
182 | if (node.expression.type === "CallExpression" &&
|
183 | expr.callee &&
|
184 | expr.callee.type === "Identifier" &&
|
185 | expr.callee.name === "importScripts") {
|
186 | for (var arg of expr.arguments) {
|
187 | var match = arg.value && arg.value.match(workerImportFilenameMatch);
|
188 | if (match) {
|
189 | if (!match[1]) {
|
190 | let filePath = path.resolve(dirname, match[2]);
|
191 | if (fs.existsSync(filePath)) {
|
192 | let additionalGlobals = getGlobalsForFile(filePath);
|
193 | results = results.concat(additionalGlobals);
|
194 | }
|
195 | } else if (match[2] in globalModules) {
|
196 | results = results.concat(globalModules[match[2]].map(name => {
|
197 | return { name, writable: true };
|
198 | }));
|
199 | }
|
200 | }
|
201 | }
|
202 | }
|
203 |
|
204 | return results;
|
205 | },
|
206 |
|
207 | |
208 |
|
209 |
|
210 |
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 | convertThisAssignmentExpressionToGlobals(node, isGlobal) {
|
224 | if (isGlobal &&
|
225 | node.expression.left &&
|
226 | node.expression.left.object &&
|
227 | node.expression.left.object.type === "ThisExpression" &&
|
228 | node.expression.left.property &&
|
229 | node.expression.left.property.type === "Identifier") {
|
230 | return [{ name: node.expression.left.property.name, writable: true }];
|
231 | }
|
232 | return [];
|
233 | },
|
234 |
|
235 | |
236 |
|
237 |
|
238 |
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 | convertCallExpressionToGlobals(node, isGlobal) {
|
252 | let express = node.expression;
|
253 | if (express.type === "CallExpression" &&
|
254 | express.callee.type === "MemberExpression" &&
|
255 | express.callee.object &&
|
256 | express.callee.object.type === "Identifier" &&
|
257 | express.arguments.length === 1 &&
|
258 | express.arguments[0].type === "ArrayExpression" &&
|
259 | express.callee.property.type === "Identifier" &&
|
260 | express.callee.property.name === "importGlobalProperties") {
|
261 | return express.arguments[0].elements.map(literal => {
|
262 | return {
|
263 | name: literal.value,
|
264 | writable: false
|
265 | };
|
266 | });
|
267 | }
|
268 |
|
269 | let source;
|
270 | try {
|
271 | source = this.getASTSource(node);
|
272 | } catch (e) {
|
273 | return [];
|
274 | }
|
275 |
|
276 | for (let reg of imports) {
|
277 | let match = source.match(reg);
|
278 | if (match) {
|
279 |
|
280 | if (node.expression.arguments.length > 1 && !isGlobal) {
|
281 | return [];
|
282 | }
|
283 |
|
284 | let globalModules = this.modulesGlobalData;
|
285 |
|
286 | if (match[1] in globalModules) {
|
287 | return globalModules[match[1]].map(name => ({ name, writable: true }));
|
288 | }
|
289 |
|
290 | return [{ name: match[2], writable: true }];
|
291 | }
|
292 | }
|
293 |
|
294 |
|
295 |
|
296 | if (!isGlobal) {
|
297 | return [];
|
298 | }
|
299 |
|
300 | for (let reg of callExpressionDefinitions) {
|
301 | let match = source.match(reg);
|
302 | if (match) {
|
303 | return [{ name: match[1], writable: true }];
|
304 | }
|
305 | }
|
306 |
|
307 | if (callExpressionMultiDefinitions.some(expr => source.startsWith(expr)) &&
|
308 | node.expression.arguments[1] &&
|
309 | node.expression.arguments[1].type === "ObjectExpression") {
|
310 | return node.expression.arguments[1].properties
|
311 | .map(p => ({ name: p.type === "Property" && p.key.name, writable: true }))
|
312 | .filter(g => g.name);
|
313 | }
|
314 |
|
315 | if (node.expression.callee.type == "MemberExpression" &&
|
316 | node.expression.callee.property.type == "Identifier" &&
|
317 | node.expression.callee.property.name == "defineLazyScriptGetter") {
|
318 |
|
319 |
|
320 | return node.expression.arguments[1].elements.map(n => ({ name: n.value, writable: true }));
|
321 | }
|
322 |
|
323 | return [];
|
324 | },
|
325 |
|
326 | |
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 |
|
337 | addVarToScope(name, scope, writable) {
|
338 | scope.__defineGeneric(name, scope.set, scope.variables, null, null);
|
339 |
|
340 | let variable = scope.set.get(name);
|
341 | variable.eslintExplicitGlobal = false;
|
342 | variable.writeable = writable;
|
343 |
|
344 |
|
345 | while (scope.type != "global") {
|
346 | scope = scope.upper;
|
347 | }
|
348 |
|
349 |
|
350 | scope.through = scope.through.filter(function(reference) {
|
351 | if (reference.identifier.name != name) {
|
352 | return true;
|
353 | }
|
354 |
|
355 |
|
356 |
|
357 | reference.resolved = variable;
|
358 | variable.references.push(reference);
|
359 | return false;
|
360 | });
|
361 | },
|
362 |
|
363 | |
364 |
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 | addGlobals(globalVars, scope) {
|
372 | globalVars.forEach(v => this.addVarToScope(v.name, scope, v.writable));
|
373 | },
|
374 |
|
375 | |
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 | getPermissiveConfig() {
|
383 | return {
|
384 | range: true,
|
385 | loc: true,
|
386 | comment: true,
|
387 | attachComment: true,
|
388 | ecmaVersion: 9,
|
389 | sourceType: "script"
|
390 | };
|
391 | },
|
392 |
|
393 | |
394 |
|
395 |
|
396 |
|
397 |
|
398 |
|
399 |
|
400 |
|
401 |
|
402 | getIsFunctionNode(node) {
|
403 | switch (node.type) {
|
404 | case "ArrowFunctionExpression":
|
405 | case "FunctionDeclaration":
|
406 | case "FunctionExpression":
|
407 | return true;
|
408 | }
|
409 | return false;
|
410 | },
|
411 |
|
412 | |
413 |
|
414 |
|
415 |
|
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 | getIsGlobalScope(ancestors) {
|
422 | for (let parent of ancestors) {
|
423 | if (this.getIsFunctionNode(parent)) {
|
424 | return false;
|
425 | }
|
426 | }
|
427 | return true;
|
428 | },
|
429 |
|
430 | |
431 |
|
432 |
|
433 |
|
434 |
|
435 |
|
436 |
|
437 |
|
438 |
|
439 |
|
440 | getIsHeadFile(scope) {
|
441 | var pathAndFilename = this.cleanUpPath(scope.getFilename());
|
442 |
|
443 | return /.*[\\/]head(_.+)?\.js$/.test(pathAndFilename);
|
444 | },
|
445 |
|
446 | |
447 |
|
448 |
|
449 |
|
450 |
|
451 |
|
452 |
|
453 |
|
454 |
|
455 |
|
456 | getTestHeadFiles(scope) {
|
457 | if (!this.getIsTest(scope)) {
|
458 | return [];
|
459 | }
|
460 |
|
461 | let filepath = this.cleanUpPath(scope.getFilename());
|
462 | let dir = path.dirname(filepath);
|
463 |
|
464 | let names =
|
465 | fs.readdirSync(dir)
|
466 | .filter(name => (name.startsWith("head") ||
|
467 | name.startsWith("xpcshell-head")) && name.endsWith(".js"))
|
468 | .map(name => path.join(dir, name));
|
469 | return names;
|
470 | },
|
471 |
|
472 | |
473 |
|
474 |
|
475 |
|
476 |
|
477 |
|
478 |
|
479 |
|
480 |
|
481 | getManifestsForDirectory(dir) {
|
482 | if (directoryManifests.has(dir)) {
|
483 | return directoryManifests.get(dir);
|
484 | }
|
485 |
|
486 | let manifests = [];
|
487 | let names = [];
|
488 | try {
|
489 | names = fs.readdirSync(dir);
|
490 | } catch (err) {
|
491 |
|
492 | if (err.code !== "ENOENT") {
|
493 | throw err;
|
494 | }
|
495 | }
|
496 |
|
497 | for (let name of names) {
|
498 | if (!name.endsWith(".ini")) {
|
499 | continue;
|
500 | }
|
501 |
|
502 | try {
|
503 | let manifest = ini.parse(fs.readFileSync(path.join(dir, name), "utf8"));
|
504 |
|
505 | manifests.push({
|
506 | file: path.join(dir, name),
|
507 | manifest
|
508 | });
|
509 | } catch (e) {
|
510 | }
|
511 | }
|
512 |
|
513 | directoryManifests.set(dir, manifests);
|
514 | return manifests;
|
515 | },
|
516 |
|
517 | |
518 |
|
519 |
|
520 |
|
521 |
|
522 |
|
523 |
|
524 |
|
525 |
|
526 |
|
527 | getTestManifest(scope) {
|
528 | let filepath = this.cleanUpPath(scope.getFilename());
|
529 |
|
530 | let dir = path.dirname(filepath);
|
531 | let filename = path.basename(filepath);
|
532 |
|
533 | for (let manifest of this.getManifestsForDirectory(dir)) {
|
534 | if (filename in manifest.manifest) {
|
535 | return manifest.file;
|
536 | }
|
537 | }
|
538 |
|
539 | return null;
|
540 | },
|
541 |
|
542 | |
543 |
|
544 |
|
545 |
|
546 |
|
547 |
|
548 |
|
549 |
|
550 |
|
551 |
|
552 | getIsTest(scope) {
|
553 |
|
554 | let manifest = this.getTestManifest(scope);
|
555 | if (manifest) {
|
556 | return true;
|
557 | }
|
558 |
|
559 | return !!this.getTestType(scope);
|
560 | },
|
561 |
|
562 | |
563 |
|
564 |
|
565 |
|
566 |
|
567 |
|
568 |
|
569 |
|
570 |
|
571 |
|
572 | getTestType(scope) {
|
573 | let testTypes = ["browser", "xpcshell", "chrome", "mochitest", "a11y"];
|
574 | let manifest = this.getTestManifest(scope);
|
575 | if (manifest) {
|
576 | let name = path.basename(manifest);
|
577 | for (let testType of testTypes) {
|
578 | if (name.startsWith(testType)) {
|
579 | return testType;
|
580 | }
|
581 | }
|
582 | }
|
583 |
|
584 | let filepath = this.cleanUpPath(scope.getFilename());
|
585 | let filename = path.basename(filepath);
|
586 |
|
587 | if (filename.startsWith("browser_")) {
|
588 | return "browser";
|
589 | }
|
590 |
|
591 | if (filename.startsWith("test_")) {
|
592 | let parent = path.basename(path.dirname(filepath));
|
593 | for (let testType of testTypes) {
|
594 | if (parent.startsWith(testType)) {
|
595 | return testType;
|
596 | }
|
597 | }
|
598 |
|
599 |
|
600 | return "unknown";
|
601 | }
|
602 |
|
603 |
|
604 | return null;
|
605 | },
|
606 |
|
607 | getIsWorker(filePath) {
|
608 | let filename = path.basename(this.cleanUpPath(filePath)).toLowerCase();
|
609 |
|
610 | return filename.includes("worker");
|
611 | },
|
612 |
|
613 | |
614 |
|
615 |
|
616 |
|
617 |
|
618 |
|
619 | get rootDir() {
|
620 | if (!gRootDir) {
|
621 | function searchUpForIgnore(dirName, filename) {
|
622 | let parsed = path.parse(dirName);
|
623 | while (parsed.root !== dirName) {
|
624 | if (fs.existsSync(path.join(dirName, filename))) {
|
625 | return dirName;
|
626 | }
|
627 |
|
628 | dirName = parsed.dir;
|
629 | parsed = path.parse(dirName);
|
630 | }
|
631 | return null;
|
632 | }
|
633 |
|
634 | let possibleRoot = searchUpForIgnore(path.dirname(module.filename), ".eslintignore");
|
635 | if (!possibleRoot) {
|
636 | possibleRoot = searchUpForIgnore(path.resolve(), ".eslintignore");
|
637 | }
|
638 | if (!possibleRoot) {
|
639 | possibleRoot = searchUpForIgnore(path.resolve(), "package.json");
|
640 | }
|
641 | if (!possibleRoot) {
|
642 |
|
643 |
|
644 |
|
645 | possibleRoot = process.cwd();
|
646 | }
|
647 |
|
648 | gRootDir = possibleRoot;
|
649 | }
|
650 |
|
651 | return gRootDir;
|
652 | },
|
653 |
|
654 | |
655 |
|
656 |
|
657 |
|
658 |
|
659 |
|
660 |
|
661 |
|
662 |
|
663 |
|
664 | getAbsoluteFilePath(context) {
|
665 | var fileName = this.cleanUpPath(context.getFilename());
|
666 | var cwd = process.cwd();
|
667 |
|
668 | if (path.isAbsolute(fileName)) {
|
669 |
|
670 |
|
671 |
|
672 | return fileName;
|
673 | } else if (path.basename(fileName) == fileName) {
|
674 |
|
675 |
|
676 | return path.join(cwd, fileName);
|
677 | }
|
678 |
|
679 |
|
680 |
|
681 | var dirName = path.dirname(fileName);
|
682 | return cwd.slice(0, cwd.length - dirName.length) + fileName;
|
683 |
|
684 | },
|
685 |
|
686 | |
687 |
|
688 |
|
689 |
|
690 |
|
691 | cleanUpPath(pathName) {
|
692 | return pathName.replace(/^"/, "").replace(/"$/, "");
|
693 | },
|
694 |
|
695 | get globalScriptsPath() {
|
696 | return path.join(this.rootDir, "browser",
|
697 | "base", "content", "global-scripts.inc");
|
698 | },
|
699 |
|
700 | isMozillaCentralBased() {
|
701 | return fs.existsSync(this.globalScriptsPath);
|
702 | },
|
703 |
|
704 | getSavedEnvironmentItems(environment) {
|
705 | return require("./environments/saved-globals.json").environments[environment];
|
706 | },
|
707 |
|
708 | getSavedRuleData(rule) {
|
709 | return require("./rules/saved-rules-data.json").rulesData[rule];
|
710 | }
|
711 | };
|