Skip to content

Commit 49b031d

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 87b93e1 commit 49b031d

27 files changed

+1073
-341
lines changed

lib/packages.js

+9-2
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

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
"less-loader": "^2.2.3",
8282
"loader-utils": "^0.2.16",
8383
"lodash": "^4.11.1",
84-
"magic-string": "^0.16.0",
84+
"magic-string": "^0.19.0",
8585
"markdown-it": "4.3.0",
8686
"markdown-it-terminal": "0.0.3",
8787
"minimatch": "^3.0.0",

packages/@ngtools/refactory/README.md

+39
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.
+36
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+
}
+74
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+
}
+10
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';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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 UnknownFile extends File {
14+
resolveSymbol(name: string): StaticSymbol {
15+
throw new Error(`Trying to resolve symbol "${name}" in an unknown file language.`);
16+
}
17+
}
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';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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+
7+
const tsFileSystem: {[path: string]: string} = {
8+
'/tsconfig.json': '{}',
9+
'/file.ts': `console.log('hello');`,
10+
'/file1.ts': `
11+
/**
12+
* @annotation
13+
*/
14+
function MyAnnotation(obj: any) { return () => {}; }
15+
function NotAnnotation() { return () => {}; }
16+
function NotFunctionCall() {}
17+
18+
@NotFunctionCall
19+
@NotAnnotation()
20+
@MyAnnotation({ hello: 'world' })
21+
class MyClass {}
22+
23+
export class MyOther {}
24+
`,
25+
'/import1.ts': `
26+
import {Symbol11} from './import1_def';
27+
import * as Def from './import1_def';
28+
import DefaultImport from './import1_def';
29+
30+
import 'import2_def';
31+
`,
32+
'/import1_def.ts': `
33+
export function Symbol11() {}
34+
export default function Symbol12() {}
35+
`,
36+
'/import2_def.ts': `
37+
export function Symbol21() {}
38+
export default function Symbol22() {}
39+
`
40+
};
41+
42+
43+
describe('TypeScriptFile', () => {
44+
let refactory: Refactory;
45+
46+
beforeEach(() => {
47+
const host = new VirtualRefactoryHost(new NullRefactoryHost('/'));
48+
for (const p of Object.keys(tsFileSystem)) {
49+
host.write(p, tsFileSystem[p]);
50+
}
51+
52+
refactory = Refactory.fromTsConfig('/tsconfig.json', host);
53+
});
54+
55+
it('works with a file', () => {
56+
let file: TypeScriptFile = refactory.getFile('/file.ts') as TypeScriptFile;
57+
expect(file).not.toBeNull();
58+
expect(file.transpile({}).outputText).toMatch(/console.log\('hello'\)/);
59+
});
60+
61+
describe('classes', () => {
62+
it('can see class names', () => {
63+
let file1: TypeScriptFile = refactory.getFile('/file1.ts') as TypeScriptFile;
64+
expect(file1.classes.map(c => c.name)).toEqual(['MyClass', 'MyOther']);
65+
});
66+
67+
it('can see decorators', () => {
68+
let file1: TypeScriptFile = refactory.getFile('/file1.ts') as TypeScriptFile;
69+
// Should ignore NotAnnotation.
70+
expect(file1.classes[0].decorators.map((a: Decorator) => a.name))
71+
.toEqual(['NotFunctionCall', 'NotAnnotation', 'MyAnnotation']);
72+
});
73+
74+
it('can remove()', () => {
75+
let file1: TypeScriptFile = refactory.getFile('/file1.ts') as TypeScriptFile;
76+
file1.classes[0].remove();
77+
78+
const output = file1.transpile({}).outputText;
79+
expect(output).toContain('MyOther');
80+
expect(output).not.toContain('MyClass');
81+
});
82+
83+
it('knows if its exported', () => {
84+
let file1: TypeScriptFile = refactory.getFile('/file1.ts') as TypeScriptFile;
85+
expect(file1.classes[0].isExported).toBe(false);
86+
expect(file1.classes[1].isExported).toBe(true);
87+
});
88+
});
89+
90+
describe('imports', () => {
91+
it('understands imports', () => {
92+
let import1: TypeScriptFile = refactory.getFile('/import1.ts') as TypeScriptFile;
93+
expect(import1.imports.map(i => i.name)).toEqual(['Symbol11']);
94+
});
95+
});
96+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import * as ts from 'typescript';
2+
import {TypeScriptFile} from './file';
3+
import {Decorator} from './decorator';
4+
import {TypeScriptStaticSymbol} from './symbol';
5+
6+
7+
export class Class extends TypeScriptStaticSymbol<ts.ClassDeclaration> {
8+
private constructor(node: ts.ClassDeclaration, file: TypeScriptFile) {
9+
super(node.name.text, node, file);
10+
}
11+
12+
static fromNode(node: ts.Node, file: TypeScriptFile): Class {
13+
if (node.kind !== ts.SyntaxKind.ClassDeclaration) {
14+
throw new Error(`Node of kind ${node.kind} is not a class declaration.`);
15+
}
16+
17+
return new Class(node as ts.ClassDeclaration, file);
18+
}
19+
20+
private _decorators: Decorator[] = null;
21+
get decorators(): Decorator[] {
22+
if (!this._decorators) {
23+
this._decorators = this._node.decorators
24+
.map(node => Decorator.fromNode(node, this, this.file))
25+
.filter(node => !!node);
26+
}
27+
return this._decorators;
28+
}
29+
private _exported: boolean | null = null;
30+
get isExported(): boolean {
31+
if (this._exported === null) {
32+
this._exported = ((this._node.modifiers || []) as Array<ts.Node>)
33+
.some((m: ts.Node) => m.kind == ts.SyntaxKind.ExportKeyword);
34+
}
35+
return this._exported;
36+
}
37+
}

0 commit comments

Comments
 (0)