Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue "Cannot find name did-you-mean" errors as suggestions in plain JS #44271

Merged
merged 18 commits into from
Jun 15, 2021

Conversation

sandersn
Copy link
Member

@sandersn sandersn commented May 26, 2021

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.

In this PR, the errors are provided as suggestions. If the suggestions are useful, future work will change the presentation to be more obvious.

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. The error is suppressed for suggestions from global files that are not the current file. These are often DOM feature detection, for example.
  4. The error is suppressed for property accesses on a class because the compiler doesn't understand JS constructor functions well enough. In particular, it doesn't understand any inheritance patterns.
  5. The error is suppressed for this property accesses for the same reason, and because some this property accesses in JS are not on classes.

Results

I tested on a random sampling of 1000 single files in a fully installed clone of Definitely Typed. I didn't get false positives from these new errors.
I also tested on all the typescript user tests that are written in TS. There, the results informed the above rules. I got the following results

Good

  • minimatch: Found a 9-year-old bug in untested code that has never worked.

Bad

  • grunt: Didn't understand grunt's convenience require wrapper.
  • debug: Didn't produce an index signature for a loop in a setup function that copies properties from one object to another.
  • Uglify-JS: Didn't understand post-initialisation addition of properties to a very large object — because the object was large, the compiler suggested a similar name already in it.
  • chrome-devtools-frontend: Didn't understand lazy initialization of properties in several classes. The access was '_property', the suggestion was 'property'.

Open Questions

  • I'm not sure of the best way to indicate that getSemanticDiagnostics should return errors for unchecked JS files. I threaded an optional boolean parameter through the interface for now.
  • The wording for the error might need to be customised to point new users to the codefix.
  • VS, for example, doesn't use tsconfig as much and may need a user preference to disable these errors, since it won't be easy to add checkJs: false to a tsconfig that doesn't exist.

Future Work

I think there are a lot of other errors that we can issue in JS with enough precision. My next step is to find them and test them.

Fixes #41582

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.
@typescript-bot typescript-bot added Author: Team For Milestone Bug PRs that fix a bug with a specific milestone labels May 26, 2021
@sandersn
Copy link
Member Author

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented May 26, 2021

Heya @sandersn, I've started to run the tarball bundle task on this PR at 141de3f. You can monitor the build here.

@@ -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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

driveby typo fix

