UNPKG

8.83 kBJavaScriptView Raw
1"use strict";
2var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 return new (P || (P = Promise))(function (resolve, reject) {
4 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6 function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
7 step((generator = generator.apply(thisArg, _arguments || [])).next());
8 });
9};
10var __importDefault = (this && this.__importDefault) || function (mod) {
11 return (mod && mod.__esModule) ? mod : { "default": mod };
12};
13var __importStar = (this && this.__importStar) || function (mod) {
14 if (mod && mod.__esModule) return mod;
15 var result = {};
16 if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
17 result["default"] = mod;
18 return result;
19};
20Object.defineProperty(exports, "__esModule", { value: true });
21const broccoli_plugin_1 = __importDefault(require("broccoli-plugin"));
22const walk_sync_1 = __importDefault(require("walk-sync"));
23const fs_extra_1 = require("fs-extra");
24const fs_tree_diff_1 = __importDefault(require("fs-tree-diff"));
25const debug_1 = __importDefault(require("debug"));
26const path_1 = require("path");
27const lodash_1 = require("lodash");
28const symlink_or_copy_1 = __importDefault(require("symlink-or-copy"));
29const traverse_1 = __importDefault(require("@babel/traverse"));
30debug_1.default.formatters.m = (modules) => {
31 return JSON.stringify(modules.map(m => ({
32 specifier: m.specifier,
33 path: m.path,
34 isDynamic: m.isDynamic,
35 package: m.package.name
36 })), null, 2);
37};
38const debug = debug_1.default('ember-auto-import:analyzer');
39/*
40 Analyzer discovers and maintains info on all the module imports that
41 appear in a broccoli tree.
42*/
43class Analyzer extends broccoli_plugin_1.default {
44 constructor(inputTree, pack) {
45 super([inputTree], {
46 annotation: 'ember-auto-import-analyzer',
47 persistentOutput: true
48 });
49 this.pack = pack;
50 this.previousTree = new fs_tree_diff_1.default();
51 this.modules = [];
52 this.paths = new Map();
53 }
54 setupParser() {
55 return __awaiter(this, void 0, void 0, function* () {
56 if (this.parse) {
57 return;
58 }
59 switch (this.pack.babelMajorVersion) {
60 case 6:
61 this.parse = yield babel6Parser(this.pack.babelOptions);
62 break;
63 case 7:
64 this.parse = yield babel7Parser(this.pack.babelOptions);
65 break;
66 default:
67 throw new Error(`don't know how to setup a parser for Babel version ${this.pack.babelMajorVersion} (used by ${this.pack.name})`);
68 }
69 });
70 }
71 get imports() {
72 if (!this.modules) {
73 this.modules = lodash_1.flatten([...this.paths.values()]);
74 debug('imports %m', this.modules);
75 }
76 return this.modules;
77 }
78 build() {
79 return __awaiter(this, void 0, void 0, function* () {
80 yield this.setupParser();
81 this.getPatchset().forEach(([operation, relativePath]) => {
82 let outputPath = path_1.join(this.outputPath, relativePath);
83 switch (operation) {
84 case 'unlink':
85 if (this.matchesExtension(relativePath)) {
86 this.removeImports(relativePath);
87 }
88 fs_extra_1.unlinkSync(outputPath);
89 break;
90 case 'rmdir':
91 fs_extra_1.rmdirSync(outputPath);
92 break;
93 case 'mkdir':
94 fs_extra_1.mkdirSync(outputPath);
95 break;
96 case 'change':
97 fs_extra_1.removeSync(outputPath);
98 // deliberate fallthrough
99 case 'create': {
100 let absoluteInputPath = path_1.join(this.inputPaths[0], relativePath);
101 if (this.matchesExtension(relativePath)) {
102 this.updateImports(relativePath, fs_extra_1.readFileSync(absoluteInputPath, 'utf8'));
103 }
104 symlink_or_copy_1.default.sync(absoluteInputPath, outputPath);
105 }
106 }
107 });
108 });
109 }
110 getPatchset() {
111 let input = walk_sync_1.default.entries(this.inputPaths[0]);
112 let previous = this.previousTree;
113 let next = (this.previousTree = fs_tree_diff_1.default.fromEntries(input));
114 return previous.calculatePatch(next);
115 }
116 matchesExtension(path) {
117 return this.pack.fileExtensions.includes(path_1.extname(path).slice(1));
118 }
119 removeImports(relativePath) {
120 debug(`removing imports for ${relativePath}`);
121 let imports = this.paths.get(relativePath);
122 if (imports) {
123 if (imports.length > 0) {
124 this.modules = null; // invalidates cache
125 }
126 this.paths.delete(relativePath);
127 }
128 }
129 updateImports(relativePath, source) {
130 debug(`updating imports for ${relativePath}, ${source.length}`);
131 let newImports = this.parseImports(relativePath, source);
132 if (!lodash_1.isEqual(this.paths.get(relativePath), newImports)) {
133 this.paths.set(relativePath, newImports);
134 this.modules = null; // invalidates cache
135 }
136 }
137 parseImports(relativePath, source) {
138 let ast;
139 try {
140 ast = this.parse(source);
141 }
142 catch (err) {
143 if (err.name !== 'SyntaxError') {
144 throw err;
145 }
146 debug('Ignoring an unparseable file');
147 }
148 let imports = [];
149 if (!ast) {
150 return imports;
151 }
152 traverse_1.default(ast, {
153 CallExpression: (path) => {
154 if (path.node.callee.type === 'Import') {
155 // it's a syntax error to have anything other than exactly one
156 // argument, so we can just assume this exists
157 let argument = path.node.arguments[0];
158 if (argument.type !== 'StringLiteral') {
159 throw new Error('ember-auto-import only supports dynamic import() with a string literal argument.');
160 }
161 imports.push({
162 isDynamic: true,
163 specifier: argument.value,
164 path: relativePath,
165 package: this.pack
166 });
167 }
168 },
169 ImportDeclaration: (path) => {
170 imports.push({
171 isDynamic: false,
172 specifier: path.node.source.value,
173 path: relativePath,
174 package: this.pack
175 });
176 },
177 ExportNamedDeclaration: (path) => {
178 if (path.node.source) {
179 imports.push({
180 isDynamic: false,
181 specifier: path.node.source.value,
182 path: relativePath,
183 package: this.pack
184 });
185 }
186 }
187 });
188 return imports;
189 }
190}
191exports.default = Analyzer;
192function babel6Parser(babelOptions) {
193 return __awaiter(this, void 0, void 0, function* () {
194 let core = Promise.resolve().then(() => __importStar(require('babel-core')));
195 let babylon = Promise.resolve().then(() => __importStar(require('babylon')));
196 // missing upstream types (or we are using private API, because babel 6 didn't
197 // have a good way to construct a parser directly from the general babel
198 // options)
199 const { Pipeline, File } = (yield core);
200 const { parse } = yield babylon;
201 let p = new Pipeline();
202 let f = new File(babelOptions, p);
203 let options = f.parserOpts;
204 return function (source) {
205 return parse(source, options);
206 };
207 });
208}
209function babel7Parser(babelOptions) {
210 return __awaiter(this, void 0, void 0, function* () {
211 let core = Promise.resolve().then(() => __importStar(require('@babel/core')));
212 const { parseSync } = yield core;
213 return function (source) {
214 return parseSync(source, babelOptions);
215 };
216 });
217}
218//# sourceMappingURL=analyzer.js.map
\No newline at end of file