1/* 2 * Copyright (c) 2021 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import ts from 'typescript'; 17import fs from 'fs'; 18import path from 'path'; 19 20import { 21 EXTNAME_ETS, 22 NODE_MODULES, 23 INDEX_ETS, 24 INDEX_TS, 25 PACKAGE_JSON, 26 STRUCT, 27 CLASS, 28 CUSTOM_COMPONENT_DEFAULT, 29 CUSTOM_DECORATOR_NAME, 30 COMPONENT_DECORATOR_ENTRY 31} from './pre_define'; 32import { 33 propertyCollection, 34 linkCollection, 35 componentCollection, 36 preprocessExtend, 37 preprocessNewExtend, 38 processSystemApi, 39 propCollection, 40 isObservedClass, 41 isCustomDialogClass, 42 observedClassCollection, 43 enumCollection, 44 getComponentSet, 45 IComponentSet 46} from './validate_ui_syntax'; 47import { LogInfo, LogType } from './utils'; 48import { projectConfig } from '../main'; 49import { INNER_COMPONENT_NAMES } from './component_map'; 50import { builderParamObjectCollection } from './process_component_member'; 51 52const IMPORT_FILE_ASTCACHE: Map<string, ts.SourceFile> = new Map(); 53 54export default function processImport(node: ts.ImportDeclaration | ts.ImportEqualsDeclaration | 55 ts.ExportDeclaration, pagesDir: string, log: LogInfo[], asName: Map<string, string> = new Map(), 56 isEntryPage: boolean = true, pathCollection: Set<string> = new Set()): void { 57 let filePath: string; 58 let defaultName: string; 59 if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) { 60 filePath = node.moduleSpecifier.getText().replace(/'|"/g, ''); 61 if (ts.isImportDeclaration(node) && node.importClause && node.importClause.name && 62 ts.isIdentifier(node.importClause.name)) { 63 defaultName = node.importClause.name.escapedText.toString(); 64 if (isEntryPage) { 65 asName.set(defaultName, defaultName); 66 } 67 } 68 if (ts.isImportDeclaration(node) && node.importClause && node.importClause.namedBindings && 69 ts.isNamedImports(node.importClause.namedBindings) && 70 node.importClause.namedBindings.elements && isEntryPage) { 71 node.importClause.namedBindings.elements.forEach(item => { 72 if (item.name && ts.isIdentifier(item.name)) { 73 validateModuleName(item.name, log); 74 if (item.propertyName && ts.isIdentifier(item.propertyName) && asName) { 75 asName.set(item.propertyName.escapedText.toString(), item.name.escapedText.toString()); 76 } else { 77 asName.set(item.name.escapedText.toString(), item.name.escapedText.toString()); 78 } 79 } 80 }); 81 } 82 } else { 83 if (node.moduleReference && ts.isExternalModuleReference(node.moduleReference) && 84 node.moduleReference.expression && ts.isStringLiteral(node.moduleReference.expression)) { 85 filePath = node.moduleReference.expression.text; 86 defaultName = node.name.escapedText.toString(); 87 if (isEntryPage) { 88 asName.set(defaultName, defaultName); 89 } 90 } 91 } 92 if (filePath && path.extname(filePath) !== EXTNAME_ETS && !isModule(filePath)) { 93 const dirIndexPath: string = path.resolve(path.resolve(pagesDir, filePath), INDEX_ETS); 94 if (/^(\.|\.\.)\//.test(filePath) && !fs.existsSync(path.resolve(pagesDir, filePath + EXTNAME_ETS)) && 95 fs.existsSync(dirIndexPath)) { 96 filePath = dirIndexPath; 97 } else { 98 filePath += EXTNAME_ETS; 99 } 100 } 101 try { 102 let fileResolvePath: string; 103 if (/^(\.|\.\.)\//.test(filePath) && filePath.indexOf(NODE_MODULES) < 0) { 104 fileResolvePath = path.resolve(pagesDir, filePath); 105 } else if (/^\//.test(filePath) && filePath.indexOf(NODE_MODULES) < 0 || 106 fs.existsSync(filePath) && fs.statSync(filePath).isFile()) { 107 fileResolvePath = filePath; 108 } else { 109 fileResolvePath = getFileResolvePath(fileResolvePath, pagesDir, filePath, projectConfig.projectPath); 110 } 111 if (fs.existsSync(fileResolvePath) && fs.statSync(fileResolvePath).isFile() && 112 !pathCollection.has(fileResolvePath)) { 113 let sourceFile: ts.SourceFile; 114 pathCollection.add(fileResolvePath); 115 if (IMPORT_FILE_ASTCACHE.has(fileResolvePath)) { 116 sourceFile = IMPORT_FILE_ASTCACHE.get(fileResolvePath); 117 } else { 118 const content: string = preprocessNewExtend(preprocessExtend(processSystemApi( 119 fs.readFileSync(fileResolvePath, { encoding: 'utf-8' }).replace( 120 new RegExp('\\b' + STRUCT + '\\b.+\\{', 'g'), item => { 121 return item.replace(new RegExp('\\b' + STRUCT + '\\b', 'g'), `${CLASS} `); 122 })))); 123 sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); 124 IMPORT_FILE_ASTCACHE[fileResolvePath] = sourceFile; 125 } 126 visitAllNode(sourceFile, sourceFile, defaultName, asName, path.dirname(fileResolvePath), log, new Set(), 127 new Set(), new Set(), new Map(), pathCollection, fileResolvePath); 128 } 129 } catch (e) { 130 // ignore 131 } 132} 133 134function visitAllNode(node: ts.Node, sourceFile: ts.SourceFile, defaultNameFromParent: string, asNameFromParent: Map<string, string>, 135 pagesDir: string, log: LogInfo[], entryCollection: Set<string>, exportCollection: Set<string>, 136 defaultCollection: Set<string>, asExportCollection: Map<string, string>, pathCollection: Set<string>, 137 fileResolvePath: string) { 138 if (isObservedClass(node)) { 139 // @ts-ignore 140 observedClassCollection.add(node.name.getText()); 141 } 142 if (isCustomDialogClass(node)) { 143 // @ts-ignore 144 componentCollection.customDialogs.add(node.name.getText()); 145 } 146 if (ts.isEnumDeclaration(node) && node.name) { 147 enumCollection.add(node.name.getText()); 148 } 149 if ((ts.isClassDeclaration(node) || ts.isStructDeclaration(node)) && ts.isIdentifier(node.name) && 150 isCustomComponent(node)) { 151 addDependencies(node, defaultNameFromParent, asNameFromParent); 152 isExportEntry(node, log, entryCollection, exportCollection); 153 if (asExportCollection.has(node.name.getText())) { 154 componentCollection.customComponents.add(asExportCollection.get(node.name.getText())); 155 } 156 if (node.modifiers && node.modifiers.length >= 2 && node.modifiers[0] && 157 node.modifiers[0].kind === ts.SyntaxKind.ExportKeyword && node.modifiers[1] && 158 node.modifiers[1].kind === ts.SyntaxKind.DefaultKeyword) { 159 if (!defaultNameFromParent && hasCollection(node.name)) { 160 addDefaultExport(node); 161 } else if (defaultNameFromParent && asNameFromParent.has(defaultNameFromParent)) { 162 componentCollection.customComponents.add(asNameFromParent.get(defaultNameFromParent)); 163 } 164 } 165 if (defaultCollection.has(node.name.getText())) { 166 componentCollection.customComponents.add('default'); 167 } 168 } 169 if (ts.isExportAssignment(node) && node.expression && ts.isIdentifier(node.expression) && 170 hasCollection(node.expression)) { 171 if (defaultNameFromParent) { 172 setDependencies(defaultNameFromParent, 173 linkCollection.get(node.expression.escapedText.toString()), 174 propertyCollection.get(node.expression.escapedText.toString()), 175 propCollection.get(node.expression.escapedText.toString()), 176 builderParamObjectCollection.get(node.expression.escapedText.toString())); 177 } 178 addDefaultExport(node); 179 } 180 if (ts.isExportAssignment(node) && node.expression && ts.isIdentifier(node.expression)) { 181 if (defaultNameFromParent) { 182 asNameFromParent.set(node.expression.getText(), asNameFromParent.get(defaultNameFromParent)); 183 } 184 defaultCollection.add(node.expression.getText()); 185 } 186 if (ts.isExportDeclaration(node) && node.exportClause && 187 ts.isNamedExports(node.exportClause) && node.exportClause.elements) { 188 node.exportClause.elements.forEach(item => { 189 if (item.name && item.propertyName && ts.isIdentifier(item.name) && 190 ts.isIdentifier(item.propertyName)) { 191 validateModuleName(item.name, log, sourceFile, fileResolvePath); 192 if (hasCollection(item.propertyName)) { 193 let asExportName: string = item.name.escapedText.toString(); 194 const asExportPropertyName: string = item.propertyName.escapedText.toString(); 195 if (asNameFromParent.has(asExportName)) { 196 asExportName = asNameFromParent.get(asExportName); 197 } 198 setDependencies(asExportName, linkCollection.get(asExportPropertyName), 199 propertyCollection.get(asExportPropertyName), 200 propCollection.get(asExportPropertyName), 201 builderParamObjectCollection.get(asExportPropertyName)); 202 } 203 asExportCollection.set(item.propertyName.escapedText.toString(), item.name.escapedText.toString()); 204 } 205 if (item.name && ts.isIdentifier(item.name) && asNameFromParent.has(item.name.escapedText.toString()) && 206 item.propertyName && ts.isIdentifier(item.propertyName)) { 207 asNameFromParent.set(item.propertyName.escapedText.toString(), 208 asNameFromParent.get(item.name.escapedText.toString())); 209 } 210 }); 211 } 212 if (ts.isExportDeclaration(node) && node.moduleSpecifier && 213 ts.isStringLiteral(node.moduleSpecifier)) { 214 if (projectConfig.isPreview && node.exportClause && ts.isNamedExports(node.exportClause) && 215 node.exportClause.elements) { 216 node.exportClause.elements.forEach(item => { 217 exportCollection.add((item.propertyName ? item.propertyName : item.name).escapedText.toString()); 218 if (item.propertyName && ts.isIdentifier(item.propertyName) && item.name && 219 ts.isIdentifier(item.name) && asNameFromParent.has(item.name.escapedText.toString())) { 220 asNameFromParent.set(item.propertyName.escapedText.toString(), 221 asNameFromParent.get(item.name.escapedText.toString())); 222 defaultCollection.add(item.name.escapedText.toString()); 223 } 224 }); 225 } 226 processImport(node, pagesDir, log, asNameFromParent, true, new Set(pathCollection)); 227 } 228 if (ts.isImportDeclaration(node)) { 229 if (node.importClause && node.importClause.name && ts.isIdentifier(node.importClause.name) && 230 asNameFromParent.has(node.importClause.name.getText())) { 231 processImport(node, pagesDir, log, asNameFromParent, false, new Set(pathCollection)); 232 } else if (node.importClause && node.importClause.namedBindings && 233 ts.isNamedImports(node.importClause.namedBindings) && node.importClause.namedBindings.elements) { 234 let nested: boolean = false; 235 node.importClause.namedBindings.elements.forEach(item => { 236 if (item.name && ts.isIdentifier(item.name) && asNameFromParent.has(item.name.escapedText.toString())) { 237 nested = true; 238 if (item.propertyName && ts.isIdentifier(item.propertyName)) { 239 asNameFromParent.set(item.propertyName.escapedText.toString(), 240 asNameFromParent.get(item.name.escapedText.toString())); 241 } 242 } 243 }); 244 if (nested) { 245 processImport(node, pagesDir, log, asNameFromParent, false, new Set(pathCollection)); 246 } 247 } 248 } 249 node.getChildren().reverse().forEach((item: ts.Node) => visitAllNode(item, sourceFile, defaultNameFromParent, 250 asNameFromParent, pagesDir, log, entryCollection, exportCollection, defaultCollection, 251 asExportCollection, pathCollection, fileResolvePath)); 252} 253 254function isExportEntry(node: ts.ClassDeclaration, log: LogInfo[], entryCollection: Set<string>, 255 exportCollection: Set<string>): void { 256 if (projectConfig.isPreview && node && node.decorators) { 257 let existExport: boolean = false; 258 let existEntry: boolean = false; 259 if (node.modifiers) { 260 for (let i = 0; i < node.modifiers.length; i++) { 261 if (node.modifiers[i].kind === ts.SyntaxKind.ExportKeyword) { 262 existExport = true; 263 break; 264 } 265 } 266 } 267 for (let i = 0; i < node.decorators.length; i++) { 268 if (node.decorators[i].getText() === COMPONENT_DECORATOR_ENTRY) { 269 entryCollection.add(node.name.escapedText.toString()); 270 existEntry = true; 271 break; 272 } 273 } 274 } 275} 276 277function addDependencies(node: ts.ClassDeclaration, defaultNameFromParent: string, 278 asNameFromParent: Map<string, string>): void { 279 const componentName: string = node.name.getText(); 280 const ComponentSet: IComponentSet = getComponentSet(node); 281 if (defaultNameFromParent && node.modifiers && node.modifiers.length >= 2 && node.modifiers[0] && 282 node.modifiers[1] && node.modifiers[0].kind === ts.SyntaxKind.ExportKeyword && 283 node.modifiers[1].kind === ts.SyntaxKind.DefaultKeyword) { 284 setDependencies(defaultNameFromParent, ComponentSet.links, ComponentSet.properties, 285 ComponentSet.props, ComponentSet.builderParams); 286 } else if (asNameFromParent.has(componentName)) { 287 setDependencies(asNameFromParent.get(componentName), ComponentSet.links, ComponentSet.properties, 288 ComponentSet.props, ComponentSet.builderParams); 289 } else { 290 setDependencies(componentName, ComponentSet.links, ComponentSet.properties, ComponentSet.props, 291 ComponentSet.builderParams); 292 } 293} 294 295function addDefaultExport(node: ts.ClassDeclaration | ts.ExportAssignment): void { 296 let name: string; 297 if (ts.isClassDeclaration(node) && node.name && ts.isIdentifier(node.name)) { 298 name = node.name.escapedText.toString(); 299 } else if (ts.isExportAssignment(node) && node.expression && ts.isIdentifier(node.expression)) { 300 name = node.expression.escapedText.toString(); 301 } else { 302 return; 303 } 304 setDependencies(CUSTOM_COMPONENT_DEFAULT, 305 linkCollection.has(CUSTOM_COMPONENT_DEFAULT) ? 306 new Set([...linkCollection.get(CUSTOM_COMPONENT_DEFAULT), ...linkCollection.get(name)]) : 307 linkCollection.get(name), 308 propertyCollection.has(CUSTOM_COMPONENT_DEFAULT) ? 309 new Set([...propertyCollection.get(CUSTOM_COMPONENT_DEFAULT), ...propertyCollection.get(name)]) : 310 propertyCollection.get(name), 311 propCollection.has(CUSTOM_COMPONENT_DEFAULT) ? 312 new Set([...propCollection.get(CUSTOM_COMPONENT_DEFAULT), ...propCollection.get(name)]) : 313 propCollection.get(name), 314 builderParamObjectCollection.has(CUSTOM_COMPONENT_DEFAULT) ? 315 new Set([...builderParamObjectCollection.get(CUSTOM_COMPONENT_DEFAULT), ...builderParamObjectCollection.get(name)]) : 316 builderParamObjectCollection.get(name)); 317 318} 319 320function setDependencies(component: string, linkArray: Set<string>, propertyArray: Set<string>, 321 propArray: Set<string>, builderParamArray: Set<string>): void { 322 linkCollection.set(component, linkArray); 323 propertyCollection.set(component, propertyArray); 324 propCollection.set(component, propArray); 325 builderParamObjectCollection.set(component, builderParamArray); 326 componentCollection.customComponents.add(component); 327} 328 329function hasCollection(node: ts.Identifier): boolean { 330 return linkCollection.has(node.escapedText.toString()) || 331 propCollection.has(node.escapedText.toString()) || 332 propertyCollection.has(node.escapedText.toString()); 333} 334 335function isModule(filePath: string): boolean { 336 return !/^(\.|\.\.)?\//.test(filePath) || filePath.indexOf(NODE_MODULES) > -1; 337} 338 339function isCustomComponent(node: ts.ClassDeclaration | ts.StructDeclaration): boolean { 340 if (node.decorators && node.decorators.length) { 341 for (let i = 0; i < node.decorators.length; ++i) { 342 const decoratorName: ts.Identifier = node.decorators[i].expression as ts.Identifier; 343 if (ts.isIdentifier(decoratorName) && 344 CUSTOM_DECORATOR_NAME.has(decoratorName.escapedText.toString())) { 345 return true; 346 } 347 } 348 } 349 return false; 350} 351 352function isPackageJsonEntry(filePath: string): boolean { 353 const packageJsonPath: string = path.join(filePath, PACKAGE_JSON); 354 if (fs.existsSync(packageJsonPath)) { 355 let entry: string; 356 try { 357 entry = JSON.parse(fs.readFileSync(packageJsonPath).toString()).main; 358 } catch (e) { 359 return false; 360 } 361 if (typeof entry === 'string' && fs.existsSync(path.join(filePath, entry))) { 362 return true; 363 } 364 } 365} 366 367function getPackageJsonEntry(filePath: string): string { 368 return path.join(filePath, JSON.parse(fs.readFileSync(path.join(filePath, PACKAGE_JSON)).toString()).main); 369} 370 371function getModuleFilePath(filePath: string): string { 372 if (filePath && path.extname(filePath) !== EXTNAME_ETS && isModule(filePath)) { 373 filePath += EXTNAME_ETS; 374 } 375 return filePath; 376} 377 378function getFileResolvePath(fileResolvePath: string, pagesDir: string, filePath: string, 379 projectPath: string): string { 380 const moduleFilePath: string = getModuleFilePath(filePath); 381 const defaultModule: string = path.join(projectPath, moduleFilePath); 382 if (fs.existsSync(defaultModule)) { 383 return defaultModule; 384 } 385 let entryModule: string; 386 let etsModule: string; 387 if (!projectConfig.aceModuleJsonPath) { 388 entryModule = path.join(projectPath, '../../../../../', moduleFilePath); 389 etsModule = path.join(projectPath, '../../', moduleFilePath); 390 } else { 391 entryModule = path.join(projectPath, '../../../../', moduleFilePath); 392 etsModule = path.join(projectPath, '../', moduleFilePath); 393 } 394 if (fs.existsSync(entryModule)) { 395 return entryModule; 396 } 397 if (fs.existsSync(etsModule)) { 398 return etsModule; 399 } 400 let curPageDir: string = pagesDir; 401 while (!fs.existsSync(fileResolvePath)) { 402 if (filePath.indexOf(NODE_MODULES) > -1) { 403 fileResolvePath = path.join(curPageDir, filePath); 404 } else { 405 fileResolvePath = path.join(curPageDir, NODE_MODULES, filePath); 406 } 407 if (fs.existsSync(fileResolvePath + EXTNAME_ETS)) { 408 fileResolvePath = fileResolvePath + EXTNAME_ETS; 409 } else if (isPackageJsonEntry(fileResolvePath)) { 410 fileResolvePath = getPackageJsonEntry(fileResolvePath); 411 if (fs.statSync(fileResolvePath).isDirectory()) { 412 if (fs.existsSync(path.join(fileResolvePath, INDEX_ETS))) { 413 fileResolvePath = path.join(fileResolvePath, INDEX_ETS); 414 } else if (fs.existsSync(path.join(fileResolvePath, INDEX_TS))) { 415 fileResolvePath = path.join(fileResolvePath, INDEX_TS); 416 } 417 } 418 } else if (fs.existsSync(path.join(fileResolvePath, INDEX_ETS))) { 419 fileResolvePath = path.join(fileResolvePath, INDEX_ETS); 420 } else if (fs.existsSync(path.join(fileResolvePath, INDEX_TS))) { 421 fileResolvePath = path.join(fileResolvePath, INDEX_TS); 422 } 423 if (curPageDir === path.parse(curPageDir).root) { 424 break; 425 } 426 curPageDir = path.dirname(curPageDir); 427 } 428 return fileResolvePath; 429} 430 431function validateModuleName(moduleNode: ts.Identifier, log: LogInfo[], sourceFile?: ts.SourceFile, 432 fileResolvePath?: string): void { 433 const moduleName: string = moduleNode.escapedText.toString(); 434 if (INNER_COMPONENT_NAMES.has(moduleName)) { 435 const error: LogInfo = { 436 type: LogType.ERROR, 437 message: `The module name '${moduleName}' can not be the same as the inner component name.`, 438 pos: moduleNode.getStart() 439 } 440 if (sourceFile && fileResolvePath) { 441 const posOfNode: ts.LineAndCharacter = sourceFile.getLineAndCharacterOfPosition(moduleNode.getStart()); 442 const line: number = posOfNode.line + 1; 443 const column: number = posOfNode.character + 1; 444 Object.assign(error, { 445 fileName: fileResolvePath, 446 line: line, 447 column: column 448 }) 449 } 450 log.push(error); 451 } 452} 453