UNPKG

@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
"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;