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() { }