Socket
Socket
Sign inDemoInstall

tsconfck

Package Overview
Dependencies
Maintainers
1
Versions
44
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

tsconfck - npm Package Compare versions

Comparing version 1.0.0-3 to 1.0.0-4

13

bin/tsconfck.js

@@ -8,3 +8,3 @@ #!/usr/bin/env node

Commands: find, parse
Commands: find, parse, parse-result

@@ -17,10 +17,15 @@ Examples:

>{
> ...json...
> ... tsconfig json
>}
> tsconfck parse src/index.ts > parsed.tsconfig.json
> tsconfck parse-result src/index.ts
>{
> ... ParseResult json
>}
`;
const HELP_ARGS = ['-h', '--help', '-?', 'help'];
const COMMANDS = ['find', 'parse'];
const COMMANDS = ['find', 'parse', 'parse-result'];
function needsHelp(args) {

@@ -48,2 +53,4 @@ if (args.some((arg) => HELP_ARGS.includes(arg))) {

return JSON.stringify((await parse(file)).tsconfig, null, 2);
} else if (command === 'parse-result') {
return JSON.stringify(await parse(file), null, 2);
}

@@ -50,0 +57,0 @@ }

@@ -21,3 +21,4 @@ /**

* @param {string} filename - path to a tsconfig.json or a .ts source file (absolute or relative to cwd)
* @returns {Promise<object|void>} tsconfig parsed as object
* @returns {Promise<ParseResult>}
* @throws {ParseError}
*/

@@ -35,2 +36,10 @@ declare function parse(filename: string): Promise<ParseResult>;

/**
* ParseResult for parent solution
*/
solution?: ParseResult;
/**
* ParseResults for all tsconfig files referenced in a solution
*/
referenced?: ParseResult[];
/**
* ParseResult for all tsconfig files

@@ -40,3 +49,3 @@ *

*/
extended?: Omit<ParseResult, 'extended'>[];
extended?: ParseResult[];
}

@@ -61,2 +70,3 @@

* @returns {Promise<ParseNativeResult>}
* @throws {ParseNativeError}
*/

@@ -70,6 +80,14 @@ declare function parseNative(filename: string): Promise<ParseNativeResult>;

/**
* parsed result, including merged values from extended
* parsed result, including merged values from extended and normalized
*/
tsconfig: any;
/**
* ParseResult for parent solution
*/
solution?: ParseNativeResult;
/**
* ParseNativeResults for all tsconfig files referenced in a solution
*/
referenced?: ParseNativeResult[];
/**
* full output of ts.parseJsonConfigFileContent

@@ -76,0 +94,0 @@ */

var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;

@@ -17,2 +19,3 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;

};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));

@@ -169,2 +172,6 @@ // src/find.ts

import { promises as fs2 } from "fs";
var POSIX_SEP_RE = new RegExp("\\" + path2.posix.sep, "g");
var NATIVE_SEP_RE = new RegExp("\\" + path2.sep, "g");
var PATTERN_REGEX_CACHE = new Map();
var GLOB_ALL_PATTERN = `**/*`;
var dynamicImportDefault = new Function("path", "return import(path).then(m => m.default)");

@@ -198,7 +205,117 @@ async function loadTS() {

function posix2native(filename) {
return path2.posix.sep !== path2.sep && filename.includes(path2.posix.sep) ? filename.split(path2.posix.sep).join(path2.sep) : filename;
return path2.posix.sep !== path2.sep && filename.includes(path2.posix.sep) ? filename.replace(POSIX_SEP_RE, path2.sep) : filename;
}
function native2posix(filename) {
return path2.posix.sep !== path2.sep && filename.includes(path2.sep) ? filename.split(path2.sep).join(path2.posix.sep) : filename;
return path2.posix.sep !== path2.sep && filename.includes(path2.sep) ? filename.replace(NATIVE_SEP_RE, path2.posix.sep) : filename;
}
function resolve2posix(dir, filename) {
if (path2.sep === path2.posix.sep) {
return dir ? path2.resolve(dir, filename) : path2.resolve(filename);
}
return native2posix(dir ? path2.resolve(posix2native(dir), posix2native(filename)) : path2.resolve(posix2native(filename)));
}
function resolveReferencedTSConfigFiles(result) {
const dir = path2.dirname(result.filename);
return result.tsconfig.references.map((ref) => {
const refPath = ref.path.endsWith(".json") ? ref.path : path2.join(ref.path, "tsconfig.json");
return resolve2posix(dir, refPath);
});
}
function resolveSolutionTSConfig(filename, result) {
var _a;
if ([".ts", ".tsx"].some((ext) => filename.endsWith(ext)) && !isIncluded(filename, result)) {
const solutionTSConfig = (_a = result.referenced) == null ? void 0 : _a.find((referenced) => isIncluded(filename, referenced));
if (solutionTSConfig) {
return __spreadProps(__spreadValues({}, solutionTSConfig), {
solution: result
});
}
}
return result;
}
function isIncluded(filename, result) {
const dir = native2posix(path2.dirname(result.filename));
const files = (result.tsconfig.files || []).map((file) => resolve2posix(dir, file));
const absoluteFilename = resolve2posix(null, filename);
if (files.includes(filename)) {
return true;
}
const isIncluded2 = isGlobMatch(absoluteFilename, dir, result.tsconfig.include || (result.tsconfig.files ? [] : [GLOB_ALL_PATTERN]));
if (isIncluded2) {
const isExcluded = isGlobMatch(absoluteFilename, dir, result.tsconfig.exclude || []);
return !isExcluded;
}
return false;
}
function isGlobMatch(filename, dir, patterns) {
return patterns.some((pattern) => {
let lastWildcardIndex = pattern.length;
let hasWildcard = false;
for (let i = pattern.length - 1; i > -1; i--) {
if (pattern[i] === "*" || pattern[i] === "?") {
lastWildcardIndex = i;
hasWildcard = true;
break;
}
}
if (lastWildcardIndex < pattern.length - 1 && !filename.endsWith(pattern.slice(lastWildcardIndex + 1))) {
return false;
}
if (pattern.endsWith("*") && !(filename.endsWith(".ts") || filename.endsWith(".tsx"))) {
return false;
}
if (pattern === GLOB_ALL_PATTERN) {
return filename.startsWith(`${dir}/`);
}
const resolvedPattern = resolve2posix(dir, pattern);
let firstWildcardIndex = -1;
for (let i = 0; i < resolvedPattern.length; i++) {
if (resolvedPattern[i] === "*" || resolvedPattern[i] === "?") {
firstWildcardIndex = i;
hasWildcard = true;
break;
}
}
if (firstWildcardIndex > 1 && !filename.startsWith(resolvedPattern.slice(0, firstWildcardIndex - 1))) {
return false;
}
if (!hasWildcard) {
return filename === resolvedPattern;
}
if (PATTERN_REGEX_CACHE.has(resolvedPattern)) {
return PATTERN_REGEX_CACHE.get(resolvedPattern).test(filename);
}
const regex = pattern2regex(resolvedPattern);
PATTERN_REGEX_CACHE.set(resolvedPattern, regex);
return regex.test(filename);
});
}
function pattern2regex(resolvedPattern) {
let regexStr = "^";
for (let i = 0; i < resolvedPattern.length; i++) {
const char = resolvedPattern[i];
if (char === "?") {
regexStr += "[^\\/]";
continue;
}
if (char === "*") {
if (resolvedPattern[i + 1] === "*" && resolvedPattern[i + 2] === "/") {
i += 2;
regexStr += "(?:[^\\/]*\\/)*";
continue;
}
regexStr += "[^\\/]*";
continue;
}
if ("/.+^${}()|[]\\".includes(char)) {
regexStr += `\\`;
}
regexStr += char;
}
if (resolvedPattern.endsWith("*")) {
regexStr += "\\.tsx?";
}
regexStr += "$";
return new RegExp(regexStr);
}

@@ -208,40 +325,37 @@ // src/parse.ts

const tsconfigFile = await resolveTSConfig(filename) || await find(filename);
const parseResult = await parseFile(tsconfigFile);
if (!Object.prototype.hasOwnProperty.call(parseResult.tsconfig, "compileOnSave")) {
parseResult.tsconfig.compileOnSave = false;
}
const result = await parseExtends(parseResult);
deleteInvalidKeys(result.tsconfig);
return result;
const result = await parseFile(tsconfigFile);
await Promise.all([parseExtends(result), parseReferences(result)]);
return resolveSolutionTSConfig(filename, result);
}
async function parseFile(tsconfigFile) {
const tsconfigJson = await fs3.readFile(tsconfigFile, "utf-8");
const json = toJson(tsconfigJson);
return {
filename: tsconfigFile,
tsconfig: JSON.parse(json)
};
try {
const tsconfigJson = await fs3.readFile(tsconfigFile, "utf-8");
const json = toJson(tsconfigJson);
return {
filename: tsconfigFile,
tsconfig: normalizeTSConfig(JSON.parse(json), path3.dirname(tsconfigFile))
};
} catch (e) {
throw new ParseError(`parsing ${tsconfigFile} failed: ${e}`, "PARSE_FILE", e);
}
}
var VALID_KEYS = [
"extends",
"compilerOptions",
"files",
"include",
"exclude",
"watchOptions",
"references",
"compileOnSave",
"typeAcquisition"
];
function deleteInvalidKeys(tsconfig) {
for (const key of Object.keys(tsconfig)) {
if (!VALID_KEYS.includes(key)) {
delete tsconfig[key];
}
function normalizeTSConfig(tsconfig, dir) {
var _a;
if (((_a = tsconfig.compilerOptions) == null ? void 0 : _a.baseUrl) && !path3.isAbsolute(tsconfig.compilerOptions.baseUrl)) {
tsconfig.compilerOptions.baseUrl = resolve2posix(dir, tsconfig.compilerOptions.baseUrl);
}
return tsconfig;
}
async function parseReferences(result) {
if (!result.tsconfig.references) {
return;
}
const referencedFiles = resolveReferencedTSConfigFiles(result);
const referenced = await Promise.all(referencedFiles.map((file) => parseFile(file)));
await Promise.all(referenced.map((ref) => parseExtends(ref)));
result.referenced = referenced;
}
async function parseExtends(result) {
if (!result.tsconfig.extends) {
return result;
return;
}

@@ -256,3 +370,3 @@ const extended = [

const circle = extended.concat({ filename: extendedTSConfigFile, tsconfig: null }).map((e) => e.filename).join(" -> ");
throw new Error(`Circular dependency in "extends": ${circle}`);
throw new ParseError(`Circular dependency in "extends": ${circle}`, "EXTENDS_CIRCULAR");
}

@@ -262,3 +376,5 @@ extended.push(await parseFile(extendedTSConfigFile));

result.extended = extended;
return mergeExtended(result);
for (const ext of result.extended.slice(1)) {
extendTSConfig(result, ext);
}
}

@@ -269,17 +385,20 @@ function resolveExtends(extended, from) {

} catch (e) {
throw new Error(`failed to resolve "extends":"${extended}" in ${from}`);
throw new ParseError(`failed to resolve "extends":"${extended}" in ${from}`, "EXTENDS_RESOLVE", e);
}
}
function mergeExtended(result) {
for (const ext of result.extended.slice(1)) {
extendTSConfig(result, ext);
}
return result;
}
var NEVER_INHERITED = ["references", "extends"];
var EXTENDABLE_KEYS = [
"compilerOptions",
"files",
"include",
"exclude",
"watchOptions",
"compileOnSave",
"typeAcquisition",
"buildOptions"
];
function extendTSConfig(extending, extended) {
const extendingConfig = extending.tsconfig;
const extendedConfig = extended.tsconfig;
const relativePath = path3.relative(path3.dirname(extending.filename), path3.dirname(extended.filename));
for (const key of Object.keys(extendedConfig).filter((key2) => !NEVER_INHERITED.includes(key2))) {
const relativePath = native2posix(path3.relative(path3.dirname(extending.filename), path3.dirname(extended.filename)));
for (const key of Object.keys(extendedConfig).filter((key2) => EXTENDABLE_KEYS.includes(key2))) {
if (key === "compilerOptions") {

@@ -296,3 +415,3 @@ for (const option of Object.keys(extendedConfig.compilerOptions)) {

for (const option of Object.keys(extendedConfig.watchOptions)) {
extendingConfig.watchOptions[option] = rebaseRelative(option, extendedConfig.compilerOptions[option], relativePath);
extendingConfig.watchOptions[option] = rebaseRelative(option, extendedConfig.watchOptions[option], relativePath);
}

@@ -310,3 +429,2 @@ } else {

"baseUrl",
"paths",
"rootDir",

@@ -327,7 +445,2 @@ "rootDirs",

return value.map((x) => rebasePath(x, prependPath));
} else if (typeof value === "object") {
return Object.entries(value).reduce((rebasedValue, [k, v]) => {
rebasedValue[k] = rebasePath(v, prependPath);
return rebasedValue;
}, {});
} else {

@@ -344,2 +457,11 @@ return rebasePath(value, prependPath);

}
var ParseError = class extends Error {
constructor(message, code, cause) {
super(message);
Object.setPrototypeOf(this, ParseError.prototype);
this.name = ParseError.name;
this.code = code;
this.cause = cause;
}
};

@@ -368,6 +490,11 @@ // src/find-native.ts

const ts = await loadTS();
const result = await parseFile2(tsconfigFile, ts);
await parseReferences2(result, ts);
return resolveSolutionTSConfig(filename, result);
}
async function parseFile2(tsconfigFile, ts) {
const { parseJsonConfigFileContent, readConfigFile, sys } = ts;
const { config, error } = readConfigFile(tsconfigFile, sys.readFile);
if (error) {
throw toError(error);
throw new ParseNativeError(error, null);
}

@@ -380,11 +507,20 @@ const host = {

};
const result = parseJsonConfigFileContent(config, host, path5.dirname(tsconfigFile), void 0, tsconfigFile);
checkErrors(result.errors);
return {
const nativeResult = parseJsonConfigFileContent(config, host, path5.dirname(tsconfigFile), void 0, tsconfigFile);
checkErrors(nativeResult);
const result = {
filename: posix2native(tsconfigFile),
tsconfig: result2tsconfig(result, ts),
result
tsconfig: result2tsconfig(nativeResult, ts),
result: nativeResult
};
return result;
}
function checkErrors(errors) {
async function parseReferences2(result, ts) {
if (!result.tsconfig.references) {
return;
}
const referencedFiles = resolveReferencedTSConfigFiles(result);
result.referenced = await Promise.all(referencedFiles.map((file) => parseFile2(file, ts)));
}
function checkErrors(nativeResult) {
var _a;
const ignoredErrorCodes = [

@@ -394,20 +530,15 @@ 18002,

];
const criticalError = errors == null ? void 0 : errors.find((error) => error.category === 1 && !ignoredErrorCodes.includes(error.code));
const criticalError = (_a = nativeResult.errors) == null ? void 0 : _a.find((error) => error.category === 1 && !ignoredErrorCodes.includes(error.code));
if (criticalError) {
throw toError(criticalError);
throw new ParseNativeError(criticalError, nativeResult);
}
}
function toError(tsError) {
return {
message: tsError.messageText,
code: `TS ${tsError.code}`,
start: tsError.start
};
}
function result2tsconfig(result, ts) {
const tsconfig = JSON.parse(JSON.stringify(result.raw));
if (Object.keys(result.options).filter((x) => x !== "configFilePath").length > 0) {
const extendedCompilerOptions = __spreadValues({}, result.options);
delete extendedCompilerOptions["configFilePath"];
tsconfig.compilerOptions = extendedCompilerOptions;
const ignoredOptions = ["configFilePath", "pathsBasePath"];
if (result.options && Object.keys(result.options).some((o) => !ignoredOptions.includes(o))) {
tsconfig.compilerOptions = __spreadValues({}, result.options);
for (const ignored of ignoredOptions) {
delete tsconfig.compilerOptions[ignored];
}
}

@@ -421,10 +552,15 @@ const compilerOptions = tsconfig.compilerOptions;

{ name: "importsNotUsedAsValues", enumeration: ts.ImportsNotUsedAsValues },
{ name: "jsxEmit", enumeration: ts.JsxEmit },
{ name: "module", enumeration: ts.ModuleKind },
{ name: "moduleResulution", enumeration: ts.ModuleResolutionKind },
{ name: "newLine", enumeration: ts.NewLineKind },
{
name: "moduleResolution",
enumeration: { 1: "classic", 2: "node" }
},
{
name: "newLine",
enumeration: { 0: "crlf", 1: "lf" }
},
{ name: "target", enumeration: ts.ScriptTarget }
];
for (const prop of enumProperties) {
if (compilerOptions[prop.name] != null) {
if (compilerOptions[prop.name] != null && typeof compilerOptions[prop.name] === "number") {
compilerOptions[prop.name] = prop.enumeration[compilerOptions[prop.name]].toLowerCase();

@@ -434,2 +570,5 @@ }

}
if (result.watchOptions) {
tsconfig.watchOptions = __spreadValues({}, result.watchOptions);
}
const watchOptions = tsconfig.watchOptions;

@@ -443,10 +582,23 @@ if (watchOptions) {

for (const prop of enumProperties) {
if (compilerOptions[prop.name] != null) {
const enumVal = prop.enumeration[compilerOptions[prop.name]];
compilerOptions[prop.name] = enumVal.charAt(0).toLowerCase() + enumVal.slice(1);
if (watchOptions[prop.name] != null && typeof watchOptions[prop.name] === "number") {
const enumVal = prop.enumeration[watchOptions[prop.name]];
watchOptions[prop.name] = enumVal.charAt(0).toLowerCase() + enumVal.slice(1);
}
}
}
if (tsconfig.compileOnSave === false) {
delete tsconfig.compileOnSave;
}
return tsconfig;
}
var ParseNativeError = class extends Error {
constructor(diagnostic, result) {
super(diagnostic.messageText);
Object.setPrototypeOf(this, ParseNativeError.prototype);
this.name = ParseNativeError.name;
this.code = `TS ${diagnostic.code}`;
this.diagnostic = diagnostic;
this.result = result;
}
};
export {

@@ -453,0 +605,0 @@ find,

{
"name": "tsconfck",
"version": "1.0.0-3",
"version": "1.0.0-4",
"description": "A utility to work with tsconfig.json without typescript",

@@ -52,9 +52,10 @@ "license": "MIT",

"@tsconfig/node12": "^1.0.9",
"@types/node": "^16.7.2",
"@typescript-eslint/eslint-plugin": "^4.29.3",
"@typescript-eslint/parser": "^4.29.3",
"@types/node": "^16.7.10",
"@typescript-eslint/eslint-plugin": "^4.30.0",
"@typescript-eslint/parser": "^4.30.0",
"c8": "^7.8.0",
"chalk": "^4.1.2",
"conventional-changelog-cli": "^2.1.1",
"enquirer": "^2.3.6",
"esbuild": "^0.12.23",
"esbuild": "^0.12.25",
"eslint": "^7.32.0",

@@ -64,3 +65,3 @@ "eslint-config-prettier": "^8.3.0",

"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.4.1",
"eslint-plugin-prettier": "^4.0.0",
"execa": "^5.1.1",

@@ -70,3 +71,3 @@ "husky": "^7.0.2",

"minimist": "^1.2.5",
"npm-check-updates": "^11.8.3",
"npm-check-updates": "^11.8.5",
"npm-run-all": "^4.1.5",

@@ -79,5 +80,5 @@ "prettier": "^2.3.2",

"tsup": "^4.14.0",
"typescript": "^4.3.5",
"typescript": "^4.4.2",
"uvu": "^0.5.1",
"watchlist": "^0.2.3"
"watchlist": "^0.3.1"
},

@@ -100,4 +101,6 @@ "lint-staged": {

"build": "pnpm run build:ci -- --dts --sourcemap && node scripts/generate-api-docs.js",
"test": "node --experimental-loader ts-node/esm node_modules/uvu/bin.js tests -i fixtures -i temp -i util",
"test": "node --experimental-loader ts-node/esm node_modules/uvu/bin.js tests -i fixtures -i temp -i util/",
"test:watch": "pnpm test; watchlist tests src -- pnpm test",
"test:coverage": "c8 --include=src --clean pnpm run test",
"test:report": "c8 report --reporter=text-lcov > coverage/coverage.lcov",
"lint": "eslint --ignore-path .gitignore '**/*.{cjs,js,ts,md}'",

