diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts
index d4d7327fbd7ee..1882c9d6eab37 100644
--- a/src/compiler/builder.ts
+++ b/src/compiler/builder.ts
@@ -693,7 +693,7 @@ namespace ts {
const cachedDiagnostics = state.semanticDiagnosticsPerFile.get(path);
// Report the bind and check diagnostics from the cache if we already have those diagnostics present
if (cachedDiagnostics) {
- return filterSemanticDiagnotics(cachedDiagnostics, state.compilerOptions);
+ return filterSemanticDiagnostics(cachedDiagnostics, state.compilerOptions);
}
}
@@ -702,7 +702,7 @@ namespace ts {
if (state.semanticDiagnosticsPerFile) {
state.semanticDiagnosticsPerFile.set(path, diagnostics);
}
- return filterSemanticDiagnotics(diagnostics, state.compilerOptions);
+ return filterSemanticDiagnostics(diagnostics, state.compilerOptions);
}
export type ProgramBuildInfoFileId = number & { __programBuildInfoFileIdBrand: any };
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 38c193758be7a..ea8526ba61464 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -1076,15 +1076,19 @@ namespace ts {
return diagnostic;
}
- function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
- const diagnostic = location
+ function createError(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
+ return location
? createDiagnosticForNode(location, message, arg0, arg1, arg2, arg3)
: createCompilerDiagnostic(message, arg0, arg1, arg2, arg3);
+ }
+
+ function error(location: Node | undefined, message: DiagnosticMessage, arg0?: string | number, arg1?: string | number, arg2?: string | number, arg3?: string | number): Diagnostic {
+ const diagnostic = createError(location, message, arg0, arg1, arg2, arg3);
diagnostics.add(diagnostic);
return diagnostic;
}
- function addErrorOrSuggestion(isError: boolean, diagnostic: DiagnosticWithLocation) {
+ function addErrorOrSuggestion(isError: boolean, diagnostic: Diagnostic) {
if (isError) {
diagnostics.add(diagnostic);
}
@@ -1708,8 +1712,8 @@ namespace ts {
nameArg: __String | Identifier | undefined,
isUse: boolean,
excludeGlobals = false,
- suggestedNameNotFoundMessage?: DiagnosticMessage): Symbol | undefined {
- return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSymbol, suggestedNameNotFoundMessage);
+ issueSuggestions?: boolean): Symbol | undefined {
+ return resolveNameHelper(location, name, meaning, nameNotFoundMessage, nameArg, isUse, excludeGlobals, getSymbol, issueSuggestions);
}
function resolveNameHelper(
@@ -1720,8 +1724,7 @@ namespace ts {
nameArg: __String | Identifier | undefined,
isUse: boolean,
excludeGlobals: boolean,
- lookup: typeof getSymbol,
- suggestedNameNotFoundMessage?: DiagnosticMessage): Symbol | undefined {
+ lookup: typeof getSymbol, issueSuggestions?: boolean): Symbol | undefined {
const originalLocation = location; // needed for did-you-mean error reporting, which gathers candidates starting from the original location
let result: Symbol | undefined;
let lastLocation: Node | undefined;
@@ -2058,15 +2061,19 @@ namespace ts {
!checkAndReportErrorForUsingNamespaceModuleAsValue(errorLocation, name, meaning) &&
!checkAndReportErrorForUsingValueAsType(errorLocation, name, meaning)) {
let suggestion: Symbol | undefined;
- if (suggestedNameNotFoundMessage && suggestionCount < maximumSuggestionCount) {
+ if (issueSuggestions && suggestionCount < maximumSuggestionCount) {
suggestion = getSuggestedSymbolForNonexistentSymbol(originalLocation, name, meaning);
- const isGlobalScopeAugmentationDeclaration = suggestion && suggestion.valueDeclaration && isAmbientModule(suggestion.valueDeclaration) && isGlobalScopeAugmentation(suggestion.valueDeclaration);
+ const isGlobalScopeAugmentationDeclaration = suggestion?.valueDeclaration && isAmbientModule(suggestion.valueDeclaration) && isGlobalScopeAugmentation(suggestion.valueDeclaration);
if (isGlobalScopeAugmentationDeclaration) {
suggestion = undefined;
}
if (suggestion) {
const suggestionName = symbolToString(suggestion);
- const diagnostic = error(errorLocation, suggestedNameNotFoundMessage, diagnosticName(nameArg!), suggestionName);
+ const isUncheckedJS = isUncheckedJSSuggestion(originalLocation, suggestion, /*excludeClasses*/ false);
+ const message = isUncheckedJS ? Diagnostics.Could_not_find_name_0_Did_you_mean_1 : Diagnostics.Cannot_find_name_0_Did_you_mean_1;
+ const diagnostic = createError(errorLocation, message, diagnosticName(nameArg!), suggestionName);
+ addErrorOrSuggestion(!isUncheckedJS, diagnostic);
+
if (suggestion.valueDeclaration) {
addRelatedInfo(
diagnostic,
@@ -21939,7 +21946,7 @@ namespace ts {
node,
!isWriteOnlyAccess(node),
/*excludeGlobals*/ false,
- Diagnostics.Cannot_find_name_0_Did_you_mean_1) || unknownSymbol;
+ /*issueSuggestions*/ true) || unknownSymbol;
}
return links.resolvedSymbol;
}
@@ -27453,7 +27460,8 @@ namespace ts {
if (!prop) {
const indexInfo = !isPrivateIdentifier(right) && (assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) || isThisTypeParameter(leftType)) ? getIndexInfoOfType(apparentType, IndexKind.String) : undefined;
if (!(indexInfo && indexInfo.type)) {
- if (isJSLiteralType(leftType)) {
+ const isUncheckedJS = isUncheckedJSSuggestion(node, leftType.symbol, /*excludeClasses*/ true);
+ if (!isUncheckedJS && isJSLiteralType(leftType)) {
return anyType;
}
if (leftType.symbol === globalThisSymbol) {
@@ -27466,7 +27474,7 @@ namespace ts {
return anyType;
}
if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) {
- reportNonexistentProperty(right, isThisTypeParameter(leftType) ? apparentType : leftType);
+ reportNonexistentProperty(right, isThisTypeParameter(leftType) ? apparentType : leftType, isUncheckedJS);
}
return errorType;
}
@@ -27499,6 +27507,26 @@ namespace ts {
return getFlowTypeOfAccessExpression(node, prop, propType, right, checkMode);
}
+ /**
+ * Determines whether a did-you-mean error should be a suggestion in an unchecked JS file.
+ * Only applies to unchecked JS files without checkJS, // @ts-check or // @ts-nocheck
+ * It does not suggest when the suggestion:
+ * - Is from a global file that is different from the reference file, or
+ * - (optionally) Is a class, or is a this.x property access expression
+ */
+ function isUncheckedJSSuggestion(node: Node | undefined, suggestion: Symbol | undefined, excludeClasses: boolean): boolean {
+ const file = getSourceFileOfNode(node);
+ if (file) {
+ if (compilerOptions.checkJs === undefined && file.checkJsDirective === undefined && (file.scriptKind === ScriptKind.JS || file.scriptKind === ScriptKind.JSX)) {
+ const declarationFile = forEach(suggestion?.declarations, getSourceFileOfNode);
+ return !(file !== declarationFile && !!declarationFile && isGlobalSourceFile(declarationFile))
+ && !(excludeClasses && suggestion && suggestion.flags & SymbolFlags.Class)
+ && !(!!node && excludeClasses && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.ThisKeyword);
+ }
+ }
+ return false;
+ }
+
function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node, checkMode: CheckMode | undefined) {
// Only compute control flow type if this is a property access expression that isn't an
// assignment target, and the referenced property was declared as a variable, property,
@@ -27630,7 +27658,7 @@ namespace ts {
return getIntersectionType(x);
}
- function reportNonexistentProperty(propNode: Identifier | PrivateIdentifier, containingType: Type) {
+ function reportNonexistentProperty(propNode: Identifier | PrivateIdentifier, containingType: Type, isUncheckedJS: boolean) {
let errorInfo: DiagnosticMessageChain | undefined;
let relatedInfo: Diagnostic | undefined;
if (!isPrivateIdentifier(propNode) && containingType.flags & TypeFlags.Union && !(containingType.flags & TypeFlags.Primitive)) {
@@ -27663,7 +27691,8 @@ namespace ts {
const suggestion = getSuggestedSymbolForNonexistentProperty(propNode, containingType);
if (suggestion !== undefined) {
const suggestedName = symbolName(suggestion);
- errorInfo = chainDiagnosticMessages(errorInfo, Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2, missingProperty, container, suggestedName);
+ const message = isUncheckedJS ? Diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2 : Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2;
+ errorInfo = chainDiagnosticMessages(errorInfo, message, missingProperty, container, suggestedName);
relatedInfo = suggestion.valueDeclaration && createDiagnosticForNode(suggestion.valueDeclaration, Diagnostics._0_is_declared_here, suggestedName);
}
else {
@@ -27679,7 +27708,7 @@ namespace ts {
if (relatedInfo) {
addRelatedInfo(resultDiagnostic, relatedInfo);
}
- diagnostics.add(resultDiagnostic);
+ addErrorOrSuggestion(!isUncheckedJS, resultDiagnostic);
}
function containerSeemsToBeEmptyDomElement(containingType: Type) {
@@ -34594,7 +34623,7 @@ namespace ts {
const rootName = getFirstIdentifier(typeName);
const meaning = (typeName.kind === SyntaxKind.Identifier ? SymbolFlags.Type : SymbolFlags.Namespace) | SymbolFlags.Alias;
- const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isRefernce*/ true);
+ const rootSymbol = resolveName(rootName, rootName.escapedText, meaning, /*nameNotFoundMessage*/ undefined, /*nameArg*/ undefined, /*isReference*/ true);
if (rootSymbol
&& rootSymbol.flags & SymbolFlags.Alias
&& symbolIsValue(rootSymbol)
diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json
index d2ddbc8b0a4ec..1e2a0329cb00b 100644
--- a/src/compiler/diagnosticMessages.json
+++ b/src/compiler/diagnosticMessages.json
@@ -2442,10 +2442,18 @@
"category": "Error",
"code": 2567
},
+ "Property '{0}' may not exist on type '{1}'. Did you mean '{2}'?": {
+ "category": "Error",
+ "code": 2568
+ },
"Type '{0}' is not an array type or a string type. Use compiler option '--downlevelIteration' to allow iterating of iterators.": {
"category": "Error",
"code": 2569
},
+ "Could not find name '{0}'. Did you mean '{1}'?": {
+ "category": "Error",
+ "code": 2570
+ },
"Object is of type 'unknown'.": {
"category": "Error",
"code": 2571
diff --git a/src/compiler/program.ts b/src/compiler/program.ts
index 91529ceada8d4..440a930983ebe 100644
--- a/src/compiler/program.ts
+++ b/src/compiler/program.ts
@@ -1896,7 +1896,7 @@ namespace ts {
function getSemanticDiagnosticsForFile(sourceFile: SourceFile, cancellationToken: CancellationToken | undefined): readonly Diagnostic[] {
return concatenate(
- filterSemanticDiagnotics(getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken), options),
+ filterSemanticDiagnostics(getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken), options),
getProgramDiagnostics(sourceFile)
);
}
@@ -1918,8 +1918,8 @@ namespace ts {
const isCheckJs = isCheckJsEnabledForFile(sourceFile, options);
const isTsNoCheck = !!sourceFile.checkJsDirective && sourceFile.checkJsDirective.enabled === false;
// By default, only type-check .ts, .tsx, 'Deferred' and 'External' files (external files are added by plugins)
- const includeBindAndCheckDiagnostics = !isTsNoCheck && (sourceFile.scriptKind === ScriptKind.TS || sourceFile.scriptKind === ScriptKind.TSX ||
- sourceFile.scriptKind === ScriptKind.External || isCheckJs || sourceFile.scriptKind === ScriptKind.Deferred);
+ const includeBindAndCheckDiagnostics = !isTsNoCheck && (sourceFile.scriptKind === ScriptKind.TS || sourceFile.scriptKind === ScriptKind.TSX
+ || sourceFile.scriptKind === ScriptKind.External || isCheckJs || sourceFile.scriptKind === ScriptKind.Deferred);
const bindDiagnostics: readonly Diagnostic[] = includeBindAndCheckDiagnostics ? sourceFile.bindDiagnostics : emptyArray;
const checkDiagnostics = includeBindAndCheckDiagnostics ? typeChecker.getDiagnostics(sourceFile, cancellationToken) : emptyArray;
@@ -3891,7 +3891,7 @@ namespace ts {
}
/*@internal*/
- export function filterSemanticDiagnotics(diagnostic: readonly Diagnostic[], option: CompilerOptions): readonly Diagnostic[] {
+ export function filterSemanticDiagnostics(diagnostic: readonly Diagnostic[], option: CompilerOptions): readonly Diagnostic[] {
return filter(diagnostic, d => !d.skippedOn || !option[d.skippedOn]);
}
diff --git a/src/services/codefixes/fixSpelling.ts b/src/services/codefixes/fixSpelling.ts
index 6d80e1f900899..2712976d7c6b4 100644
--- a/src/services/codefixes/fixSpelling.ts
+++ b/src/services/codefixes/fixSpelling.ts
@@ -3,7 +3,9 @@ namespace ts.codefix {
const fixId = "fixSpelling";
const errorCodes = [
Diagnostics.Property_0_does_not_exist_on_type_1_Did_you_mean_2.code,
+ Diagnostics.Property_0_may_not_exist_on_type_1_Did_you_mean_2.code,
Diagnostics.Cannot_find_name_0_Did_you_mean_1.code,
+ Diagnostics.Could_not_find_name_0_Did_you_mean_1.code,
Diagnostics.Cannot_find_name_0_Did_you_mean_the_instance_member_this_0.code,
Diagnostics.Cannot_find_name_0_Did_you_mean_the_static_member_1_0.code,
Diagnostics._0_has_no_exported_member_named_1_Did_you_mean_2.code,
diff --git a/tests/baselines/reference/argumentsReferenceInConstructor3_Js.types b/tests/baselines/reference/argumentsReferenceInConstructor3_Js.types
index ccdf9b660674d..4b6557cd1b972 100644
--- a/tests/baselines/reference/argumentsReferenceInConstructor3_Js.types
+++ b/tests/baselines/reference/argumentsReferenceInConstructor3_Js.types
@@ -43,11 +43,11 @@ class B extends A {
* @type object
*/
this.bar = super.arguments.foo;
->this.bar = super.arguments.foo : any
+>this.bar = super.arguments.foo : error
>this.bar : any
>this : this
>bar : any
->super.arguments.foo : any
+>super.arguments.foo : error
>super.arguments : { bar: {}; }
>super : A
>arguments : { bar: {}; }
diff --git a/tests/baselines/reference/jsObjectsMarkedAsOpenEnded.types b/tests/baselines/reference/jsObjectsMarkedAsOpenEnded.types
index 777f779e9c78c..adb07b7bae781 100644
--- a/tests/baselines/reference/jsObjectsMarkedAsOpenEnded.types
+++ b/tests/baselines/reference/jsObjectsMarkedAsOpenEnded.types
@@ -27,7 +27,7 @@ class C {
this.member.a = 0;
>this.member.a = 0 : 0
->this.member.a : any
+>this.member.a : error
>this.member : {}
>this : this
>member : {}
@@ -48,7 +48,7 @@ var obj = {
obj.property.a = 0;
>obj.property.a = 0 : 0
->obj.property.a : any
+>obj.property.a : error
>obj.property : {}
>obj : { property: {}; }
>property : {}
diff --git a/tests/baselines/reference/spellingUncheckedJS.symbols b/tests/baselines/reference/spellingUncheckedJS.symbols
new file mode 100644
index 0000000000000..59355de95cb92
--- /dev/null
+++ b/tests/baselines/reference/spellingUncheckedJS.symbols
@@ -0,0 +1,95 @@
+=== tests/cases/conformance/salsa/spellingUncheckedJS.js ===
+export var inModule = 1
+>inModule : Symbol(inModule, Decl(spellingUncheckedJS.js, 0, 10))
+
+inmodule.toFixed()
+
+function f() {
+>f : Symbol(f, Decl(spellingUncheckedJS.js, 1, 18))
+
+ var locals = 2 + true
+>locals : Symbol(locals, Decl(spellingUncheckedJS.js, 4, 7))
+
+ locale.toFixed()
+ // @ts-expect-error
+ localf.toExponential()
+ // @ts-expect-error
+ "this is fine"
+}
+class Classe {
+>Classe : Symbol(Classe, Decl(spellingUncheckedJS.js, 10, 1))
+
+ non = 'oui'
+>non : Symbol(Classe.non, Decl(spellingUncheckedJS.js, 11, 14))
+
+ methode() {
+>methode : Symbol(Classe.methode, Decl(spellingUncheckedJS.js, 12, 15))
+
+ // no error on 'this' references
+ return this.none
+>this : Symbol(Classe, Decl(spellingUncheckedJS.js, 10, 1))
+ }
+}
+class Derivee extends Classe {
+>Derivee : Symbol(Derivee, Decl(spellingUncheckedJS.js, 17, 1))
+>Classe : Symbol(Classe, Decl(spellingUncheckedJS.js, 10, 1))
+
+ methode() {
+>methode : Symbol(Derivee.methode, Decl(spellingUncheckedJS.js, 18, 30))
+
+ // no error on 'super' references
+ return super.none
+>super : Symbol(Classe, Decl(spellingUncheckedJS.js, 10, 1))
+ }
+}
+
+
+var object = {
+>object : Symbol(object, Decl(spellingUncheckedJS.js, 26, 3), Decl(spellingUncheckedJS.js, 29, 15), Decl(spellingUncheckedJS.js, 30, 18))
+
+ spaaace: 3
+>spaaace : Symbol(spaaace, Decl(spellingUncheckedJS.js, 26, 14))
+}
+object.spaaaace // error on read
+>object : Symbol(object, Decl(spellingUncheckedJS.js, 26, 3), Decl(spellingUncheckedJS.js, 29, 15), Decl(spellingUncheckedJS.js, 30, 18))
+
+object.spaace = 12 // error on write
+>object : Symbol(object, Decl(spellingUncheckedJS.js, 26, 3), Decl(spellingUncheckedJS.js, 29, 15), Decl(spellingUncheckedJS.js, 30, 18))
+
+object.fresh = 12 // OK
+>object : Symbol(object, Decl(spellingUncheckedJS.js, 26, 3), Decl(spellingUncheckedJS.js, 29, 15), Decl(spellingUncheckedJS.js, 30, 18))
+
+other.puuuce // OK, from another file
+>other : Symbol(other, Decl(other.js, 3, 3))
+
+new Date().getGMTDate() // OK, from another file
+>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --))
+
+// No suggestions for globals from other files
+const atoc = setIntegral(() => console.log('ok'), 500)
+>atoc : Symbol(atoc, Decl(spellingUncheckedJS.js, 36, 5))
+>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
+>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
+>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
+
+AudioBuffin // etc
+Jimmy
+>Jimmy : Symbol(Jimmy, Decl(other.js, 0, 3))
+
+Jon
+
+=== tests/cases/conformance/salsa/other.js ===
+var Jimmy = 1
+>Jimmy : Symbol(Jimmy, Decl(other.js, 0, 3))
+
+var John = 2
+>John : Symbol(John, Decl(other.js, 1, 3))
+
+Jon // error, it's from the same file
+var other = {
+>other : Symbol(other, Decl(other.js, 3, 3))
+
+ puuce: 4
+>puuce : Symbol(puuce, Decl(other.js, 3, 13))
+}
+
diff --git a/tests/baselines/reference/spellingUncheckedJS.types b/tests/baselines/reference/spellingUncheckedJS.types
new file mode 100644
index 0000000000000..61723e8da16ea
--- /dev/null
+++ b/tests/baselines/reference/spellingUncheckedJS.types
@@ -0,0 +1,152 @@
+=== tests/cases/conformance/salsa/spellingUncheckedJS.js ===
+export var inModule = 1
+>inModule : number
+>1 : 1
+
+inmodule.toFixed()
+>inmodule.toFixed() : error
+>inmodule.toFixed : error
+>inmodule : any
+>toFixed : any
+
+function f() {
+>f : () => void
+
+ var locals = 2 + true
+>locals : any
+>2 + true : any
+>2 : 2
+>true : true
+
+ locale.toFixed()
+>locale.toFixed() : error
+>locale.toFixed : error
+>locale : any
+>toFixed : any
+
+ // @ts-expect-error
+ localf.toExponential()
+>localf.toExponential() : error
+>localf.toExponential : error
+>localf : any
+>toExponential : any
+
+ // @ts-expect-error
+ "this is fine"
+>"this is fine" : "this is fine"
+}
+class Classe {
+>Classe : Classe
+
+ non = 'oui'
+>non : string
+>'oui' : "oui"
+
+ methode() {
+>methode : () => any
+
+ // no error on 'this' references
+ return this.none
+>this.none : error
+>this : this
+>none : any
+ }
+}
+class Derivee extends Classe {
+>Derivee : Derivee
+>Classe : Classe
+
+ methode() {
+>methode : () => any
+
+ // no error on 'super' references
+ return super.none
+>super.none : error
+>super : Classe
+>none : any
+ }
+}
+
+
+var object = {
+>object : { spaaace: number; }
+>{ spaaace: 3} : { spaaace: number; }
+
+ spaaace: 3
+>spaaace : number
+>3 : 3
+}
+object.spaaaace // error on read
+>object.spaaaace : error
+>object : { spaaace: number; }
+>spaaaace : any
+
+object.spaace = 12 // error on write
+>object.spaace = 12 : 12
+>object.spaace : error
+>object : { spaaace: number; }
+>spaace : any
+>12 : 12
+
+object.fresh = 12 // OK
+>object.fresh = 12 : 12
+>object.fresh : error
+>object : { spaaace: number; }
+>fresh : any
+>12 : 12
+
+other.puuuce // OK, from another file
+>other.puuuce : any
+>other : { puuce: number; }
+>puuuce : any
+
+new Date().getGMTDate() // OK, from another file
+>new Date().getGMTDate() : error
+>new Date().getGMTDate : error
+>new Date() : Date
+>Date : DateConstructor
+>getGMTDate : any
+
+// No suggestions for globals from other files
+const atoc = setIntegral(() => console.log('ok'), 500)
+>atoc : error
+>setIntegral(() => console.log('ok'), 500) : error
+>setIntegral : error
+>() => console.log('ok') : () => void
+>console.log('ok') : void
+>console.log : (...data: any[]) => void
+>console : Console
+>log : (...data: any[]) => void
+>'ok' : "ok"
+>500 : 500
+
+AudioBuffin // etc
+>AudioBuffin : error
+
+Jimmy
+>Jimmy : number
+
+Jon
+>Jon : error
+
+=== tests/cases/conformance/salsa/other.js ===
+var Jimmy = 1
+>Jimmy : number
+>1 : 1
+
+var John = 2
+>John : number
+>2 : 2
+
+Jon // error, it's from the same file
+>Jon : error
+
+var other = {
+>other : { puuce: number; }
+>{ puuce: 4} : { puuce: number; }
+
+ puuce: 4
+>puuce : number
+>4 : 4
+}
+
diff --git a/tests/cases/conformance/salsa/spellingUncheckedJS.ts b/tests/cases/conformance/salsa/spellingUncheckedJS.ts
new file mode 100644
index 0000000000000..d5a1e8ac5602f
--- /dev/null
+++ b/tests/cases/conformance/salsa/spellingUncheckedJS.ts
@@ -0,0 +1,51 @@
+// @noEmit: true
+// @allowJs: true
+// @filename: spellingUncheckedJS.js
+export var inModule = 1
+inmodule.toFixed()
+
+function f() {
+ var locals = 2 + true
+ locale.toFixed()
+ // @ts-expect-error
+ localf.toExponential()
+ // @ts-expect-error
+ "this is fine"
+}
+class Classe {
+ non = 'oui'
+ methode() {
+ // no error on 'this' references
+ return this.none
+ }
+}
+class Derivee extends Classe {
+ methode() {
+ // no error on 'super' references
+ return super.none
+ }
+}
+
+
+var object = {
+ spaaace: 3
+}
+object.spaaaace // error on read
+object.spaace = 12 // error on write
+object.fresh = 12 // OK
+other.puuuce // OK, from another file
+new Date().getGMTDate() // OK, from another file
+
+// No suggestions for globals from other files
+const atoc = setIntegral(() => console.log('ok'), 500)
+AudioBuffin // etc
+Jimmy
+Jon
+
+// @filename: other.js
+var Jimmy = 1
+var John = 2
+Jon // error, it's from the same file
+var other = {
+ puuce: 4
+}
diff --git a/tests/cases/fourslash/codeFixSpellingJs1.ts b/tests/cases/fourslash/codeFixSpellingJs1.ts
new file mode 100644
index 0000000000000..4ad4d000d45f5
--- /dev/null
+++ b/tests/cases/fourslash/codeFixSpellingJs1.ts
@@ -0,0 +1,27 @@
+///
+
+// @allowjs: true
+// @noEmit: true
+
+// @filename: a.js
+//// function f() {
+//// var locals = 2
+//// [|locale|].toFixed()
+//// return locals
+//// }
+
+verify.noErrors()
+verify.codeFixAvailable([
+ { description: "Change spelling to 'locals'" },
+]);
+
+verify.codeFix({
+ description: "Change spelling to 'locals'",
+ index: 0,
+ newFileContent:
+`function f() {
+ var locals = 2
+ locals.toFixed()
+ return locals
+}`,
+});
diff --git a/tests/cases/fourslash/codeFixSpellingJs2.ts b/tests/cases/fourslash/codeFixSpellingJs2.ts
new file mode 100644
index 0000000000000..110737ade86c4
--- /dev/null
+++ b/tests/cases/fourslash/codeFixSpellingJs2.ts
@@ -0,0 +1,16 @@
+///
+
+// @allowjs: true
+// @noEmit: true
+
+// @filename: a.js
+//// export var inModule = 1
+//// [|inmodule|].toFixed()
+
+verify.codeFix({
+ description: "Change spelling to 'inModule'",
+ index: 0,
+ newFileContent:
+`export var inModule = 1
+inModule.toFixed()`,
+});
diff --git a/tests/cases/fourslash/codeFixSpellingJs3.ts b/tests/cases/fourslash/codeFixSpellingJs3.ts
new file mode 100644
index 0000000000000..6451ce662ae3c
--- /dev/null
+++ b/tests/cases/fourslash/codeFixSpellingJs3.ts
@@ -0,0 +1,21 @@
+///
+
+// @allowjs: true
+// @noEmit: true
+
+// @filename: a.js
+//// class Classe {
+//// non = 'oui'
+//// methode() {
+//// // no error on 'this' references
+//// return this.none
+//// }
+//// }
+//// class Derivee extends Classe {
+//// methode() {
+//// // no error on 'super' references
+//// return super.none
+//// }
+//// }
+verify.noErrors()
+
diff --git a/tests/cases/fourslash/codeFixSpellingJs4.ts b/tests/cases/fourslash/codeFixSpellingJs4.ts
new file mode 100644
index 0000000000000..97c6af2dce166
--- /dev/null
+++ b/tests/cases/fourslash/codeFixSpellingJs4.ts
@@ -0,0 +1,23 @@
+///
+
+// @allowjs: true
+// @noEmit: true
+
+// @filename: a.js
+//// var object = {
+//// spaaace: 3
+//// }
+//// object.spaaaace // error on read
+//// object.spaace = 12 // error on write
+//// object.fresh = 12 // OK
+verify.codeFixAll({
+ fixId: "fixSpelling",
+ fixAllDescription: "Fix all detected spelling errors",
+ newFileContent:
+`var object = {
+ spaaace: 3
+}
+object.spaaace // error on read
+object.spaaace = 12 // error on write
+object.fresh = 12 // OK`,
+});
diff --git a/tests/cases/fourslash/codeFixSpellingJs5.ts b/tests/cases/fourslash/codeFixSpellingJs5.ts
new file mode 100644
index 0000000000000..478cc9208e178
--- /dev/null
+++ b/tests/cases/fourslash/codeFixSpellingJs5.ts
@@ -0,0 +1,26 @@
+///
+
+// @allowjs: true
+// @noEmit: true
+
+// @filename: a.js
+//// var other = {
+//// puuce: 4
+//// }
+//// var Jimmy = 1
+//// var John = 2
+
+// @filename: b.js
+//// other.puuuce // OK, from another file
+//// new Date().getGMTDate() // OK, from another file
+//// window.argle // OK, from globalThis
+//// self.blargle // OK, from globalThis
+////
+//// // No suggestions for globals from other files
+//// const atoc = setIntegral(() => console.log('ok'), 500)
+//// AudioBuffin // etc
+//// Jimmy
+//// Jon
+
+
+verify.noErrors()
diff --git a/tests/cases/fourslash/codeFixSpellingJs6.ts b/tests/cases/fourslash/codeFixSpellingJs6.ts
new file mode 100644
index 0000000000000..535a80d2d80c3
--- /dev/null
+++ b/tests/cases/fourslash/codeFixSpellingJs6.ts
@@ -0,0 +1,56 @@
+///
+
+// @allowjs: true
+// @checkjs: false
+// @noEmit: true
+// @filename: spellingUncheckedJS.js
+//// export var inModule = 1
+//// inmodule.toFixed()
+////
+//// function f() {
+//// var locals = 2 + true
+//// locale.toFixed()
+//// }
+//// class Classe {
+//// non = 'oui'
+//// methode() {
+//// // no error on 'this' references
+//// return this.none
+//// }
+//// }
+//// class Derivee extends Classe {
+//// methode() {
+//// // no error on 'super' references
+//// return super.none
+//// }
+//// }
+////
+////
+//// var object = {
+//// spaaace: 3
+//// }
+//// object.spaaaace // error on read
+//// object.spaace = 12 // error on write
+//// object.fresh = 12 // OK
+//// other.puuuce // OK, from another file
+//// new Date().getGMTDate() // OK, from another file
+////
+//// // No suggestions for globals from other files
+//// const atoc = setIntegral(() => console.log('ok'), 500)
+//// AudioBuffin // etc
+//// Jimmy
+//// Jon
+//// window.argle
+//// self.blargle
+
+// @filename: other.js
+//// var Jimmy = 1
+//// var John = 2
+//// Jon // error, it's from the same file
+//// var other = {
+//// puuce: 4
+//// }
+//// window.argle
+//// self.blargle
+
+verify.noErrors()
diff --git a/tests/cases/fourslash/codeFixSpellingJs7.ts b/tests/cases/fourslash/codeFixSpellingJs7.ts
new file mode 100644
index 0000000000000..b5c6df2c9d08a
--- /dev/null
+++ b/tests/cases/fourslash/codeFixSpellingJs7.ts
@@ -0,0 +1,57 @@
+///
+
+// @allowjs: true
+// @noEmit: true
+// @filename: spellingUncheckedJS.js
+//// // @ts-nocheck
+//// export var inModule = 1
+//// inmodule.toFixed()
+////
+//// function f() {
+//// var locals = 2 + true
+//// locale.toFixed()
+//// }
+//// class Classe {
+//// non = 'oui'
+//// methode() {
+//// // no error on 'this' references
+//// return this.none
+//// }
+//// }
+//// class Derivee extends Classe {
+//// methode() {
+//// // no error on 'super' references
+//// return super.none
+//// }
+//// }
+////
+////
+//// var object = {
+//// spaaace: 3
+//// }
+//// object.spaaaace // error on read
+//// object.spaace = 12 // error on write
+//// object.fresh = 12 // OK
+//// other.puuuce // OK, from another file
+//// new Date().getGMTDate() // OK, from another file
+////
+//// // No suggestions for globals from other files
+//// const atoc = setIntegral(() => console.log('ok'), 500)
+//// AudioBuffin // etc
+//// Jimmy
+//// Jon
+//// window.argle
+//// self.blargle
+
+// @filename: other.js
+//// // @ts-nocheck
+//// var Jimmy = 1
+//// var John = 2
+//// Jon // error, it's from the same file
+//// var other = {
+//// puuce: 4
+//// }
+//// window.argle
+//// self.blargle
+
+verify.noErrors()
diff --git a/tests/cases/fourslash/codeFixSpellingJs8.ts b/tests/cases/fourslash/codeFixSpellingJs8.ts
new file mode 100644
index 0000000000000..f43f9902592c0
--- /dev/null
+++ b/tests/cases/fourslash/codeFixSpellingJs8.ts
@@ -0,0 +1,10 @@
+///
+
+// @allowjs: true
+// @noEmit: true
+
+// @filename: a.js
+//// var locals = {}
+//// // @ts-expect-error
+//// Object.keys(locale)
+verify.noErrors()
diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_delete_templateTag.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_delete_templateTag.ts
index 5c0dae5b521dd..8d4466c777b29 100644
--- a/tests/cases/fourslash/codeFixUnusedIdentifier_delete_templateTag.ts
+++ b/tests/cases/fourslash/codeFixUnusedIdentifier_delete_templateTag.ts
@@ -53,6 +53,7 @@ function second(p) { return p; }`,
goTo.file("/both.js");
verify.codeFix({
description: "Remove template tag",
+ index: 0,
newFileContent:
`/**
* */
diff --git a/tests/cases/fourslash/codeFixUnusedIdentifier_jsdocTypeParameter.ts b/tests/cases/fourslash/codeFixUnusedIdentifier_jsdocTypeParameter.ts
index 9367a1214d8f0..4d6d624bd3f9a 100644
--- a/tests/cases/fourslash/codeFixUnusedIdentifier_jsdocTypeParameter.ts
+++ b/tests/cases/fourslash/codeFixUnusedIdentifier_jsdocTypeParameter.ts
@@ -10,6 +10,7 @@
verify.codeFix({
description: "Remove type parameters",
+ index: 0,
newFileContent:
`/**
* @type {() => void}
diff --git a/tests/cases/fourslash/convertToEs6Class_emptyCatchClause.ts b/tests/cases/fourslash/convertToEs6Class_emptyCatchClause.ts
index fa6353397ca39..9c23d6c0378ed 100644
--- a/tests/cases/fourslash/convertToEs6Class_emptyCatchClause.ts
+++ b/tests/cases/fourslash/convertToEs6Class_emptyCatchClause.ts
@@ -9,6 +9,7 @@
verify.codeFix({
description: "Convert function to an ES2015 class",
+ index: 0,
newFileContent:
`class MyClass {
constructor() { }