@@ -2059,10 +2059,13 @@ namespace ts {
let suggestion: Symbol | undefined;
if (suggestedNameNotFoundMessage && 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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

driveby upgrade to ?.

Comment on lines 1920 to 1922
const isCheckJsUndefined = includeUncheckedJS
&& sourceFile.checkJsDirective === undefined && options.checkJs === undefined
&& (sourceFile.scriptKind === ScriptKind.JS || sourceFile.scriptKind === ScriptKind.JSX);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

github does a poor job diffing in this function. isCheckJsUndefined is the only real addition here, in includeBindAndCheckDiagnostics, and the new if (isCheckJsUndefined) ...

I also renamed isCheckJs -> isCheckJsTrue, which is what confuses the differ.

@@ -1832,7 +1833,7 @@ namespace ts {
}

function getBindAndCheckDiagnostics(sourceFile: SourceFile, cancellationToken?: CancellationToken): readonly Diagnostic[] {
return getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken);
return getBindAndCheckDiagnosticsForFile(sourceFile, cancellationToken, /*includeUncheckedJS*/ undefined);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having two different behavior between LS and command line could also mean that we cant use tsbuildinfo cached semantic diagnostics if and when we start using tsbuildinfo to report errors on closed files.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can think of a couple ways to fix this

  • Always return all errors, then have tsc have a filter when displaying errors for JS files. This would add errors to all other API consumers, though.
  • Give the language service a separate way to request unchecked JS errors. The checker will have to store these errors separately and the language service will have to add them late.
  • Possibly: Not report these errors in the "errors in closed files" list. But I don't know if that would be what users of that feature expect.

Opinions? Ideas?

I'm pretty sure that this feature is going to stick around and grow, so I'd like for it to be easier to use than to not use. Specifically, it's annoying if the future status quo for the language service is to request diagnostics twice, once for unchecked JS and again for everything else.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Give the language service a separate way to request unchecked JS errors. The checker will have to store these errors separately and the language service will have to add them late.

Isn't that what suggestion diagnostics are suppose to be and seems like a good place to add those errors ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are quite similar, and that would work except that the display should be squiggly underlines, like an error, instead of three grey dots, like a suggestion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mjbvz Would VS Code be OK to add a separate call to a new function getUncheckedJSDiagnostics with a matching protocol message uncheckedJSDiagnosticsSync? It would make it easier to create a setting to disable the errors and for Code to style them differently if desired.

Also @uniqueiniquity @jessetrinity you probably have opinions from the VS side.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We certainly can add an additional call on the VS side.
I'm not sure I understand why it's inappropriate for tsc to report these, though?

Copy link
Member

@sheetalkamat sheetalkamat Jun 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@uniqueiniquity is right, why should the behavior be different between LS and tsc ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think treading lightly might be the way to go - we've never put errors in JS files unless they're specifically parsing errors.

Providing these as suggestion diagnostics keeps these as "quiet" by default. We can then potentially have nightly/insiders editors promote these to green or red squiggles (similar to how they specially-handle unused variable diagnostics) and see if we get any feedback on it. What do you all think? (CC @mjbvz @RyanCavanaugh)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@uniqueiniquity @sheetalkamat

I'm not sure I understand why it's inappropriate for tsc to report these, though?

There are two scenarios here. The first is mixed TS/JS, where people have legacy JS as part of the compilation via allowJs. These people are running tsc regularly, and I don't want to block their build on a bogus error.

@DanielRosenwasser
The other scenario is pure JS users who never run tsc. I believe it's these people that @DanielRosenwasser is thinking with

I do think treading lightly might be the way to go

I agree. Shipping this as a suggestion at first is a good start. Based on the time I added all implicit any errors as suggestions, I think nobody on VS or Code will notice. Then styling them differently is a question of whether TS provides the errors in a separate function or whether the editor filters the suggestion list. I think either is fine but leaving it to the editors is certainly easier for me. =)

@DanielRosenwasser
Copy link
Member

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jun 2, 2021

Heya @DanielRosenwasser, I've started to run the tarball bundle task on this PR at 1b44d72. You can monitor the build here.

@sandersn
Copy link
Member Author

sandersn commented Jun 3, 2021

@DanielRosenwasser you'll have to test this locally; I found that the playground always has checkJs: true for JS files,

(It's a great default since it shows off our JS checking, but makes it impossible to test this PR.)

@sandersn sandersn changed the title Always issue cannot find name did-you-mean error Issue "Cannot find name did-you-mean" errors as suggestions in unchecked JS Jun 8, 2021
@sandersn
Copy link
Member Author

sandersn commented Jun 8, 2021

Hi all, this is ready to review again. It now logs all errors as suggestions in unchecked JS files. The filtering in program.ts is gone entirely since suggestions are handled separately from normal errors.

@sandersn sandersn added this to Not started in PR Backlog Jun 8, 2021
@sandersn sandersn moved this from Not started to Needs review in PR Backlog Jun 8, 2021
PR Backlog automation moved this from Needs review to Needs merge Jun 8, 2021
Copy link
Contributor

@uniqueiniquity uniqueiniquity left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm much happier from the consumer side for these to just be suggestion diags.

Straw text for the messages, I just changed the modals to avoid name
collisions.
@sandersn sandersn changed the title Issue "Cannot find name did-you-mean" errors as suggestions in unchecked JS Issue "Cannot find name did-you-mean" errors as suggestions in plain JS Jun 15, 2021
@sandersn
Copy link
Member Author

After talking to @DanielRosenwasser, I'm going to merge this as-is, with the error messages as slight variants of the originals.

As I add errors to plain JS, better error display will become more important — for this PR, errors are hardly visible with Code's default suggestion UI — so we're going to investigate improved type display next. As part of that, I expect to learn how the error messages should change.

@sandersn sandersn merged commit e53f19f into main Jun 15, 2021
PR Backlog automation moved this from Needs merge to Done Jun 15, 2021
@sandersn sandersn deleted the always-issue-cannot-find-name-dym-error branch June 15, 2021 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team For Milestone Bug PRs that fix a bug with a specific milestone
Projects
Archived in project
PR Backlog
  
Done
Development

Successfully merging this pull request may close these issues.

Provide high-confidence spelling suggestions as suggestion diagnostics
5 participants