microsoft/TypeScript · error · Error
Global option '${key}' already exists
Error message
Global option '${key}' already exists What it means
Thrown by parseTestData when a global @option (a // @key: value line outside any @FileName block) is declared more than once. The harness stores global options in a plain object keyed by lower-cased name and refuses to overwrite an existing key. File-metadata keys (those in fileMetadataNames, e.g. @FileName, @symlink) are exempt and handled separately.
Source
Thrown at src/harness/fourslashImpl.ts:4804
}
else if (line.substr(0, 3) === "///" && currentFileContent !== undefined) {
throw new Error("Three-slash line in the middle of four-slash region at line " + i);
}
else if (line.substr(0, 2) === "//") {
const possiblySymlinks = Harness.TestCaseParser.parseSymlinkFromTest(line, symlinks);
if (possiblySymlinks) {
symlinks = possiblySymlinks;
}
else {
// Comment line, check for global/file @options and record them
const match = optionRegex.exec(line.substr(2));
if (match) {
const key = match[1].toLowerCase();
const value = match[2];
if (!ts.contains(fileMetadataNames, key)) {
// Check if the match is already existed in the global options
if (globalOptions[key] !== undefined) {
throw new Error(`Global option '${key}' already exists`);
}
globalOptions[key] = value;
}
else {
switch (key) {
case MetadataOptionNames.fileName:
// Found an @FileName directive, if this is not the first then create a new subfile
nextFile();
currentFileName = ts.isRootedDiskPath(value) ? value : basePath + "/" + value;
currentFileOptions[key] = value;
break;
case MetadataOptionNames.symlink:
currentFileSymlinks = ts.append(currentFileSymlinks, value);
break;
default:
// Add other fileMetadata flag
currentFileOptions[key] = value;
}
View on GitHub (pinned to b465fdbfe1)
Solutions
- Search the test file for repeated @key lines (case-insensitive) and keep only one.
- If you intended per-file settings, move the option inside the relevant @FileName block so it is treated as file metadata rather than a global option.
- Rename one of the options if they genuinely represent different settings.
Example fix
// before // @module: commonjs // @module: esnext // after // @module: esnext
Defensive patterns
Strategy: validation
Validate before calling
function assertUniqueGlobalOptions(lines: string[]) {
const seen = new Set<string>();
for (const line of lines) {
const m = /^\/\/\s*@([\w-]+):/.exec(line);
if (m && !seen.has(m[1].toLowerCase())) seen.add(m[1].toLowerCase());
else if (m) throw new Error(`Duplicate global option '${m[1]}'`);
}
} Prevention
- Deduplicate global @options after merging test files.
- Remember option keys are matched case-insensitively, so casing differences still collide.
When it happens
Trigger: A fourslash test file contains two or more // @compilerOption: ... lines with the same key outside of any @FileName directive, so globalOptions[key] is already defined when the second is parsed.
Common situations: Merging two test files duplicates a global setting; an author sets the same option twice expecting the later one to win. Configuration keys are case-insensitive so a different casing still collides.
Related errors
- Three-slash line in the middle of four-slash region at line
- ${fileName}(${line},${col}): ${message}
- Could not resolve JS module '${moduleName}' starting at '${i
- ${option.name} is a string value; tsconfig JSON must be pars
- verifyCurrentLineContent\n
AI-assisted analysis of microsoft/TypeScript@b465fdbfe1 (2026-08-12).
Data as JSON: /api/errors/33ade2be36edd411.
Report an issue: GitHub.