@@ -104,0 +107,0 @@ "lint:fix": "pnpm run lint -- --fix",

@@ -14,2 +14,3 @@ # tsconfck

- [x] resolve "extends"
- [x] resolve "references" of solution-style tsconfig
- [x] optional findNative and parseNative to use official typescript api

@@ -26,3 +27,5 @@

tsconfig, // tsconfig object including merged values from extended configs
extended // separate unmerged results of all tsconfig files
extended, // separate unmerged results of all tsconfig files that contributed to tsconfig
solution, // solution result if tsconfig is part of a solution
referenced // referenced tsconfig results if tsconfig is a solution
} = await parse('foo/bar.ts');

@@ -38,3 +41,5 @@ ```

tsconfig, // tsconfig object including merged values from extended configs
result // output of ts.parseJsonConfigFileContent
result, // output of ts.parseJsonConfigFileContent
solution, // solution result if tsconfig is part of a solution
referenced // referenced tsconfig results if tsconfig is a solution
} = await parseNative('foo/bar.ts');

@@ -88,2 +93,12 @@ ```

#### parse-result
```shell
# print content of ParseResult on stdout
tsconfck parse-result src/index.ts
# print to file
tsconfck parse-result src/index.ts > output.json
```
#### help

@@ -90,0 +105,0 @@

