Skip to content

Commit 839754e

Browse files
committed
feat(refoundry): publish a new package for refactoring.
This package isnt public or stable yet, so no documentation. Also included fixes for: - Fixes angular#2887.
1 parent 39b9522 commit 839754e

30 files changed

+1282
-347
lines changed

lib/packages.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,19 @@ const packages =
1616
return { name: pkgName, root: path.join(packageRoot, pkgName) };
1717
})
1818
.reduce((packages, pkg) => {
19-
let pkgJson = JSON.parse(fs.readFileSync(path.join(pkg.root, 'package.json'), 'utf8'));
19+
const packageJsonPath = path.join(pkg.root, 'package.json');
20+
let pkgJson;
21+
try {
22+
pkgJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
23+
} catch (e) {
24+
console.error(`Error while parsing "${packageJsonPath}":\n${e.stack}`);
25+
process.exit(1);
26+
}
2027
let name = pkgJson['name'];
2128

2229
packages[name] = {
2330
dist: path.join(__dirname, '../dist', pkg.name),
24-
packageJson: path.join(pkg.root, 'package.json'),
31+
packageJson: packageJsonPath,
2532
root: pkg.root,
2633
relative: path.relative(path.dirname(__dirname), pkg.root),
2734
main: path.resolve(pkg.root, 'src/index.ts')

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
"less-loader": "^2.2.3",
7777
"loader-utils": "^0.2.16",
7878
"lodash": "^4.11.1",
79-
"magic-string": "^0.16.0",
79+
"magic-string": "^0.19.0",
8080
"markdown-it": "4.3.0",
8181
"markdown-it-terminal": "0.0.3",
8282
"minimatch": "^3.0.3",

packages/@ngtools/refactory/README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Angular Ahead-of-Time Webpack Plugin
2+
3+
Webpack plugin that AoT compiles your Angular components and modules.
4+
5+
## Usage
6+
In your webpack config, add the following plugin and loader:
7+
8+
```typescript
9+
import {AotPlugin} from '@ngtools/webpack'
10+
11+
exports = { /* ... */
12+
module: {
13+
rules: [
14+
{
15+
test: /\.ts$/,
16+
loader: '@ngtools/webpack',
17+
}
18+
]
19+
},
20+
21+
plugins: [
22+
new AotPlugin({
23+
tsConfigPath: 'path/to/tsconfig.json',
24+
entryModule: 'path/to/app.module#AppModule'
25+
})
26+
]
27+
}
28+
```
29+
30+
The loader works with the webpack plugin to compile your TypeScript. It's important to include both, and to not include any other TypeScript compiler loader.
31+
32+
## Options
33+
34+
* `tsConfigPath`. The path to the `tsconfig.json` file. This is required. In your `tsconfig.json`, you can pass options to the Angular Compiler with `angularCompilerOptions`.
35+
* `basePath`. Optional. The root to use by the compiler to resolve file paths. By default, use the `tsConfigPath` root.
36+
* `entryModule`. Optional if specified in `angularCompilerOptions`. The path and classname of the main application module. This follows the format `path/to/file#ClassName`.
37+
* `mainPath`. Optional if `entryModule` is specified. The `main.ts` file containing the bootstrap code. The plugin will use AST to determine the `entryModule`.
38+
* `skipCodeGeneration`. Optional, defaults to false. Disable code generation and do not refactor the code to bootstrap. This replaces `templateUrl: "string"` with `template: require("string")` (and similar for styles) to allow for webpack to properly link the resources.
39+
* `typeChecking`. Optional, defaults to true. Enable type checking through your application. This will slow down compilation, but show syntactic and semantic errors in webpack.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "@ngtools/refactory",
3+
"version": "0.0.1",
4+
"description": "Library for refactoring angular 2 apps and code.",
5+
"main": "./src/index.js",
6+
"typings": "src/index.d.ts",
7+
"license": "MIT",
8+
"keywords": [
9+
"ngtools",
10+
"angular",
11+
"refactor",
12+
"typescript",
13+
"library"
14+
],
15+
"repository": {
16+
"type": "git",
17+
"url": "https://github.com/angular/angular-cli.git"
18+
},
19+
"author": "angular",
20+
"bugs": {
21+
"url": "https://github.com/angular/angular-cli/issues"
22+
},
23+
"homepage": "https://github.com/angular/angular-cli/tree/master/packages/@ngtools/refactory",
24+
"engines": {
25+
"node": ">= 4.1.0",
26+
"npm": ">= 3.0.0"
27+
},
28+
"dependencies": {
29+
"magic-string": "^0.19.0",
30+
"minimatch": "^3.0.0",
31+
"source-map": "^0.5.6"
32+
},
33+
"peerDependencies": {
34+
"typescript": "^2.0.2"
35+
}
36+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
export interface Constructor<T> {
3+
new (...args: any[]): T;
4+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
4+
5+
export interface RefactoryHost {
6+
readonly basePath: string;
7+
8+
stat(filePath: string): fs.Stats | null;
9+
10+
write(filePath: string, content: string): void;
11+
read(filePath: string): string;
12+
exists(filePath: string): boolean;
13+
14+
list(dirPath: string): string[];
15+
isDirectory(dirPath: string): boolean;
16+
}
17+
18+
19+
export class InvalidRefactoryHost implements RefactoryHost {
20+
get basePath(): string { throw new Error('Unimplemented: basePath'); }
21+
22+
write(filePath: string, content: string): void { throw new Error('Unimplemented: write'); }
23+
read(filePath: string): string { throw new Error('Unimplemented: read'); }
24+
exists(filePath: string): boolean { throw new Error('Unimplemented: exists'); }
25+
26+
list(dirPath: string): string[] { throw new Error('Unimplemented: list'); }
27+
isDirectory(dirPath: string): boolean { throw new Error('Unimplemented: isDirectory'); }
28+
29+
stat(dirPath: string): fs.Stats { throw new Error('Unimplemented: stat'); }
30+
}
31+
32+
33+
export class NullRefactoryHost implements RefactoryHost {
34+
constructor(public readonly basePath: string) {}
35+
36+
write(filePath: string, content: string): void { throw new Error('Unimplemented: write'); }
37+
read(filePath: string): string { throw new Error('Unimplemented: read'); }
38+
exists(filePath: string): boolean { return false; }
39+
40+
list(dirPath: string): string[] { return []; }
41+
isDirectory(dirPath: string): boolean { return false; }
42+
43+
stat(dirPath: string): fs.Stats { return null; }
44+
}
45+
46+
47+
export class NodeRefactoryHost implements RefactoryHost {
48+
constructor(private _basePath: string = '/') {}
49+
private _normalize(p: string) {
50+
return path.normalize(path.isAbsolute(p) ? p : path.join(this._basePath, p));
51+
}
52+
53+
get basePath(): string { return this._basePath; }
54+
55+
stat(filePath: string): fs.Stats {
56+
return fs.statSync(filePath);
57+
}
58+
59+
write(filePath: string, content: string): void {
60+
fs.writeFileSync(this._normalize(filePath), content, 'utf8');
61+
}
62+
read(filePath: string): string {
63+
return fs.readFileSync(this._normalize(filePath), 'utf8');
64+
}
65+
exists(filePath: string): boolean {
66+
return fs.existsSync(this._normalize(filePath));
67+
}
68+
list(dirPath: string): string[] {
69+
return fs.readdirSync(this._normalize(dirPath));
70+
}
71+
isDirectory(dirPath: string): boolean {
72+
return fs.statSync(this._normalize(dirPath)).isDirectory();
73+
}
74+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
export {Refactory} from './refactory';
3+
4+
// Hosts
5+
export {RefactoryHost, NullRefactoryHost, NodeRefactoryHost, InvalidRefactoryHost} from './host';
6+
export {VirtualRefactoryHost} from './utils/virtual_refactory_host'
7+
export * from './utils/host_adapters';
8+
9+
// Languages
10+
export * from './language/index';
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {Refactory} from '../refactory';
2+
import {StaticSymbol} from './symbol';
3+
4+
export abstract class File {
5+
constructor(protected _filePath: string, protected _refactory: Refactory) {}
6+
7+
get path() { return this._filePath; }
8+
get refactory() { return this._refactory; }
9+
10+
abstract resolveSymbol(name: string): StaticSymbol;
11+
}
12+
13+
export class NonExistentFile extends File {
14+
resolveSymbol(name: string): StaticSymbol {
15+
throw new Error(`Trying to resolve symbol "${name}" in non existent file "${this.path}".`);
16+
}
17+
}
18+
19+
export class UnknownFile extends File {
20+
resolveSymbol(name: string): StaticSymbol {
21+
throw new Error(`Trying to resolve symbol "${name}" in an unknown file language.`);
22+
}
23+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
export * from './file';
3+
export * from './symbol';
4+
5+
6+
// Typescript
7+
export * from './typescript/index';
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {File} from './file';
2+
3+
4+
export class StaticSymbol {
5+
constructor(protected _name: string, protected _file: File) {}
6+
7+
get file() { return this._file; }
8+
get name() { return this._name; }
9+
}
10+
11+
export class UnknownStaticSymbol extends StaticSymbol {}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import {Decorator} from './typescript/decorator';
2+
import {TypeScriptFile} from './typescript/file';
3+
import {Refactory} from '../refactory';
4+
import {VirtualRefactoryHost} from '../utils/virtual_refactory_host';
5+
import {NullRefactoryHost} from '../host';
6+
import {Path} from '../path';
7+
8+
9+
function pathOf(s: string) {
10+
return s as Path;
11+
}
12+
13+
14+
const tsFileSystem: {[path: string]: string} = {
15+
'/tsconfig.json': '{}',
16+
'/file.ts': `console.log('hello');`,
17+
'/file1.ts': `
18+
/**
19+
* @annotation
20+
*/
21+
function MyAnnotation(obj: any) { return () => {}; }
22+
function NotAnnotation() { return () => {}; }
23+
function NotFunctionCall() {}
24+
25+
@NotFunctionCall
26+
@NotAnnotation()
27+
@MyAnnotation({ hello: 'world' })
28+
class MyClass {}
29+
30+
export class MyOther {}
31+
`,
32+
'/file2.ts': `
33+
import {Symbol11} from './import1_def';
34+
import * as Def from './import1_def';
35+
import DefaultImport from './import1_def';
36+
37+
import 'import2_def';
38+
`,
39+
'/import1_def.ts': `
40+
export function Symbol11() {}
41+
export default function Symbol12() {}
42+
`,
43+
'/import2_def.ts': `
44+
export function Symbol21() {}
45+
export default function Symbol22() {}
46+
`,
47+
'/file3.ts': `
48+
import {someFunction} from './import3_def';
49+
50+
const someArgs = 1;
51+
someFunction().someMethod(someArgs);
52+
`,
53+
'/import3_def.ts': `
54+
export function someFunction() {
55+
return someObject;
56+
}
57+
58+
export class SomeClass {
59+
someMethod(arg: number) { return arg + 1; }
60+
}
61+
const someObject = new SomeClass();
62+
`
63+
};
64+
65+
66+
fdescribe('TypeScriptFile', () => {
67+
let refactory: Refactory;
68+
69+
beforeEach(() => {
70+
const host = new VirtualRefactoryHost(new NullRefactoryHost('/'));
71+
for (const p of Object.keys(tsFileSystem)) {
72+
host.write(p, tsFileSystem[p]);
73+
}
74+
75+
refactory = Refactory.fromTsConfig('/tsconfig.json', host);
76+
});
77+
78+
it('works with a file', () => {
79+
let file: TypeScriptFile = refactory.getFile(pathOf('/file.ts'), TypeScriptFile);
80+
expect(file).not.toBeNull();
81+
expect(file.transpile({}).outputText).toMatch(/console.log\('hello'\)/);
82+
});
83+
84+
describe('classes', () => {
85+
it('can see class names', () => {
86+
let file1: TypeScriptFile = refactory.getFile(pathOf('/file1.ts')) as TypeScriptFile;
87+
expect(file1.classes.map(c => c.name)).toEqual(['MyClass', 'MyOther']);
88+
});
89+
90+
it('can see decorators', () => {
91+
let file1: TypeScriptFile = refactory.getFile(pathOf('/file1.ts')) as TypeScriptFile;
92+
// Should ignore NotAnnotation.
93+
expect(file1.classes[0].decorators.map((a: Decorator) => a.name))
94+
.toEqual(['NotFunctionCall', 'NotAnnotation', 'MyAnnotation']);
95+
});
96+
97+
it('can remove()', () => {
98+
let file1: TypeScriptFile = refactory.getFile(pathOf('/file1.ts')) as TypeScriptFile;
99+
file1.classes[0].remove();
100+
101+
const output = file1.transpile({}).outputText;
102+
expect(output).toContain('MyOther');
103+
expect(output).not.toContain('MyClass');
104+
});
105+
106+
it('knows if its exported', () => {
107+
let file1: TypeScriptFile = refactory.getFile(pathOf('/file1.ts')) as TypeScriptFile;
108+
expect(file1.classes[0].isExported).toBe(false);
109+
expect(file1.classes[1].isExported).toBe(true);
110+
});
111+
});
112+
113+
describe('imports', () => {
114+
it('understands imports', () => {
115+
let import1: TypeScriptFile = refactory.getFile(pathOf('/file2.ts')) as TypeScriptFile;
116+
expect(import1.imports.map(i => i.name)).toEqual(['Symbol11']);
117+
});
118+
});
119+
120+
121+
describe('symbols', () => {
122+
it('understands imports', () => {
123+
const file3: TypeScriptFile = refactory.getFile(pathOf('/file3.ts'), TypeScriptFile);
124+
// expect(file3).toBe
125+
const symbol = file3.resolveSymbol('someFunction', false);
126+
127+
expect(symbol).not.toBeNull();
128+
// expect(import1.imports.map(i => i.name)).toEqual(['Symbol11']);
129+
});
130+
});
131+
});

0 commit comments

Comments
 (0)