diff --git a/src/linter/ui5Types/amdTranspiler/moduleDeclarationToDefinition.ts b/src/linter/ui5Types/amdTranspiler/moduleDeclarationToDefinition.ts index aae78b67e..1caaffa94 100644 --- a/src/linter/ui5Types/amdTranspiler/moduleDeclarationToDefinition.ts +++ b/src/linter/ui5Types/amdTranspiler/moduleDeclarationToDefinition.ts @@ -1,4 +1,5 @@ import ts from "typescript"; +import * as path from "node:path"; import {getLogger} from "@ui5/logger"; import {ModuleDeclaration} from "./parseModuleDeclaration.js"; import rewriteExtendCall, {UnsupportedExtendCall} from "./rewriteExtendCall.js"; @@ -17,9 +18,10 @@ export interface ModuleDefinition { } export default function ( - moduleDeclaration: ModuleDeclaration, sourceFile: ts.SourceFile, nodeFactory: ts.NodeFactory + moduleDeclaration: ModuleDeclaration, sourceFile: ts.SourceFile, nodeFactory: ts.NodeFactory, + defaultModuleID?: string ): ModuleDefinition { - const {imports, identifiers: importIdentifiers} = collectImports(moduleDeclaration, nodeFactory); + const {imports, identifiers: importIdentifiers} = collectImports(moduleDeclaration, nodeFactory, defaultModuleID); const {body, oldFactoryBlock, moveComments} = getModuleBody(moduleDeclaration, sourceFile, nodeFactory, importIdentifiers); /* Ignore module name and export flag for now */ @@ -40,10 +42,13 @@ export default function ( */ function collectImports( moduleDeclaration: ModuleDeclaration, - nodeFactory: ts.NodeFactory + nodeFactory: ts.NodeFactory, + defaultModuleID: string | undefined ): {imports: ts.ImportDeclaration[]; identifiers: ts.Identifier[]} { const imports: ts.ImportDeclaration[] = []; const identifiers: ts.Identifier[] = []; + const hasRelativeSegment = /(^|\/)\.{1,2}\//; + if (!moduleDeclaration.dependencies) { // No dependencies declared return {imports, identifiers}; @@ -91,6 +96,25 @@ function collectImports( } else { moduleSpecifier = dep; } + if (hasRelativeSegment.test(moduleSpecifier.text)) { + const baseModuleID = moduleDeclaration.moduleName?.text ?? defaultModuleID; + if (baseModuleID == null) { + log.verbose(`Skipping resolution of relative moduleID ${moduleSpecifier.text} (no base known) at ` + + toPosStr(dep)); + } else { + const resolvedModuleID = + path.posix.resolve( + path.posix.dirname("/" + baseModuleID), + moduleSpecifier.text).slice(1); + log.silly(`Resolved relative module ID ${moduleSpecifier.text} to ${resolvedModuleID}`); + const resolvedModuleSpecifier = nodeFactory.createStringLiteral( + resolvedModuleID + ); + // Set range to the original range to preserve source mapping capability + ts.setTextRange(resolvedModuleSpecifier, moduleSpecifier); + moduleSpecifier = resolvedModuleSpecifier; + } + } let identifier: ts.Identifier | undefined; if (factoryRequiresCallWrapper) { // Generate variable name based on import module diff --git a/src/linter/ui5Types/amdTranspiler/tsTransformer.ts b/src/linter/ui5Types/amdTranspiler/tsTransformer.ts index a0726a192..1a8ee4600 100644 --- a/src/linter/ui5Types/amdTranspiler/tsTransformer.ts +++ b/src/linter/ui5Types/amdTranspiler/tsTransformer.ts @@ -103,6 +103,14 @@ function transform( const metadata = context.getMetadata(resourcePath); findDirectives(sourceFile, metadata); + // For now, only resolve relative imoprts in productive code. + // (In test code, issues with externally configured resource roots are + // more likely) + const defaultModuleID = + resourcePath.startsWith("/resources/") ? + resourcePath.slice("/resources/".length, -2) : // extract runtime module ID + undefined; + // Visit the AST depth-first and collect module definitions function visit(nodeIn: ts.Node): ts.VisitResult { const node = ts.visitEachChild(nodeIn, visit, tContext); @@ -111,7 +119,8 @@ function transform( if (matchPropertyAccessExpression(node.expression, "sap.ui.define")) { try { const moduleDeclaration = parseModuleDeclaration(node.arguments, checker); - const moduleDefinition = moduleDeclarationToDefinition(moduleDeclaration, sourceFile, nodeFactory); + const moduleDefinition = moduleDeclarationToDefinition(moduleDeclaration, sourceFile, + nodeFactory, defaultModuleID); moduleDefinitions.push(moduleDefinition); if (moduleDefinition.imports.length) { moduleDefinition.imports.forEach((importStatement) =>