1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | Object.defineProperty(exports, "__esModule", { value: true });
|
16 | const babel = require("@babel/types");
|
17 | const util = require("util");
|
18 | const esutil = require("../javascript/esutil");
|
19 | const feature_1 = require("./feature");
|
20 | const warning_1 = require("./warning");
|
21 |
|
22 |
|
23 |
|
24 | class ScannedReference extends feature_1.ScannedFeature {
|
25 | constructor(kind, identifier, sourceRange, astNode, astPath, description, jsdoc, warnings) {
|
26 | super(sourceRange, astNode, description, jsdoc, warnings);
|
27 | this.kind = kind;
|
28 | this.astNode = astNode;
|
29 | this.astPath = astPath;
|
30 | this.sourceRange = sourceRange;
|
31 | this.identifier = identifier;
|
32 | }
|
33 | resolve(document) {
|
34 | return this.resolveWithKind(document, this.kind);
|
35 | }
|
36 |
|
37 |
|
38 | resolveWithKind(document, kind) {
|
39 | let feature;
|
40 | const warnings = [...this.warnings];
|
41 | const scopedResult = resolveScopedAt(this.astPath, this.identifier, document, kind, this.sourceRange);
|
42 | if (scopedResult.successful) {
|
43 | feature = scopedResult.value;
|
44 | }
|
45 | else {
|
46 | if (scopedResult.error !== undefined) {
|
47 | warnings.push(scopedResult.error);
|
48 | }
|
49 | }
|
50 |
|
51 |
|
52 |
|
53 | if (feature === undefined) {
|
54 |
|
55 |
|
56 | const features = document.getFeatures({ imported: true, externalPackages: true, kind, id: this.identifier });
|
57 | if (this.sourceRange) {
|
58 | if (features.size === 0) {
|
59 | let message = `Could not resolve reference to ${this.kind}`;
|
60 | if (kind === 'behavior') {
|
61 | message += `. Is it annotated with @polymerBehavior?`;
|
62 | }
|
63 | warnings.push(new warning_1.Warning({
|
64 | code: 'could-not-resolve-reference',
|
65 | sourceRange: this.sourceRange,
|
66 | message,
|
67 | parsedDocument: document.parsedDocument,
|
68 | severity: warning_1.Severity.WARNING
|
69 | }));
|
70 | }
|
71 | else if (features.size > 1) {
|
72 | warnings.push(new warning_1.Warning({
|
73 | code: 'multiple-global-declarations',
|
74 | sourceRange: this.sourceRange,
|
75 | message: `Multiple global declarations of ${this.kind} with identifier ${this.identifier}`,
|
76 | parsedDocument: document.parsedDocument,
|
77 | severity: warning_1.Severity.WARNING
|
78 | }));
|
79 | }
|
80 | }
|
81 | [feature] = features;
|
82 | }
|
83 | return new Reference(this.identifier, this.sourceRange, this.astNode, feature, warnings);
|
84 | }
|
85 | }
|
86 | exports.ScannedReference = ScannedReference;
|
87 | function resolveScopedAt(path, identifier, document, kind, sourceRange) {
|
88 |
|
89 | if (isSomeKindOfImport(path)) {
|
90 | const exportedIdentifier = getExportedIdentifier(path.node, identifier);
|
91 | if (exportedIdentifier === undefined) {
|
92 | if (sourceRange === undefined) {
|
93 | return { successful: false, error: undefined };
|
94 | }
|
95 | else {
|
96 | return {
|
97 | successful: false,
|
98 | error: new warning_1.Warning({
|
99 | code: 'could-not-resolve-reference',
|
100 | message: `Could not resolve reference to '${identifier}' ` +
|
101 | `with kind ${kind}`,
|
102 | severity: warning_1.Severity.WARNING,
|
103 | sourceRange,
|
104 | parsedDocument: document.parsedDocument,
|
105 | }),
|
106 | };
|
107 | }
|
108 | }
|
109 | return resolveThroughImport(path, exportedIdentifier, document, kind, sourceRange);
|
110 | }
|
111 | if (babel.isExportNamedDeclaration(path.node) && !path.node.source) {
|
112 | for (const specifier of path.node.specifiers) {
|
113 | if (specifier.exported.name !== specifier.local.name &&
|
114 | specifier.exported.name === identifier) {
|
115 |
|
116 |
|
117 | return resolveScopedAt(path, specifier.local.name, document, kind, sourceRange);
|
118 | }
|
119 | }
|
120 | }
|
121 | const statement = esutil.getCanonicalStatement(path);
|
122 | if (statement === undefined) {
|
123 | return { successful: false, error: undefined };
|
124 | }
|
125 | const features = document.getFeatures({ kind, id: identifier, statement });
|
126 | if (features.size > 1) {
|
127 |
|
128 | return { successful: false, error: undefined };
|
129 | }
|
130 | const [feature] = features;
|
131 | if (feature !== undefined) {
|
132 | return { successful: true, value: feature };
|
133 | }
|
134 |
|
135 |
|
136 | const hasASingleDotInName = /^[^\.]+\.[^\.]+$/;
|
137 | if (hasASingleDotInName.test(identifier)) {
|
138 | const [namespace, name] = identifier.split('.');
|
139 | const namespaceBinding = path.scope.getBinding(namespace);
|
140 | if (namespaceBinding !== undefined) {
|
141 | const node = namespaceBinding.path.node;
|
142 | if (babel.isImportNamespaceSpecifier(node)) {
|
143 | return resolveThroughImport(namespaceBinding.path, name, document, kind, sourceRange);
|
144 | }
|
145 | }
|
146 | }
|
147 | const binding = path.scope.getBinding(identifier);
|
148 | if (binding === undefined || binding.path.node === path.node) {
|
149 | return { successful: false, error: undefined };
|
150 | }
|
151 | return resolveScopedAt(binding.path, identifier, document, kind, sourceRange);
|
152 | }
|
153 | function resolveThroughImport(path, exportedAs, document, kind, sourceRange) {
|
154 | const statement = esutil.getCanonicalStatement(path);
|
155 | if (statement === undefined) {
|
156 | throw new Error(`Internal error, could not get statement for node of type ${path.node.type}`);
|
157 | }
|
158 | const [import_] = document.getFeatures({ kind: 'import', statement });
|
159 | if (import_ === undefined || import_.document === undefined) {
|
160 |
|
161 | return { successful: false, error: undefined };
|
162 | }
|
163 |
|
164 |
|
165 | const [export_] = import_.document.getFeatures({ kind: 'export', id: exportedAs });
|
166 | if (export_ === undefined) {
|
167 |
|
168 | return { successful: false, error: undefined };
|
169 | }
|
170 | return resolveScopedAt(export_.astNodePath, exportedAs, import_.document, kind, sourceRange);
|
171 | }
|
172 | function isSomeKindOfImport(path) {
|
173 | const node = path.node;
|
174 | return babel.isImportSpecifier(node) ||
|
175 | babel.isImportDefaultSpecifier(node) ||
|
176 | (babel.isExportNamedDeclaration(node) && node.source != null) ||
|
177 | (babel.isExportAllDeclaration(node));
|
178 | }
|
179 | function getExportedIdentifier(node, localIdentifier) {
|
180 | switch (node.type) {
|
181 | case 'ImportDefaultSpecifier':
|
182 | return 'default';
|
183 | case 'ExportNamedDeclaration':
|
184 | for (const specifier of node.specifiers) {
|
185 | if (specifier.exported.name === localIdentifier) {
|
186 | return specifier.local.name;
|
187 | }
|
188 | }
|
189 | return undefined;
|
190 | case 'ExportAllDeclaration':
|
191 |
|
192 |
|
193 | return localIdentifier;
|
194 | case 'ImportSpecifier':
|
195 | return node.imported.name;
|
196 | }
|
197 | return assertNever(node);
|
198 | }
|
199 | function assertNever(never) {
|
200 | throw new Error(`Unexpected ast node: ${util.inspect(never)}`);
|
201 | }
|
202 | const referenceSet = new Set(['reference']);
|
203 | const emptySet = new Set();
|
204 |
|
205 |
|
206 |
|
207 | class Reference {
|
208 | constructor(identifier, sourceRange, astNode, feature, warnings) {
|
209 | this.kinds = referenceSet;
|
210 | this.identifiers = emptySet;
|
211 | this.identifier = identifier;
|
212 | this.sourceRange = sourceRange;
|
213 | this.astNode = astNode;
|
214 | this.warnings = warnings;
|
215 | this.feature = feature;
|
216 | }
|
217 | }
|
218 | exports.Reference = Reference;
|
219 |
|
\ | No newline at end of file |