Skip to content

Commit 2a22211

Browse files
MikeRyanDevbrandonroberts
authored andcommitted
feat(Codegen): Add base code and build for @ngrx/codegen (#534)
1 parent 88f672c commit 2a22211

27 files changed

+556
-13
lines changed

build/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,8 @@ export const packages: PackageDescription[] = [
2929
name: 'entity',
3030
hasTestingModule: false,
3131
},
32+
{
33+
name: 'codegen',
34+
hasTestingModule: false,
35+
},
3236
];

modules/codegen/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@ngrx/codegen
2+
=======
3+
4+
The sources for this package are in the main [ngrx/platform](https://github.com/ngrx/platform) repo. Please file issues and pull requests against that repo.
5+
6+
License: MIT

modules/codegen/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* DO NOT EDIT
3+
*
4+
* This file is automatically generated at build
5+
*/
6+
7+
export * from './public_api';

modules/codegen/package.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "@ngrx/codegen",
3+
"version": "4.1.0",
4+
"description": "Codegen for Ngrx and Redux actions",
5+
"module": "@ngrx/codegen.es5.js",
6+
"es2015": "@ngrx/codegen.js",
7+
"main": "bundles/codegen.umd.js",
8+
"typings": "codegen.d.ts",
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/ngrx/platform.git"
12+
},
13+
"authors": ["Mike Ryan"],
14+
"license": "MIT",
15+
"dependencies": {
16+
"glob": "^7.1.2",
17+
"lodash": "^4.17.4",
18+
"ora": "^1.3.0",
19+
"typescript": "^2.4.0"
20+
}
21+
}

modules/codegen/public_api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './src/index';

