-
-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Codegen): Add base code and build for @ngrx/codegen (#534)
- Loading branch information
1 parent
88f672c
commit 2a22211
Showing
27 changed files
with
556 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
@ngrx/codegen | ||
======= | ||
|
||
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. | ||
|
||
License: MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/** | ||
* DO NOT EDIT | ||
* | ||
* This file is automatically generated at build | ||
*/ | ||
|
||
export * from './public_api'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"name": "@ngrx/codegen", | ||
"version": "4.1.0", | ||
"description": "Codegen for Ngrx and Redux actions", | ||
"module": "@ngrx/codegen.es5.js", | ||
"es2015": "@ngrx/codegen.js", | ||
"main": "bundles/codegen.umd.js", | ||
"typings": "codegen.d.ts", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/ngrx/platform.git" | ||
}, | ||
"authors": ["Mike Ryan"], | ||
"license": "MIT", | ||
"dependencies": { | ||
"glob": "^7.1.2", | ||
"lodash": "^4.17.4", | ||
"ora": "^1.3.0", | ||
"typescript": "^2.4.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './src/index'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
export default { | ||
entry: './dist/codegen/@ngrx/codegen.es5.js', | ||
dest: './dist/codegen/bundles/codegen.umd.js', | ||
format: 'umd', | ||
exports: 'named', | ||
moduleName: 'ngrx.codegen', | ||
globals: { | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import * as _ from 'lodash'; | ||
|
||
export interface ActionInterfaceProperty { | ||
name: string; | ||
required: boolean; | ||
} | ||
|
||
export interface ActionInterface { | ||
name: string; | ||
actionType: string; | ||
properties: ActionInterfaceProperty[]; | ||
} | ||
|
||
const actionTypeRegex = new RegExp(/\[(.*?)\](.*)/); | ||
function parseActionType(type: string) { | ||
const result = actionTypeRegex.exec(type); | ||
|
||
if (result === null) { | ||
throw new Error(`Could not parse action type "${type}"`); | ||
} | ||
|
||
return { | ||
category: result[1] as string, | ||
name: result[2] as string, | ||
}; | ||
} | ||
|
||
export const getActionType = (enterface: ActionInterface) => | ||
enterface.actionType; | ||
export const getActionName = (enterface: ActionInterface) => enterface.name; | ||
export const getActionCategory = _.flow( | ||
getActionType, | ||
parseActionType, | ||
v => v.category | ||
); | ||
export const getActionCategoryToken = _.flow( | ||
getActionCategory, | ||
_.camelCase, | ||
_.upperFirst | ||
); | ||
export const getActionEnumName = _.flow( | ||
getActionCategoryToken, | ||
v => `${v}ActionType` | ||
); | ||
export const getActionEnumPropName = _.flow(getActionName, _.snakeCase, v => | ||
v.toUpperCase() | ||
); | ||
export const getActionUnionName = _.flow( | ||
getActionCategoryToken, | ||
v => `${v}Actions` | ||
); | ||
export const getActionLookupName = _.flow( | ||
getActionCategoryToken, | ||
v => `${v}ActionLookup` | ||
); | ||
export const getActionFactoryName = _.flow( | ||
getActionName, | ||
_.camelCase, | ||
_.upperFirst, | ||
v => `create${v}` | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import * as fs from 'fs'; | ||
import * as path from 'path'; | ||
import * as ts from 'typescript'; | ||
import { collectMetadata, printActionFactory } from './collect-metadata'; | ||
import { findFiles } from './find-files'; | ||
const ora = require('ora'); | ||
|
||
async function readFile(file: string): Promise<string> { | ||
return new Promise<string>((resolve, reject) => { | ||
fs.readFile(file, 'utf8', (error, data) => { | ||
if (error) { | ||
reject(error); | ||
} else { | ||
resolve(data); | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
async function writeFile(file: string, contents: string): Promise<any> { | ||
return new Promise((resolve, reject) => { | ||
fs.writeFile(file, contents, { encoding: 'utf8' }, error => { | ||
if (error) { | ||
reject(error); | ||
} else { | ||
resolve(); | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
function createSourceFile(data: string) { | ||
return ts.createSourceFile('', data, ts.ScriptTarget.ES2015, true); | ||
} | ||
|
||
export async function codegen(glob: string) { | ||
const filesIndicator = ora(`Searching for files matching "${glob}"`).start(); | ||
const files = await findFiles(glob); | ||
filesIndicator.succeed(`Found ${files.length} files for pattern "${glob}"`); | ||
|
||
for (let file of files) { | ||
const indicator = ora(file).start(); | ||
|
||
try { | ||
const parsedPath = path.parse(file); | ||
const contents = await readFile(file); | ||
const sourceFile = createSourceFile(contents); | ||
const ast = collectMetadata(parsedPath.name, sourceFile); | ||
|
||
if (!ast) { | ||
throw new Error(`No actions found for file "${file}"`); | ||
} | ||
|
||
const output = printActionFactory(ast); | ||
const target = path.resolve( | ||
parsedPath.dir, | ||
`./${parsedPath.name}.helpers.ts` | ||
); | ||
await writeFile(target, output); | ||
|
||
indicator.succeed(`Found ${ast.length} actions in ${file}`); | ||
} catch (e) { | ||
indicator.fail((e as Error).message); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import * as ts from 'typescript'; | ||
import * as _ from 'lodash'; | ||
import * as fs from 'fs'; | ||
import * as path from 'path'; | ||
import * as collector from './metadata/index'; | ||
import * as printers from './printers/index'; | ||
import { ActionInterface } from './action-interface'; | ||
|
||
export interface ActionMetadata { | ||
name: string; | ||
type: string; | ||
properties: { name: string; optional: boolean }[]; | ||
} | ||
|
||
export function collectMetadata( | ||
fileName: string, | ||
sourceFile: ts.SourceFile | ||
): ts.Node[] | undefined { | ||
const interfaces = sourceFile.statements | ||
.filter(ts.isInterfaceDeclaration) | ||
.filter(collector.isExported) | ||
.filter(collector.isActionDescendent) | ||
.filter(m => !!collector.getType(m)) | ||
.map((enterface): ActionInterface => ({ | ||
name: enterface.name.getText(), | ||
actionType: _.trim( | ||
collector.getType(enterface)!.literal.getFullText(), | ||
' \'"`' | ||
), | ||
properties: [ | ||
...collector.getRequiredProperties(collector.getProperties(enterface)), | ||
...collector.getOptionalProperties(collector.getProperties(enterface)), | ||
], | ||
})); | ||
|
||
if (interfaces.length === 0) { | ||
undefined; | ||
} | ||
|
||
return [ | ||
printers.printImportDeclaration(fileName, interfaces), | ||
printers.printEnumDeclaration(interfaces), | ||
printers.printTypeUnionDeclaration(interfaces), | ||
printers.printTypeDictionaryDeclaration(interfaces), | ||
...interfaces.map(action => printers.printActionFactoryDeclaration(action)), | ||
]; | ||
} | ||
|
||
export function printActionFactory(ast: ts.Node[]) { | ||
const resultFile = ts.createSourceFile( | ||
'', | ||
'', | ||
ts.ScriptTarget.ES2015, | ||
false, | ||
ts.ScriptKind.TS | ||
); | ||
|
||
const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); | ||
|
||
return ast | ||
.map(statement => | ||
printer.printNode(ts.EmitHint.Unspecified, statement, resultFile) | ||
) | ||
.join('\n\n'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import * as path from 'path'; | ||
const glob = require('glob'); | ||
|
||
export function findFiles(globPattern: string): Promise<string[]> { | ||
return new Promise((resolve, reject) => { | ||
glob( | ||
globPattern, | ||
{ cwd: process.cwd(), ignore: ['**/node_modules/**'] }, | ||
(error: any, files: string[]) => { | ||
if (error) { | ||
return reject(error); | ||
} | ||
|
||
resolve(files); | ||
} | ||
); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { codegen } from './codegen'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import * as ts from 'typescript'; | ||
import { ActionInterfaceProperty } from '../action-interface'; | ||
|
||
export function getOptionalProperties( | ||
props: ts.PropertySignature[] | ||
): ActionInterfaceProperty[] { | ||
return props.filter(prop => prop.questionToken).map(prop => ({ | ||
name: prop.name.getText(), | ||
required: false, | ||
})); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import * as ts from 'typescript'; | ||
|
||
export function getProperties( | ||
node: ts.InterfaceDeclaration | ||
): ts.PropertySignature[] { | ||
return node.members.filter(ts.isPropertySignature); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import * as ts from 'typescript'; | ||
import { ActionInterfaceProperty } from '../action-interface'; | ||
|
||
export function getRequiredProperties( | ||
props: ts.PropertySignature[] | ||
): ActionInterfaceProperty[] { | ||
return props | ||
.filter(prop => !prop.questionToken) | ||
.map(prop => ({ | ||
name: prop.name.getText(), | ||
required: true, | ||
})) | ||
.filter(({ name }) => name !== 'type'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import * as ts from 'typescript'; | ||
import { getProperties } from './get-properties'; | ||
|
||
export function getType( | ||
action: ts.InterfaceDeclaration | ||
): ts.LiteralTypeNode | undefined { | ||
const typeProperty = getProperties(action).find( | ||
property => property.name.getText() === 'type' | ||
); | ||
|
||
if (!typeProperty) { | ||
return undefined; | ||
} | ||
|
||
return ts.isLiteralTypeNode(typeProperty.type as any) | ||
? typeProperty.type as any | ||
: undefined; | ||
|
||
// return !!typeProperty && ts.isLiteralTypeNode(typeProperty.type) ? typeProperty.type : undefined; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export * from './get-optional-properties'; | ||
export * from './get-properties'; | ||
export * from './get-required-properties'; | ||
export * from './get-type'; | ||
export * from './is-action-descendent'; | ||
export * from './is-exported'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import * as ts from 'typescript'; | ||
|
||
export function isActionDescendent( | ||
statement: ts.InterfaceDeclaration | ||
): boolean { | ||
const heritageClauses = statement.heritageClauses; | ||
|
||
if (heritageClauses) { | ||
return heritageClauses.some(clause => { | ||
/** | ||
* TODO: This breaks if the interface looks like this: | ||
* | ||
* interface MyAction extends ngrx.Action { } | ||
* | ||
*/ | ||
return clause.types.some(type => type.expression.getText() === 'Action'); | ||
}); | ||
} | ||
|
||
return false; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import * as ts from 'typescript'; | ||
|
||
function hasExportModifier(node: ts.Node): boolean { | ||
return (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0; | ||
} | ||
|
||
function isTopLevel(node: ts.Node): boolean { | ||
return !!node.parent && node.parent.kind === ts.SyntaxKind.SourceFile; | ||
} | ||
|
||
export function isExported(node: ts.Node): boolean { | ||
return hasExportModifier(node) && isTopLevel(node); | ||
} |
Oops, something went wrong.