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

Add option for organize imports case sensitivity #51733

Merged

Conversation

andrewbranch
Copy link
Member

Fixes #51616
Related: #51579

A new user preference (editor PR to follow) allows setting the case sensitivity for import sorting. By default, we detect.

@jakebailey
Copy link
Member

I'm finding it hard to deduce from the tests, but what is the behavior when you have both HasDecorator and hasDecorator, in various orders? Does it always output the same thing?

This is something that frustrates me currently about the eslint plugin; it doesn't care which order you have, which leaves a hole where we can end up with conflicts when the result should not depend on the input ordering at all.

@andrewbranch
Copy link
Member Author

andrewbranch commented Dec 6, 2022

Currently, case-insensitive is truly case-insensitive like eslint, which means any order between specifiers that differ only in case is allowed, and a reshuffle will preserve the pre-existing relative order (eslint doesn’t use a stable sort, which means pre-existing order between case-different specifiers is not preserved, resulting in sorts that look totally random). This is something we can definitely change or improve on over the eslint behavior, but if we change back to case sensitive sorting, I wasn’t sure if anyone would end up caring about it.

}

/** @internal */
export function getImportDeclarationInsertionIndex(sortedImports: SortedReadonlyArray<AnyImportOrRequireStatement>, newImport: AnyImportOrRequireStatement) {
const index = binarySearch(sortedImports, newImport, identity, compareImportsOrRequireStatements);
export const detectImportSpecifierSorting = memoizeWeak((specifiers: readonly ImportSpecifier[]): SortKind => {
Copy link
Member

Choose a reason for hiding this comment

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

Is there a particular reason why this one is cached but others aren't? Would everything benefit from being cached like this?

Copy link
Member Author

Choose a reason for hiding this comment

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

As it stands now, this is the only one that benefits from being cached. It gets called several times from auto-imports in ways that are cumbersome to cache locally.

src/compiler/core.ts Show resolved Hide resolved
let prevElement = array[0];
for (const element of array.slice(1)) {
if (comparer(prevElement, element) === Comparison.GreaterThan) {
for (let i = 1, len = array.length; i < len; i++) {
Copy link
Member

Choose a reason for hiding this comment

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

:yikes: nice catch

Copy link
Member Author

Choose a reason for hiding this comment

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

@DanielRosenwasser noticed this while I was screensharing working on this. Turns out I wrote it years ago, before I was terrified of allocations 🙈

for (let i = 1, len = array.length; i < len; i++) {
const prevElement = array[i - 1];
const element = array[i];
if (caseSensitiveComparer(prevElement, element) === Comparison.GreaterThan) {
Copy link
Member

Choose a reason for hiding this comment

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

It would be more obvious to me what was going on if this was something like !== Comparison.LessThanOrEqualTo

Copy link
Member Author

Choose a reason for hiding this comment

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

“less than or equal to” can’t be represented in a single value the way our Comparison system works:

export const enum Comparison {
    LessThan    = -1,
    EqualTo     = 0,
    GreaterThan = 1
}

Copy link
Member

Choose a reason for hiding this comment

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

Yeah - just an idle sort of "it would be nice".

Comment on lines 981 to 989
if (caseSensitiveComparer(prevElement, element) === Comparison.GreaterThan) {
kind &= ~SortKind.CaseSensitive;
}
if (caseInsensitiveComparer(prevElement, element) === Comparison.GreaterThan) {
kind &= ~SortKind.CaseInsensitive;
}
if (kind === SortKind.None) {
return kind;
}
Copy link
Member

@DanielRosenwasser DanielRosenwasser Dec 13, 2022

Choose a reason for hiding this comment

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

I think that you can optimize this.

  1. If you know that you're not case-sensitively sorted, you never have to check again; you can just always check the case-insensitive branch.
  2. If you are not case-insensitively sorted, you can just bail out at that point. You won't be case-sensitively sorted.

That means the only times you ever have to do two checks for a pair of elements is the first pair that forces you to transition to only checking for case-insensitive comparison.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point on (1), but (2) is not true. ['B', 'a'] is not case-insensitively sorted but is case-sensitively sorted. (That’s the entire reason this is bit flags.)

It sure sounds like the “case-insensitively sorted lists” ought to be a superset of “case-sensitively sorted lists,” doesn’t it? They’re actually a classic Venn diagram shape where there is a non-empty intersection but neither is a subset of the other. When Jake was first trying to explain the problem to me verbally, I made the same mistake.

Copy link
Member

Choose a reason for hiding this comment

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

Ugh, why was I so convinced this was true? But yes, I see that. At least you can avoid each kind of comparison after the first failure.

Copy link
Member Author

Choose a reason for hiding this comment

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

(Quiz ChatGPT about this for a good laugh)

}
return kind;
}

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change

@DanielRosenwasser
Copy link
Member

@typescript-bot user test tsserver
@typescript-bot test tsserver top100
@typescript-bot perf test this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 13, 2022

Heya @DanielRosenwasser, I've started to run the diff-based user code test suite (tsserver) on this PR at 4257fe4. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 13, 2022

Heya @DanielRosenwasser, I've started to run the diff-based top-repos suite (tsserver) on this PR at 4257fe4. You can monitor the build here.

Update: The results are in!

@typescript-bot
Copy link
Collaborator

typescript-bot commented Dec 13, 2022

Heya @DanielRosenwasser, I've started to run the perf test suite on this PR at 4257fe4. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

@DanielRosenwasser Here are the results of running the user test suite comparing main and refs/pull/51733/merge:

Everything looks good!

@andrewbranch andrewbranch merged commit 4076ff8 into microsoft:main Dec 13, 2022
@andrewbranch andrewbranch deleted the bug/organize-imports-case-sensitivity branch December 13, 2022 22:37
@typescript-bot
Copy link
Collaborator

@DanielRosenwasser Here are the results of running the top-repos suite comparing main and refs/pull/51733/merge:

Something interesting changed - please have a look.

Details

backstage/backstage

⚠️ Note that built also had errors ⚠️
Req #4960 - completionInfo
    at hasCovariantVoidArgument (/typescript-main/built/local/tsserver.js:59960:91)
    at relateVariances (/typescript-main/built/local/tsserver.js:59210:64)
    at structuredTypeRelatedToWorker (/typescript-main/built/local/tsserver.js:58879:32)
    at structuredTypeRelatedTo (/typescript-main/built/local/tsserver.js:58800:21)
    at recursiveTypeRelatedTo (/typescript-main/built/local/tsserver.js:58770:19)
    at isRelatedTo (/typescript-main/built/local/tsserver.js:58329:16)
    at compareProperties2 (/typescript-main/built/local/tsserver.js:60116:12)
    at propertiesIdenticalTo (/typescript-main/built/local/tsserver.js:59578:25)
    at propertiesRelatedTo (/typescript-main/built/local/tsserver.js:59430:16)
    at structuredTypeRelatedToWorker (/typescript-main/built/local/tsserver.js:59169:21)
    at structuredTypeRelatedTo (/typescript-main/built/local/tsserver.js:58800:21)
    at recursiveTypeRelatedTo (/typescript-main/built/local/tsserver.js:58770:19)
    at isRelatedTo (/typescript-main/built/local/tsserver.js:58329:16)
    at checkTypeRelatedTo (/typescript-main/built/local/tsserver.js:58063:20)
    at isTypeRelatedTo (/typescript-main/built/local/tsserver.js:58018:14)
    at isTypeIdenticalTo (/typescript-main/built/local/tsserver.js:57345:12)
    at isTypeOrBaseIdenticalTo (/typescript-main/built/local/tsserver.js:61563:42)
    at inferFromMatchingTypes (/typescript-main/built/local/tsserver.js:61233:15)
    at inferFromTypes (/typescript-main/built/local/tsserver.js:61065:44)
    at inferTypes (/typescript-main/built/local/tsserver.js:61040:5)
    at inferReverseMappedType (/typescript-main/built/local/tsserver.js:60873:5)
    at /typescript-main/built/local/tsserver.js:60852:65
    at map (/typescript-main/built/local/tsserver.js:2921:19)
    at createReverseMappedType (/typescript-main/built/local/tsserver.js:60852:28)
    at inferTypeForHomomorphicMappedType (/typescript-main/built/local/tsserver.js:60836:18)
    at inferToMappedType (/typescript-main/built/local/tsserver.js:61353:32)
    at inferFromObjectTypes (/typescript-main/built/local/tsserver.js:61440:13)
    at invokeOnce (/typescript-main/built/local/tsserver.js:61218:9)
    at inferFromTypes (/typescript-main/built/local/tsserver.js:61176:11)
    at inferWithPriority (/typescript-main/built/local/tsserver.js:61183:7)
    at inferFromObjectTypes (/typescript-main/built/local/tsserver.js:61481:17)
    at invokeOnce (/typescript-main/built/local/tsserver.js:61218:9)
    at inferFromTypes (/typescript-main/built/local/tsserver.js:61176:11)
    at inferToMultipleTypes (/typescript-main/built/local/tsserver.js:61303:15)
    at inferFromTypes (/typescript-main/built/local/tsserver.js:61157:9)
    at inferFromProperties (/typescript-main/built/local/tsserver.js:61510:11)
    at inferFromObjectTypes (/typescript-main/built/local/tsserver.js:61499:9)
    at invokeOnce (/typescript-main/built/local/tsserver.js:61218:9)
    at inferFromTypes (/typescript-main/built/local/tsserver.js:61176:11)
    at inferTypes (/typescript-main/built/local/tsserver.js:61040:5)
    at inferTypeArguments (/typescript-main/built/local/tsserver.js:66805:11)
    at chooseOverload (/typescript-main/built/local/tsserver.js:67426:33)
    at resolveCall (/typescript-main/built/local/tsserver.js:67293:16)
    at resolveCallExpression (/typescript-main/built/local/tsserver.js:67624:12)
    at resolveSignature (/typescript-main/built/local/tsserver.js:67969:16)
    at getResolvedSignature (/typescript-main/built/local/tsserver.js:67989:20)
    at getContextualTypeForArgumentAtIndex (/typescript-main/built/local/tsserver.js:64299:112)
    at getContextualTypeForArgument (/typescript-main/built/local/tsserver.js:64293:39)
    at getContextualType2 (/typescript-main/built/local/tsserver.js:64673:16)
    at getApparentTypeOfContextualType (/typescript-main/built/local/tsserver.js:64615:120)
    at getContextualTypeForObjectLiteralElement (/typescript-main/built/local/tsserver.js:64495:18)
    at getContextualType2 (/typescript-main/built/local/tsserver.js:64681:16)
    at getApparentTypeOfContextualType (/typescript-main/built/local/tsserver.js:64615:120)
    at getContextualType2 (/typescript-main/built/local/tsserver.js:64686:22)
    at Object.getContextualType (/typescript-main/built/local/tsserver.js:44085:14)
    at getContextualType (/typescript-main/built/local/tsserver.js:135744:348)
    at getCompletionData (/typescript-main/built/local/tsserver.js:135968:43)
    at Object.getCompletionsAtPosition (/typescript-main/built/local/tsserver.js:134376:26)
    at Object.getCompletionsAtPosition2 [as getCompletionsAtPosition] (/typescript-main/built/local/tsserver.js:122893:35)
    at IOSession.getCompletions (/typescript-main/built/local/tsserver.js:160896:54)
    at completionInfo (/typescript-main/built/local/tsserver.js:159577:43)
    at /typescript-main/built/local/tsserver.js:161606:69
    at IOSession.executeWithRequestId (/typescript-main/built/local/tsserver.js:161598:14)
    at IOSession.executeCommand (/typescript-main/built/local/tsserver.js:161606:29)
    at IOSession.onMessage (/typescript-main/built/local/tsserver.js:161634:51)
    at Interface.<anonymous> (/typescript-main/built/local/tsserver.js:163033:14)
Req #4960 - completionInfo
    at hasCovariantVoidArgument (/typescript-51733/built/local/tsserver.js:60013:91)
    at relateVariances (/typescript-51733/built/local/tsserver.js:59263:64)
    at structuredTypeRelatedToWorker (/typescript-51733/built/local/tsserver.js:58932:32)
    at structuredTypeRelatedTo (/typescript-51733/built/local/tsserver.js:58853:21)
    at recursiveTypeRelatedTo (/typescript-51733/built/local/tsserver.js:58823:19)
    at isRelatedTo (/typescript-51733/built/local/tsserver.js:58382:16)
    at compareProperties2 (/typescript-51733/built/local/tsserver.js:60169:12)
    at propertiesIdenticalTo (/typescript-51733/built/local/tsserver.js:59631:25)
    at propertiesRelatedTo (/typescript-51733/built/local/tsserver.js:59483:16)
    at structuredTypeRelatedToWorker (/typescript-51733/built/local/tsserver.js:59222:21)
    at structuredTypeRelatedTo (/typescript-51733/built/local/tsserver.js:58853:21)
    at recursiveTypeRelatedTo (/typescript-51733/built/local/tsserver.js:58823:19)
    at isRelatedTo (/typescript-51733/built/local/tsserver.js:58382:16)
    at checkTypeRelatedTo (/typescript-51733/built/local/tsserver.js:58116:20)
    at isTypeRelatedTo (/typescript-51733/built/local/tsserver.js:58071:14)
    at isTypeIdenticalTo (/typescript-51733/built/local/tsserver.js:57398:12)
    at isTypeOrBaseIdenticalTo (/typescript-51733/built/local/tsserver.js:61616:42)
    at inferFromMatchingTypes (/typescript-51733/built/local/tsserver.js:61286:15)
    at inferFromTypes (/typescript-51733/built/local/tsserver.js:61118:44)
    at inferTypes (/typescript-51733/built/local/tsserver.js:61093:5)
    at inferReverseMappedType (/typescript-51733/built/local/tsserver.js:60926:5)
    at /typescript-51733/built/local/tsserver.js:60905:65
    at map (/typescript-51733/built/local/tsserver.js:2925:19)
    at createReverseMappedType (/typescript-51733/built/local/tsserver.js:60905:28)
    at inferTypeForHomomorphicMappedType (/typescript-51733/built/local/tsserver.js:60889:18)
    at inferToMappedType (/typescript-51733/built/local/tsserver.js:61406:32)
    at inferFromObjectTypes (/typescript-51733/built/local/tsserver.js:61493:13)
    at invokeOnce (/typescript-51733/built/local/tsserver.js:61271:9)
    at inferFromTypes (/typescript-51733/built/local/tsserver.js:61229:11)
    at inferWithPriority (/typescript-51733/built/local/tsserver.js:61236:7)
    at inferFromObjectTypes (/typescript-51733/built/local/tsserver.js:61534:17)
    at invokeOnce (/typescript-51733/built/local/tsserver.js:61271:9)
    at inferFromTypes (/typescript-51733/built/local/tsserver.js:61229:11)
    at inferToMultipleTypes (/typescript-51733/built/local/tsserver.js:61356:15)
    at inferFromTypes (/typescript-51733/built/local/tsserver.js:61210:9)
    at inferFromProperties (/typescript-51733/built/local/tsserver.js:61563:11)
    at inferFromObjectTypes (/typescript-51733/built/local/tsserver.js:61552:9)
    at invokeOnce (/typescript-51733/built/local/tsserver.js:61271:9)
    at inferFromTypes (/typescript-51733/built/local/tsserver.js:61229:11)
    at inferTypes (/typescript-51733/built/local/tsserver.js:61093:5)
    at inferTypeArguments (/typescript-51733/built/local/tsserver.js:66858:11)
    at chooseOverload (/typescript-51733/built/local/tsserver.js:67479:33)
    at resolveCall (/typescript-51733/built/local/tsserver.js:67346:16)
    at resolveCallExpression (/typescript-51733/built/local/tsserver.js:67677:12)
    at resolveSignature (/typescript-51733/built/local/tsserver.js:68022:16)
    at getResolvedSignature (/typescript-51733/built/local/tsserver.js:68042:20)
    at getContextualTypeForArgumentAtIndex (/typescript-51733/built/local/tsserver.js:64352:112)
    at getContextualTypeForArgument (/typescript-51733/built/local/tsserver.js:64346:39)
    at getContextualType2 (/typescript-51733/built/local/tsserver.js:64726:16)
    at getApparentTypeOfContextualType (/typescript-51733/built/local/tsserver.js:64668:120)
    at getContextualTypeForObjectLiteralElement (/typescript-51733/built/local/tsserver.js:64548:18)
    at getContextualType2 (/typescript-51733/built/local/tsserver.js:64734:16)
    at getApparentTypeOfContextualType (/typescript-51733/built/local/tsserver.js:64668:120)
    at getContextualType2 (/typescript-51733/built/local/tsserver.js:64739:22)
    at Object.getContextualType (/typescript-51733/built/local/tsserver.js:44138:14)
    at getContextualType (/typescript-51733/built/local/tsserver.js:135817:348)
    at getCompletionData (/typescript-51733/built/local/tsserver.js:136041:43)
    at Object.getCompletionsAtPosition (/typescript-51733/built/local/tsserver.js:134449:26)
    at Object.getCompletionsAtPosition2 [as getCompletionsAtPosition] (/typescript-51733/built/local/tsserver.js:122950:35)
    at IOSession.getCompletions (/typescript-51733/built/local/tsserver.js:161016:54)
    at completionInfo (/typescript-51733/built/local/tsserver.js:159697:43)
    at /typescript-51733/built/local/tsserver.js:161726:69
    at IOSession.executeWithRequestId (/typescript-51733/built/local/tsserver.js:161718:14)
    at IOSession.executeCommand (/typescript-51733/built/local/tsserver.js:161726:29)
    at IOSession.onMessage (/typescript-51733/built/local/tsserver.js:161754:51)
    at Interface.<anonymous> (/typescript-51733/built/local/tsserver.js:163153:14)

That is a filtered view of the text. To see the raw error text, go to RepoResults4/backstage.backstage.rawError.txt in the artifact folder

Last few requests

{"seq":4957,"type":"request","command":"completionEntryDetails","arguments":{"file":"@PROJECT_ROOT@/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.test.ts","line":25,"offset":50,"entryNames":["app-defaults"]}}
{"seq":4958,"type":"request","command":"completionInfo","arguments":{"file":"@PROJECT_ROOT@/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.test.ts","line":52,"offset":18,"includeExternalModuleExports":false,"includeInsertTextCompletions":true,"triggerKind":1}}
{"seq":4959,"type":"request","command":"completionEntryDetails","arguments":{"file":"@PROJECT_ROOT@/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.test.ts","line":52,"offset":18,"entryNames":["addedProviders"]}}
{"seq":4960,"type":"request","command":"completionInfo","arguments":{"file":"@PROJECT_ROOT@/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.test.ts","line":81,"offset":18,"includeExternalModuleExports":false,"includeInsertTextCompletions":true,"triggerKind":1}}

Repro Steps

  1. git clone https://github.com/backstage/backstage --recurse-submodules
  2. In dir backstage, run git reset --hard 5b0701ea1b1eaf16d505959f13977b2f3f757d88
  3. Install packages (exact steps are below, but it might be easier to follow the repo readme)
    1. In dir backstage/cypress, run yarn install --no-immutable --mode=skip-build
    2. In dir backstage/microsite-next, run yarn install --no-immutable --mode=skip-build
    3. In dir backstage/microsite, run yarn install --no-immutable --mode=skip-build
    4. In dir backstage, run yarn install --no-immutable --mode=skip-build
    5. In dir backstage/storybook, run yarn install --no-immutable --mode=skip-build
  4. Back in the initial folder, download RepoResults4/backstage.backstage.replay.txt from the artifact folder
  5. npm install --no-save @typescript/server-replay
  6. npx tsreplay ./backstage ./backstage.backstage.replay.txt path/to/tsserver.js
  7. npx tsreplay --help to learn about helpful switches for debugging, logging, etc

felixrieseberg/windows95

⚠️ Note that built also had errors ⚠️
Req #542 - references
    at formatMessage2 (/typescript-main/built/local/tsserver.js:158985:21)
    at IOSession.writeMessage (/typescript-main/built/local/tsserver.js:159992:21)
    at IOSession.send (/typescript-main/built/local/tsserver.js:159989:10)
    at IOSession.doOutput (/typescript-main/built/local/tsserver.js:160036:10)
    at IOSession.onMessage (/typescript-main/built/local/tsserver.js:161647:14)
    at Interface.<anonymous> (/typescript-main/built/local/tsserver.js:163033:14)
Req #542 - references
    at formatMessage2 (/typescript-51733/built/local/tsserver.js:159105:21)
    at IOSession.writeMessage (/typescript-51733/built/local/tsserver.js:160112:21)
    at IOSession.send (/typescript-51733/built/local/tsserver.js:160109:10)
    at IOSession.doOutput (/typescript-51733/built/local/tsserver.js:160156:10)
    at IOSession.onMessage (/typescript-51733/built/local/tsserver.js:161767:14)
    at Interface.<anonymous> (/typescript-51733/built/local/tsserver.js:163153:14)

That is a filtered view of the text. To see the raw error text, go to RepoResults4/felixrieseberg.windows95.rawError.txt in the artifact folder

Last few requests

{"seq":539,"type":"request","command":"definitionAndBoundSpan","arguments":{"file":"@PROJECT_ROOT@/src/renderer/lib/_libwabt.js","line":13,"offset":135424}}
{"seq":540,"type":"request","command":"references","arguments":{"file":"@PROJECT_ROOT@/src/renderer/lib/_libwabt.js","line":13,"offset":135723}}
{"seq":541,"type":"request","command":"definitionAndBoundSpan","arguments":{"file":"@PROJECT_ROOT@/src/renderer/lib/_libwabt.js","line":13,"offset":137242}}
{"seq":542,"type":"request","command":"references","arguments":{"file":"@PROJECT_ROOT@/src/renderer/lib/_libwabt.js","line":13,"offset":138991}}

Repro Steps

  1. git clone https://github.com/felixrieseberg/windows95 --recurse-submodules
  2. In dir windows95, run git reset --hard 17a81393467e2668eb2eea291ab4b6a749756dad
  3. In dir windows95, run yarn install --ignore-engines --ignore-scripts --silent
  4. Back in the initial folder, download RepoResults4/felixrieseberg.windows95.replay.txt from the artifact folder
  5. npm install --no-save @typescript/server-replay
  6. npx tsreplay ./windows95 ./felixrieseberg.windows95.replay.txt path/to/tsserver.js
  7. npx tsreplay --help to learn about helpful switches for debugging, logging, etc

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
None yet
Development

Successfully merging this pull request may close these issues.

Presence of underscore prefixed item in sorted import list causes auto-import to place new import at the end
6 participants