modules/codegen/rollup.config.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export default {
2+
entry: './dist/codegen/@ngrx/codegen.es5.js',
3+
dest: './dist/codegen/bundles/codegen.umd.js',
4+
format: 'umd',
5+
exports: 'named',
6+
moduleName: 'ngrx.codegen',
7+
globals: {
8+
9+
}
10+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import * as _ from 'lodash';
2+
3+
export interface ActionInterfaceProperty {
4+
name: string;
5+
required: boolean;
6+
}
7+
8+
export interface ActionInterface {
9+
name: string;
10+
actionType: string;
11+
properties: ActionInterfaceProperty[];
12+
}
13+
14+
const actionTypeRegex = new RegExp(/\[(.*?)\](.*)/);
15+
function parseActionType(type: string) {
16+
const result = actionTypeRegex.exec(type);
17+
18+
if (result === null) {
19+
throw new Error(`Could not parse action type "${type}"`);
20+
}
21+
22+
return {
23+
category: result[1] as string,
24+
name: result[2] as string,
25+
};
26+
}
27+
28+
export const getActionType = (enterface: ActionInterface) =>
29+
enterface.actionType;
30+
export const getActionName = (enterface: ActionInterface) => enterface.name;
31+
export const getActionCategory = _.flow(
32+
getActionType,
33+
parseActionType,
34+
v => v.category
35+
);
36+
export const getActionCategoryToken = _.flow(
37+
getActionCategory,
38+
_.camelCase,
39+
_.upperFirst
40+
);
41+
export const getActionEnumName = _.flow(
42+
getActionCategoryToken,
43+
v => `${v}ActionType`
44+
);
45+
export const getActionEnumPropName = _.flow(getActionName, _.snakeCase, v =>
46+
v.toUpperCase()
47+
);
48+
export const getActionUnionName = _.flow(
49+
getActionCategoryToken,
50+
v => `${v}Actions`
51+
);
52+
export const getActionLookupName = _.flow(
53+
getActionCategoryToken,
54+
v => `${v}ActionLookup`
55+
);
56+
export const getActionFactoryName = _.flow(
57+
getActionName,
58+
_.camelCase,
59+
_.upperFirst,
60+
v => `create${v}`
61+
);

modules/codegen/src/codegen.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
import * as ts from 'typescript';
4+
import { collectMetadata, printActionFactory } from './collect-metadata';
5+
import { findFiles } from './find-files';
6+
const ora = require('ora');
7+
8+
async function readFile(file: string): Promise<string> {
9+
return new Promise<string>((resolve, reject) => {
10+
fs.readFile(file, 'utf8', (error, data) => {
11+
if (error) {
12+
reject(error);
13+
} else {
14+
resolve(data);
15+
}
16+
});
17+
});
18+
}
19+
20+
async function writeFile(file: string, contents: string): Promise<any> {
21+
return new Promise((resolve, reject) => {
22+
fs.writeFile(file, contents, { encoding: 'utf8' }, error => {
23+
if (error) {
24+
reject(error);
25+
} else {
26+
resolve();
27+
}
28+
});
29+
});
30+
}
31+
32+
function createSourceFile(data: string) {
33+
return ts.createSourceFile('', data, ts.ScriptTarget.ES2015, true);
34+
}
35+
36+
export async function codegen(glob: string) {
37+
const filesIndicator = ora(`Searching for files matching "${glob}"`).start();
38+
const files = await findFiles(glob);
39+
filesIndicator.succeed(`Found ${files.length} files for pattern "${glob}"`);
40+
41+
for (let file of files) {
42+
const indicator = ora(file).start();
43+
44+
try {
45+
const parsedPath = path.parse(file);
46+
const contents = await readFile(file);
47+
const sourceFile = createSourceFile(contents);
48+
const ast = collectMetadata(parsedPath.name, sourceFile);
49+
50+
if (!ast) {
51+
throw new Error(`No actions found for file "${file}"`);
52+
}
53+
54+
const output = printActionFactory(ast);
55+
const target = path.resolve(
56+
parsedPath.dir,
57+
`./${parsedPath.name}.helpers.ts`
58+
);
59+
await writeFile(target, output);
60+
61+
indicator.succeed(`Found ${ast.length} actions in ${file}`);
62+
} catch (e) {
63+
indicator.fail((e as Error).message);
64+
}
65+
}
66+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import * as ts from 'typescript';
2+
import * as _ from 'lodash';
3+
import * as fs from 'fs';
4+
import * as path from 'path';
5+
import * as collector from './metadata/index';
6+
import * as printers from './printers/index';
7+
import { ActionInterface } from './action-interface';
8+
9+
export interface ActionMetadata {
10+
name: string;
11+
type: string;
12+
properties: { name: string; optional: boolean }[];
13+
}
14+
15+
export function collectMetadata(
16+
fileName: string,
17+
sourceFile: ts.SourceFile
18+
): ts.Node[] | undefined {
19+
const interfaces = sourceFile.statements
20+
.filter(ts.isInterfaceDeclaration)
21+
.filter(collector.isExported)
22+
.filter(collector.isActionDescendent)
23+
.filter(m => !!collector.getType(m))
24+
.map((enterface): ActionInterface => ({
25+
name: enterface.name.getText(),
26+
actionType: _.trim(
27+
collector.getType(enterface)!.literal.getFullText(),
28+
' \'"`'
29+
),
30+
properties: [
31+
...collector.getRequiredProperties(collector.getProperties(enterface)),
32+
...collector.getOptionalProperties(collector.getProperties(enterface)),
33+
],
34+
}));
35+
36+
if (interfaces.length === 0) {
37+
undefined;
38+
}
39+
40+
return [
41+
printers.printImportDeclaration(fileName, interfaces),
42+
printers.printEnumDeclaration(interfaces),
43+
printers.printTypeUnionDeclaration(interfaces),
44+
printers.printTypeDictionaryDeclaration(interfaces),
45+
...interfaces.map(action => printers.printActionFactoryDeclaration(action)),
46+
];
47+
}
48+
49+
export function printActionFactory(ast: ts.Node[]) {
50+
const resultFile = ts.createSourceFile(
51+
'',
52+
'',
53+
ts.ScriptTarget.ES2015,
54+
false,
55+
ts.ScriptKind.TS
56+
);
57+
58+
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
59+
60+
return ast
61+
.map(statement =>
62+
printer.printNode(ts.EmitHint.Unspecified, statement, resultFile)
63+
)
64+
.join('\n\n');
65+
}

modules/codegen/src/find-files.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as path from 'path';
2+
const glob = require('glob');
3+
4+
export function findFiles(globPattern: string): Promise<string[]> {
5+
return new Promise((resolve, reject) => {
6+
glob(
7+
globPattern,
8+
{ cwd: process.cwd(), ignore: ['**/node_modules/**'] },
9+
(error: any, files: string[]) => {
10+
if (error) {
11+
return reject(error);
12+
}
13+
14+
resolve(files);
15+
}
16+
);
17+
});
18+
}

0 commit comments

Comments
 (0)