Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
cf04a93
build: use webpack for building apps.
TheLarkInn Jul 14, 2016
7786ed3
chore(lint) Corrected any eslint errors and cleaned up unused files
TheLarkInn Jul 14, 2016
cf441a7
chore(cleanup) additional cleanup tasks performed. Removed unneeded c…
TheLarkInn Jul 15, 2016
541d698
chore: remove bind from serve command
filipesilva Jul 15, 2016
0d1c633
chore: simplify test command
filipesilva Jul 15, 2016
4189b9b
chore: fix test warnings
filipesilva Jul 15, 2016
41653cf
chore: revert whitespace changes
filipesilva Jul 15, 2016
f0e6e51
chore: re-add mapRoot to tsconfig
filipesilva Jul 15, 2016
bdb8690
chore: remove unused import
filipesilva Jul 15, 2016
8ea2e72
bugfix: fix broken tests
filipesilva Jul 15, 2016
8b6fdc9
fix: fix mobile-specific feature test
filipesilva Jul 15, 2016
87b4cbd
bugfix: fix public folder test
filipesilva Jul 15, 2016
860526c
fix: fixes for path mappings, tests, and coverage (#1359)
TheLarkInn Jul 17, 2016
435cd18
fix: add environment configuration replacement (#1364)
TheLarkInn Jul 18, 2016
18318a2
fix(tests): re-enable sass/less/stylus tests (#1363)
filipesilva Jul 19, 2016
de98a64
chore: remove systemjs (#1376)
filipesilva Jul 19, 2016
89b4dd5
chore: remove vendor.ts (#1383)
filipesilva Jul 19, 2016
a144db1
chore: remove material2 test option (#1385)
filipesilva Jul 19, 2016
30fd942
fix(build): re-add support for sourceDir (#1378)
filipesilva Jul 20, 2016
aef92b4
chore(build): remove broccoli (#1386)
filipesilva Jul 20, 2016
05b5ca3
chore: remove windows elevation check (#1384)
filipesilva Jul 20, 2016
86c425c
fix(test): add missing sourceDir (#1387)
filipesilva Jul 20, 2016
5b21617
fix: update node polyfill support properties for prod config to allow…
TheLarkInn Jul 20, 2016
e6bdf50
chore(tests): add prod env e2e test (#1394)
filipesilva Jul 20, 2016
1a68ce6
chore: cleanup of new files (#1388)
filipesilva Jul 20, 2016
4cfc463
fix(serve): disable HMR (#1402)
filipesilva Jul 21, 2016
698ba0c
chore: review docs (#1407)
filipesilva Jul 21, 2016
23e5eb9
feat(env): decouple build from env file (#1404)
filipesilva Jul 22, 2016
06fc077
fix: fix tsconfig for editors (#1420)
filipesilva Jul 22, 2016
8695be4
chore: Merge master into webpack
hansl Jul 22, 2016
6562f78
chore: fix webpack branch after merge conflicts (#1423)
hansl Jul 22, 2016
29f899c
fix: update to awesome-typescript-loader 2.1.0 (#1425)
TheLarkInn Jul 22, 2016
b8f502e
chore(blueprints): remove module.id (#1426)
filipesilva Jul 22, 2016
930785f
feat: add host flag support for ng serve (#1442)
TheLarkInn Jul 25, 2016
c3fe959
feat(test): add karma plugin (#1437)
filipesilva Jul 25, 2016
8a2e0e5
chore(blueprints): re-add outDir (#1428)
filipesilva Jul 25, 2016
3d22394
feat: use @types instead of typings (#1449)
filipesilva Jul 26, 2016
9d42071
fix: make sourcemaps work in test (#1447)
TheLarkInn Jul 26, 2016
a3b1bf4
feat: add utilities for typescript ast (#1159) (#1453)
hansl Jul 26, 2016
755fb7a
Merge branch 'master' into webpack
hansl Jul 26, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: add utilities for typescript ast (#1159) (#1453)
'ast-utils.ts' provides typescript ast utility functions
  • Loading branch information
hansl authored Jul 26, 2016
commit a3b1bf42702c6698928d9df5fe77a186443a5eb3
54 changes: 54 additions & 0 deletions addon/ng2/utilities/ast-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as ts from 'typescript';
import { InsertChange } from './change';

/**
* Find all nodes from the AST in the subtree of node of SyntaxKind kind.
* @param node
* @param kind
* @return all nodes of kind kind, or [] if none is found
*/
export function findNodes(node: ts.Node, kind: ts.SyntaxKind): ts.Node[] {
if (!node) {
return [];
}
let arr: ts.Node[] = [];
if (node.kind === kind) {
arr.push(node);
}
return node.getChildren().reduce((foundNodes, child) =>
foundNodes.concat(findNodes(child, kind)), arr);
}

/**
* Helper for sorting nodes.
* @return function to sort nodes in increasing order of position in sourceFile
*/
function nodesByPosition(first: ts.Node, second: ts.Node): number {
return first.pos - second.pos;
}

/**
* Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`
* or after the last of occurence of `syntaxKind` if the last occurence is a sub child
* of ts.SyntaxKind[nodes[i].kind] and save the changes in file.
*
* @param nodes insert after the last occurence of nodes
* @param toInsert string to insert
* @param file file to insert changes into
* @param fallbackPos position to insert if toInsert happens to be the first occurence
* @param syntaxKind the ts.SyntaxKind of the subchildren to insert after
* @return Change instance
* @throw Error if toInsert is first occurence but fall back is not set
*/
export function insertAfterLastOccurrence(nodes: ts.Node[], toInsert: string,
file: string, fallbackPos?: number, syntaxKind?: ts.SyntaxKind): Change {
var lastItem = nodes.sort(nodesByPosition).pop();
if (syntaxKind) {
lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();
}
if (!lastItem && fallbackPos == undefined) {
throw new Error(`tried to insert ${toInsert} as first occurence with no fallback position`);
}
let lastItemPosition: number = lastItem ? lastItem.end : fallbackPos;
return new InsertChange(file, lastItemPosition, toInsert);
}
3 changes: 2 additions & 1 deletion addon/ng2/utilities/dynamic-path-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,5 @@ module.exports = function dynamicPathParser(project, entityName) {
parsedPath.appRoot = appRoot

return parsedPath;
};
};

177 changes: 177 additions & 0 deletions tests/acceptance/ast-utils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import * as mockFs from 'mock-fs';
import { expect } from 'chai';
import * as ts from 'typescript';
import * as fs from 'fs';
import { InsertChange, RemoveChange } from '../../addon/ng2/utilities/change';
import * as Promise from 'ember-cli/lib/ext/promise';
import {
findNodes,
insertAfterLastOccurrence
} from '../../addon/ng2/utilities/ast-utils';

const readFile = Promise.denodeify(fs.readFile);

describe('ast-utils: findNodes', () => {
const sourceFile = 'tmp/tmp.ts';

beforeEach(() => {
let mockDrive = {
'tmp': {
'tmp.ts': `import * as myTest from 'tests' \n` +
'hello.'
}
};
mockFs(mockDrive);
});

afterEach(() => {
mockFs.restore();
});

it('finds no imports', () => {
let editedFile = new RemoveChange(sourceFile, 0, `import * as myTest from 'tests' \n`);
return editedFile
.apply()
.then(() => {
let rootNode = getRootNode(sourceFile);
let nodes = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);
expect(nodes).to.be.empty;
});
});
it('finds one import', () => {
let rootNode = getRootNode(sourceFile);
let nodes = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);
expect(nodes.length).to.equal(1);
});
it('finds two imports from inline declarations', () => {
// remove new line and add an inline import
let editedFile = new RemoveChange(sourceFile, 32, '\n');
return editedFile
.apply()
.then(() => {
let insert = new InsertChange(sourceFile, 32, `import {Routes} from '@angular/routes'`);
return insert.apply();
})
.then(() => {
let rootNode = getRootNode(sourceFile);
let nodes = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);
expect(nodes.length).to.equal(2);
});
});
it('finds two imports from new line separated declarations', () => {
let editedFile = new InsertChange(sourceFile, 33, `import {Routes} from '@angular/routes'`);
return editedFile
.apply()
.then(() => {
let rootNode = getRootNode(sourceFile);
let nodes = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);
expect(nodes.length).to.equal(2);
});
});
});

describe('ast-utils: insertAfterLastOccurrence', () => {
const sourceFile = 'tmp/tmp.ts';
beforeEach(() => {
let mockDrive = {
'tmp': {
'tmp.ts': ''
}
};
mockFs(mockDrive);
});

afterEach(() => {
mockFs.restore();
});

it('inserts at beginning of file', () => {
let imports = getNodesOfKind(ts.SyntaxKind.ImportDeclaration, sourceFile);
return insertAfterLastOccurrence(imports, `\nimport { Router } from '@angular/router';`,
sourceFile, 0)
.apply()
.then(() => {
return readFile(sourceFile, 'utf8');
}).then((content) => {
let expected = '\nimport { Router } from \'@angular/router\';';
expect(content).to.equal(expected);
});
});
it('throws an error if first occurence with no fallback position', () => {
let imports = getNodesOfKind(ts.SyntaxKind.ImportDeclaration, sourceFile);
expect(() => insertAfterLastOccurrence(imports, `import { Router } from '@angular/router';`,
sourceFile)).to.throw(Error);
});
it('inserts after last import', () => {
let content = `import { foo, bar } from 'fizz';`;
let editedFile = new InsertChange(sourceFile, 0, content);
return editedFile
.apply()
.then(() => {
let imports = getNodesOfKind(ts.SyntaxKind.ImportDeclaration, sourceFile);
return insertAfterLastOccurrence(imports, ', baz', sourceFile,
0, ts.SyntaxKind.Identifier)
.apply();
}).then(() => {
return readFile(sourceFile, 'utf8');
}).then(newContent => expect(newContent).to.equal(`import { foo, bar, baz } from 'fizz';`));
});
it('inserts after last import declaration', () => {
let content = `import * from 'foo' \n import { bar } from 'baz'`;
let editedFile = new InsertChange(sourceFile, 0, content);
return editedFile
.apply()
.then(() => {
let imports = getNodesOfKind(ts.SyntaxKind.ImportDeclaration, sourceFile);
return insertAfterLastOccurrence(imports, `\nimport Router from '@angular/router'`,
sourceFile)
.apply();
}).then(() => {
return readFile(sourceFile, 'utf8');
}).then(newContent => {
let expected = `import * from 'foo' \n import { bar } from 'baz'` +
`\nimport Router from '@angular/router'`;
expect(newContent).to.equal(expected);
});
});
it('inserts correctly if no imports', () => {
let content = `import {} from 'foo'`;
let editedFile = new InsertChange(sourceFile, 0, content);
return editedFile
.apply()
.then(() => {
let imports = getNodesOfKind(ts.SyntaxKind.ImportDeclaration, sourceFile);
return insertAfterLastOccurrence(imports, ', bar', sourceFile, undefined,
ts.SyntaxKind.Identifier)
.apply();
}).catch(() => {
return readFile(sourceFile, 'utf8');
})
.then(newContent => {
expect(newContent).to.equal(content);
// use a fallback position for safety
let imports = getNodesOfKind(ts.SyntaxKind.ImportDeclaration, sourceFile);
let pos = findNodes(imports.sort((a, b) => a.pos - b.pos).pop(),
ts.SyntaxKind.CloseBraceToken).pop().pos;
return insertAfterLastOccurrence(imports, ' bar ',
sourceFile, pos, ts.SyntaxKind.Identifier)
.apply();
}).then(() => {
return readFile(sourceFile, 'utf8');
}).then(newContent => {
expect(newContent).to.equal(`import { bar } from 'foo'`);
});
});
});

/**
* Gets node of kind kind from sourceFile
*/
function getNodesOfKind(kind: ts.SyntaxKind, sourceFile: string) {
return findNodes(getRootNode(sourceFile), kind);
}

function getRootNode(sourceFile: string) {
return ts.createSourceFile(sourceFile, fs.readFileSync(sourceFile).toString(),
ts.ScriptTarget.ES6, true);
}