1 | "use strict";
|
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
3 | if (k2 === undefined) k2 = k;
|
4 | var desc = Object.getOwnPropertyDescriptor(m, k);
|
5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
6 | desc = { enumerable: true, get: function() { return m[k]; } };
|
7 | }
|
8 | Object.defineProperty(o, k2, desc);
|
9 | }) : (function(o, m, k, k2) {
|
10 | if (k2 === undefined) k2 = k;
|
11 | o[k2] = m[k];
|
12 | }));
|
13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
14 | Object.defineProperty(o, "default", { enumerable: true, value: v });
|
15 | }) : function(o, v) {
|
16 | o["default"] = v;
|
17 | });
|
18 | var __importStar = (this && this.__importStar) || function (mod) {
|
19 | if (mod && mod.__esModule) return mod;
|
20 | var result = {};
|
21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
22 | __setModuleDefault(result, mod);
|
23 | return result;
|
24 | };
|
25 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
26 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
27 | };
|
28 | Object.defineProperty(exports, "__esModule", { value: true });
|
29 | exports.Parser = void 0;
|
30 | const doctrine_1 = __importDefault(require("@teppeis/doctrine"));
|
31 | const espree = __importStar(require("espree"));
|
32 | const estraverse_fb_1 = require("estraverse-fb");
|
33 | const lodash_difference_1 = __importDefault(require("lodash.difference"));
|
34 | const def = __importStar(require("./default"));
|
35 | const visitor_1 = require("./visitor");
|
36 | const tagsHavingType = new Set([
|
37 | "const",
|
38 | "define",
|
39 | "enum",
|
40 | "extends",
|
41 | "implements",
|
42 | "param",
|
43 | "private",
|
44 | "protected",
|
45 | "public",
|
46 | "return",
|
47 | "this",
|
48 | "type",
|
49 | "typedef",
|
50 | ]);
|
51 | class Parser {
|
52 | constructor(opt_options) {
|
53 | this.minLine_ = Number.MAX_VALUE;
|
54 | this.maxLine_ = 0;
|
55 | const options = (this.options = opt_options || {});
|
56 | if (options.provideRoots) {
|
57 | this.provideRoots_ = new Set(options.provideRoots);
|
58 | }
|
59 | else {
|
60 | this.provideRoots_ = def.getRoots();
|
61 | }
|
62 | this.replaceMap_ = def.getReplaceMap();
|
63 | if (options.replaceMap) {
|
64 | options.replaceMap.forEach((value, key) => {
|
65 | this.replaceMap_.set(key, value);
|
66 | });
|
67 | }
|
68 | this.providedNamespaces_ = new Set();
|
69 | if (options.providedNamespace) {
|
70 | options.providedNamespace.forEach((method) => {
|
71 | this.providedNamespaces_.add(method);
|
72 | });
|
73 | }
|
74 | if (options.ignoreProvides != null) {
|
75 | this.ignoreProvides_ = options.ignoreProvides;
|
76 | }
|
77 | else {
|
78 | this.ignoreProvides_ = false;
|
79 | }
|
80 | this.ignorePackages_ = def.getIgnorePackages();
|
81 | }
|
82 | parse(src) {
|
83 | const options = {
|
84 | loc: true,
|
85 | comment: true,
|
86 | ecmaVersion: 2019,
|
87 | sourceType: "script",
|
88 | ecmaFeatures: {
|
89 | jsx: true,
|
90 | },
|
91 | ...this.options.parserOptions,
|
92 | };
|
93 | const program = espree.parse(src, options);
|
94 | const { comments } = program;
|
95 |
|
96 | if (!comments) {
|
97 | throw new Error("Enable `comment` option for espree parser");
|
98 | }
|
99 | return this.parseAst(program, comments);
|
100 | }
|
101 | parseAst(program, comments) {
|
102 | const parsed = this.traverseProgram_(program);
|
103 | const provided = this.extractProvided_(parsed);
|
104 | const required = this.extractRequired_(parsed);
|
105 | const requireTyped = this.extractRequireTyped_(parsed);
|
106 | const forwardDeclared = this.extractForwardDeclared_(parsed);
|
107 | const ignored = this.extractIgnored_(parsed, comments);
|
108 | const toProvide = this.ignoreProvides_
|
109 | ? provided
|
110 | : this.extractToProvide_(parsed, comments);
|
111 | const fromJsDoc = this.extractToRequireTypeFromJsDoc_(comments);
|
112 | const toRequire = this.extractToRequire_(parsed, toProvide, comments, fromJsDoc.toRequire);
|
113 | const toRequireType = (0, lodash_difference_1.default)(fromJsDoc.toRequireType, toProvide, toRequire);
|
114 | return {
|
115 | provided,
|
116 | required,
|
117 | requireTyped,
|
118 | forwardDeclared,
|
119 | toProvide,
|
120 | toRequire,
|
121 | toRequireType,
|
122 | toForwardDeclare: [],
|
123 | ignoredProvide: ignored.provide,
|
124 | ignoredRequire: ignored.require,
|
125 | ignoredRequireType: ignored.requireType,
|
126 | ignoredForwardDeclare: ignored.forwardDeclare,
|
127 |
|
128 | provideStart: this.minLine_,
|
129 |
|
130 | provideEnd: this.maxLine_,
|
131 | };
|
132 | }
|
133 | extractToProvide_(parsed, comments) {
|
134 | const suppressComments = this.getSuppressProvideComments_(comments);
|
135 | return parsed
|
136 | .filter((namespace) => this.suppressFilter_(suppressComments, namespace))
|
137 | .map((namespace) => this.toProvideMapper_(comments, namespace))
|
138 | .filter(isDefAndNotNull)
|
139 | .filter((provide) => this.provideRootFilter_(provide))
|
140 | .sort()
|
141 | .reduce(uniq, []);
|
142 | }
|
143 | |
144 |
|
145 |
|
146 |
|
147 |
|
148 | hasTypedefAnnotation_(node, comments) {
|
149 | const { line } = getLoc(node).start;
|
150 | const jsDocComments = comments.filter((comment) => getLoc(comment).end.line === line - 1 &&
|
151 | isBlockComment(comment) &&
|
152 | /^\*/.test(comment.value));
|
153 | if (jsDocComments.length === 0) {
|
154 | return false;
|
155 | }
|
156 | return jsDocComments.every((comment) => {
|
157 | const jsdoc = doctrine_1.default.parse(`/*${comment.value}*/`, { unwrap: true });
|
158 | return (jsdoc.tags.some((tag) => tag.title === "typedef") &&
|
159 | !jsdoc.tags.some((tag) => tag.title === "private"));
|
160 | });
|
161 | }
|
162 | getSuppressProvideComments_(comments) {
|
163 | return comments.filter((comment) => isLineComment(comment) &&
|
164 | /^\s*fixclosure\s*:\s*suppressProvide\b/.test(comment.value));
|
165 | }
|
166 | getSuppressRequireComments_(comments) {
|
167 | return comments.filter((comment) => isLineComment(comment) &&
|
168 | /^\s*fixclosure\s*:\s*suppressRequire\b/.test(comment.value));
|
169 | }
|
170 | extractToRequire_(parsed, toProvide, comments, opt_required) {
|
171 | const additional = opt_required || [];
|
172 | const suppressComments = this.getSuppressRequireComments_(comments);
|
173 | const toRequire = parsed
|
174 | .filter((namespace) => this.toRequireFilter_(namespace))
|
175 | .filter((namespace) => this.suppressFilter_(suppressComments, namespace))
|
176 | .map((namespace) => this.toRequireMapper_(namespace))
|
177 | .concat(additional)
|
178 | .filter(isDefAndNotNull)
|
179 | .sort()
|
180 | .reduce(uniq, []);
|
181 | return (0, lodash_difference_1.default)(toRequire, toProvide);
|
182 | }
|
183 | extractToRequireTypeFromJsDoc_(comments) {
|
184 | const toRequire = [];
|
185 | const toRequireType = [];
|
186 | comments
|
187 | .filter((comment) =>
|
188 |
|
189 | isBlockComment(comment) && /^\*/.test(comment.value))
|
190 | .forEach((comment) => {
|
191 | const { tags } = doctrine_1.default.parse(`/*${comment.value}*/`, {
|
192 | unwrap: true,
|
193 | });
|
194 | tags
|
195 | .filter((tag) => tagsHavingType.has(tag.title) && tag.type)
|
196 |
|
197 | .map((tag) => this.extractType(tag.type))
|
198 | .forEach((names) => {
|
199 | toRequireType.push(...names);
|
200 | });
|
201 | tags
|
202 | .filter((tag) => (tag.title === "implements" || tag.title === "extends") &&
|
203 | tag.type)
|
204 |
|
205 | .map((tag) => this.extractType(tag.type))
|
206 | .forEach((names) => {
|
207 | toRequire.push(...names);
|
208 | });
|
209 | });
|
210 | return {
|
211 | toRequire: toRequire
|
212 | .filter((name) => this.isProvidedNamespace_(name))
|
213 | .sort()
|
214 | .reduce(uniq, []),
|
215 | toRequireType: toRequireType
|
216 | .map((name) => this.getRequiredPackageName_(name))
|
217 | .filter(isDefAndNotNull)
|
218 | .sort()
|
219 | .reduce(uniq, []),
|
220 | };
|
221 | }
|
222 | extractType(type) {
|
223 | if (!type) {
|
224 | return [];
|
225 | }
|
226 | let result;
|
227 | switch (type.type) {
|
228 | case "NameExpression":
|
229 | return [type.name];
|
230 | case "NullableType":
|
231 | case "NonNullableType":
|
232 | case "OptionalType":
|
233 | case "RestType":
|
234 | return this.extractType(type.expression);
|
235 | case "TypeApplication":
|
236 | result = this.extractType(type.expression);
|
237 | result.push(...type.applications.map((app) => this.extractType(app)).flat());
|
238 | break;
|
239 | case "UnionType":
|
240 | return type.elements.map((el) => this.extractType(el)).flat();
|
241 | case "RecordType":
|
242 | return type.fields.map((field) => this.extractType(field)).flat();
|
243 | case "FieldType":
|
244 | if (type.value) {
|
245 | return this.extractType(type.value);
|
246 | }
|
247 | else {
|
248 | return [];
|
249 | }
|
250 | case "FunctionType":
|
251 | result = type.params.map((param) => this.extractType(param)).flat();
|
252 | if (type.result) {
|
253 | result.push(...this.extractType(type.result));
|
254 | }
|
255 | if (type.this) {
|
256 | result.push(...this.extractType(type.this));
|
257 | }
|
258 | break;
|
259 | default:
|
260 | result = [];
|
261 | }
|
262 | return result;
|
263 | }
|
264 | |
265 |
|
266 |
|
267 | extractIgnored_(parsed, comments) {
|
268 | const suppresses = comments
|
269 | .filter((comment) => isLineComment(comment) &&
|
270 | /^\s*fixclosure\s*:\s*ignore\b/.test(comment.value))
|
271 | .reduce((prev, item) => {
|
272 | prev[getLoc(item).start.line] = true;
|
273 | return prev;
|
274 | }, {});
|
275 | if (Object.keys(suppresses).length === 0) {
|
276 | return { provide: [], require: [], requireType: [], forwardDeclare: [] };
|
277 | }
|
278 | const getSuppressedNamespaces = (method) => parsed
|
279 | .filter(isSimpleCallExpression)
|
280 | .filter(isCalledMethodName(method))
|
281 | .filter((namespace) => this.updateMinMaxLine_(namespace))
|
282 | .filter((req) => !!suppresses[getLoc(req.node).start.line])
|
283 | .map(getArgStringLiteralOrNull)
|
284 | .filter(isDefAndNotNull)
|
285 | .sort();
|
286 | return {
|
287 | provide: getSuppressedNamespaces("goog.provide"),
|
288 | require: getSuppressedNamespaces("goog.require"),
|
289 | requireType: getSuppressedNamespaces("goog.requireType"),
|
290 | forwardDeclare: getSuppressedNamespaces("goog.forwardDeclare"),
|
291 | };
|
292 | }
|
293 | extractProvided_(parsed) {
|
294 | return this.extractGoogDeclaration_(parsed, "goog.provide");
|
295 | }
|
296 | extractRequired_(parsed) {
|
297 | return this.extractGoogDeclaration_(parsed, "goog.require");
|
298 | }
|
299 | extractRequireTyped_(parsed) {
|
300 | return this.extractGoogDeclaration_(parsed, "goog.requireType");
|
301 | }
|
302 | extractForwardDeclared_(parsed) {
|
303 | return this.extractGoogDeclaration_(parsed, "goog.forwardDeclare");
|
304 | }
|
305 | |
306 |
|
307 |
|
308 |
|
309 | extractGoogDeclaration_(parsed, method) {
|
310 | return parsed
|
311 | .filter(isSimpleCallExpression)
|
312 | .filter(isCalledMethodName(method))
|
313 | .filter((namespace) => this.updateMinMaxLine_(namespace))
|
314 | .map(getArgStringLiteralOrNull)
|
315 | .filter(isDefAndNotNull)
|
316 | .sort();
|
317 | }
|
318 | traverseProgram_(node) {
|
319 | const uses = [];
|
320 | (0, estraverse_fb_1.traverse)(node, {
|
321 | leave(currentNode, parent) {
|
322 | visitor_1.leave.call(this, currentNode, uses);
|
323 | },
|
324 | });
|
325 | return uses;
|
326 | }
|
327 | |
328 |
|
329 |
|
330 | provideRootFilter_(item) {
|
331 | const root = item.split(".")[0];
|
332 | return this.provideRoots_.has(root);
|
333 | }
|
334 | |
335 |
|
336 |
|
337 | toProvideMapper_(comments, use) {
|
338 | let name = use.name.join(".");
|
339 | switch (use.node.type) {
|
340 | case "AssignmentExpression":
|
341 | if (use.key === "left" && getLoc(use.node).start.column === 0) {
|
342 | return this.getProvidedPackageName_(name);
|
343 | }
|
344 | break;
|
345 | case "ExpressionStatement":
|
346 | if (this.hasTypedefAnnotation_(use.node, comments)) {
|
347 | const parent = use.name.slice(0, -1);
|
348 | const parentLastname = parent[parent.length - 1];
|
349 | if (/^[A-Z]/.test(parentLastname)) {
|
350 | name = parent.join(".");
|
351 | }
|
352 | return this.getProvidedPackageName_(name);
|
353 | }
|
354 | break;
|
355 | default:
|
356 | break;
|
357 | }
|
358 | return null;
|
359 | }
|
360 | |
361 |
|
362 |
|
363 | toRequireMapper_(use) {
|
364 | const name = use.name.join(".");
|
365 | return this.getRequiredPackageName_(name);
|
366 | }
|
367 | toRequireFilter_(use) {
|
368 | switch (use.node.type) {
|
369 | case "ExpressionStatement":
|
370 | return false;
|
371 | case "AssignmentExpression":
|
372 | if (use.key === "left" && getLoc(use.node).start.column === 0) {
|
373 | return false;
|
374 | }
|
375 | break;
|
376 | default:
|
377 | break;
|
378 | }
|
379 | return true;
|
380 | }
|
381 | |
382 |
|
383 |
|
384 | suppressFilter_(comments, use) {
|
385 | const start = getLoc(use.node).start.line;
|
386 | const suppressComment = comments.some((comment) => getLoc(comment).start.line + 1 === start);
|
387 | return !suppressComment;
|
388 | }
|
389 | getRequiredPackageName_(name) {
|
390 | let names = name.split(".");
|
391 | do {
|
392 | const name = this.replaceMethod_(names.join("."));
|
393 | if (this.providedNamespaces_.has(name) && !this.isIgnorePackage_(name)) {
|
394 | return name;
|
395 | }
|
396 | names = names.slice(0, -1);
|
397 | } while (names.length > 0);
|
398 | return null;
|
399 | }
|
400 | getProvidedPackageName_(name) {
|
401 | name = this.replaceMethod_(name);
|
402 | let names = name.split(".");
|
403 | let lastname = names[names.length - 1];
|
404 |
|
405 | names = names.reduceRight((prev, cur) => {
|
406 | if (cur === "prototype") {
|
407 | return [];
|
408 | }
|
409 | else {
|
410 | prev.unshift(cur);
|
411 | return prev;
|
412 | }
|
413 | }, []);
|
414 | if (!this.isProvidedNamespace_(name)) {
|
415 | lastname = names[names.length - 1];
|
416 | if (/^[a-z$]/.test(lastname)) {
|
417 |
|
418 | names.pop();
|
419 | }
|
420 | while (names.length > 0) {
|
421 | lastname = names[names.length - 1];
|
422 | if (/^[A-Z][_0-9A-Z]+$/.test(lastname)) {
|
423 |
|
424 | names.pop();
|
425 | }
|
426 | else {
|
427 | break;
|
428 | }
|
429 | }
|
430 | }
|
431 | if (this.isPrivateProp_(names)) {
|
432 | return null;
|
433 | }
|
434 | const pkg = names.join(".");
|
435 | if (pkg && !this.isIgnorePackage_(pkg)) {
|
436 | return this.replaceMethod_(pkg);
|
437 | }
|
438 | else {
|
439 |
|
440 | return null;
|
441 | }
|
442 | }
|
443 | isIgnorePackage_(name) {
|
444 | return this.ignorePackages_.has(name);
|
445 | }
|
446 | isPrivateProp_(names) {
|
447 | return names.some((name) => name.endsWith("_"));
|
448 | }
|
449 | replaceMethod_(method) {
|
450 | return this.replaceMap_.has(method)
|
451 | ?
|
452 | this.replaceMap_.get(method)
|
453 | : method;
|
454 | }
|
455 | isProvidedNamespace_(name) {
|
456 | return this.providedNamespaces_.has(name);
|
457 | }
|
458 | updateMinMaxLine_(use) {
|
459 | const start = getLoc(use.node).start.line;
|
460 | const end = getLoc(use.node).end.line;
|
461 | this.minLine_ = Math.min(this.minLine_, start);
|
462 | this.maxLine_ = Math.max(this.maxLine_, end);
|
463 | return true;
|
464 | }
|
465 | }
|
466 | exports.Parser = Parser;
|
467 | function isSimpleCallExpression(use) {
|
468 | return use.node.type === "CallExpression";
|
469 | }
|
470 | function isCalledMethodName(method) {
|
471 | return (use) => use.name.join(".") === method;
|
472 | }
|
473 | function getArgStringLiteralOrNull(use) {
|
474 | const arg = use.node.arguments[0];
|
475 | if (arg.type === "Literal" && typeof arg.value === "string") {
|
476 | return arg.value;
|
477 | }
|
478 | return null;
|
479 | }
|
480 |
|
481 |
|
482 |
|
483 | function isLineComment(comment) {
|
484 | return comment.type === "CommentLine" || comment.type === "Line";
|
485 | }
|
486 |
|
487 |
|
488 |
|
489 | function isBlockComment(comment) {
|
490 | return comment.type === "CommentBlock" || comment.type === "Block";
|
491 | }
|
492 |
|
493 |
|
494 |
|
495 | function getLoc(node) {
|
496 |
|
497 | if (!node.loc) {
|
498 | throw new TypeError(`Enable "loc" option of your parser. The node doesn't have "loc" property: ${node}`);
|
499 | }
|
500 | return node.loc;
|
501 | }
|
502 |
|
503 |
|
504 |
|
505 | function isDefAndNotNull(item) {
|
506 | return item != null;
|
507 | }
|
508 |
|
509 |
|
510 |
|
511 | function uniq(prev, cur) {
|
512 | if (prev[prev.length - 1] !== cur) {
|
513 | prev.push(cur);
|
514 | }
|
515 | return prev;
|
516 | }
|