import path from 'path';
import { loadTS, native2posix, posix2native, resolveTSConfig } from './util.js';
import {
loadTS,
native2posix,
posix2native,
resolveReferencedTSConfigFiles,
resolveSolutionTSConfig,
resolveTSConfig
} from './util.js';
import { findNative } from './find-native.js';

@@ -12,2 +19,3 @@

* @returns {Promise<ParseNativeResult>}
* @throws {ParseNativeError}
*/

@@ -24,6 +32,13 @@ export async function parseNative(filename: string): Promise<ParseNativeResult> {

const ts = await loadTS();
const result = await parseFile(tsconfigFile, ts);
await parseReferences(result, ts);
//@ts-ignore
return resolveSolutionTSConfig(filename, result);
}
async function parseFile(tsconfigFile: string, ts: any) {
const { parseJsonConfigFileContent, readConfigFile, sys } = ts;
const { config, error } = readConfigFile(tsconfigFile, sys.readFile);
if (error) {
throw toError(error);
throw new ParseNativeError(error, null);
}

@@ -38,3 +53,3 @@

const result = parseJsonConfigFileContent(
const nativeResult = parseJsonConfigFileContent(
config,

@@ -46,11 +61,19 @@ host,

);
checkErrors(result.errors);
return {
checkErrors(nativeResult);
const result: ParseNativeResult = {
filename: posix2native(tsconfigFile),
tsconfig: result2tsconfig(result, ts),
result: result
tsconfig: result2tsconfig(nativeResult, ts),
result: nativeResult
};
return result;
}
async function parseReferences(result: ParseNativeResult, ts: any) {
if (!result.tsconfig.references) {
return;
}
const referencedFiles = resolveReferencedTSConfigFiles(result);
result.referenced = await Promise.all(referencedFiles.map((file) => parseFile(file, ts)));
}
/**

@@ -62,6 +85,6 @@ * check errors reported by parseJsonConfigFileContent

*
* @param errors errors to check
* @throws {message: string, code: string, start?: number} for critical error
* @param {nativeResult} any - native typescript parse result to check for errors
* @throws {ParseNativeError} for critical error
*/
function checkErrors(errors: { code: number; category: number }[]) {
function checkErrors(nativeResult: any) {
const ignoredErrorCodes = [

@@ -72,18 +95,10 @@ // see https://github.com/microsoft/TypeScript/blob/main/src/compiler/diagnosticMessages.json

];
const criticalError = errors?.find(
(error) => error.category === 1 && !ignoredErrorCodes.includes(error.code)
const criticalError = nativeResult.errors?.find(
(error: TSDiagnosticError) => error.category === 1 && !ignoredErrorCodes.includes(error.code)
);
if (criticalError) {
throw toError(criticalError);
throw new ParseNativeError(criticalError, nativeResult);
}
}
function toError(tsError: any) {
return {
message: tsError.messageText,
code: `TS ${tsError.code}`,
start: tsError.start
};
}
/**

@@ -104,10 +119,13 @@ * convert the result of `parseJsonConfigFileContent` to a tsconfig that can be parsed again

// for some reason the extended compilerOptions are not available in result.raw but only in result.options
// and contain an extra field 'configFilePath'. Use everything but that field
if (Object.keys(result.options).filter((x) => x !== 'configFilePath').length > 0) {
const extendedCompilerOptions = {
// and contain an extra fields 'configFilePath' and 'pathsBasePath'. Use everything but those 2
const ignoredOptions = ['configFilePath', 'pathsBasePath'];
if (result.options && Object.keys(result.options).some((o) => !ignoredOptions.includes(o))) {
tsconfig.compilerOptions = {
...result.options
};
delete extendedCompilerOptions['configFilePath'];
tsconfig.compilerOptions = extendedCompilerOptions;
for (const ignored of ignoredOptions) {
delete tsconfig.compilerOptions[ignored];
}
}
const compilerOptions = tsconfig.compilerOptions;

@@ -123,10 +141,15 @@ if (compilerOptions) {

{ name: 'importsNotUsedAsValues', enumeration: ts.ImportsNotUsedAsValues },
{ name: 'jsxEmit', enumeration: ts.JsxEmit },
{ name: 'module', enumeration: ts.ModuleKind },
{ name: 'moduleResulution', enumeration: ts.ModuleResolutionKind },
{ name: 'newLine', enumeration: ts.NewLineKind },
{
name: 'moduleResolution',
enumeration: { 1: 'classic', 2: 'node' } /*ts.ModuleResolutionKind uses different names*/
},
{
name: 'newLine',
enumeration: { 0: 'crlf', 1: 'lf' } /*ts.NewLineKind uses different names*/
},
{ name: 'target', enumeration: ts.ScriptTarget }
];
for (const prop of enumProperties) {
if (compilerOptions[prop.name] != null) {
if (compilerOptions[prop.name] != null && typeof compilerOptions[prop.name] === 'number') {
compilerOptions[prop.name] = prop.enumeration[compilerOptions[prop.name]].toLowerCase();

@@ -137,2 +160,9 @@ }

// merged watchOptions
if (result.watchOptions) {
tsconfig.watchOptions = {
...result.watchOptions
};
}
const watchOptions = tsconfig.watchOptions;

@@ -146,8 +176,13 @@ if (watchOptions) {

for (const prop of enumProperties) {
if (compilerOptions[prop.name] != null) {
const enumVal = prop.enumeration[compilerOptions[prop.name]];
compilerOptions[prop.name] = enumVal.charAt(0).toLowerCase() + enumVal.slice(1);
if (watchOptions[prop.name] != null && typeof watchOptions[prop.name] === 'number') {
const enumVal = prop.enumeration[watchOptions[prop.name]];
watchOptions[prop.name] = enumVal.charAt(0).toLowerCase() + enumVal.slice(1);
}
}
}
if (tsconfig.compileOnSave === false) {
// ts adds this property even if it isn't present in the actual config
// delete if it is false to match content of tsconfig
delete tsconfig.compileOnSave;
}
return tsconfig;

@@ -161,7 +196,19 @@ }

filename: string;
/**
* parsed result, including merged values from extended
* parsed result, including merged values from extended and normalized
*/
tsconfig: any;
/**
* ParseResult for parent solution
*/
solution?: ParseNativeResult;
/**
* ParseNativeResults for all tsconfig files referenced in a solution
*/
referenced?: ParseNativeResult[];
/**
* full output of ts.parseJsonConfigFileContent

@@ -171,1 +218,35 @@ */

}
export class ParseNativeError extends Error {
constructor(diagnostic: TSDiagnosticError, result?: any) {
super(diagnostic.messageText);
// Set the prototype explicitly.
Object.setPrototypeOf(this, ParseNativeError.prototype);
this.name = ParseNativeError.name;
this.code = `TS ${diagnostic.code}`;
this.diagnostic = diagnostic;
this.result = result;
}
/**
* code of typescript diagnostic, prefixed with "TS "
*/
code: string;
/**
* full ts diagnostic that caused this error
*/
diagnostic: any;
/**
* native result if present, contains all errors in result.errors
*/
result: any | undefined;
}
interface TSDiagnosticError {
code: number;
category: number;
messageText: string;
start?: number;
}

@@ -6,3 +6,9 @@ import path from 'path';

import { toJson } from './to-json.js';
import { resolveTSConfig } from './util.js';
import {
native2posix,
resolve2posix,
resolveReferencedTSConfigFiles,
resolveSolutionTSConfig,
resolveTSConfig
} from './util.js';

@@ -13,41 +19,34 @@ /**

* @param {string} filename - path to a tsconfig.json or a .ts source file (absolute or relative to cwd)
* @returns {Promise<object|void>} tsconfig parsed as object
* @returns {Promise<ParseResult>}
* @throws {ParseError}
*/
export async function parse(filename: string): Promise<ParseResult> {
const tsconfigFile = (await resolveTSConfig(filename)) || (await find(filename));
const parseResult = await parseFile(tsconfigFile);
if (!Object.prototype.hasOwnProperty.call(parseResult.tsconfig, 'compileOnSave')) {
// ts.parseJsonConfigFileContent returns compileOnSave even if it is not set explicitly so add it if it wasn't
parseResult.tsconfig.compileOnSave = false;
}
const result = await parseExtends(parseResult);
deleteInvalidKeys(result.tsconfig);
return result;
const result = await parseFile(tsconfigFile);
await Promise.all([parseExtends(result), parseReferences(result)]);
return resolveSolutionTSConfig(filename, result);
}
async function parseFile(tsconfigFile: string): Promise<ParseResult> {
const tsconfigJson = await fs.readFile(tsconfigFile, 'utf-8');
const json = toJson(tsconfigJson);
return {
filename: tsconfigFile,
tsconfig: JSON.parse(json)
};
try {
const tsconfigJson = await fs.readFile(tsconfigFile, 'utf-8');
const json = toJson(tsconfigJson);
return {
filename: tsconfigFile,
tsconfig: normalizeTSConfig(JSON.parse(json), path.dirname(tsconfigFile))
};
} catch (e) {
throw new ParseError(`parsing ${tsconfigFile} failed: ${e}`, 'PARSE_FILE', e);
}
}
const VALID_KEYS = [
'extends',
'compilerOptions',
'files',
'include',
'exclude',
'watchOptions',
'references',
'compileOnSave',
'typeAcquisition'
];
function deleteInvalidKeys(tsconfig: any) {
for (const key of Object.keys(tsconfig)) {
if (!VALID_KEYS.includes(key)) {
delete tsconfig[key];
}
/**
* normalize to match the output of ts.parseJsonConfigFileContent
*
* @param tsconfig
*/
function normalizeTSConfig(tsconfig: any, dir: string) {
// set baseUrl to absolute path
if (tsconfig.compilerOptions?.baseUrl && !path.isAbsolute(tsconfig.compilerOptions.baseUrl)) {
tsconfig.compilerOptions.baseUrl = resolve2posix(dir, tsconfig.compilerOptions.baseUrl);
}

@@ -57,5 +56,15 @@ return tsconfig;

async function parseExtends(result: ParseResult): Promise<ParseResult> {
async function parseReferences(result: ParseResult) {
if (!result.tsconfig.references) {
return;
}
const referencedFiles = resolveReferencedTSConfigFiles(result);
const referenced = await Promise.all(referencedFiles.map((file) => parseFile(file)));
await Promise.all(referenced.map((ref) => parseExtends(ref)));
result.referenced = referenced;
}
async function parseExtends(result: ParseResult) {
if (!result.tsconfig.extends) {
return result;
return;
}

@@ -76,3 +85,3 @@ // use result as first element in extended

.join(' -> ');
throw new Error(`Circular dependency in "extends": ${circle}`);
throw new ParseError(`Circular dependency in "extends": ${circle}`, 'EXTENDS_CIRCULAR');
}

@@ -82,3 +91,6 @@ extended.push(await parseFile(extendedTSConfigFile));

result.extended = extended;
return mergeExtended(result);
// skip first as it is the original config
for (const ext of result.extended!.slice(1)) {
extendTSConfig(result, ext);
}
}

@@ -90,24 +102,28 @@

} catch (e) {
throw new Error(`failed to resolve "extends":"${extended}" in ${from}`);
throw new ParseError(
`failed to resolve "extends":"${extended}" in ${from}`,
'EXTENDS_RESOLVE',
e
);
}
}
function mergeExtended(result: ParseResult): any {
// skip first as it is the original config
for (const ext of result.extended!.slice(1)) {
extendTSConfig(result, ext);
}
return result;
}
// references is never inherited according to docs
const NEVER_INHERITED = ['references', 'extends'];
// references, extends and custom keys are not carried over
const EXTENDABLE_KEYS = [
'compilerOptions',
'files',
'include',
'exclude',
'watchOptions',
'compileOnSave',
'typeAcquisition',
'buildOptions'
];
function extendTSConfig(extending: ParseResult, extended: ParseResult): any {
const extendingConfig = extending.tsconfig;
const extendedConfig = extended.tsconfig;
const relativePath = path.relative(
path.dirname(extending.filename),
path.dirname(extended.filename)
const relativePath = native2posix(
path.relative(path.dirname(extending.filename), path.dirname(extended.filename))
);
for (const key of Object.keys(extendedConfig).filter((key) => !NEVER_INHERITED.includes(key))) {
for (const key of Object.keys(extendedConfig).filter((key) => EXTENDABLE_KEYS.includes(key))) {
if (key === 'compilerOptions') {

@@ -130,3 +146,3 @@ for (const option of Object.keys(extendedConfig.compilerOptions)) {

option,
extendedConfig.compilerOptions[option],
extendedConfig.watchOptions[option],
relativePath

@@ -149,3 +165,2 @@ );

'baseUrl',
'paths',
'rootDir',

@@ -162,3 +177,3 @@ 'rootDirs',

type PathValue = string | string[] | { [key: string]: string };
type PathValue = string | string[];

@@ -171,7 +186,2 @@ function rebaseRelative(key: string, value: PathValue, prependPath: string): PathValue {

return value.map((x) => rebasePath(x, prependPath));
} else if (typeof value === 'object') {
return Object.entries(value).reduce((rebasedValue, [k, v]) => {
rebasedValue[k] = rebasePath(v, prependPath);
return rebasedValue;
}, {} as { [key: string]: string });
} else {

@@ -196,2 +206,3 @@ return rebasePath(value as string, prependPath);

filename: string;
/**

@@ -203,2 +214,12 @@ * parsed result, including merged values from extended

/**
* ParseResult for parent solution
*/
solution?: ParseResult;
/**
* ParseResults for all tsconfig files referenced in a solution
*/
referenced?: ParseResult[];
/**
* ParseResult for all tsconfig files

@@ -208,3 +229,23 @@ *

*/
extended?: Omit<ParseResult, 'extended'>[];
extended?: ParseResult[];
}
export class ParseError extends Error {
constructor(message: string, code: string, cause?: Error) {
super(message);
// Set the prototype explicitly.
Object.setPrototypeOf(this, ParseError.prototype);
this.name = ParseError.name;
this.code = code;
this.cause = cause;
}
/**
* error code
*/
code: string;
/**
* code of typescript diagnostic, prefixed with "TS "
*/
cause: Error | undefined;
}
import path from 'path';
import { promises as fs } from 'fs';
import { ParseResult } from './parse';
const POSIX_SEP_RE = new RegExp('\\' + path.posix.sep, 'g');
const NATIVE_SEP_RE = new RegExp('\\' + path.sep, 'g');
const PATTERN_REGEX_CACHE = new Map<string, RegExp>();
const GLOB_ALL_PATTERN = `**/*`;
// hide dynamic import from ts transform to prevent it turning into a require

@@ -49,3 +55,3 @@ // see https://github.com/microsoft/TypeScript/issues/43329#issuecomment-811606238

return path.posix.sep !== path.sep && filename.includes(path.posix.sep)
? filename.split(path.posix.sep).join(path.sep)
? filename.replace(POSIX_SEP_RE, path.sep)
: filename;

@@ -66,4 +72,169 @@ }

return path.posix.sep !== path.sep && filename.includes(path.sep)
? filename.split(path.sep).join(path.posix.sep)
? filename.replace(NATIVE_SEP_RE, path.posix.sep)
: filename;
}
/**
* converts params to native separator, resolves path and converts native back to posix
*
* needed on windows to handle posix paths in tsconfig
*
* @param dir {string} directory to resolve from
* @param filename {string} filename or pattern to resolve
*/
export function resolve2posix(dir: string | null, filename: string) {
if (path.sep === path.posix.sep) {
return dir ? path.resolve(dir, filename) : path.resolve(filename);
}
return native2posix(
dir
? path.resolve(posix2native(dir), posix2native(filename))
: path.resolve(posix2native(filename))
);
}
export function resolveReferencedTSConfigFiles(result: ParseResult): string[] {
const dir = path.dirname(result.filename);
return result.tsconfig.references.map((ref: { path: string }) => {
const refPath = ref.path.endsWith('.json') ? ref.path : path.join(ref.path, 'tsconfig.json');
return resolve2posix(dir, refPath);
});
}
export function resolveSolutionTSConfig(filename: string, result: ParseResult): ParseResult {
if (['.ts', '.tsx'].some((ext) => filename.endsWith(ext)) && !isIncluded(filename, result)) {
const solutionTSConfig = result.referenced?.find((referenced) =>
isIncluded(filename, referenced)
);
if (solutionTSConfig) {
return {
...solutionTSConfig,
solution: result
};
}
}
return result;
}
function isIncluded(filename: string, result: ParseResult): boolean {
const dir = native2posix(path.dirname(result.filename));
const files = (result.tsconfig.files || []).map((file: string) => resolve2posix(dir, file));
const absoluteFilename = resolve2posix(null, filename);
if (files.includes(filename)) {
return true;
}
const isIncluded = isGlobMatch(
absoluteFilename,
dir,
result.tsconfig.include || (result.tsconfig.files ? [] : [GLOB_ALL_PATTERN])
);
if (isIncluded) {
const isExcluded = isGlobMatch(absoluteFilename, dir, result.tsconfig.exclude || []);
return !isExcluded;
}
return false;
}
/**
* test filenames agains glob patterns in tsconfig
*
* @param filename {string} posix style abolute path to filename to test
* @param dir {string} posix style absolute path to directory of tsconfig containing patterns
* @param patterns {string[]} glob patterns to match against
* @returns {boolean} true when at least one pattern matches filename
*/
export function isGlobMatch(filename: string, dir: string, patterns: string[]): boolean {
return patterns.some((pattern) => {
// filename must end with part of pattern that comes after last wildcard
let lastWildcardIndex = pattern.length;
let hasWildcard = false;
for (let i = pattern.length - 1; i > -1; i--) {
if (pattern[i] === '*' || pattern[i] === '?') {
lastWildcardIndex = i;
hasWildcard = true;
break;
}
}
// if pattern does not end with wildcard, filename must end with pattern after last wildcard
if (
lastWildcardIndex < pattern.length - 1 &&
!filename.endsWith(pattern.slice(lastWildcardIndex + 1))
) {
return false;
}
// if pattern ends with *, filename must end with .ts or .tsx
if (pattern.endsWith('*') && !(filename.endsWith('.ts') || filename.endsWith('.tsx'))) {
return false;
}
// for **/* , filename must start with the dir
if (pattern === GLOB_ALL_PATTERN) {
return filename.startsWith(`${dir}/`);
}
const resolvedPattern = resolve2posix(dir, pattern);
// filename must start with part of pattern that comes before first wildcard
let firstWildcardIndex = -1;
for (let i = 0; i < resolvedPattern.length; i++) {
if (resolvedPattern[i] === '*' || resolvedPattern[i] === '?') {
firstWildcardIndex = i;
hasWildcard = true;
break;
}
}
if (
firstWildcardIndex > 1 &&
!filename.startsWith(resolvedPattern.slice(0, firstWildcardIndex - 1))
) {
return false;
}
// if no wildcard in pattern, filename must be equal to resolved pattern
if (!hasWildcard) {
return filename === resolvedPattern;
}
// complex pattern, use regex to check it
if (PATTERN_REGEX_CACHE.has(resolvedPattern)) {
return PATTERN_REGEX_CACHE.get(resolvedPattern)!.test(filename);
}
const regex = pattern2regex(resolvedPattern);
PATTERN_REGEX_CACHE.set(resolvedPattern, regex);
return regex.test(filename);
});
}
function pattern2regex(resolvedPattern: string): RegExp {
let regexStr = '^';
for (let i = 0; i < resolvedPattern.length; i++) {
const char = resolvedPattern[i];
if (char === '?') {
regexStr += '[^\\/]';
continue;
}
if (char === '*') {
if (resolvedPattern[i + 1] === '*' && resolvedPattern[i + 2] === '/') {
i += 2;
regexStr += '(?:[^\\/]*\\/)*'; // zero or more path segments
continue;
}
regexStr += '[^\\/]*';
continue;
}
if ('/.+^${}()|[]\\'.includes(char)) {
regexStr += `\\`;
}
regexStr += char;
}
// add known file endings if pattern ends on *
if (resolvedPattern.endsWith('*')) {
regexStr += '\\.tsx?';
}
regexStr += '$';
return new RegExp(regexStr);
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc