@jqassistant/ts-lce
Version:
Tool to extract language concepts from a TypeScript codebase and export them to a JSON file.
279 lines (278 loc) • 11.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ModulePathUtils = void 0;
const fs = __importStar(require("fs"));
const p = __importStar(require("path"));
const path_1 = __importDefault(require("path"));
const context_1 = require("../context");
const typescript_module_concept_1 = require("../concepts/typescript-module.concept");
const file_utils_1 = require("./file.utils");
/**
* Utility class that provides functionality regarding the paths of TypeScript modules, like they occur in import statements.
*/
class ModulePathUtils {
/**
* @param path regular or import path
* @returns type of the given path
*/
static getPathType(path) {
if (p.isAbsolute(path)) {
return "absolute";
}
else if (path.startsWith(".") && !file_utils_1.FileUtils.normalizePath(path).startsWith("./node_modules")) {
return "relative";
}
else {
return "node";
}
}
/**
* Converts any regular path to a regular path relative to the project root.
* Leaves node paths unchanged.
* @param projectPath absolute path to project Root
* @param path any regular path
* @param originPath project-relative path to the file where `path` is relative to (needed if `path` is relative, but not to the project root)
* @returns relative or node version of the input path
*/
static normalize(projectPath, path, originPath) {
const pathType = this.getPathType(path);
if (pathType === "node") {
return path;
}
else {
let relPath;
if (pathType === "absolute") {
relPath = file_utils_1.FileUtils.normalizePath(p.relative(projectPath, path));
}
else {
if (!originPath)
throw new Error("originPath is required if path is relative");
relPath = file_utils_1.FileUtils.normalizePath(p.relative(projectPath, p.resolve(projectPath, originPath.slice(0, originPath.lastIndexOf("/")), path)));
}
if (!relPath.startsWith(".")) {
return "./" + relPath;
}
else {
return relPath;
}
}
}
/**
* Converts an import path to an absolute regular path.
* Leaves node paths unchanged.
* @param projectRootPath absolute path to project Root
* @param importPath any import path
* @param originPath project-relative path to the file where `path` is relative to (needed if `path` is relative, but not to the project root)
* @returns absolute or node version of the input path
*/
static normalizeImportPath(projectRootPath, importPath, originPath) {
const pathType = this.getPathType(importPath);
if (pathType === "absolute") {
return file_utils_1.FileUtils.normalizePath(this.addFileEnding(importPath));
}
else if (pathType === "relative" && originPath) {
return file_utils_1.FileUtils.normalizePath(this.addFileEnding(p.resolve(projectRootPath, originPath.slice(0, originPath.lastIndexOf("/")), importPath)));
}
else {
return importPath;
}
}
static addFileEnding(absoluteFilePath) {
if (fs.existsSync(absoluteFilePath)) {
return absoluteFilePath;
}
else if (fs.existsSync(absoluteFilePath + ".ts")) {
return absoluteFilePath + ".ts";
}
else if (fs.existsSync(absoluteFilePath + ".tsx")) {
return absoluteFilePath + ".tsx";
}
else if (fs.existsSync(absoluteFilePath + ".mts")) {
return absoluteFilePath + ".mts";
}
else if (fs.existsSync(absoluteFilePath + ".js")) {
return absoluteFilePath + ".js";
}
else if (fs.existsSync(absoluteFilePath + ".jsx")) {
return absoluteFilePath + ".jsx";
}
else if (fs.existsSync(absoluteFilePath + ".mjs")) {
return absoluteFilePath + ".mjs";
}
else if (fs.existsSync(absoluteFilePath + ".d.ts")) {
return absoluteFilePath + ".d.ts";
}
else if (fs.existsSync(absoluteFilePath + ".d.mts")) {
return absoluteFilePath + ".d.mts";
}
return absoluteFilePath;
}
/**
* @param tcFQN FQN obtained using `getFullyQualifiedName(symbol)` of TS TypeChecker
* @param sourceFilePathAbsolute absolute path to the source file of the symbol
* @returns normalized FQN with absolute/node module path
*/
static normalizeTypeCheckerFQN(tcFQN, sourceFilePathAbsolute) {
if (tcFQN.startsWith('"')) {
const fqnPath = this.extractFQNPath(tcFQN);
const pathType = this.getPathType(fqnPath);
if (pathType === "node") {
return tcFQN;
}
else if (pathType === "absolute") {
let sourceFilePath = this.addFileEnding(fqnPath);
// re-introduce case-sensitive naming
sourceFilePath = fs.realpathSync.native(sourceFilePath);
// normalize paths on Windows platforms
if (process.platform === "win32") {
sourceFilePath = file_utils_1.FileUtils.normalizePath(sourceFilePath);
}
// remove index.* filename from FQN path
sourceFilePath = sourceFilePath.replace(/\/index\.\w+$/, "");
return (`"${sourceFilePath}"${tcFQN.slice(tcFQN.lastIndexOf('"') + 1)}`);
}
else {
throw new Error("Encountered relative TypeChecker FQN path: " + tcFQN);
}
}
else {
let sourceFilePath = fs.realpathSync.native(sourceFilePathAbsolute);
if (process.platform === "win32") {
sourceFilePath = file_utils_1.FileUtils.normalizePath(sourceFilePath);
}
return this.toFQN(sourceFilePath).globalFqn + "." + tcFQN;
}
}
/**
* Converts a global and, optionally, local path to their corresponding FQN path representation.
* Use relative paths for local FQNs and absolute paths for global FQNs.
*/
static toFQN(globalPath, localPath) {
const indexSourceFileRegEx = /\/index\.\w+$/;
const basicGlobalFQN = '"' + (file_utils_1.FileUtils.normalizePath(globalPath)).replace(indexSourceFileRegEx, "") + '"';
const basicLocalFQN = localPath ? '"' + (file_utils_1.FileUtils.normalizePath(localPath)).replace(indexSourceFileRegEx, "") + '"' : "";
return new context_1.FQN(basicGlobalFQN, basicLocalFQN);
}
/**
* Extracts a regular path from the given FQN
*/
static extractFQNPath(fqn) {
if (fqn.startsWith('"')) {
return fqn.slice(1, fqn.lastIndexOf('"'));
}
else {
return "";
}
}
/**
* extracts the identifier part of an FQN
*/
static extractFQNIdentifier(fqn) {
if (fqn.startsWith('"')) {
return fqn.slice(fqn.lastIndexOf('"') + 2);
}
else {
return fqn;
}
}
/**
* @returns whether the provided FQN is a module or not
*/
static isFQNModule(fqn) {
return fqn.startsWith('"') && fqn.endsWith('"');
}
/**
* converts a regular path into a path for the graph
*/
static toGraphPath(path) {
if (path.startsWith(".")) {
return path.substring(1);
}
else {
return path;
}
}
/**
* NOTE: This function can only be used after all projects have been processed.
*
* @param modulePath absolute, relative, or node path
* @param projectInfo ProjectInfo of the project that is currently being post-processed
* @param projects list of all processed projects
* @returns whether the path is outside the project (and its subprojects) or not
*/
static isExternal(modulePath, projectInfo, projects) {
let moduleIndex = this.moduleIndexes.get(projectInfo.configPath);
if (!moduleIndex) {
moduleIndex = new Map();
// create module index by registering all processed modules for project and all subprojects
for (const subproject of projects) {
if (!projectInfo.subProjectPaths.includes(subproject.projectInfo.configPath) && subproject.projectInfo.configPath !== projectInfo.configPath) {
continue;
}
const modules = subproject.concepts.get(typescript_module_concept_1.LCEModule.conceptId);
for (const module of modules) {
if (module.fqn.globalFqn.match(/\/index\.[a-z]+$/)) {
// use directory path for index.* modules
moduleIndex.set(module.fqn.globalFqn.replace(/\/index\.[a-z]+$/, ""), true);
}
else {
moduleIndex.set(module.fqn.globalFqn, true);
}
}
}
this.moduleIndexes.set(projectInfo.configPath, moduleIndex);
}
const pathType = this.getPathType(modulePath);
if (pathType === "node") {
return true;
}
else if (pathType === "relative") {
return !moduleIndex.get(path_1.default.resolve(projectInfo.rootPath, modulePath));
}
else {
return !moduleIndex.get(modulePath);
}
}
/**
* Used for caching module indexes used for isExternal()
* Maps projectPaths to ModuleIndex maps
*/
static moduleIndexes = new Map();
}
exports.ModulePathUtils = ModulePathUtils;