UNPKG

ng-alain

Version:

Schematics specific to NG-ALAIN

187 lines (166 loc) 6.04 kB
import { tags, normalize } from '@angular-devkit/core'; import { SchematicsException, Tree } from '@angular-devkit/schematics'; import { getSourceNodes, getMetadataField, addProviderToModule as _addProviderToModule } from '@schematics/angular/utility/ast-utils'; import { Change, InsertChange } from '@schematics/angular/utility/change'; import * as ts from 'typescript'; import { addImportToModule } from './alain'; export const ROUTINS_FILENAME = 'routes.ts'; /** Reads file given path and returns TypeScript source file. */ export function getSourceFile(tree: Tree, path: string): ts.SourceFile { const buffer = tree.read(path); if (!buffer) { throw new SchematicsException(`Could not find file for path: ${path}`); } const content = buffer.toString(); return ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true); } export function applyChanges(tree: Tree, path: string, changes: Change[]): void { const exportRecorder = tree.beginUpdate(path); for (const change of changes) { if (change instanceof InsertChange) { exportRecorder.insertLeft(change.pos, change.toAdd); } } tree.commitUpdate(exportRecorder); } function getComponentMetadata(source: ts.SourceFile): ts.Node[] { const allNodes = getSourceNodes(source); const identifier = 'Component'; return allNodes .filter(node => { return ( node.kind == ts.SyntaxKind.Decorator && (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression ); }) .map(node => (node as ts.Decorator).expression as ts.CallExpression) .filter(expr => { if (expr.expression.kind == ts.SyntaxKind.Identifier) { const id = expr.expression as ts.Identifier; return id.text == identifier; } return false; }) .filter(expr => expr.arguments[0] && expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression) .map(expr => expr.arguments[0] as ts.ObjectLiteralExpression); } function addSymbolToComponentMetadata( source: ts.SourceFile, filePath: string, symbolName: string, metadataField = 'imports' ): Change[] { const nodes = getComponentMetadata(source); if (nodes.length <= 0) return []; const node = nodes[0]; if (!node || !ts.isObjectLiteralExpression(node)) { return []; } // Get all the children property assignment of object literals. const matchingProperties = getMetadataField(node, metadataField); if (matchingProperties.length == 0) { // We haven't found the field in the metadata declaration. Insert a new field. let position: number; let toInsert: string; if (node.properties.length == 0) { position = node.getEnd() - 1; toInsert = `\n ${metadataField}: [\n${tags.indentBy(4)`${symbolName}`}\n ]\n`; } else { const childNode = node.properties[node.properties.length - 1]; position = childNode.getEnd(); // Get the indentation of the last element, if any. const text = childNode.getFullText(source); const matches = text.match(/^(\r?\n)(\s*)/); if (matches) { toInsert = `,${matches[0]}${metadataField}: [${matches[1]}` + `${tags.indentBy(matches[2].length + 2)`${symbolName}`}${matches[0]}]`; } else { toInsert = `, ${metadataField}: [${symbolName}]`; } } return [new InsertChange(filePath, position, toInsert)]; } const assignment = matchingProperties[0] as ts.Node; // If it's not an array, nothing we can do really. if (!ts.isPropertyAssignment(assignment) || !ts.isArrayLiteralExpression(assignment.initializer)) { return []; } let expresssion: ts.Expression | ts.ArrayLiteralExpression; const assignmentInit = assignment.initializer; const elements = assignmentInit.elements; if (elements.length) { const symbolsArray = elements.map(node => tags.oneLine`${node.getText()}`); if (symbolsArray.includes(tags.oneLine`${symbolName}`)) { return []; } expresssion = elements[elements.length - 1]; } else { expresssion = assignmentInit; } let toInsert: string; let position = expresssion.getEnd(); if (ts.isArrayLiteralExpression(expresssion)) { // We found the field but it's empty. Insert it just before the `]`. position--; toInsert = `\n${tags.indentBy(4)`${symbolName}`}\n `; } else { // Get the indentation of the last element, if any. const text = expresssion.getFullText(source); const matches = text.match(/^(\r?\n)(\s*)/); if (matches) { toInsert = `,${matches[1]}${tags.indentBy(matches[2].length)`${symbolName}`}`; } else { toInsert = `, ${symbolName}`; } } return [new InsertChange(filePath, position, toInsert)]; } export function findRoutesPath(tree: Tree, path: string): string { let dir = tree.getDir(path); while (dir) { const found = dir.subfiles.filter(p => p.endsWith(ROUTINS_FILENAME)); if (found.length > 0) { return normalize(`${dir.path}/${ROUTINS_FILENAME}`); } dir = dir.parent; } return ''; } export function importInStandalone( tree: Tree, filePath: string, componentName: string, componentPath: string, metadataField = 'imports' ): void { // imports addImportToModule(tree, filePath, componentName, componentPath); // import in component const source = getSourceFile(tree, filePath); const changes = addSymbolToComponentMetadata(source, filePath, componentName, metadataField); applyChanges(tree, filePath, changes); } export function addServiceToModuleOrStandalone( tree: Tree, standalone: boolean, filePath: string, serviceName: string, importPath: string ): void { const source = getSourceFile(tree, filePath); if (standalone) { importInStandalone(tree, filePath, serviceName, importPath, 'providers'); } else { const changes = _addProviderToModule(source, filePath, serviceName, importPath); applyChanges(tree, filePath, changes); } } export function consoleTree(tree: Tree): void { tree.visit(filePath => { console.log(filePath); }); }