From 89b7dba7014deaa41c86704830e3d23896f9fe4e Mon Sep 17 00:00:00 2001 From: "arash.mousavi" Date: Sat, 27 Sep 2025 12:45:00 +0330 Subject: [PATCH 1/9] fix: add bounds checks to prevent slice bounds out of range panic --- internal/ls/autoimports.go | 27 +++++++++++++++++++-------- internal/ls/autoimportsexportinfo.go | 4 ++++ internal/ls/completions.go | 17 +++++++++++++++-- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/internal/ls/autoimports.go b/internal/ls/autoimports.go index 3b38e0d637..ca9cd31603 100644 --- a/internal/ls/autoimports.go +++ b/internal/ls/autoimports.go @@ -117,16 +117,27 @@ func (e *exportInfoMap) add( topLevelNodeModulesIndex := nodeModulesPathParts.TopLevelNodeModulesIndex topLevelPackageNameIndex := nodeModulesPathParts.TopLevelPackageNameIndex packageRootIndex := nodeModulesPathParts.PackageRootIndex - packageName = module.UnmangleScopedPackageName(modulespecifiers.GetPackageNameFromTypesPackageName(moduleFile.FileName()[topLevelPackageNameIndex+1 : packageRootIndex])) - if strings.HasPrefix(string(importingFile), string(moduleFile.Path())[0:topLevelNodeModulesIndex]) { - nodeModulesPath := moduleFile.FileName()[0 : topLevelPackageNameIndex+1] - if prevDeepestNodeModulesPath, ok := e.packages[packageName]; ok { - prevDeepestNodeModulesIndex := strings.Index(prevDeepestNodeModulesPath, "/node_modules/") - if topLevelNodeModulesIndex > prevDeepestNodeModulesIndex { + + // Bounds check to prevent slice bounds out of range panic + fileName := moduleFile.FileName() + if topLevelPackageNameIndex+1 >= 0 && packageRootIndex >= 0 && topLevelPackageNameIndex+1 <= packageRootIndex && packageRootIndex <= len(fileName) { + packageName = module.UnmangleScopedPackageName(modulespecifiers.GetPackageNameFromTypesPackageName(fileName[topLevelPackageNameIndex+1 : packageRootIndex])) + } + + // Bounds check for module path slice + modulePath := string(moduleFile.Path()) + if topLevelNodeModulesIndex >= 0 && topLevelNodeModulesIndex <= len(modulePath) && strings.HasPrefix(string(importingFile), modulePath[0:topLevelNodeModulesIndex]) { + // Bounds check for node modules path slice + if topLevelPackageNameIndex+1 >= 0 && topLevelPackageNameIndex+1 <= len(fileName) { + nodeModulesPath := fileName[0 : topLevelPackageNameIndex+1] + if prevDeepestNodeModulesPath, ok := e.packages[packageName]; ok { + prevDeepestNodeModulesIndex := strings.Index(prevDeepestNodeModulesPath, "/node_modules/") + if topLevelNodeModulesIndex > prevDeepestNodeModulesIndex { + e.packages[packageName] = nodeModulesPath + } + } else { e.packages[packageName] = nodeModulesPath } - } else { - e.packages[packageName] = nodeModulesPath } } } diff --git a/internal/ls/autoimportsexportinfo.go b/internal/ls/autoimportsexportinfo.go index 65de5e82ad..55918bf8fb 100644 --- a/internal/ls/autoimportsexportinfo.go +++ b/internal/ls/autoimportsexportinfo.go @@ -100,6 +100,10 @@ func (l *LanguageService) searchExportInfosForCompletions( return false } // Do not try to auto-import something with a lowercase first letter for a JSX tag + if len(symbolName) == 0 { + symbolNameMatches[symbolName] = false + return false + } firstChar := rune(symbolName[0]) if isRightOfOpenTag && (firstChar < 'A' || firstChar > 'Z') { symbolNameMatches[symbolName] = false diff --git a/internal/ls/completions.go b/internal/ls/completions.go index 9a1dc9836d..ebd158d945 100644 --- a/internal/ls/completions.go +++ b/internal/ls/completions.go @@ -2376,7 +2376,14 @@ var wordSeparators = collections.NewSetFromItems( // e.g. for "abc def.ghi|jkl", the word length is 3 and the word start is 'g'. func getWordLengthAndStart(sourceFile *ast.SourceFile, position int) (wordLength int, wordStart rune) { // !!! Port other case of vscode's `DEFAULT_WORD_REGEXP` that covers words that start like numbers, e.g. -123.456abcd. - text := sourceFile.Text()[:position] + + // Bounds check to prevent slice bounds out of range panic + sourceText := sourceFile.Text() + if position < 0 || position > len(sourceText) { + return 0, 0 + } + + text := sourceText[:position] totalSize := 0 var firstRune rune for r, size := utf8.DecodeLastRuneInString(text); size != 0; r, size = utf8.DecodeLastRuneInString(text[:len(text)-totalSize]) { @@ -2485,7 +2492,13 @@ func getFilterText( // Ported from vscode's `provideCompletionItems`. func getDotAccessor(file *ast.SourceFile, position int) string { - text := file.Text()[:position] + // Bounds check to prevent slice bounds out of range panic + fileText := file.Text() + if position < 0 || position > len(fileText) { + return "" + } + + text := fileText[:position] totalSize := 0 if strings.HasSuffix(text, "?.") { totalSize += 2 From c359bdbb92eece1d681a9eecc4401ceaf5d73231 Mon Sep 17 00:00:00 2001 From: Arash <75931882+arash-mosavi@users.noreply.github.com> Date: Tue, 30 Sep 2025 15:48:46 +0330 Subject: [PATCH 2/9] Update internal/ls/autoimports.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/ls/autoimports.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ls/autoimports.go b/internal/ls/autoimports.go index ca9cd31603..6352ed8ca2 100644 --- a/internal/ls/autoimports.go +++ b/internal/ls/autoimports.go @@ -128,7 +128,7 @@ func (e *exportInfoMap) add( modulePath := string(moduleFile.Path()) if topLevelNodeModulesIndex >= 0 && topLevelNodeModulesIndex <= len(modulePath) && strings.HasPrefix(string(importingFile), modulePath[0:topLevelNodeModulesIndex]) { // Bounds check for node modules path slice - if topLevelPackageNameIndex+1 >= 0 && topLevelPackageNameIndex+1 <= len(fileName) { + if topLevelPackageNameIndex >= 0 && topLevelPackageNameIndex+1 <= len(fileName) { nodeModulesPath := fileName[0 : topLevelPackageNameIndex+1] if prevDeepestNodeModulesPath, ok := e.packages[packageName]; ok { prevDeepestNodeModulesIndex := strings.Index(prevDeepestNodeModulesPath, "/node_modules/") From 620d469db9a289d51df19b7425a616f7927808b8 Mon Sep 17 00:00:00 2001 From: "arash.mousavi" Date: Wed, 1 Oct 2025 07:50:27 +0330 Subject: [PATCH 3/9] Add comprehensive bounds checking to prevent slice panics in autoimports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add bounds check before accessing names[0] when symbol names array may be empty - Add bounds checking in getNodeModuleRootSpecifier for component array access - Prevent panics when accessing array elements without verifying length Fixes #1691 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/ls/autoimports.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/internal/ls/autoimports.go b/internal/ls/autoimports.go index 6352ed8ca2..654f3fdafb 100644 --- a/internal/ls/autoimports.go +++ b/internal/ls/autoimports.go @@ -164,6 +164,10 @@ func (e *exportInfoMap) add( names = getNamesForExportedSymbol(namedSymbol, ch, core.ScriptTargetNone) } + // Bounds check to prevent slice bounds out of range panic + if len(names) == 0 { + return + } symbolName := names[0] if symbolNameMatch != nil && !symbolNameMatch(symbolName) { return @@ -674,12 +678,23 @@ func (l *LanguageService) createPackageJsonImportFilter(fromFile *ast.SourceFile sourceFileCache := map[*ast.SourceFile]packageJsonFilterResult{} getNodeModuleRootSpecifier := func(fullSpecifier string) string { - components := tspath.GetPathComponents(modulespecifiers.GetPackageNameFromTypesPackageName(fullSpecifier), "")[1:] + components := tspath.GetPathComponents(modulespecifiers.GetPackageNameFromTypesPackageName(fullSpecifier), "") + // Bounds check to prevent slice bounds out of range panic + if len(components) < 2 { + return "" + } + components = components[1:] // Scoped packages - if strings.HasPrefix(components[0], "@") { + if len(components) > 0 && strings.HasPrefix(components[0], "@") { + if len(components) < 2 { + return components[0] + } return fmt.Sprintf("%s/%s", components[0], components[1]) } - return components[0] + if len(components) > 0 { + return components[0] + } + return "" } moduleSpecifierIsCoveredByPackageJson := func(specifier string) bool { From 27fd2f8af576bf6ee6e8ec003f0486d77297ab8d Mon Sep 17 00:00:00 2001 From: "arash.mousavi" Date: Wed, 1 Oct 2025 09:03:25 +0330 Subject: [PATCH 4/9] fix: initialize exportInfoId in exportInfoMap --- internal/ls/autoimports.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/ls/autoimports.go b/internal/ls/autoimports.go index 654f3fdafb..3d8bf95bc1 100644 --- a/internal/ls/autoimports.go +++ b/internal/ls/autoimports.go @@ -224,6 +224,7 @@ func (e *exportInfoMap) add( targetFlags: target.Flags, isFromPackageJson: isFromPackageJson, }) + e.exportInfoId = id } func (e *exportInfoMap) search( From 88db44574fb518a5700c80273b85375f0ac661f8 Mon Sep 17 00:00:00 2001 From: "arash.mousavi" Date: Wed, 1 Oct 2025 09:33:48 +0330 Subject: [PATCH 5/9] Fix race condition by incrementing exportInfoId counter The exportInfoId counter was being read but never incremented, causing all export info entries to receive the same ID (1). When multiple goroutines called add() concurrently, they would all write to the same map entry (e.symbols[1]), creating a data race. This fix properly increments the counter after adding each entry, ensuring unique IDs and eliminating the race condition. --- CLAUDE.md | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..d0ed64c605 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,148 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +TypeScript 7 is a native port of the TypeScript compiler and language server written in Go. This is a work-in-progress project that aims to achieve feature parity with TypeScript 5.8, with most development happening in the `internal` directory. + +## Build System and Commands + +This project uses `hereby` as the primary build tool. The following npm scripts are available: + +```bash +# Core build and test commands +npm run build # Build the tsgo binary using hereby +npm run build:watch # Build with watch mode +npm run build:watch:debug # Build with debug output in watch mode +npm run test # Run all tests + +# Package-specific commands +npm run api:build # Build the API package +npm run extension:build # Build the VS Code extension +npm run extension:watch # Build extension in watch mode + +# Development utilities +npm run node # Run node with special conditions +npm run convertfourslash # Convert fourslash test files +npm run updatefailing # Update failing test baselines +npm run makemanual # Create manual test files +``` + +Additional hereby commands (run with `npx hereby `): +- `npx hereby format` - Format the code using dprint +- `npx hereby lint` - Run Go linters +- `npx hereby baseline-accept` - Update test baselines/snapshots + +### Build Options + +The build system supports several flags for debugging and performance analysis: +- `--debug` - Include debug symbols and disable optimizations +- `--race` - Enable Go race detector (use `TSGO_HEREBY_RACE=true`) +- Build tags: `release` (default), `noembed` for debugging + +## Testing + +### Running Specific Tests + +```bash +# For pre-existing "submodule" tests from _submodules/TypeScript +go test -run='TestSubmodule/' ./internal/testrunner + +# For new "local" tests in testdata/tests/cases +go test -run='TestLocal/' ./internal/testrunner + +# Run tests with race detection (for debugging concurrency issues) +go test -race ./internal/testrunner + +# Run tests for a specific package (e.g., parser, checker, etc.) +go test ./internal/parser +go test ./internal/checker +``` + +### Writing New Compiler Tests + +New compiler tests go in `testdata/tests/cases/compiler/` and use TypeScript syntax with special comments: + +```ts +// @target: esnext +// @module: preserve +// @moduleResolution: bundler +// @strict: true +// @checkJs: true + +// @filename: fileA.ts +export interface Person { + name: string; + age: number; +} + +// @filename: fileB.js +/** @import { Person } from "./fileA" */ +function greet(person) { + console.log(`Hello, ${person.name}!`); +} +``` + +**Always enable strict mode (`@strict: true`) unless testing non-strict behavior.** + +Test outputs are generated in `testdata/baselines/local` and compared against `testdata/baselines/reference`. Use `npx hereby baseline-accept` to update baselines after test changes. + +## Architecture + +### Key Directories + +- **`internal/`** - Main compiler and language server code (Go) + - `compiler/` - Core compilation logic + - `parser/` - TypeScript parsing + - `checker/` - Type checking + - `binder/` - Symbol binding + - `scanner/` - Lexical analysis + - `lsp/` - Language Server Protocol implementation + - `testrunner/` - Test execution framework +- **`_extension/`** - VS Code extension (TypeScript/JavaScript) +- **`_packages/`** - NPM packages + - `native-preview/` - Preview npm package + - `api/` - TypeScript API bindings +- **`_submodules/TypeScript`** - Reference TypeScript implementation +- **`testdata/`** - Test cases and baselines + +### Development Workflow + +1. Write minimal test cases demonstrating the bug/feature +2. Run tests to verify failure (bugs) or expected behavior (features) +3. Accept generated baselines if needed +4. Implement the fix/feature in `internal/` +5. Re-run tests and accept new baselines +6. Ensure code is formatted (`npx hereby format`) and linted (`npx hereby lint`) + +### Code Reference + +The `internal/` directory is ported from `_submodules/TypeScript`. When implementing features or fixing bugs, search the TypeScript submodule for reference implementations. + +## Language Server Integration + +The LSP implementation is in `internal/lsp/`. Editor functionality requires integration testing with the language server, not just compiler tests. + +## Go Development Notes + +### Module Structure +- Uses Go 1.25+ with workspace mode +- Main module: `github.com/microsoft/typescript-go` +- Key dependencies: `regexp2`, `go-json-experiment/json`, `xxh3` for hashing +- Build tools included as Go tools: `moq`, `stringer`, `gofumpt` + +### Performance Considerations +- UTF-8 string handling differs from TypeScript's UTF-16 approach +- Node positions use UTF-8 offsets, not UTF-16 (affects non-ASCII character positions) +- xxh3 hashing used for performance-critical operations +- Memory pooling and string interning used in hot paths + +### Debugging +- Use `--debug` flag for builds to include debug symbols +- Race detector available with `--race` or `TSGO_HEREBY_RACE=true` +- LSP debugging can be enabled through VS Code extension settings + +## Intentional Changes + +See `CHANGES.md` for documented differences between this Go port and the original TypeScript compiler, including scanner, parser, and JSDoc handling changes. \ No newline at end of file From 78a03a46697fe617fdf2a866dbf89f4ff73574ad Mon Sep 17 00:00:00 2001 From: "arash.mousavi" Date: Wed, 1 Oct 2025 09:35:44 +0330 Subject: [PATCH 6/9] Remove CLAUDE.md file --- CLAUDE.md | 148 ------------------------------------------------------ 1 file changed, 148 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index d0ed64c605..0000000000 --- a/CLAUDE.md +++ /dev/null @@ -1,148 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Project Overview - -TypeScript 7 is a native port of the TypeScript compiler and language server written in Go. This is a work-in-progress project that aims to achieve feature parity with TypeScript 5.8, with most development happening in the `internal` directory. - -## Build System and Commands - -This project uses `hereby` as the primary build tool. The following npm scripts are available: - -```bash -# Core build and test commands -npm run build # Build the tsgo binary using hereby -npm run build:watch # Build with watch mode -npm run build:watch:debug # Build with debug output in watch mode -npm run test # Run all tests - -# Package-specific commands -npm run api:build # Build the API package -npm run extension:build # Build the VS Code extension -npm run extension:watch # Build extension in watch mode - -# Development utilities -npm run node # Run node with special conditions -npm run convertfourslash # Convert fourslash test files -npm run updatefailing # Update failing test baselines -npm run makemanual # Create manual test files -``` - -Additional hereby commands (run with `npx hereby `): -- `npx hereby format` - Format the code using dprint -- `npx hereby lint` - Run Go linters -- `npx hereby baseline-accept` - Update test baselines/snapshots - -### Build Options - -The build system supports several flags for debugging and performance analysis: -- `--debug` - Include debug symbols and disable optimizations -- `--race` - Enable Go race detector (use `TSGO_HEREBY_RACE=true`) -- Build tags: `release` (default), `noembed` for debugging - -## Testing - -### Running Specific Tests - -```bash -# For pre-existing "submodule" tests from _submodules/TypeScript -go test -run='TestSubmodule/' ./internal/testrunner - -# For new "local" tests in testdata/tests/cases -go test -run='TestLocal/' ./internal/testrunner - -# Run tests with race detection (for debugging concurrency issues) -go test -race ./internal/testrunner - -# Run tests for a specific package (e.g., parser, checker, etc.) -go test ./internal/parser -go test ./internal/checker -``` - -### Writing New Compiler Tests - -New compiler tests go in `testdata/tests/cases/compiler/` and use TypeScript syntax with special comments: - -```ts -// @target: esnext -// @module: preserve -// @moduleResolution: bundler -// @strict: true -// @checkJs: true - -// @filename: fileA.ts -export interface Person { - name: string; - age: number; -} - -// @filename: fileB.js -/** @import { Person } from "./fileA" */ -function greet(person) { - console.log(`Hello, ${person.name}!`); -} -``` - -**Always enable strict mode (`@strict: true`) unless testing non-strict behavior.** - -Test outputs are generated in `testdata/baselines/local` and compared against `testdata/baselines/reference`. Use `npx hereby baseline-accept` to update baselines after test changes. - -## Architecture - -### Key Directories - -- **`internal/`** - Main compiler and language server code (Go) - - `compiler/` - Core compilation logic - - `parser/` - TypeScript parsing - - `checker/` - Type checking - - `binder/` - Symbol binding - - `scanner/` - Lexical analysis - - `lsp/` - Language Server Protocol implementation - - `testrunner/` - Test execution framework -- **`_extension/`** - VS Code extension (TypeScript/JavaScript) -- **`_packages/`** - NPM packages - - `native-preview/` - Preview npm package - - `api/` - TypeScript API bindings -- **`_submodules/TypeScript`** - Reference TypeScript implementation -- **`testdata/`** - Test cases and baselines - -### Development Workflow - -1. Write minimal test cases demonstrating the bug/feature -2. Run tests to verify failure (bugs) or expected behavior (features) -3. Accept generated baselines if needed -4. Implement the fix/feature in `internal/` -5. Re-run tests and accept new baselines -6. Ensure code is formatted (`npx hereby format`) and linted (`npx hereby lint`) - -### Code Reference - -The `internal/` directory is ported from `_submodules/TypeScript`. When implementing features or fixing bugs, search the TypeScript submodule for reference implementations. - -## Language Server Integration - -The LSP implementation is in `internal/lsp/`. Editor functionality requires integration testing with the language server, not just compiler tests. - -## Go Development Notes - -### Module Structure -- Uses Go 1.25+ with workspace mode -- Main module: `github.com/microsoft/typescript-go` -- Key dependencies: `regexp2`, `go-json-experiment/json`, `xxh3` for hashing -- Build tools included as Go tools: `moq`, `stringer`, `gofumpt` - -### Performance Considerations -- UTF-8 string handling differs from TypeScript's UTF-16 approach -- Node positions use UTF-8 offsets, not UTF-16 (affects non-ASCII character positions) -- xxh3 hashing used for performance-critical operations -- Memory pooling and string interning used in hot paths - -### Debugging -- Use `--debug` flag for builds to include debug symbols -- Race detector available with `--race` or `TSGO_HEREBY_RACE=true` -- LSP debugging can be enabled through VS Code extension settings - -## Intentional Changes - -See `CHANGES.md` for documented differences between this Go port and the original TypeScript compiler, including scanner, parser, and JSDoc handling changes. \ No newline at end of file From 847db7c9a87db92bb5fd345e90d8aed8da3736ed Mon Sep 17 00:00:00 2001 From: "arash.mousavi" Date: Wed, 1 Oct 2025 10:04:13 +0330 Subject: [PATCH 7/9] revert(ls): drop completions guards to keep PR scoped to autoimports (#1691) --- internal/ls/completions.go | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/internal/ls/completions.go b/internal/ls/completions.go index ebd158d945..9a1dc9836d 100644 --- a/internal/ls/completions.go +++ b/internal/ls/completions.go @@ -2376,14 +2376,7 @@ var wordSeparators = collections.NewSetFromItems( // e.g. for "abc def.ghi|jkl", the word length is 3 and the word start is 'g'. func getWordLengthAndStart(sourceFile *ast.SourceFile, position int) (wordLength int, wordStart rune) { // !!! Port other case of vscode's `DEFAULT_WORD_REGEXP` that covers words that start like numbers, e.g. -123.456abcd. - - // Bounds check to prevent slice bounds out of range panic - sourceText := sourceFile.Text() - if position < 0 || position > len(sourceText) { - return 0, 0 - } - - text := sourceText[:position] + text := sourceFile.Text()[:position] totalSize := 0 var firstRune rune for r, size := utf8.DecodeLastRuneInString(text); size != 0; r, size = utf8.DecodeLastRuneInString(text[:len(text)-totalSize]) { @@ -2492,13 +2485,7 @@ func getFilterText( // Ported from vscode's `provideCompletionItems`. func getDotAccessor(file *ast.SourceFile, position int) string { - // Bounds check to prevent slice bounds out of range panic - fileText := file.Text() - if position < 0 || position > len(fileText) { - return "" - } - - text := fileText[:position] + text := file.Text()[:position] totalSize := 0 if strings.HasSuffix(text, "?.") { totalSize += 2 From 36ec2efc59cfab08bf390d8b32481952c392332b Mon Sep 17 00:00:00 2001 From: Arash <75931882+arash-mosavi@users.noreply.github.com> Date: Thu, 2 Oct 2025 08:33:31 +0330 Subject: [PATCH 8/9] Update internal/ls/autoimports.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/ls/autoimports.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/ls/autoimports.go b/internal/ls/autoimports.go index 3d8bf95bc1..654f3fdafb 100644 --- a/internal/ls/autoimports.go +++ b/internal/ls/autoimports.go @@ -224,7 +224,6 @@ func (e *exportInfoMap) add( targetFlags: target.Flags, isFromPackageJson: isFromPackageJson, }) - e.exportInfoId = id } func (e *exportInfoMap) search( From 341f9470b079a7ef197ec0da8044777fdd9b1278 Mon Sep 17 00:00:00 2001 From: "arash.mousavi" Date: Tue, 7 Oct 2025 08:14:34 +0330 Subject: [PATCH 9/9] fix: add bounds checking function to prevent slice out of range panic --- internal/ls/autoimports.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/ls/autoimports.go b/internal/ls/autoimports.go index e21a5c8bcc..9ee37fa1e2 100644 --- a/internal/ls/autoimports.go +++ b/internal/ls/autoimports.go @@ -21,6 +21,11 @@ import ( "github.com/microsoft/typescript-go/internal/tspath" ) +// isValidSliceRange checks if the given start and end indices are valid for slicing a string/slice of the given length. +func isValidSliceRange(start, end, length int) bool { + return start >= 0 && end >= 0 && start <= end && end <= length +} + type SymbolExportInfo struct { symbol *ast.Symbol moduleSymbol *ast.Symbol @@ -120,7 +125,7 @@ func (e *exportInfoMap) add( // Bounds check to prevent slice bounds out of range panic fileName := moduleFile.FileName() - if topLevelPackageNameIndex+1 >= 0 && packageRootIndex >= 0 && topLevelPackageNameIndex+1 <= packageRootIndex && packageRootIndex <= len(fileName) { + if isValidSliceRange(topLevelPackageNameIndex+1, packageRootIndex, len(fileName)) { packageName = module.UnmangleScopedPackageName(modulespecifiers.GetPackageNameFromTypesPackageName(fileName[topLevelPackageNameIndex+1 : packageRootIndex])) }