UNPKG

@nx/angular

Version:

The Nx Plugin for Angular contains executors, generators, and utilities for managing Angular applications and libraries within an Nx workspace. It provides: - Integration with libraries such as Storybook, Jest, ESLint, Tailwind CSS, Playwright and Cypre

215 lines (214 loc) • 9.46 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getModuleDeclarations = getModuleDeclarations; exports.getModuleFilePaths = getModuleFilePaths; const devkit_1 = require("@nx/devkit"); const js_1 = require("@nx/js"); const path_1 = require("path"); const ast_utils_1 = require("../../../utils/nx-devkit/ast-utils"); const ensure_typescript_1 = require("@nx/js/src/utils/typescript/ensure-typescript"); let tsModule; function getModuleDeclarations(file, moduleFilePath, projectName) { const ngModuleDecorator = getNgModuleDecorator(file, moduleFilePath); const declarationsPropertyAssignment = getNgModuleDeclarationsPropertyAssignment(ngModuleDecorator, moduleFilePath, projectName); if (!declarationsPropertyAssignment) { return []; } const declarationsArray = getDeclarationsArray(file, declarationsPropertyAssignment, moduleFilePath, projectName); if (!declarationsArray) { return []; } return getDeclaredComponentNodes(declarationsArray).map((node) => node.getText()); } function getModuleFilePaths(tree, entryPoint) { let moduleFilePaths = []; (0, devkit_1.visitNotIgnoredFiles)(tree, entryPoint.path, (filePath) => { const normalizedFilePath = (0, devkit_1.normalizePath)(filePath); if (entryPoint.excludeDirs?.some((excludeDir) => normalizedFilePath.startsWith(excludeDir))) { return; } if ((0, path_1.extname)(normalizedFilePath) === '.ts' && !normalizedFilePath.includes('.storybook') && hasNgModule(tree, normalizedFilePath)) { moduleFilePaths.push(normalizedFilePath); } }); return moduleFilePaths; } function hasNgModule(tree, filePath) { (0, ensure_typescript_1.ensureTypescript)(); const { tsquery } = require('@phenomnomnominal/tsquery'); const fileContent = tree.read(filePath, 'utf-8'); const ast = tsquery.ast(fileContent); const ngModule = tsquery(ast, 'ClassDeclaration > Decorator > CallExpression > Identifier[name=NgModule]', { visitAllChildren: true }); return ngModule.length > 0; } function getDeclaredComponentNodes(declarationsArray) { if (!tsModule) { tsModule = (0, ensure_typescript_1.ensureTypescript)(); } const cmps = declarationsArray .getChildren() .find((node) => node.kind === tsModule.SyntaxKind.SyntaxList) .getChildren() .map((node) => { if (node.kind === tsModule.SyntaxKind.Identifier) { return node; } // if the node is a destructuring, follow the variable if (node.kind === tsModule.SyntaxKind.SpreadElement) { const declarationVariableNode = node .getChildren() .find((node) => node.kind === tsModule.SyntaxKind.Identifier); // try to find the variable declaration in the same component const declarationVariable = getVariableDeclaration(declarationVariableNode.getText(), declarationVariableNode.getSourceFile()); if (declarationVariable && declarationVariable.initializer.kind === tsModule.SyntaxKind.ArrayLiteralExpression) { const nodes = getDeclaredComponentNodes(declarationVariable.initializer); return nodes; } } return null; }) .filter((node) => !!node); return flatten(cmps); } function flatten(arr) { let flattened = []; for (const entry of arr) { if (Array.isArray(entry)) { flattened.push(...flatten(entry)); } else { flattened.push(entry); } } return flattened; } function getDeclarationsArray(file, declarationsPropertyAssignment, moduleFilePath, projectName) { if (!tsModule) { tsModule = (0, ensure_typescript_1.ensureTypescript)(); } let declarationArray = declarationsPropertyAssignment .getChildren() .find((node) => node.kind === tsModule.SyntaxKind.ArrayLiteralExpression); if (declarationArray) { return declarationArray; } // Attempt to follow a variable instead of the literal declarationArray = getModuleDeclaredComponentsFromVariable(file, declarationsPropertyAssignment); if (declarationArray) { return declarationArray; } // Attempt to follow a class declaration instead of the literal declarationArray = getModuleDeclaredComponentsFromClass(file, declarationsPropertyAssignment); if (!declarationArray) { devkit_1.logger.warn((0, devkit_1.stripIndents) `No stories generated because the declarations in ${moduleFilePath} is not an array literal or the variable could not be found. Hint: you can always generate stories later with the 'nx generate @nx/angular:stories --name=${projectName}' command.`); } return declarationArray; } /** * Try to get declared components like `declarations: someComponentsArrayConst` */ function getModuleDeclaredComponentsFromVariable(file, declarationsPropertyAssignment) { if (!tsModule) { tsModule = (0, ensure_typescript_1.ensureTypescript)(); } let declarationsVariable = declarationsPropertyAssignment .getChildren() .filter((node) => node.kind === tsModule.SyntaxKind.Identifier)[1]; if (!declarationsVariable) { return undefined; } // Attempt to find variable declaration in the file let variableDeclaration = getVariableDeclaration(declarationsVariable.getText(), file); if (!variableDeclaration) { return undefined; } const declarationArray = variableDeclaration .getChildren() .find((node) => node.kind === tsModule.SyntaxKind.ArrayLiteralExpression); return declarationArray; } /** * Try to get declared components like `declarations: SomeClass.components` as in * https://github.com/nrwl/nx/issues/7276. */ function getModuleDeclaredComponentsFromClass(file, declarationsPropertyAssignment) { if (!tsModule) { tsModule = (0, ensure_typescript_1.ensureTypescript)(); } const propertyAccessExpression = declarationsPropertyAssignment .getChildren() .filter((node) => node.kind === tsModule.SyntaxKind.PropertyAccessExpression)[0]; if (!propertyAccessExpression) { return undefined; } // Should contain 2 identifiers [SomeClass, components] const [clazz, componentsProperty] = propertyAccessExpression .getChildren() .filter((node) => node.kind === tsModule.SyntaxKind.Identifier); if (!clazz || !componentsProperty) { return undefined; } // Attempt to find class declaration in the file let classDeclaration = getClassDeclaration(clazz.getText(), file); if (!classDeclaration) { return undefined; } const declarationArray = classDeclaration.members .filter((node) => node.kind === tsModule.SyntaxKind.PropertyDeclaration) .find((propertyDeclaration) => propertyDeclaration .getChildren() .find((node) => node.kind === tsModule.SyntaxKind.Identifier && node.getText() === componentsProperty.getText())) .getChildren() .find((node) => node.kind === tsModule.SyntaxKind.ArrayLiteralExpression); return declarationArray; } function getClassDeclaration(className, file) { if (!tsModule) { tsModule = (0, ensure_typescript_1.ensureTypescript)(); } const classDeclaration = (0, js_1.findNodes)(file, tsModule.SyntaxKind.ClassDeclaration).find((classDeclaration) => classDeclaration .getChildren() .find((node) => node.kind === tsModule.SyntaxKind.Identifier && node.getText() === className)); return classDeclaration; } function getVariableDeclaration(variableName, file) { if (!tsModule) { tsModule = (0, ensure_typescript_1.ensureTypescript)(); } const variableDeclaration = (0, js_1.findNodes)(file, tsModule.SyntaxKind.VariableDeclaration).find((variableDeclaration) => variableDeclaration .getChildren() .find((node) => node.kind === tsModule.SyntaxKind.Identifier && node.getText() === variableName)); return variableDeclaration; } function getNgModuleDeclarationsPropertyAssignment(ngModuleDecorator, moduleFilePath, projectName) { if (!tsModule) { tsModule = (0, ensure_typescript_1.ensureTypescript)(); } const syntaxList = ngModuleDecorator.getChildren().find((node) => { return node.kind === tsModule.SyntaxKind.SyntaxList; }); const declarationsPropertyAssignment = syntaxList .getChildren() .find((node) => { return (node.kind === tsModule.SyntaxKind.PropertyAssignment && node.getChildren()[0].getText() === 'declarations'); }); if (!declarationsPropertyAssignment) { devkit_1.logger.warn((0, devkit_1.stripIndents) `No stories generated because there were no components declared in ${moduleFilePath}. Hint: you can always generate stories later with the 'nx generate @nx/angular:stories --name=${projectName}' command.`); } return declarationsPropertyAssignment; } function getNgModuleDecorator(file, moduleFilePath) { const ngModuleDecorators = (0, ast_utils_1.getDecoratorMetadata)(file, 'NgModule', '@angular/core'); if (ngModuleDecorators.length === 0) { throw new Error(`No @NgModule decorator in ${moduleFilePath}.`); } return ngModuleDecorators[0]; }