Issue "Cannot find name did-you-mean" errors as suggestions in plain JS (#44271)
* Always issue cannot find name did-you-mean error This PR issues "cannot find ${name}, did you mean ${name}" errors for identifiers and propery access expressions in JS files *without* `// @ts-check` and without `// @ts-nocheck`. This brings some benefits of Typescript's binder to all Javascript users, even those who haven't opted into Typescript checking. ```js export var inModule = 1 inmodule.toFixed() // errors on exports function f() { var locals = 2 locale.toFixed() // errors on locals } var object = { spaaace: 3 } object.spaaaace // error on read object.spaace = 2 // error on write object.fresh = 12 // OK, no spelling correction to offer ``` To disable the errors, add `// @ts-nocheck` to the file. To get the normal checkJs experience, add `// @ts-check`. == Why This Works == In a word: precision. This change has low recall — it misses lots of correct errors that would be nice to show — but it has high precision: almost all the errors it shows are correct. And they come with a suggested correction. Here are the ingredients: 1. For unchecked JS files, the compiler suppresses all errors except two did-you-mean name resolution errors. 2. Did-you-mean spelling correction is already tuned for high precision/low recall, and doesn't show many bogus errors even in JS. 3. For identifiers, the error is suppressed for suggestions from global files. These are often DOM feature detection, for example. 4. For property accesses, the error is suppressed for suggestions from other files, for the same reason. 5. For property accesses, the error is suppressed for `this` property accesses because the compiler doesn't understand JS constructor functions well enough. In particular, it doesn't understand any inheritance patterns. == Work Remaining == 1. Code cleanup. 2. Fix a couple of failures in existing tests. 3. Suppress errors on property access suggestions from large objects. 4. Combine (3) and (4) above to suppress errors on suggestions from other, global files. 5. A little more testing on random files to make sure that precision is good there too. 6. Have people from the regular Code editor meeting test the code and suggest ideas. * all (most?) tests pass * NOW they all pass * add tonnes of semi-colons * restore this.x check+add a test case * make ts-ignore/no-check codefix work in unchecked js * Issues errors only in the language service * add a few more tests * fix incorrect parentheses * More cleanup in program.ts * Improve readability of isExcludedJSError * make diff in program.ts smaller via closure * Switch unchecked JS did-you-mean to suggestion Instead of selectively letting errors through. * undo more missed changes * disallow ignoring suggestions * Issue different messages for plain JS than others Straw text for the messages, I just changed the modals to avoid name collisions.
This commit is contained in:
parent
5be0d7156d
commit
e53f19f8f2
|
@ -683,7 +683,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -692,7 +692,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 };
|
||||
|
|
|
@ -1072,15 +1072,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);
|
||||
}
|
||||
|
@ -1704,8 +1708,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(
|
||||
|
@ -1716,8 +1720,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;
|
||||
|
@ -2054,15 +2057,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,
|
||||
|
@ -21923,7 +21930,7 @@ namespace ts {
|
|||
node,
|
||||
!isWriteOnlyAccess(node),
|
||||
/*excludeGlobals*/ false,
|
||||
Diagnostics.Cannot_find_name_0_Did_you_mean_1) || unknownSymbol;
|
||||
/*issueSuggestions*/ true) || unknownSymbol;
|
||||
}
|
||||
return links.resolvedSymbol;
|
||||
}
|
||||
|
@ -27437,7 +27444,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) {
|
||||
|
@ -27450,7 +27458,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;
|
||||
}
|
||||
|
@ -27483,6 +27491,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,
|
||||
|
@ -27614,7 +27642,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)) {
|
||||
|
@ -27647,7 +27675,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 {
|
||||
|
@ -27663,7 +27692,7 @@ namespace ts {
|
|||
if (relatedInfo) {
|
||||
addRelatedInfo(resultDiagnostic, relatedInfo);
|
||||
}
|
||||
diagnostics.add(resultDiagnostic);
|
||||
addErrorOrSuggestion(!isUncheckedJS, resultDiagnostic);
|
||||
}
|
||||
|
||||
function containerSeemsToBeEmptyDomElement(containingType: Type) {
|
||||
|
@ -34582,7 +34611,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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -3894,7 +3894,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]);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: {}; }
|
||||
|
|
|
@ -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 : {}
|
||||
|
|
95
tests/baselines/reference/spellingUncheckedJS.symbols
Normal file
95
tests/baselines/reference/spellingUncheckedJS.symbols
Normal file
|
@ -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))
|
||||
}
|
||||
|
152
tests/baselines/reference/spellingUncheckedJS.types
Normal file
152
tests/baselines/reference/spellingUncheckedJS.types
Normal file
|
@ -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
|
||||
}
|
||||
|
51
tests/cases/conformance/salsa/spellingUncheckedJS.ts
Normal file
51
tests/cases/conformance/salsa/spellingUncheckedJS.ts
Normal file
|
@ -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
|
||||
}
|
27
tests/cases/fourslash/codeFixSpellingJs1.ts
Normal file
27
tests/cases/fourslash/codeFixSpellingJs1.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @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
|
||||
}`,
|
||||
});
|
16
tests/cases/fourslash/codeFixSpellingJs2.ts
Normal file
16
tests/cases/fourslash/codeFixSpellingJs2.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @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()`,
|
||||
});
|
21
tests/cases/fourslash/codeFixSpellingJs3.ts
Normal file
21
tests/cases/fourslash/codeFixSpellingJs3.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @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()
|
||||
|
23
tests/cases/fourslash/codeFixSpellingJs4.ts
Normal file
23
tests/cases/fourslash/codeFixSpellingJs4.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @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`,
|
||||
});
|
26
tests/cases/fourslash/codeFixSpellingJs5.ts
Normal file
26
tests/cases/fourslash/codeFixSpellingJs5.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @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()
|
56
tests/cases/fourslash/codeFixSpellingJs6.ts
Normal file
56
tests/cases/fourslash/codeFixSpellingJs6.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @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()
|
57
tests/cases/fourslash/codeFixSpellingJs7.ts
Normal file
57
tests/cases/fourslash/codeFixSpellingJs7.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @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()
|
10
tests/cases/fourslash/codeFixSpellingJs8.ts
Normal file
10
tests/cases/fourslash/codeFixSpellingJs8.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @allowjs: true
|
||||
// @noEmit: true
|
||||
|
||||
// @filename: a.js
|
||||
//// var locals = {}
|
||||
//// // @ts-expect-error
|
||||
//// Object.keys(locale)
|
||||
verify.noErrors()
|
|
@ -53,6 +53,7 @@ function second(p) { return p; }`,
|
|||
goTo.file("/both.js");
|
||||
verify.codeFix({
|
||||
description: "Remove template tag",
|
||||
index: 0,
|
||||
newFileContent:
|
||||
`/**
|
||||
* */
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
verify.codeFix({
|
||||
description: "Remove type parameters",
|
||||
index: 0,
|
||||
newFileContent:
|
||||
`/**
|
||||
* @type {() => void}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
verify.codeFix({
|
||||
description: "Convert function to an ES2015 class",
|
||||
index: 0,
|
||||
newFileContent:
|
||||
`class MyClass {
|
||||
constructor() { }
|
||||
|
|
Loading…
Reference in a new issue