UNPKG

@hadss/hmrouter-plugin

Version:

HMRouter Compiler Plugin

344 lines (343 loc) 15.5 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AnalyzerService = exports.AnalyzerController = void 0; const ts_morph_1 = require("ts-morph"); const Logger_1 = require("./common/Logger"); const TsAstUtil_1 = require("./utils/TsAstUtil"); const CommonConstants_1 = __importDefault(require("./constants/CommonConstants")); const FileUtil_1 = __importDefault(require("./utils/FileUtil")); const ConfigConstants_1 = __importDefault(require("./constants/ConfigConstants")); const PluginStore_1 = __importDefault(require("./store/PluginStore")); class AnalyzerController { constructor() { this.analyzeResult = new Set(); } analyzeFile(sourceFilePath, config) { let analyzerService = new AnalyzerService(sourceFilePath, config); analyzerService.start(); analyzerService.getResult().forEach(item => { item.pageSourceFile = sourceFilePath; this.analyzeResult.add(item); }); this.parseConstants(); } parseConstants() { this.analyzeResult.forEach((item) => { Object.getOwnPropertyNames(item).forEach((key) => { let propertyValue = Reflect.get(item, key); propertyValue = this.parsePropertyValue(propertyValue); if (propertyValue === '') { Logger_1.Logger.error(Logger_1.PluginError.ERR_NOT_EMPTY_STRING); throw new Error('constants value cannot be an empty string, filePath:' + item.pageSourceFile); } Reflect.set(item, key, propertyValue); }); }); } parsePropertyValue(propertyValue) { if (propertyValue.type === 'constant') { try { return TsAstUtil_1.TsAstUtil.parseConstantValue(TsAstUtil_1.TsAstUtil.getSourceFile(propertyValue.variableFilePath), propertyValue.variableName); } catch (error) { Logger_1.Logger.error(`Failed to parse constant ${propertyValue.variableName}: ${error.message}`); throw new Error(`Failed to parse constant ${propertyValue.variableName}: ${error.message}`); } } else if (propertyValue.type === 'object') { try { return TsAstUtil_1.TsAstUtil.parseConstantValue(TsAstUtil_1.TsAstUtil.getSourceFile(propertyValue.variableFilePath), propertyValue.variableName, propertyValue.propertyName); } catch (error) { Logger_1.Logger.error(`Failed to parse object property ${propertyValue.variableName}.${propertyValue.propertyName}: ${error.message}`); throw new Error(`Failed to parse object property ${propertyValue.variableName}.${propertyValue.propertyName}: ${error.message}`); } } else if (propertyValue.type === 'array') { return propertyValue.value.map((item) => { return this.parsePropertyValue(item); }); } else { return propertyValue; } } getAnalyzeResultSet() { return this.analyzeResult; } clearAnalyzeResultSet() { this.analyzeResult.clear(); } } exports.AnalyzerController = AnalyzerController; class AnalyzerService { constructor(sourceFilePath, config) { this.analyzerResultSet = new Set(); this.importMap = new Map(); this.sourceFilePath = sourceFilePath; this.sourceFile = TsAstUtil_1.TsAstUtil.getSourceFile(sourceFilePath); this.config = config; } start() { this.analyzeImport(); this.analyzeRouter(); this.analyzeComponent(); this.parseFileByLineOrder(); } getResult() { let HMRouterNum = 0; this.analyzerResultSet.forEach((analyzerResult) => { if (analyzerResult.annotation === CommonConstants_1.default.ROUTER_ANNOTATION) { HMRouterNum++; } }); if (HMRouterNum > 1) { Logger_1.Logger.error(Logger_1.PluginError.ERR_REPEAT_ANNOTATION, this.sourceFilePath); throw new Error(`File:${this.sourceFilePath} exists more than one @HMRouter annotation`); } return this.analyzerResultSet; } analyzeImport() { this.sourceFile.getImportDeclarations().forEach((importDeclaration) => { const moduleSpecifier = importDeclaration.getModuleSpecifierValue(); const namedImports = importDeclaration.getNamedImports().map((namedImport) => namedImport.getName()); const defaultImport = importDeclaration.getDefaultImport()?.getText(); const namespaceImport = importDeclaration.getNamespaceImport()?.getText(); const importNames = []; if (namedImports.length > 0) { importNames.push(...namedImports); } if (defaultImport) { importNames.push(defaultImport); } if (namespaceImport) { importNames.push(namespaceImport); } if (importNames.length > 0) { if (this.importMap.has(moduleSpecifier)) { const existingImports = this.importMap.get(moduleSpecifier); this.importMap.set(moduleSpecifier, [...new Set([...existingImports, ...importNames])]); } else { this.importMap.set(moduleSpecifier, importNames); } } }); } analyzeRouter() { let viewNameArr = this.sourceFile .getChildrenOfKind(ts_morph_1.SyntaxKind.ExpressionStatement) .map((node) => { return node.getText(); }) .filter((text) => { return text != 'struct'; }); this.sourceFile.getChildrenOfKind(ts_morph_1.SyntaxKind.MissingDeclaration).forEach((node, index) => { node.getChildrenOfKind(ts_morph_1.SyntaxKind.Decorator).forEach((decorator) => { this.addToResultSet(decorator, viewNameArr[index]); }); }); this.sourceFile.getExportAssignments().forEach((exportAssignment, index) => { exportAssignment.getDescendantsOfKind(ts_morph_1.SyntaxKind.Decorator).forEach((decorator) => { let result = this.addToResultSet(decorator, viewNameArr[index]); result.isDefaultExport = true; }); }); } parseFileByLineOrder() { const statements = this.sourceFile.getStatements(); const sortedStatements = statements.sort((a, b) => a.getStart() - b.getStart()); let HMRouterExists = false; let useNavDst = false; sortedStatements.forEach((statement) => { if (statement.getKind() === ts_morph_1.SyntaxKind.MissingDeclaration && statement.getText().includes('HMRouter')) { HMRouterExists = true; useNavDst = false; const text = statement.getText(); if (/(useNavDst\s*:\s*true)/.test(text)) { useNavDst = true; Logger_1.Logger.info('Found HMRouter with useNavDst=true, skipping NavDestination check'); } } if (statement.getKind() === ts_morph_1.SyntaxKind.Block && HMRouterExists) { HMRouterExists = false; if (useNavDst) { return; } let reg = new RegExp(/NavDestination\s*\(\s*\)/); let text = statement.getText(); const cleanedCodeBlock = text .replace(/(["'`]).*?\1/g, '') .replace(/\/\/.*|\/\*[\s\S]*?\*\//g, ''); if (reg.test(cleanedCodeBlock)) { Logger_1.Logger.error(Logger_1.PluginError.ERR_WRONG_DECORATION); throw new Error('NavDestination is not allowed in HMRouter, filePath:' + this.sourceFilePath); } } }); } analyzeComponent() { this.sourceFile.getClasses().forEach((cls) => { cls.getDecorators().forEach((decorator) => { if (this.config.annotation.includes(decorator.getName())) { this.addToResultSet(decorator, cls.getName()); } }); cls.getMethods().forEach((method) => { method.getDecorators().forEach((decorator) => { let serviceResult = this.addToResultSet(decorator, cls.getName()); serviceResult.functionName = method.getName(); }); }); }); } addToResultSet(decorator, componentName) { let decoratorResult = this.parseDecorator(decorator); decoratorResult.name = componentName; if (decoratorResult.annotation) { this.analyzerResultSet.add(decoratorResult); } return decoratorResult; } parseDecorator(decorator) { let decoratorResult = {}; let decoratorName = decorator.getName(); if (this.config.annotation.includes(decoratorName)) { decoratorResult.annotation = decoratorName; let args = this.parseDecoratorArguments(decorator); Object.assign(decoratorResult, args); } return decoratorResult; } parseDecoratorArguments(decorator) { let argResult = {}; decorator.getArguments().map((arg) => { const objLiteral = arg.asKind(ts_morph_1.SyntaxKind.ObjectLiteralExpression); if (objLiteral) { objLiteral.getProperties().forEach((prop) => { let propertyName = prop.getName(); let propertyValue = this.parseIdentifierPropertyValue(prop.getInitializer()); Reflect.set(argResult, propertyName, propertyValue); }); } }); return argResult; } parseIdentifierPropertyValue(value) { switch (value.getKind()) { case ts_morph_1.SyntaxKind.Identifier: if (value.getText() === 'undefined') { throw new Error(`Invalid property value, in ${this.sourceFilePath}`); } return { type: 'constant', variableName: value.getText(), variableFilePath: this.getVariableFilePath(value.getText()), }; case ts_morph_1.SyntaxKind.PropertyAccessExpression: return { type: 'object', variableName: value.getExpression().getText(), propertyName: value?.getName(), variableFilePath: this.getVariableFilePath(value?.getExpression().getText()), }; case ts_morph_1.SyntaxKind.ArrayLiteralExpression: return { type: 'array', value: value .asKind(ts_morph_1.SyntaxKind.ArrayLiteralExpression) ?.getElements() .map((item) => this.parseIdentifierPropertyValue(item)), }; default: return this.parsePrimitiveValue(value); } } parsePrimitiveValue(value) { let propertyValue; switch (value.getKind()) { case ts_morph_1.SyntaxKind.StringLiteral: propertyValue = value.asKind(ts_morph_1.SyntaxKind.StringLiteral)?.getLiteralValue(); break; case ts_morph_1.SyntaxKind.NumericLiteral: propertyValue = value.asKind(ts_morph_1.SyntaxKind.NumericLiteral)?.getLiteralValue(); break; case ts_morph_1.SyntaxKind.TrueKeyword: propertyValue = true; break; case ts_morph_1.SyntaxKind.FalseKeyword: propertyValue = false; break; case ts_morph_1.SyntaxKind.ArrayLiteralExpression: propertyValue = value .asKind(ts_morph_1.SyntaxKind.ArrayLiteralExpression) ?.getElements() .map((item) => item.asKind(ts_morph_1.SyntaxKind.StringLiteral)?.getLiteralValue()); break; } return propertyValue; } getVariableFilePath(variableName) { let filePath = ''; let classesNames = this.sourceFile.getClasses().map((classes) => { return classes.getName(); }); let variableNames = this.sourceFile.getVariableDeclarations().map((variableDeclaration) => { return variableDeclaration.getName(); }); if (classesNames.includes(variableName) || variableNames.includes(variableName)) { return this.sourceFilePath; } for (const [importPath, importNames] of this.importMap.entries()) { if (importNames.includes(variableName)) { try { let currentDir = FileUtil_1.default.pathResolve(this.sourceFilePath, CommonConstants_1.default.PARENT_DELIMITER); let tempFilePath = FileUtil_1.default.pathResolve(currentDir, importPath + CommonConstants_1.default.ETS_SUFFIX); if (FileUtil_1.default.exist(tempFilePath)) { filePath = tempFilePath; } else { try { filePath = this.getOtherModuleVariableFilePath(importPath, variableName); } catch (error) { Logger_1.Logger.warn(`Cannot find variable ${variableName} in module ${importPath}: ${error.message}`); } } } catch (error) { Logger_1.Logger.warn(`Error parsing import path ${importPath}: ${error.message}`); } } } if (!filePath) { Logger_1.Logger.warn(`Cannot find file path for variable ${variableName}`); } return filePath; } getOtherModuleVariableFilePath(moduleName, variableName) { let moduleFilePath = FileUtil_1.default.pathResolve(this.config.modulePath, CommonConstants_1.default.OH_MODULE_PATH, moduleName, ConfigConstants_1.default.DEFAULT_SCAN_DIR); if (!FileUtil_1.default.exist(moduleFilePath)) { moduleFilePath = FileUtil_1.default.pathResolve(PluginStore_1.default.getInstance().projectFilePath, CommonConstants_1.default.OH_MODULE_PATH, moduleName, ConfigConstants_1.default.DEFAULT_SCAN_DIR); } let variableMap; if (PluginStore_1.default.getInstance().variableCache.has(moduleName)) { variableMap = PluginStore_1.default.getInstance().variableCache.get(moduleName); } else { variableMap = TsAstUtil_1.TsAstUtil.parseCrossModuleVariable(moduleFilePath); PluginStore_1.default.getInstance().variableCache.set(moduleName, variableMap); } for (let [key, value] of variableMap) { if (value.includes(variableName)) { return key; } } throw new Error(`Unknown variable ${variableName} in ${moduleName}`); } } exports.AnalyzerService = AnalyzerService;