diff --git a/.gitignore b/.gitignore index 6d74258..107ce06 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ node_modules node_modules/ test/ +.vscode + # Logs logs *.log diff --git a/package.json b/package.json index 61669ff..7b052b2 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,19 @@ { - "name": "tspath", - "version": "2.5.4", - "description": "TypeScript path alias resolver, re-writes JS files with relative paths according to @paths specified in tsconfig", - "license": "LGPL-2.1", - "scripts": { - "copy": "copyfiles package.json lib", - "tspath": "node ./tspath.js" + "name": "tspath", + "version": "2.5.4", + "description": "TypeScript path alias resolver, re-writes JS files with relative paths according to @paths specified in tsconfig", + "license": "LGPL-2.1", + "scripts": { + "copy": "copyfiles package.json lib", + "tspath": "node ./tspath.js", + "watch": "tsc -w" }, - "preferGlobal": true, + "preferGlobal": true, "main": "index.js", - "bin": { + "bin": { "tspath": "./tspath.js" }, - "keywords": [ + "keywords": [ "tspath", "relative", "path", @@ -21,26 +22,28 @@ "resolve", "alias" ], - "author": "Patrik Forsberg ", - "repository": { + "author": "Patrik Forsberg ", + "repository": { "type": "git", - "url": "git+https://github.com/duffman/tspath.git" + "url": "git+https://github.com/duffman/tspath.git" }, "devDependencies": { - "@types/chai": "^4.0.4", + "@types/chai": "^4.0.4", "@types/mocha": "^2.2.42", - "@types/node": "^8.0.25", - "chai": "^4.1.2", - "copyfiles": "^2.4.1", - "mocha": "^3.5.0", - "prettier": "^2.7.1" + "@types/node": "^8.0.25", + "@types/supports-color": "8.1.1", + "chai": "^4.1.2", + "copyfiles": "^2.4.1", + "mocha": "^3.5.0", + "prettier": "^2.7.1" }, - "dependencies": { - "chalk": "^2.3.0", - "escodegen": "^1.8.1", - "esprima": "^4.0.0", + "dependencies": { + "chalk": "^2.3.0", + "escodegen": "^1.8.1", + "esprima-next": "^5.8.4", "prompt-confirm": "^1.2.0", - "typescript": "^4.4.2", - "yargs": "^11.0.0" + "supports-color": "8.1.1", + "typescript": "^4.4.2", + "yargs": "^11.0.0" } } diff --git a/src/parser-engine.ts b/src/parser-engine.ts index 34b1d03..81b0cd9 100644 --- a/src/parser-engine.ts +++ b/src/parser-engine.ts @@ -22,18 +22,19 @@ =----------------------------------------------------------------= */ -let esprima = require("esprima"); -let escodegen = require("escodegen"); -let chalk = require("chalk"); - -import { Const } from "./tspath.const"; -import { Logger } from "./utils/logger"; -import { PathUtils } from "./utils/path.utils"; -import { Utils } from "./utils/utils"; -import { JsonCommentStripper } from "./utils/json-comment-stripper"; -import { ProjectOptions } from "./project-options"; -import * as fs from "fs"; -import * as path from "path"; +const escodegen = require("escodegen"); + +import esprima from "esprima-next"; +import { Const } from "./tspath.const"; +import { Logger } from "./utils/logger"; +import { PathUtils } from "./utils/path.utils"; +import { Utils } from "./utils/utils"; +import { JsonCommentStripper } from "./utils/json-comment-stripper"; +import { ProjectOptions } from "./project-options"; +import * as fs from "fs"; +import * as path from "path"; +import { bold, green, red, underline, yellow } from "./utils/color"; +import type { Node, Program, ArgumentListElement } from "esprima-next"; const log = console.log; const testRun = false; @@ -42,6 +43,7 @@ export class ParserEngine { public projectPath: string; nrFilesProcessed: number = 0; + nrMappedPaths: number = 0; nrPathsProcessed: number = 0; srcRoot: string; basePath: string; @@ -62,7 +64,7 @@ export class ParserEngine { public setProjectPath(projectPath: string): boolean { if (!Utils.isEmpty(projectPath) && !this.validateProjectPath(projectPath)) { - log(chalk.red.bold("Project Path \"" + chalk.underline(projectPath) + "\" is invalid!")); + log(red(bold(("Project Path \"" + underline(projectPath) + "\" is invalid!")))); return false; } @@ -92,7 +94,7 @@ export class ParserEngine { } if (!fs.existsSync(configFile)) { - log("TypeScript Compiler - Configuration file " + chalk.underline(Const.TS_CONFIG) + " is missing!"); + log("TypeScript Compiler - Configuration file " + underline(Const.TS_CONFIG) + " is missing!"); } return result; @@ -122,7 +124,7 @@ export class ParserEngine { console.time(PROCESS_TIME); if (!this.validateProjectPath(this.projectPath)) { - log(chalk.bold.red("Invalid project path!")); + log(bold(red("Invalid project path!"))); this.exit(10); } @@ -130,10 +132,10 @@ export class ParserEngine { let projectName = this.readProjectName(); if (!Utils.isEmpty(projectName)) { - log(chalk.yellow("Parsing project: ") + chalk.bold(projectName) + " " + chalk.underline(this.projectPath)); + log(yellow("Parsing project: ") + bold(projectName) + " " + underline(this.projectPath)); } else { - log(chalk.yellow.bold("Parsing project at: ") + "\"" + this.projectPath + "\""); + log(yellow(bold("Parsing project at: ") + "\"" + this.projectPath + "\"")); } this.distRoot = path.resolve(this.projectPath, this.projectOptions.outDir); @@ -167,11 +169,12 @@ export class ParserEngine { this.processFile(filename); } - log(chalk.bold("Total files processed:"), this.nrFilesProcessed); - log(chalk.bold("Total paths processed:"), this.nrPathsProcessed); + log(bold("\nTotal files processed:"), this.nrFilesProcessed); + log(bold("Total paths processed:"), this.nrPathsProcessed); + log(bold("Total mapped paths resolved:"), this.nrMappedPaths); console.timeEnd(PROCESS_TIME); - log(chalk.bold.green("Project is prepared, now run it normally!")); + log(bold(green("Project is prepared, now run it normally!"))); } private shouldSkipFile(filename: string): boolean { @@ -181,74 +184,73 @@ export class ParserEngine { /** * - * @param sourceFilename - * @param jsRequire - require in javascript source "require("jsRequire") + * @param sourceFilename - source file that the `require` statement was found in + * @param jsRequire - require in javascript source `require("jsRequire")` * @returns {string} */ getRelativePathForRequiredFile(sourceFilename: string, jsRequire: string) { - let options = this.projectOptions; + const options = this.projectOptions; + // absolute path of "baseUrl" specified in tsconfig.json + const baseUrl = path.join(this.projectPath, this.projectOptions.baseUrl); if (Const.DEBUG_MODE) { - console.log("getRelativePathForRequiredFile ::---", sourceFilename); + console.log("\ngetRelativePathForRequiredFile"); + console.log("\tsourceFilename == ", sourceFilename); + console.log("\tjsRequire == ", jsRequire); } - for (let alias in options.pathMappings) { - let mapping = options.pathMappings[ alias ]; + // iterate over all of the "paths" specified in tsconfig.json + for (const aliasRaw in options.pathMappings) { + // get the mapping of this alias + const mappingRaw = options.pathMappings[aliasRaw]; //TODO: Handle * properly - alias = Utils.stripWildcard(alias); - mapping = Utils.stripWildcard(mapping); + const alias = Utils.stripWildcard(aliasRaw); + const mapping = Utils.stripWildcard(mappingRaw); // 2018-06-02: Workaround for bug with same prefix Aliases e.g @db and @dbCore + // 2022-10-19: Workaround for if Unix paths are used on Windows machines (which they usually are) // Cut alias prefix for mapping comparison - let requirePrefix = jsRequire.substring(0, jsRequire.indexOf(path.sep)); - - if (requirePrefix == alias) { - let result = jsRequire.replace(alias, mapping); - Utils.replaceDoubleSlashes(result); + const requirePrefix = jsRequire.substring(0, jsRequire.indexOf(path.sep)) || jsRequire.substring(0, jsRequire.indexOf("/")); - let absoluteJsRequire = path.join(this.basePath, result); + // if no match, go to next alias + // N.B. Please use guard clauses, like this + if (requirePrefix !== alias) continue; - if (!fs.existsSync(`${ absoluteJsRequire }.js`)) { - const newResult = jsRequire.replace(alias, ""); - absoluteJsRequire = path.join(this.basePath, newResult); - } + this.nrMappedPaths++; - let sourceDir = path.dirname(sourceFilename); + // Path to required file relative to baseUrl (from tsconfig.json) + const requireMapped = jsRequire.replace(alias, mapping); + Utils.replaceDoubleSlashes(requireMapped); - if (Const.DEBUG_MODE) { - console.log("sourceDir == ", sourceDir); - console.log("absoluteJsRequire == ", absoluteJsRequire); - console.log("this.distRoot == ", this.distRoot); - console.log("sourceFilename == ", sourceFilename); - } + // let absoluteJsRequire = path.join(this.basePath, requireMapped); - const fromPath = path.dirname(sourceFilename); - const toPath = path.dirname(absoluteJsRequire + ".js"); + // idk what this is for but don't seem to need it rn + // if (!fs.existsSync(`${ absoluteJsRequire }.js`)) { + // const newResult = jsRequire.replace(alias, ""); + // absoluteJsRequire = path.join(this.basePath, newResult); + // } - let relativePath = PathUtils.getRelativePath(fromPath, toPath); + // directory of the source file + const sourceFileDir = path.dirname(sourceFilename); - /* - let relativePath = path.relative(fromPath, toPath); + // path of baseUrl (from tsconfig.json) relative to sourceFileDir + const pathToBase = PathUtils.getRelativePath(sourceFileDir, baseUrl); - if (!relativePath.trim().length) { - relativePath = "."; - } + // final path of required file relative to source file + const relativeJsRequire = path.join(pathToBase, requireMapped); - relativePath = Utils.ensureTrailingPathDelimiter(relativePath); - */ + if (Const.DEBUG_MODE) { + console.log("\tbaseUrl == ", baseUrl); + console.log("\tsourceFileDir == ", sourceFileDir); + console.log("\trequireMapped == ", requireMapped); + console.log("\trelativeJsRequire == ", relativeJsRequire) + // console.log("absoluteJsRequire == ", absoluteJsRequire); + } - // - // If the path does not start with .. it´ not a sub directory - // as in ../ or ..\ so assume it´ the same dir... - // - if (relativePath[ 0 ] !== ".") { - relativePath = "./" + relativePath; - } + jsRequire = relativeJsRequire; - jsRequire = relativePath + path.parse(absoluteJsRequire).base; - break; - } + break; } return jsRequire; @@ -258,19 +260,19 @@ export class ParserEngine { * Processes the filename specified in require("filename") * @param node * @param sourceFilename - * @returns {any} + * @returns */ - processJsRequire(node: any, sourceFilename: string): any { - let resultNode = node; - let requireInJsFile = Utils.safeGetAstNodeValue(node); + processJsRequire(node: ArgumentListElement, sourceFilename: string): ArgumentListElement { + let resultNode: ArgumentListElement = node; + const requireInJsFile = Utils.safeGetAstNodeValue(node); // // Only proceed if the "require" contains a full file path, not // single references like "inversify" // if (!Utils.isEmpty(requireInJsFile) && Utils.fileHavePath(requireInJsFile)) { - let relativePath = this.getRelativePathForRequiredFile(sourceFilename, requireInJsFile); - resultNode = { type: "Literal", value: relativePath, raw: relativePath }; + const relativePath = this.getRelativePathForRequiredFile(sourceFilename, requireInJsFile); + resultNode = { type: esprima.Syntax.Literal, value: relativePath, raw: relativePath }; this.nrPathsProcessed++; } @@ -287,7 +289,7 @@ export class ParserEngine { let scope = this; let inputSourceCode = fs.readFileSync(filename, Const.FILE_ENCODING); - let ast = null; + let ast: Program | null = null; try { ast = esprima.parse(inputSourceCode); //, { raw: true, tokens: true, range: true, comment: true }); @@ -299,7 +301,12 @@ export class ParserEngine { } this.traverseSynTree(ast, this, (node) => { - if (node != undefined && node.type == "CallExpression" && node.callee.name == "require") { + if ( + node != undefined + && node.type == "CallExpression" + && node.callee.type === "Identifier" + && node.callee.name == "require" + ) { node.arguments[ 0 ] = scope.processJsRequire(node.arguments[ 0 ], filename); } }); @@ -313,7 +320,7 @@ export class ParserEngine { } } catch (error) { - log(chalk.bold.red("Unable to write file:"), filename); + log(bold(red("Unable to write file:")), filename); this.exit(); } } @@ -360,7 +367,7 @@ export class ParserEngine { for (let key in reqFields) { let field = reqFields[ key ]; if (Utils.isEmpty(field)) { - log(chalk.red.bold("Missing required field:") + " \"" + chalk.bold.underline(key) + "\""); + log(red(bold("Missing required field:")) + " \"" + bold(underline(key)) + "\""); this.exit(22); } } @@ -374,7 +381,7 @@ export class ParserEngine { * @param scope * @param func */ - private traverseSynTree(ast, scope, func): void { + private traverseSynTree(ast: Node, scope: ParserEngine, func: (a: Node) => void): void { func(ast); for (let key in ast) { if (ast.hasOwnProperty(key)) { diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..6109472 --- /dev/null +++ b/src/test.ts @@ -0,0 +1,3 @@ +import { bold, red, underline } from "./utils/color"; + +console.log(`This is some ${red("red")} text and this is ${bold("bold and " + underline("underlined") )} text!`); diff --git a/src/tspath.ts b/src/tspath.ts index 50e6257..303bae6 100644 --- a/src/tspath.ts +++ b/src/tspath.ts @@ -24,15 +24,15 @@ =----------------------------------------------------------------= */ -let chalk = require("chalk"); -let log = console.log; -let Confirm = require("prompt-confirm"); -let yargs = require("yargs").argv; +const log = console.log; +const Confirm = require("prompt-confirm"); +const yargs = require("yargs").argv; -import { ParserEngine } from "./parser-engine"; -import { Const } from "./tspath.const"; -import { JsonFile } from "./utils/json-file"; -import { ParentFileFinder } from "./utils/parent-file-finder"; +import { bold, red, yellow } from "./utils/color"; +import { ParserEngine } from "./parser-engine"; +import { Const } from "./tspath.const"; +import { JsonFile } from "./utils/json-file"; +import { ParentFileFinder } from "./utils/parent-file-finder"; export class TSPath { private engine = new ParserEngine(); @@ -40,7 +40,7 @@ export class TSPath { constructor() { const pkg: any = new JsonFile("package.json"); - log(chalk.yellow(`TSPath v${ Const.VERSION }`)); + log(yellow(`TSPath v${ Const.VERSION }`)); let filter = ["js"]; const force: boolean = yargs.force || yargs.f; const verbose: boolean = yargs.verbose || yargs.v; @@ -56,7 +56,7 @@ export class TSPath { } if (filter.length === 0) { - log(chalk.bold.red("File filter missing!")); + log(bold(red("File filter missing!"))); process.exit(23); } @@ -72,7 +72,7 @@ export class TSPath { } }); } else { - log(chalk.bold("No project root found!")); + log(bold("No project root found!")); } } diff --git a/src/utils/color.ts b/src/utils/color.ts new file mode 100644 index 0000000..206a8ae --- /dev/null +++ b/src/utils/color.ts @@ -0,0 +1,64 @@ +import * as supportsColor from "supports-color"; + +const ESCAPE = "\x1b["; + +const formatters = { + reset: "0m", + bold: "1m", + bold_reset: "22m", + underline: "4m", + underline_reset: "24m" +} + +const colors = { + "default": "39m", + "red": "31m", + "yellow": "33m", + "green": "32m", + "blue": "34m", + "magenta": "35m", + "cyan": "36m", + "red_bright": "91m" +} + +const ac = (str: string) => + supportsColor.stdout + ? ESCAPE + str + : ""; + +const RESET_ALL = ac(formatters.reset); + +// colors +const DEFAULT_COLOR = ac(colors.default); + +const RED = ac(colors.red); +export const red = (str: string) => `${RED}${str}${DEFAULT_COLOR}`; + +const RED_BRIGHT = ac(colors.red_bright); +export const redBright = (str: string) => `${RED_BRIGHT}${str}${DEFAULT_COLOR}`; + +const YELLOW = ac(colors.yellow); +export const yellow = (str: string) => `${YELLOW}${str}${DEFAULT_COLOR}`; + +const GREEN = ac(colors.green); +export const green = (str: string) => `${GREEN}${str}${DEFAULT_COLOR}`; + +const BLUE = ac(colors.blue); +export const blue = (str: string) => `${BLUE}${str}${DEFAULT_COLOR}`; + +const MAGENTA = ac(colors.magenta); +export const magenta = (str: string) => `${MAGENTA}${str}${DEFAULT_COLOR}`; + +const CYAN = ac(colors.cyan); +export const cyan = (str: string) => `${CYAN}${str}${DEFAULT_COLOR}`; + +// formatting +const BOLD = ac(formatters.bold); +const BOLD_RESET = ac(formatters.bold_reset) +export const bold = (str: string) => `${BOLD}${str}${BOLD_RESET}`; + +const UNDERLINE = ac(formatters.underline); +const UNDERLINE_RESET = ac(formatters.underline_reset); +export const underline = (str: string) => `${UNDERLINE}${str}${UNDERLINE_RESET}`; + + diff --git a/src/utils/logger.ts b/src/utils/logger.ts index a543805..b8544b3 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,9 +1,9 @@ +import { blue, bold, cyan, magenta, redBright, yellow } from "./color"; + /** * @author: Patrik Forsberg * @date: 2022-09-28 10:07 */ -import chalk from "chalk"; - const log = console.log; export class Logger { @@ -55,35 +55,35 @@ export class Logger { } public static logRed(logMessage: string, ...logData:any[]): void { - Logger.logBase(chalk.redBright, logMessage, logData); + Logger.logBase(redBright, logMessage, logData); } public static logYellow(logMessage: string, logData: any = ""): void { - log(chalk.yellow(logMessage), logData); + log(yellow(logMessage), logData); } public static logCyan(logMessage: string, logData: any = ""): void { if (logData) { - log(chalk.cyan(logMessage), logData); + log(cyan(logMessage), logData); } else { - log(chalk.cyan(logMessage)); + log(cyan(logMessage)); } } public static logText(logMessage: string, ...logText: string[]): void { let text = logText.join(" :: "); - log(chalk.bold.cyan(logMessage), `:: ${logText}`); + log(bold(cyan(logMessage)), `:: ${logText}`); } public static logBlue(logMessage: string, logData: any = ""): void { - console.log(chalk.blue(logMessage), logData); + console.log(blue(logMessage), logData); } public static logPurple(logMessage: string, logData: any = null): void { if (logData == null) { - log(chalk.magenta(logMessage)); + log(magenta(logMessage)); } else { - log(chalk.magenta(logMessage), logData); + log(magenta(logMessage), logData); } } } diff --git a/yarn.lock b/yarn.lock index eb8cbd4..d600a52 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17,6 +17,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3" integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== +"@types/supports-color@8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/supports-color/-/supports-color-8.1.1.tgz#1b44b1b096479273adf7f93c75fc4ecc40a61ee4" + integrity sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw== + ansi-bgblack@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ansi-bgblack/-/ansi-bgblack-0.1.1.tgz#a68ba5007887701b6aafbe3fa0dadfdfa8ee3ca2" @@ -571,7 +576,12 @@ escodegen@^1.8.1: optionalDependencies: source-map "~0.6.1" -esprima@^4.0.0, esprima@^4.0.1: +esprima-next@^5.8.4: + version "5.8.4" + resolved "https://registry.yarnpkg.com/esprima-next/-/esprima-next-5.8.4.tgz#9f82c8093a33da7207a4e8621e997c66878c145a" + integrity sha512-8nYVZ4ioIH4Msjb/XmhnBdz5WRRBaYqevKa1cv9nGJdCehMbzZCPNEEnqfLCZVetUVrUPEcb5IYyu1GG4hFqgg== + +esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== @@ -706,6 +716,11 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + he@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" @@ -1529,6 +1544,13 @@ supports-color@3.1.2: dependencies: has-flag "^1.0.0" +supports-color@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"