Skip to content

Commit 7a38980

Browse files
authored
Validate the options passed in are numbers instead of strings for our custom commandline options (#58388)
1 parent 66dd12e commit 7a38980

File tree

3 files changed

+84
-0
lines changed

3 files changed

+84
-0
lines changed

src/compiler/commandLineParser.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1639,6 +1639,12 @@ export const configDirTemplateSubstitutionWatchOptions: readonly CommandLineOpti
16391639
option => option.allowConfigDirTemplateSubstitution || (!option.isCommandLineOnly && option.isFilePath),
16401640
);
16411641

1642+
/** @internal */
1643+
export const commandLineOptionOfCustomType: readonly CommandLineOptionOfCustomType[] = optionDeclarations.filter(isCommandLineOptionOfCustomType);
1644+
function isCommandLineOptionOfCustomType(option: CommandLineOption): option is CommandLineOptionOfCustomType {
1645+
return !isString(option.type);
1646+
}
1647+
16421648
// Build related options
16431649
/** @internal */
16441650
export const optionsForBuild: CommandLineOption[] = [

src/compiler/program.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
changesAffectingProgramStructure,
1616
changesAffectModuleResolution,
1717
combinePaths,
18+
commandLineOptionOfCustomType,
1819
CommentDirective,
1920
CommentDirectivesMap,
2021
compareDataObjects,
@@ -1550,6 +1551,13 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
15501551
const createProgramOptions = isArray(rootNamesOrOptions) ? createCreateProgramOptions(rootNamesOrOptions, _options!, _host, _oldProgram, _configFileParsingDiagnostics) : rootNamesOrOptions; // TODO: GH#18217
15511552
const { rootNames, options, configFileParsingDiagnostics, projectReferences, typeScriptVersion } = createProgramOptions;
15521553
let { oldProgram } = createProgramOptions;
1554+
for (const option of commandLineOptionOfCustomType) {
1555+
if (hasProperty(options, option.name)) {
1556+
if (typeof options[option.name] === "string") {
1557+
throw new Error(`${option.name} is a string value; tsconfig JSON must be parsed with parseJsonSourceFileConfigFileContent or getParsedCommandLineOfConfigFile before passing to createProgram`);
1558+
}
1559+
}
1560+
}
15531561

15541562
const reportInvalidIgnoreDeprecations = memoize(() => createOptionValueDiagnostic("ignoreDeprecations", Diagnostics.Invalid_value_for_ignoreDeprecations));
15551563

src/testRunner/unittests/publicApi.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import * as fakes from "../_namespaces/fakes";
33
import * as Harness from "../_namespaces/Harness";
44
import * as ts from "../_namespaces/ts";
55
import * as vfs from "../_namespaces/vfs";
6+
import { jsonToReadableText } from "./helpers";
7+
import { libContent } from "./helpers/contents";
8+
import { loadProjectFromFiles } from "./helpers/vfs";
69

710
describe("unittests:: Public APIs", () => {
811
function verifyApi(fileName: string) {
@@ -262,3 +265,70 @@ class C {
262265
assert.equal(ts.ModifierFlags.None, ts.getSyntacticModifierFlags(propNode));
263266
});
264267
});
268+
269+
describe("unittests:: Public APIs:: createProgram", () => {
270+
function verifyAPI(useJsonParsingApi: boolean) {
271+
const fs = loadProjectFromFiles({
272+
"/src/projects/project/packages/a/index.js": `export const a = 'a';`,
273+
"/src/projects/project/packages/a/test/index.js": `import 'a';`,
274+
"/src/projects/project/packages/a/tsconfig.json": jsonToReadableText({
275+
compilerOptions: {
276+
checkJs: true,
277+
composite: true,
278+
declaration: true,
279+
emitDeclarationOnly: true,
280+
module: "nodenext",
281+
outDir: "types",
282+
},
283+
}),
284+
"/src/projects/project/packages/a/package.json": jsonToReadableText({
285+
name: "a",
286+
version: "0.0.0",
287+
type: "module",
288+
exports: {
289+
".": {
290+
types: "./types/index.d.ts",
291+
default: "./index.js",
292+
},
293+
},
294+
}),
295+
"/lib/lib.esnext.full.d.ts": libContent,
296+
}, { cwd: "/src/projects/project", executingFilePath: "/lib/tsc.js" });
297+
const sys = new fakes.System(fs, { executingFilePath: "/lib/tsc.js" });
298+
const commandLine = ts.getParsedCommandLineOfConfigFile(
299+
"/src/projects/project/packages/a/tsconfig.json",
300+
/*optionsToExtend*/ undefined,
301+
{
302+
fileExists: sys.fileExists.bind(sys),
303+
getCurrentDirectory: sys.getCurrentDirectory.bind(sys),
304+
onUnRecoverableConfigFileDiagnostic: () => {},
305+
readDirectory: sys.readDirectory.bind(sys),
306+
readFile: sys.readFile.bind(sys),
307+
useCaseSensitiveFileNames: sys.useCaseSensitiveFileNames,
308+
directoryExists: sys.directoryExists.bind(sys),
309+
getDirectories: sys.getDirectories.bind(sys),
310+
realpath: sys.realpath.bind(sys),
311+
},
312+
)!;
313+
const config = !useJsonParsingApi ? JSON.parse(fs.readFileSync("/src/projects/project/packages/a/tsconfig.json", "utf-8")) : undefined;
314+
// This is really createCompilerHost but we want to use our own sys so simple usage
315+
const host = ts.createCompilerHostWorker(
316+
useJsonParsingApi ? commandLine.options : config.compilerOptions,
317+
/*setParentNodes*/ undefined,
318+
sys,
319+
);
320+
(useJsonParsingApi ? assert.doesNotThrow : assert.throws)(() =>
321+
ts.createProgram({
322+
rootNames: commandLine.fileNames,
323+
options: useJsonParsingApi ? commandLine.options : config.compilerOptions,
324+
host,
325+
})
326+
);
327+
}
328+
it("when using correct config file API", () => {
329+
verifyAPI(/*useJsonParsingApi*/ true);
330+
});
331+
it("when using direct json read", () => {
332+
verifyAPI(/*useJsonParsingApi*/ false);
333+
});
334+
});

0 commit comments

Comments
 (0)