UNPKG

12.4 kBJavaScriptView Raw
1"use strict";
2// inspired by:
3// https://github.com/angular/angular-cli/blob/d202480a1707be6575b2c8cf0383cfe6db44413c/packages/schematics/angular/utility/ast-utils.ts
4// https://github.com/angular/angular-cli/blob/d202480a1707be6575b2c8cf0383cfe6db44413c/packages/schematics/angular/utility/ng-ast-utils.ts
5// https://github.com/NativeScript/nativescript-schematics/blob/438b9e3ef613389980bfa9d071e28ca1f32ab04f/src/ast-utils.ts
6Object.defineProperty(exports, "__esModule", { value: true });
7// important notes:
8// 1) DO NOT USE `null` when building nodes or you will get `Cannot read property 'transformFlags' of null`
9// https://github.com/Microsoft/TypeScript/issues/22372#issuecomment-371221056
10// 2) DO NOT USE `node.getText()` or `node.getFullText()` while analyzing the AST - it is trying to read
11// the text from the source file and if the node is affected by another transformer, it will lead to
12// an unexpected behavior. You can use `identifier.text` instead.
13// 3) DO NOT USE `node.parent` while analyzing the AST. It will be null when the node is replaced by
14// another transformer and will lead to an exception. Take a look at `findMethodCallInSource` for an
15// example of a working workaround by searching for content in each parent.
16// 4) Always test your transformer both single and in combinations with the other ones.
17const path_1 = require("path");
18const ts = require("typescript");
19const fs_1 = require("fs");
20const transformers_1 = require("@ngtools/webpack/src/transformers");
21function getMainModulePath(entryFilePath, tsConfigName) {
22 try {
23 // backwards compatibility
24 tsConfigName = tsConfigName || "tsconfig.tns.json";
25 const tsModuleName = findBootstrappedModulePath(entryFilePath);
26 const result = tsResolve(tsModuleName, entryFilePath, tsConfigName);
27 return result;
28 }
29 catch (e) {
30 return null;
31 }
32}
33exports.getMainModulePath = getMainModulePath;
34/**
35 * Returns the real path to the ts/d.ts of the specified `moduleName` relative to the specified `containingFilePath`. (e.g. `~/app/file` -> `./app/file.ts`)
36 * @param moduleName The name of the module to be resolved (e.g. `~/config.js`, `lodash`, `./already-relative.js`, `@custom-path/file`).
37 * @param containingFilePath An absolute path to the file where the `moduleName` is imported. The relative result will be based on this file.
38 * @param tsConfigName The name of the tsconfig which will be used during the module resolution (e.g. `tsconfig.json`).
39 * We need this config in order to get its compiler options into account (e.g. resolve any custom `paths` like `~` or `@src`).
40 */
41function tsResolve(moduleName, containingFilePath, tsConfigName) {
42 let result = moduleName;
43 try {
44 const parseConfigFileHost = {
45 getCurrentDirectory: ts.sys.getCurrentDirectory,
46 useCaseSensitiveFileNames: false,
47 readDirectory: ts.sys.readDirectory,
48 fileExists: ts.sys.fileExists,
49 readFile: ts.sys.readFile,
50 onUnRecoverableConfigFileDiagnostic: undefined
51 };
52 const tsConfig = ts.getParsedCommandLineOfConfigFile(tsConfigName, ts.getDefaultCompilerOptions(), parseConfigFileHost);
53 const compilerOptions = tsConfig.options || ts.getDefaultCompilerOptions();
54 const moduleResolutionHost = {
55 fileExists: ts.sys.fileExists,
56 readFile: ts.sys.readFile
57 };
58 const resolutionResult = ts.resolveModuleName(moduleName, containingFilePath, compilerOptions, moduleResolutionHost);
59 if (resolutionResult && resolutionResult.resolvedModule && resolutionResult.resolvedModule.resolvedFileName) {
60 result = path_1.relative(path_1.dirname(containingFilePath), resolutionResult.resolvedModule.resolvedFileName);
61 }
62 }
63 catch (err) { }
64 return result;
65}
66function findBootstrapModuleCall(mainPath) {
67 const source = getSourceFile(mainPath);
68 return findBootstrapModuleCallInSource(source);
69}
70exports.findBootstrapModuleCall = findBootstrapModuleCall;
71function findBootstrapModuleCallInSource(source) {
72 return findMethodCallInSource(source, "bootstrapModule") || findMethodCallInSource(source, "bootstrapModuleFactory");
73}
74exports.findBootstrapModuleCallInSource = findBootstrapModuleCallInSource;
75function findNativeScriptPlatformCallInSource(source) {
76 return findMethodCallInSource(source, "platformNativeScriptDynamic") || findMethodCallInSource(source, "platformNativeScript");
77}
78exports.findNativeScriptPlatformCallInSource = findNativeScriptPlatformCallInSource;
79function findMethodCallInSource(source, methodName) {
80 const allMethodCalls = transformers_1.collectDeepNodes(source, ts.SyntaxKind.CallExpression);
81 let methodCallNode = null;
82 for (const callNode of allMethodCalls) {
83 const currentMethodName = getExpressionName(callNode.expression);
84 if (methodName === currentMethodName) {
85 methodCallNode = callNode;
86 }
87 }
88 return methodCallNode;
89}
90exports.findMethodCallInSource = findMethodCallInSource;
91function findBootstrappedModulePath(mainPath) {
92 const source = getSourceFile(mainPath);
93 return findBootstrappedModulePathInSource(source);
94}
95exports.findBootstrappedModulePath = findBootstrappedModulePath;
96function findBootstrappedModulePathInSource(source) {
97 const bootstrapCall = findBootstrapModuleCallInSource(source);
98 if (!bootstrapCall) {
99 throw new Error("Bootstrap call not found");
100 }
101 const appModulePath = getExpressionImportPath(source, bootstrapCall.arguments[0]);
102 return appModulePath;
103}
104exports.findBootstrappedModulePathInSource = findBootstrappedModulePathInSource;
105function findNativeScriptPlatformPathInSource(source) {
106 const nsPlatformCall = findNativeScriptPlatformCallInSource(source);
107 if (!nsPlatformCall) {
108 throw new Error("NativeScriptPlatform call not found");
109 }
110 const nsPlatformImportPath = getExpressionImportPath(source, nsPlatformCall.expression);
111 return nsPlatformImportPath;
112}
113exports.findNativeScriptPlatformPathInSource = findNativeScriptPlatformPathInSource;
114function getImportPathInSource(source, importName) {
115 const allImports = transformers_1.collectDeepNodes(source, ts.SyntaxKind.ImportDeclaration);
116 const importPath = allImports
117 .filter(imp => {
118 return findIdentifierNode(imp, importName);
119 })
120 .map((imp) => {
121 const modulePathStringLiteral = imp.moduleSpecifier;
122 return modulePathStringLiteral.text;
123 })[0];
124 return importPath;
125}
126function getAppModulePath(mainPath) {
127 const moduleRelativePath = findBootstrappedModulePath(mainPath);
128 const mainDir = path_1.dirname(mainPath);
129 const modulePath = path_1.join(mainDir, `${moduleRelativePath}.ts`);
130 return modulePath;
131}
132exports.getAppModulePath = getAppModulePath;
133function findIdentifierNode(node, text) {
134 if (node.kind === ts.SyntaxKind.Identifier && node.text === text) {
135 return node;
136 }
137 let foundNode = null;
138 ts.forEachChild(node, childNode => {
139 foundNode = foundNode || findIdentifierNode(childNode, text);
140 });
141 return foundNode;
142}
143exports.findIdentifierNode = findIdentifierNode;
144function getObjectPropertyMatches(objectNode, sourceFile, targetPropertyName) {
145 return objectNode.properties
146 .filter(prop => prop.kind == ts.SyntaxKind.PropertyAssignment)
147 .filter((prop) => {
148 const name = prop.name;
149 switch (name.kind) {
150 case ts.SyntaxKind.Identifier:
151 return name.text == targetPropertyName;
152 case ts.SyntaxKind.StringLiteral:
153 return name.text == targetPropertyName;
154 }
155 return false;
156 });
157}
158exports.getObjectPropertyMatches = getObjectPropertyMatches;
159function getDecoratorMetadata(source, identifier, module) {
160 const angularImports = transformers_1.collectDeepNodes(source, ts.SyntaxKind.ImportDeclaration)
161 .map((node) => angularImportsFromNode(node, source))
162 .reduce((acc, current) => {
163 for (const key of Object.keys(current)) {
164 acc[key] = current[key];
165 }
166 return acc;
167 }, {});
168 return transformers_1.collectDeepNodes(source, ts.SyntaxKind.Decorator)
169 .filter(node => {
170 return node.expression.kind == ts.SyntaxKind.CallExpression;
171 })
172 .map(node => node.expression)
173 .filter(expr => {
174 if (expr.expression.kind == ts.SyntaxKind.Identifier) {
175 const id = expr.expression;
176 return id.getFullText(source) == identifier
177 && angularImports[id.getFullText(source)] === module;
178 }
179 else if (expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression) {
180 // This covers foo.NgModule when importing * as foo.
181 const paExpr = expr.expression;
182 // If the left expression is not an identifier, just give up at that point.
183 if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {
184 return false;
185 }
186 const id = paExpr.name.text;
187 const moduleId = paExpr.expression.text;
188 return id === identifier && (angularImports[moduleId + '.'] === module);
189 }
190 return false;
191 })
192 .filter(expr => expr.arguments[0]
193 && (expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression ||
194 expr.arguments[0].kind == ts.SyntaxKind.Identifier))
195 .map(expr => expr.arguments[0]);
196}
197exports.getDecoratorMetadata = getDecoratorMetadata;
198function angularImportsFromNode(node, _sourceFile) {
199 const ms = node.moduleSpecifier;
200 let modulePath;
201 switch (ms.kind) {
202 case ts.SyntaxKind.StringLiteral:
203 modulePath = ms.text;
204 break;
205 default:
206 return {};
207 }
208 if (!modulePath.startsWith('@angular/')) {
209 return {};
210 }
211 if (node.importClause) {
212 if (node.importClause.name) {
213 // This is of the form `import Name from 'path'`. Ignore.
214 return {};
215 }
216 else if (node.importClause.namedBindings) {
217 const nb = node.importClause.namedBindings;
218 if (nb.kind == ts.SyntaxKind.NamespaceImport) {
219 // This is of the form `import * as name from 'path'`. Return `name.`.
220 return {
221 [nb.name.text + '.']: modulePath,
222 };
223 }
224 else {
225 // This is of the form `import {a,b,c} from 'path'`
226 const namedImports = nb;
227 return namedImports.elements
228 .map((is) => is.propertyName ? is.propertyName.text : is.name.text)
229 .reduce((acc, curr) => {
230 acc[curr] = modulePath;
231 return acc;
232 }, {});
233 }
234 }
235 return {};
236 }
237 else {
238 // This is of the form `import 'path';`. Nothing to do.
239 return {};
240 }
241}
242exports.angularImportsFromNode = angularImportsFromNode;
243function getExpressionName(expression) {
244 let text = "";
245 if (!expression) {
246 return text;
247 }
248 if (expression.kind == ts.SyntaxKind.Identifier) {
249 text = expression.text;
250 }
251 else if (expression.kind == ts.SyntaxKind.PropertyAccessExpression) {
252 text = expression.name.text;
253 }
254 return text;
255}
256exports.getExpressionName = getExpressionName;
257function getExpressionImportPath(source, expression) {
258 let importString = "";
259 if (!expression) {
260 return undefined;
261 }
262 if (expression.kind == ts.SyntaxKind.Identifier) {
263 importString = expression.text;
264 }
265 else if (expression.kind == ts.SyntaxKind.PropertyAccessExpression) {
266 const targetPAArg = expression;
267 if (targetPAArg.expression.kind == ts.SyntaxKind.Identifier) {
268 importString = targetPAArg.expression.text;
269 }
270 }
271 const importPath = getImportPathInSource(source, importString);
272 return importPath;
273}
274function getSourceFile(mainPath) {
275 if (!fs_1.existsSync(mainPath)) {
276 throw new Error(`Main file (${mainPath}) not found`);
277 }
278 const mainText = fs_1.readFileSync(mainPath, "utf8");
279 const source = ts.createSourceFile(mainPath, mainText, ts.ScriptTarget.Latest, true);
280 return source;
281}
282//# sourceMappingURL=ast-utils.js.map
\No newline at end of file