Skip to content

Fix auto-import lowercasing PascalCase default-import bindings on case-insensitive file systems#3952

Merged
RyanCavanaugh merged 5 commits into
mainfrom
copilot/fix-auto-import-lowercase-capitals
May 19, 2026
Merged

Fix auto-import lowercasing PascalCase default-import bindings on case-insensitive file systems#3952
RyanCavanaugh merged 5 commits into
mainfrom
copilot/fix-auto-import-lowercase-capitals

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 18, 2026

  • Investigate the auto-import code paths for default export binding name resolution
  • Identify root cause: moduleID (from file.Path()) is lowercased on case-insensitive file systems, losing PascalCase capitals in the fallback path
  • Write fourslash tests to reproduce and verify the fix across multiple scenarios
  • Implement the fix: use moduleFileName (preserves original casing) in fallback paths
  • Run tests and verify all pass
  • Run build, lint, format checks - all pass
  • Address code review feedback for clarity
  • Add test for re-exported anonymous default on case-insensitive FS (covers targetSymbol fallback branch)
  • Simplify obtuse fallback logic with extracted helper function

Copilot AI and others added 2 commits May 18, 2026 15:03
…e-insensitive file systems

Use the original-case moduleFileName instead of the lowercased moduleID (from
file.Path()) when deriving a fallback identifier name for default/export-equals
exports. On case-insensitive file systems, Path() lowercases the entire path, so
using it to derive an identifier from the filename loses internal capitals
(e.g., "ChargerHeader" becomes "chargerheader", then "Chargerheader" after JSX
capitalization).

Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/906fc5d5-16e0-4a35-b14f-4b160557b961

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix auto-import for lowercased internal capitals in PascalCase bindings Fix auto-import lowercasing PascalCase default-import bindings on case-insensitive file systems May 18, 2026
Copilot AI requested a review from jakebailey May 18, 2026 15:07
@jakebailey jakebailey marked this pull request as ready for review May 18, 2026 15:21
Copilot AI review requested due to automatic review settings May 18, 2026 15:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes auto-import fallback names for default-like exports so PascalCase file names are preserved on case-insensitive file systems, fitting into the language service auto-import extraction path.

Changes:

  • Uses original-cased file names instead of lowercased module paths when deriving fallback local names.
  • Adds fourslash coverage for PascalCase default imports across named, anonymous, alias, and case-insensitive scenarios.
  • Adds reference baselines for the new auto-import tests.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
internal/ls/autoimport/extract.go Updates fallback local-name derivation for default-like exports.
internal/fourslash/tests/autoImportDefaultPascalCase_test.go Adds fourslash auto-import tests for PascalCase default bindings.
testdata/baselines/reference/fourslash/autoImports/autoImportDefaultPascalCase.baseline.md Adds baseline for named default PascalCase auto-import.
testdata/baselines/reference/fourslash/autoImports/autoImportDefaultPascalCaseAnonymous.baseline.md Adds baseline for anonymous default PascalCase auto-import.
testdata/baselines/reference/fourslash/autoImports/autoImportDefaultPascalCaseCaseInsensitive.baseline.md Adds baseline for named default on case-insensitive file systems.
testdata/baselines/reference/fourslash/autoImports/autoImportDefaultPascalCaseAnonymousCaseInsensitive.baseline.md Adds baseline for anonymous default on case-insensitive file systems.
testdata/baselines/reference/fourslash/autoImports/autoImportDefaultPascalCaseAliasCaseInsensitive.baseline.md Adds baseline for aliased default on case-insensitive file systems.

Comment thread internal/ls/autoimport/extract.go Outdated
…ive FS

This test exercises the targetSymbol file-name fallback branch (lines 340-347
in extract.go) by using `export { default } from "./ChargerHeader"` where the
source file has an anonymous default export.

Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/f6824fed-6f67-4b85-b0ef-a0512ad6611a

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Comment thread internal/ls/autoimport/extract.go Outdated
Comment on lines +339 to +347
targetFileName := ""
if len(targetSymbol.Declarations) > 0 {
targetFileName = ast.GetSourceFileOfNode(targetSymbol.Declarations[0]).FileName()
}
if targetFileName == "" {
targetFileName = moduleFileName
}
if targetFileName != "" {
export.localName = lsutil.ModuleSpecifierToValidIdentifier(targetFileName, false)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot this code is really obtuse. I also wonder if this same problem has already been solved elsewhere, e.g. the modulespeciifers package or something. Even elsewhere in this file or the LS

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Simplified in 8d290bf. Extracted a fileNameForDefaultExportName helper that encapsulates the priority logic (target symbol's source file → module file name → moduleID). The main flow now has a single fallback call at the end instead of deeply nested conditionals. I checked modulespecifiers and completions — they don't have this exact pattern since they work with module symbols (via ModuleSymbolToValidIdentifier) rather than the raw file path fallback for anonymous defaults.

Extract fileNameForDefaultExportName() to consolidate the repeated
conditional logic for choosing a file name to derive fallback identifiers.
This makes the code clearer and removes deep nesting.

Agent-Logs-Url: https://github.com/microsoft/typescript-go/sessions/1503940b-1479-45c6-b5a1-b491d287f2c6

Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
Copilot AI requested a review from jakebailey May 18, 2026 16:33
if moduleFileName != "" {
return moduleFileName
}
return string(moduleID)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@copilot Does this ever actually happen? Can you temporarily panic here and see what happens? (Don't commit it, just tell me how it goes)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I added a panic and ran all fourslash tests plus the internal/ls/... tests — it never triggers. The only call site that passes moduleFileName="" is line 118 (extractFromModuleDeclaration for ambient modules like declare module "foo"), but in that scenario targetSymbol.Declarations[0] always has a valid source file, so it returns from line 453 before reaching line 460. In practice this is dead code, but it's there as a safety net for the ambient module edge case. Happy to remove it if you'd prefer.

@RyanCavanaugh RyanCavanaugh added this pull request to the merge queue May 19, 2026
Merged via the queue into main with commit fd881b4 May 19, 2026
22 checks passed
@RyanCavanaugh RyanCavanaugh deleted the copilot/fix-auto-import-lowercase-capitals branch May 19, 2026 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto-import lowercases internal capitals in PascalCase default-import bindings

4 participants