@eclipse-scout/cli
Version:
CLI for Eclipse Scout
85 lines (76 loc) • 3.26 kB
JavaScript
/*
* Copyright (c) 2010, 2025 BSI Business Systems Integration AG
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
const ts = require('typescript');
/**
* Class to compute the external module name (e.g. '@eclipse-scout/core') of types used in the TS file given.
*
* It parses all imports in the file and stores all names imported from external modules together with the module the name is declared in.
* External module means an import from a module different from the one this files belongs to.
*/
module.exports = class ModuleDetector {
/**
* @param node A node in the TS file for which the module detector should be created. All external modules used (imported) in this file are stored.
*/
constructor(node) {
this.sourceFile = this._findSourceFile(node);
const imports = this._findImportDeclarations(this.sourceFile);
this._moduleByTypeMap = this._computeImportMap(imports); // Map only contains types in 'other' modules (modules different from the one of the source file)
}
/**
* @param typeNode {ts.TypeReferenceNode}
* @returns {{module: string;exportName:string}} The name of the module (e.g. '@eclipse-scout/core') the given type is declared in and the name it was exported in this module.
* Only returns a value if it is a different module than the one this detector was created in (only external modules).
* For imports in the same module, this function returns undefined.
*/
detectExportInfoOf(typeNode) {
const name = typeNode?.typeName?.escapedText;
return this._moduleByTypeMap.get(name);
}
_computeImportMap(imports) {
const moduleByTypeMap = new Map();
for (let imp of imports) {
const moduleName = imp.moduleSpecifier?.text; // e.g. '@eclipse-scout/core' or './index'
const isExternalModule = !moduleName?.startsWith('.'); // only store imports to other modules
if (isExternalModule) {
this._putExternalNamedBindings(imp.importClause?.namedBindings, moduleByTypeMap, moduleName);
}
}
return moduleByTypeMap;
}
_putExternalNamedBindings(namedBindings, moduleByTypeMap, moduleName) {
const importElements = namedBindings?.elements;
if (Array.isArray(importElements)) {
// multi import e.g.: import {a, b, c as d} from 'whatever'
for (let importElement of importElements) {
const name = importElement?.name?.escapedText;
if (!name) {
continue;
}
const exportedName = importElement?.propertyName?.escapedText || name;
moduleByTypeMap.set(name, {module: moduleName, exportName: exportedName});
}
} else {
// single import e.g.: import * as self from './index';
const name = namedBindings?.name?.escapedText;
if (name) {
moduleByTypeMap.set(name, {module: moduleName, exportName: name});
}
}
}
_findImportDeclarations(sourceFile) {
return sourceFile.statements.filter(s => ts.isImportDeclaration(s));
}
_findSourceFile(node) {
while (!ts.isSourceFile(node)) {
node = node.parent;
}
return node;
}
};