Skip to content

Commit b5e86c9

Browse files
authored
feature: splitting the ast into its own packages (angular#1828)
1 parent f9df8bb commit b5e86c9

24 files changed

+1540
-1363
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
dist/
12
node_modules/
23
npm-debug.log
34

.travis.yml

+8-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ env:
1212
- DBUS_SESSION_BUS_ADDRESS=/dev/null
1313
matrix:
1414
- SCRIPT=lint
15+
# - SCRIPT=build
1516
- SCRIPT=test
1617
# - TARGET=mobile SCRIPT=mobile_test
1718
matrix:
@@ -21,8 +22,13 @@ matrix:
2122
- os: osx
2223
node_js: "5"
2324
env: SCRIPT=lint
24-
# - os: osx
25-
# env: TARGET=mobile SCRIPT=mobile_test
25+
- node_js: "6"
26+
env: SCRIPT=build
27+
- os: osx
28+
node_js: "5"
29+
env: SCRIPT=build
30+
- os: osx
31+
env: TARGET=mobile SCRIPT=mobile_test
2632

2733
before_install:
2834
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi

addon/ng2/tsconfig.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
"sourceMap": true,
1414
"sourceRoot": "/",
1515
"target": "es5",
16-
"lib": ["es6"]
16+
"lib": ["es6"],
17+
"paths": {
18+
"@angular-cli/ast-tools": [ "../../packages/ast-tools/src" ]
19+
}
1720
},
1821
"includes": [
1922
"./custom-typings.d.ts"

addon/ng2/utilities/ast-utils.ts

+12-304
Original file line numberDiff line numberDiff line change
@@ -1,304 +1,12 @@
1-
import * as ts from 'typescript';
2-
import * as fs from 'fs';
3-
import {Symbols} from '@angular/tsc-wrapped/src/symbols';
4-
import {
5-
isMetadataImportedSymbolReferenceExpression,
6-
isMetadataModuleReferenceExpression
7-
} from '@angular/tsc-wrapped';
8-
import {Change, InsertChange, NoopChange, MultiChange} from './change';
9-
import {insertImport} from './route-utils';
10-
11-
import {Observable} from 'rxjs/Observable';
12-
import {ReplaySubject} from 'rxjs/ReplaySubject';
13-
import 'rxjs/add/observable/of';
14-
import 'rxjs/add/operator/do';
15-
import 'rxjs/add/operator/filter';
16-
import 'rxjs/add/operator/last';
17-
import 'rxjs/add/operator/map';
18-
import 'rxjs/add/operator/mergeMap';
19-
import 'rxjs/add/operator/toArray';
20-
import 'rxjs/add/operator/toPromise';
21-
22-
23-
/**
24-
* Get TS source file based on path.
25-
* @param filePath
26-
* @return source file of ts.SourceFile kind
27-
*/
28-
export function getSource(filePath: string): ts.SourceFile {
29-
return ts.createSourceFile(filePath, fs.readFileSync(filePath).toString(),
30-
ts.ScriptTarget.ES6, true);
31-
}
32-
33-
34-
/**
35-
* Get all the nodes from a source, as an observable.
36-
* @param sourceFile The source file object.
37-
* @returns {Observable<ts.Node>} An observable of all the nodes in the source.
38-
*/
39-
export function getSourceNodes(sourceFile: ts.SourceFile): Observable<ts.Node> {
40-
const subject = new ReplaySubject<ts.Node>();
41-
let nodes: ts.Node[] = [sourceFile];
42-
43-
while(nodes.length > 0) {
44-
const node = nodes.shift();
45-
46-
if (node) {
47-
subject.next(node);
48-
if (node.getChildCount(sourceFile) >= 0) {
49-
nodes.unshift(...node.getChildren());
50-
}
51-
}
52-
}
53-
54-
subject.complete();
55-
return subject.asObservable();
56-
}
57-
58-
59-
/**
60-
* Find all nodes from the AST in the subtree of node of SyntaxKind kind.
61-
* @param node
62-
* @param kind
63-
* @param max The maximum number of items to return.
64-
* @return all nodes of kind, or [] if none is found
65-
*/
66-
export function findNodes(node: ts.Node, kind: ts.SyntaxKind, max: number = Infinity): ts.Node[] {
67-
if (!node || max == 0) {
68-
return [];
69-
}
70-
71-
let arr: ts.Node[] = [];
72-
if (node.kind === kind) {
73-
arr.push(node);
74-
max--;
75-
}
76-
if (max > 0) {
77-
for (const child of node.getChildren()) {
78-
findNodes(child, kind, max).forEach(node => {
79-
if (max > 0) {
80-
arr.push(node);
81-
}
82-
max--;
83-
});
84-
85-
if (max <= 0) {
86-
break;
87-
}
88-
}
89-
}
90-
return arr;
91-
}
92-
93-
94-
/**
95-
* Helper for sorting nodes.
96-
* @return function to sort nodes in increasing order of position in sourceFile
97-
*/
98-
function nodesByPosition(first: ts.Node, second: ts.Node): number {
99-
return first.pos - second.pos;
100-
}
101-
102-
103-
/**
104-
* Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`
105-
* or after the last of occurence of `syntaxKind` if the last occurence is a sub child
106-
* of ts.SyntaxKind[nodes[i].kind] and save the changes in file.
107-
*
108-
* @param nodes insert after the last occurence of nodes
109-
* @param toInsert string to insert
110-
* @param file file to insert changes into
111-
* @param fallbackPos position to insert if toInsert happens to be the first occurence
112-
* @param syntaxKind the ts.SyntaxKind of the subchildren to insert after
113-
* @return Change instance
114-
* @throw Error if toInsert is first occurence but fall back is not set
115-
*/
116-
export function insertAfterLastOccurrence(nodes: ts.Node[], toInsert: string,
117-
file: string, fallbackPos?: number, syntaxKind?: ts.SyntaxKind): Change {
118-
var lastItem = nodes.sort(nodesByPosition).pop();
119-
if (syntaxKind) {
120-
lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();
121-
}
122-
if (!lastItem && fallbackPos == undefined) {
123-
throw new Error(`tried to insert ${toInsert} as first occurence with no fallback position`);
124-
}
125-
let lastItemPosition: number = lastItem ? lastItem.end : fallbackPos;
126-
return new InsertChange(file, lastItemPosition, toInsert);
127-
}
128-
129-
130-
export function getContentOfKeyLiteral(source: ts.SourceFile, node: ts.Node): string {
131-
if (node.kind == ts.SyntaxKind.Identifier) {
132-
return (<ts.Identifier>node).text;
133-
} else if (node.kind == ts.SyntaxKind.StringLiteral) {
134-
try {
135-
return JSON.parse(node.getFullText(source))
136-
} catch (e) {
137-
return null;
138-
}
139-
} else {
140-
return null;
141-
}
142-
}
143-
144-
145-
export function getDecoratorMetadata(source: ts.SourceFile, identifier: string,
146-
module: string): Observable<ts.Node> {
147-
const symbols = new Symbols(source);
148-
149-
return getSourceNodes(source)
150-
.filter(node => {
151-
return node.kind == ts.SyntaxKind.Decorator
152-
&& (<ts.Decorator>node).expression.kind == ts.SyntaxKind.CallExpression;
153-
})
154-
.map(node => <ts.CallExpression>(<ts.Decorator>node).expression)
155-
.filter(expr => {
156-
if (expr.expression.kind == ts.SyntaxKind.Identifier) {
157-
const id = <ts.Identifier>expr.expression;
158-
const metaData = symbols.resolve(id.getFullText(source));
159-
if (isMetadataImportedSymbolReferenceExpression(metaData)) {
160-
return metaData.name == identifier && metaData.module == module;
161-
}
162-
} else if (expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression) {
163-
// This covers foo.NgModule when importing * as foo.
164-
const paExpr = <ts.PropertyAccessExpression>expr.expression;
165-
// If the left expression is not an identifier, just give up at that point.
166-
if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {
167-
return false;
168-
}
169-
170-
const id = paExpr.name;
171-
const moduleId = <ts.Identifier>paExpr.expression;
172-
const moduleMetaData = symbols.resolve(moduleId.getFullText(source));
173-
if (isMetadataModuleReferenceExpression(moduleMetaData)) {
174-
return moduleMetaData.module == module && id.getFullText(source) == identifier;
175-
}
176-
}
177-
return false;
178-
})
179-
.filter(expr => expr.arguments[0]
180-
&& expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression)
181-
.map(expr => <ts.ObjectLiteralExpression>expr.arguments[0]);
182-
}
183-
184-
185-
function _addSymbolToNgModuleMetadata(ngModulePath: string, metadataField: string,
186-
symbolName: string, importPath: string) {
187-
const source: ts.SourceFile = getSource(ngModulePath);
188-
let metadata = getDecoratorMetadata(source, 'NgModule', '@angular/core');
189-
190-
// Find the decorator declaration.
191-
return metadata
192-
.toPromise()
193-
.then((node: ts.ObjectLiteralExpression) => {
194-
if (!node) {
195-
return null;
196-
}
197-
198-
// Get all the children property assignment of object literals.
199-
return node.properties
200-
.filter(prop => prop.kind == ts.SyntaxKind.PropertyAssignment)
201-
// Filter out every fields that's not "metadataField". Also handles string literals
202-
// (but not expressions).
203-
.filter(prop => {
204-
switch (prop.name.kind) {
205-
case ts.SyntaxKind.Identifier:
206-
return prop.name.getText(source) == metadataField;
207-
case ts.SyntaxKind.StringLiteral:
208-
return prop.name.text == metadataField;
209-
}
210-
211-
return false;
212-
});
213-
})
214-
// Get the last node of the array literal.
215-
.then(matchingProperties => {
216-
if (!matchingProperties) {
217-
return;
218-
}
219-
if (matchingProperties.length == 0) {
220-
return metadata
221-
.toPromise();
222-
}
223-
224-
const assignment = <ts.PropertyAssignment>matchingProperties[0];
225-
226-
// If it's not an array, nothing we can do really.
227-
if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {
228-
return Observable.empty();
229-
}
230-
231-
const arrLiteral = <ts.ArrayLiteralExpression>assignment.initializer;
232-
if (arrLiteral.elements.length == 0) {
233-
// Forward the property.
234-
return arrLiteral;
235-
}
236-
return arrLiteral.elements;
237-
})
238-
.then((node: ts.Node) => {
239-
if (!node) {
240-
console.log('No app module found. Please add your new class to your component.');
241-
return new NoopChange();
242-
}
243-
if (Array.isArray(node)) {
244-
node = node[node.length - 1];
245-
}
246-
247-
let toInsert;
248-
let position = node.getEnd();
249-
if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {
250-
// We haven't found the field in the metadata declaration. Insert a new
251-
// field.
252-
let expr = <ts.ObjectLiteralExpression>node;
253-
if (expr.properties.length == 0) {
254-
position = expr.getEnd() - 1;
255-
toInsert = ` ${metadataField}: [${symbolName}]\n`;
256-
} else {
257-
node = expr.properties[expr.properties.length - 1];
258-
position = node.getEnd();
259-
// Get the indentation of the last element, if any.
260-
const text = node.getFullText(source);
261-
if (text.startsWith('\n')) {
262-
toInsert = `,${text.match(/^\n(\r?)\s+/)[0]}${metadataField}: [${symbolName}]`;
263-
} else {
264-
toInsert = `, ${metadataField}: [${symbolName}]`;
265-
}
266-
}
267-
} else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {
268-
// We found the field but it's empty. Insert it just before the `]`.
269-
position--;
270-
toInsert = `${symbolName}`;
271-
} else {
272-
// Get the indentation of the last element, if any.
273-
const text = node.getFullText(source);
274-
if (text.startsWith('\n')) {
275-
toInsert = `,${text.match(/^\n(\r?)\s+/)[0]}${symbolName}`;
276-
} else {
277-
toInsert = `, ${symbolName}`;
278-
}
279-
}
280-
281-
const insert = new InsertChange(ngModulePath, position, toInsert);
282-
const importInsert: Change = insertImport(ngModulePath, symbolName, importPath);
283-
return new MultiChange([insert, importInsert]);
284-
});
285-
}
286-
287-
/**
288-
* Custom function to insert a declaration (component, pipe, directive)
289-
* into NgModule declarations. It also imports the component.
290-
*/
291-
export function addComponentToModule(modulePath: string, classifiedName: string,
292-
importPath: string): Promise<Change> {
293-
294-
return _addSymbolToNgModuleMetadata(modulePath, 'declarations', classifiedName, importPath);
295-
}
296-
297-
/**
298-
* Custom function to insert a provider into NgModule. It also imports it.
299-
*/
300-
export function addProviderToModule(modulePath: string, classifiedName: string,
301-
importPath: string): Promise<Change> {
302-
return _addSymbolToNgModuleMetadata(modulePath, 'providers', classifiedName, importPath);
303-
}
304-
1+
// In order to keep refactoring low, simply export from ast-tools.
2+
// TODO: move all dependencies of this file to ast-tools directly.
3+
export {
4+
getSource,
5+
getSourceNodes,
6+
findNodes,
7+
insertAfterLastOccurrence,
8+
getContentOfKeyLiteral,
9+
getDecoratorMetadata,
10+
addComponentToModule,
11+
addProviderToModule
12+
} from '@angular-cli/ast-tools';

0 commit comments

Comments
 (0)