@hadss/hmrouter-plugin
Version:
HMRouter Compiler Plugin
344 lines (343 loc) • 15.5 kB
JavaScript
;
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;