From 3b5989551217dc346f4b4559fb87fe9aacc7dacd Mon Sep 17 00:00:00 2001 From: Marius Schulz Date: Thu, 11 Jun 2026 16:13:07 +0100 Subject: [PATCH 01/49] perf(checker): defer orderedSet map allocation until >16 elements (#4277) --- internal/checker/checker.go | 1 - internal/checker/utilities.go | 19 +++++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/internal/checker/checker.go b/internal/checker/checker.go index c86cd263d91..5a66fd5cee2 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -25925,7 +25925,6 @@ func (c *Checker) getIntersectionType(types []*Type) *Type { func (c *Checker) getIntersectionTypeEx(types []*Type, flags IntersectionFlags, alias *TypeAlias) *Type { var orderedTypes orderedSet[*Type] orderedTypes.values = make([]*Type, 0, len(types)) - orderedTypes.valuesByKey = make(map[*Type]struct{}, len(types)) includes := c.addTypesToIntersection(&orderedTypes, 0, types) typeSet := orderedTypes.values objectFlags := ObjectFlagsNone diff --git a/internal/checker/utilities.go b/internal/checker/utilities.go index 46afe93a8eb..490d6887f8d 100644 --- a/internal/checker/utilities.go +++ b/internal/checker/utilities.go @@ -806,22 +806,37 @@ func isDeclarationReadonly(declaration *ast.Node) bool { return ast.GetCombinedModifierFlags(declaration)&ast.ModifierFlagsReadonly != 0 && !ast.IsParameterPropertyDeclaration(declaration, declaration.Parent) } +// orderedSetMapThreshold is the size at which an orderedSet materializes its dedup map. +// Below this, contains() scans the values slice. +const orderedSetMapThreshold = 16 + type orderedSet[T comparable] struct { valuesByKey map[T]struct{} values []T } func (s *orderedSet[T]) contains(value T) bool { + if s.valuesByKey == nil { + return slices.Contains(s.values, value) + } _, ok := s.valuesByKey[value] return ok } func (s *orderedSet[T]) add(value T) { + s.values = append(s.values, value) + // Small sets are served by a linear scan over values; only materialize the map once the set + // grows large enough for hashing to win. if s.valuesByKey == nil { - s.valuesByKey = make(map[T]struct{}) + if len(s.values) <= orderedSetMapThreshold { + return + } + s.valuesByKey = make(map[T]struct{}, len(s.values)) + for _, v := range s.values[:len(s.values)-1] { + s.valuesByKey[v] = struct{}{} + } } s.valuesByKey[value] = struct{}{} - s.values = append(s.values, value) } func getContainingFunctionOrClassStaticBlock(node *ast.Node) *ast.Node { From cd961a7dd5b9fd9ed20cf195140cc2369812bda3 Mon Sep 17 00:00:00 2001 From: Marius Schulz Date: Thu, 11 Jun 2026 18:01:10 +0100 Subject: [PATCH 02/49] Several performance optimizations for the scanner's hot path (#4238) --- internal/scanner/scanner.go | 172 +++++++++++++++++++++++------------- 1 file changed, 113 insertions(+), 59 deletions(-) diff --git a/internal/scanner/scanner.go b/internal/scanner/scanner.go index 92cff16279d..d0f52c900dd 100644 --- a/internal/scanner/scanner.go +++ b/internal/scanner/scanner.go @@ -447,6 +447,14 @@ func (s *Scanner) charAt(offset int) rune { } func (s *Scanner) charAndSize() (rune, int) { + // Fast path: a single ASCII byte. The vast majority of source bytes are + // ASCII; handling them here avoids constructing a string slice header and + // calling the non-inlined utf8.DecodeRuneInString on every byte. + if s.pos < s.end { + if b := s.text[s.pos]; b < utf8.RuneSelf { + return rune(b), 1 + } + } r, size := utf8.DecodeRuneInString(s.text[s.pos:]) if size > 1 { s.containsNonASCII = true @@ -454,12 +462,28 @@ func (s *Scanner) charAndSize() (rune, int) { return r, size } +// scanASCIIWhile advances s.pos over the longest run of ASCII bytes for which +// pred returns true. It stops at end-of-text, the first non-ASCII byte, or the +// first byte where pred is false. +func (s *Scanner) scanASCIIWhile(pred func(byte) bool) { + text := s.text[s.pos:s.end] + i := 0 + for i < len(text) { + b := text[i] + if b >= utf8.RuneSelf || !pred(b) { + break + } + i++ + } + s.pos += i +} + func (s *Scanner) Scan() ast.Kind { s.fullStartPos = s.pos s.tokenFlags = ast.TokenFlagsNone for { - s.tokenStart = s.pos ch := s.char() + s.tokenStart = s.pos switch ch { case '\t', '\v', '\f', ' ': @@ -479,6 +503,9 @@ func (s *Scanner) Scan() ast.Kind { s.tokenFlags |= ast.TokenFlagsPrecedingLineBreak if s.skipTrivia { s.pos++ + s.scanASCIIWhile(func(b byte) bool { + return b == ' ' || (b >= '\t' && b <= '\r') + }) continue } if ch == '\r' && s.charAt(1) == '\n' { @@ -514,7 +541,8 @@ func (s *Scanner) Scan() ast.Kind { s.token = ast.KindPercentToken } case '&': - if s.charAt(1) == '&' { + next := s.charAt(1) + if next == '&' { if s.charAt(2) == '=' { s.pos += 3 s.token = ast.KindAmpersandAmpersandEqualsToken @@ -522,7 +550,7 @@ func (s *Scanner) Scan() ast.Kind { s.pos += 2 s.token = ast.KindAmpersandAmpersandToken } - } else if s.charAt(1) == '=' { + } else if next == '=' { s.pos += 2 s.token = ast.KindAmpersandEqualsToken } else { @@ -536,10 +564,11 @@ func (s *Scanner) Scan() ast.Kind { s.pos++ s.token = ast.KindCloseParenToken case '*': - if s.charAt(1) == '=' { + next := s.charAt(1) + if next == '=' { s.pos += 2 s.token = ast.KindAsteriskEqualsToken - } else if s.charAt(1) == '*' { + } else if next == '*' { if s.charAt(2) == '=' { s.pos += 3 s.token = ast.KindAsteriskAsteriskEqualsToken @@ -558,10 +587,11 @@ func (s *Scanner) Scan() ast.Kind { s.token = ast.KindAsteriskToken } case '+': - if s.charAt(1) == '=' { + next := s.charAt(1) + if next == '=' { s.pos += 2 s.token = ast.KindPlusEqualsToken - } else if s.charAt(1) == '+' { + } else if next == '+' { s.pos += 2 s.token = ast.KindPlusPlusToken } else { @@ -572,10 +602,11 @@ func (s *Scanner) Scan() ast.Kind { s.pos++ s.token = ast.KindCommaToken case '-': - if s.charAt(1) == '=' { + next := s.charAt(1) + if next == '=' { s.pos += 2 s.token = ast.KindMinusEqualsToken - } else if s.charAt(1) == '-' { + } else if next == '-' { s.pos += 2 s.token = ast.KindMinusMinusToken } else { @@ -583,9 +614,10 @@ func (s *Scanner) Scan() ast.Kind { s.token = ast.KindMinusToken } case '.': - if stringutil.IsDigit(s.charAt(1)) { + next := s.charAt(1) + if stringutil.IsDigit(next) { s.token = s.scanNumber() - } else if s.charAt(1) == '.' && s.charAt(2) == '.' { + } else if next == '.' && s.charAt(2) == '.' { s.pos += 3 s.token = ast.KindDotDotDotToken } else { @@ -598,6 +630,9 @@ func (s *Scanner) Scan() ast.Kind { s.pos += 2 for { + s.scanASCIIWhile(func(b byte) bool { + return b != '\n' && b != '\r' + }) ch1, size := s.charAndSize() if size == 0 || stringutil.IsLineBreak(ch1) { break @@ -621,6 +656,9 @@ func (s *Scanner) Scan() ast.Kind { commentClosed := false lastLineStart := s.tokenStart for { + s.scanASCIIWhile(func(b byte) bool { + return b != '*' && b != '\n' && b != '\r' + }) ch1, size := s.charAndSize() if size == 0 { break @@ -729,7 +767,7 @@ func (s *Scanner) Scan() ast.Kind { s.pos++ s.token = ast.KindSemicolonToken case '<': - if isConflictMarkerTrivia(s.text, s.pos) { + if s.charAt(1) == '<' && isConflictMarkerTrivia(s.text, s.pos) { s.pos = scanConflictMarkerTrivia(s.text, s.pos, s.errorAt) if s.skipTrivia { continue @@ -757,7 +795,7 @@ func (s *Scanner) Scan() ast.Kind { s.token = ast.KindLessThanToken } case '=': - if isConflictMarkerTrivia(s.text, s.pos) { + if s.charAt(1) == '=' && isConflictMarkerTrivia(s.text, s.pos) { s.pos = scanConflictMarkerTrivia(s.text, s.pos, s.errorAt) if s.skipTrivia { continue @@ -782,7 +820,7 @@ func (s *Scanner) Scan() ast.Kind { s.token = ast.KindEqualsToken } case '>': - if isConflictMarkerTrivia(s.text, s.pos) { + if s.charAt(1) == '>' && isConflictMarkerTrivia(s.text, s.pos) { s.pos = scanConflictMarkerTrivia(s.text, s.pos, s.errorAt) if s.skipTrivia { continue @@ -827,7 +865,7 @@ func (s *Scanner) Scan() ast.Kind { s.pos++ s.token = ast.KindOpenBraceToken case '|': - if isConflictMarkerTrivia(s.text, s.pos) { + if s.charAt(1) == '|' && isConflictMarkerTrivia(s.text, s.pos) { s.pos = scanConflictMarkerTrivia(s.text, s.pos, s.errorAt) if s.skipTrivia { continue @@ -994,29 +1032,33 @@ func (s *Scanner) ReScanLessThanToken() ast.Kind { func (s *Scanner) ReScanGreaterThanToken() ast.Kind { if s.token == ast.KindGreaterThanToken { - s.pos = s.tokenStart + 1 - if s.char() == '>' { - if s.charAt(1) == '>' { - if s.charAt(2) == '=' { - s.pos += 3 - s.token = ast.KindGreaterThanGreaterThanGreaterThanEqualsToken - } else { - s.pos += 2 - s.token = ast.KindGreaterThanGreaterThanGreaterThanToken - } - } else if s.charAt(1) == '=' { - s.pos += 2 - s.token = ast.KindGreaterThanGreaterThanEqualsToken + s.reScanGreaterThanTokenInner() + } + return s.token +} + +func (s *Scanner) reScanGreaterThanTokenInner() { + s.pos = s.tokenStart + 1 + if s.char() == '>' { + if s.charAt(1) == '>' { + if s.charAt(2) == '=' { + s.pos += 3 + s.token = ast.KindGreaterThanGreaterThanGreaterThanEqualsToken } else { - s.pos++ - s.token = ast.KindGreaterThanGreaterThanToken + s.pos += 2 + s.token = ast.KindGreaterThanGreaterThanGreaterThanToken } - } else if s.char() == '=' { + } else if s.charAt(1) == '=' { + s.pos += 2 + s.token = ast.KindGreaterThanGreaterThanEqualsToken + } else { s.pos++ - s.token = ast.KindGreaterThanEqualsToken + s.token = ast.KindGreaterThanGreaterThanToken } + } else if s.char() == '=' { + s.pos++ + s.token = ast.KindGreaterThanEqualsToken } - return s.token } func (s *Scanner) ReScanTemplateToken(isTaggedTemplate bool) ast.Kind { @@ -1500,13 +1542,11 @@ func (s *Scanner) scanIdentifier(prefixLength int) bool { ch := s.char() // Fast path for simple ASCII identifiers if stringutil.IsASCIILetter(ch) || ch == '_' || ch == '$' { - for { - s.pos++ - ch = s.char() - if !(isWordCharacter(ch) || ch == '$') { - break - } - } + s.pos++ + s.scanASCIIWhile(func(b byte) bool { + return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') || b == '_' || b == '$' + }) + ch = s.char() if ch < utf8.RuneSelf && ch != '\\' { s.tokenValue = s.text[start:s.pos] return true @@ -1562,14 +1602,15 @@ func (s *Scanner) scanString(jsxAttributeString bool) string { } s.pos++ // Fast path for simple strings without escape sequences. - strLen := strings.IndexRune(s.text[s.pos:], quote) + strLen := strings.IndexByte(s.text[s.pos:], byte(quote)) if strLen == 0 { s.pos++ return "" } if strLen > 0 { str := s.text[s.pos : s.pos+strLen] - if !jsxAttributeString && !strings.ContainsAny(str, "\r\n\\") { + if jsxAttributeString || + strings.IndexByte(str, '\\') < 0 && strings.IndexByte(str, '\r') < 0 && strings.IndexByte(str, '\n') < 0 { s.pos += strLen + 1 return str } @@ -1613,6 +1654,9 @@ func (s *Scanner) scanTemplateAndSetTokenValue(shouldEmitInvalidEscapeError bool parts := make([]string, 0, 4) var token ast.Kind for { + s.scanASCIIWhile(func(b byte) bool { + return b != '`' && b != '$' && b != '\\' && b != '\r' + }) ch := s.char() if ch < 0 || ch == '`' { parts = append(parts, s.text[start:s.pos]) @@ -2016,6 +2060,14 @@ func (s *Scanner) scanNumberFragment() string { isPreviousTokenSeparator := false var result strings.Builder for { + before := s.pos + s.scanASCIIWhile(func(b byte) bool { + return b >= '0' && b <= '9' + }) + if s.pos > before { + allowSeparator = true + isPreviousTokenSeparator = false + } ch := s.char() if ch == '_' { s.tokenFlags |= ast.TokenFlagsContainsSeparator @@ -2035,18 +2087,15 @@ func (s *Scanner) scanNumberFragment() string { start = s.pos continue } - if stringutil.IsDigit(ch) { - allowSeparator = true - isPreviousTokenSeparator = false - s.pos++ - continue - } break } if isPreviousTokenSeparator { s.tokenFlags |= ast.TokenFlagsContainsInvalidSeparator s.errorAt(diagnostics.Numeric_separators_are_not_allowed_here, s.pos-1, 1) } + if result.Len() == 0 { + return s.text[start:s.pos] + } result.WriteString(s.text[start:s.pos]) return result.String() } @@ -2375,12 +2424,20 @@ func isConflictMarkerTrivia(text string, pos int) bool { panic("pos < 0") } + // Fast reject: a conflict marker is the same byte repeated seven times. If the + // second byte differs (the overwhelmingly common case for `<`, `>`, `=`, `|` + // tokens), it cannot be a marker, so skip the line-start check entirely. + if pos+1 >= len(text) || text[pos+1] != text[pos] { + return false + } + // Conflict markers must be at the start of a line. - var prev rune - if pos >= 2 { - prev, _ = utf8.DecodeLastRuneInString(text[:pos-2]) + atLineStart := pos == 0 || stringutil.IsLineBreak(rune(text[pos-1])) + if !atLineStart && pos >= 2 { + prev, _ := utf8.DecodeLastRuneInString(text[:pos-2]) + atLineStart = stringutil.IsLineBreak(prev) } - if pos == 0 || stringutil.IsLineBreak(prev) || pos >= 1 && stringutil.IsLineBreak(rune(text[pos-1])) { + if atLineStart { ch := text[pos] if (pos + mergeConflictMarkerLength) < len(text) { @@ -2831,13 +2888,10 @@ func iterateCommentRanges(f *ast.NodeFactory, text string, pos int, trailing boo pos += s } } else { - for pos < len(text) { - c, s := utf8.DecodeRuneInString(text[pos:]) - if c == '*' && pos+1 < len(text) && text[pos+1] == '/' { - pos += 2 - break - } - pos += s + if i := strings.Index(text[pos:], "*/"); i >= 0 { + pos += i + 2 + } else { + pos = len(text) } } From c7f7cb8e220ae506c8a8a367a5b8ebc3bec39ac5 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 10:45:27 -0700 Subject: [PATCH 03/49] Fix false TS1006 for included tsconfig JSON imports (#4279) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/compiler/fileloader.go | 6 +- internal/execute/tsctests/tscbuild_test.go | 25 ++++++ ...nfig-json-can-be-imported-as-json-input.js | 78 +++++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 testdata/baselines/reference/tsbuild/commandLine/included-tsconfig-json-can-be-imported-as-json-input.js diff --git a/internal/compiler/fileloader.go b/internal/compiler/fileloader.go index f7ea2d9015f..f8d006e9258 100644 --- a/internal/compiler/fileloader.go +++ b/internal/compiler/fileloader.go @@ -206,7 +206,7 @@ func (p *fileLoader) addRootFileTask(fileName string, libFile *LibFile, includeR if p.opts.Config.ConfigFile != nil { containingFile = tspath.GetNormalizedAbsolutePath(p.opts.Config.ConfigFile.SourceFile.FileName(), currDir) } - resolvedFile, diagnostic := p.getSourceFileFromReference(absPath, fileName, containingFile) + resolvedFile, diagnostic := p.getSourceFileFromReference(absPath, fileName, containingFile, includeReason) rootTask := &parseTask{ normalizedFilePath: resolvedFile, libFile: libFile, @@ -388,6 +388,7 @@ func (p *fileLoader) getSourceFileFromReference( fileName string, referenceText string, containingFile string, + includeReason *FileIncludeReason, ) (string, *sourceFileFromReferenceDiagnostic) { options := p.opts.Config.CompilerOptions() allowNonTsExtensions := options.AllowNonTsExtensions.IsTrue() @@ -406,7 +407,7 @@ func (p *fileLoader) getSourceFileFromReference( return "", &sourceFileFromReferenceDiagnostic{message: diagnostics.File_0_not_found, args: []any{diagnosticFileName}} } - if tspath.GetCanonicalFileName(containingFile, p.opts.Host.FS().UseCaseSensitiveFileNames()) == canonicalFileName { + if includeReason.isReferencedFile() && tspath.GetCanonicalFileName(containingFile, p.opts.Host.FS().UseCaseSensitiveFileNames()) == canonicalFileName { return "", &sourceFileFromReferenceDiagnostic{message: diagnostics.A_file_cannot_have_a_reference_to_itself} } return fileName, nil @@ -450,6 +451,7 @@ func (p *fileLoader) resolveTripleslashPathReference(moduleName string, containi normalizedFileName, moduleName, containingFile, + includeReason, ) if diagnostic != nil { return nil, &processingDiagnostic{ diff --git a/internal/execute/tsctests/tscbuild_test.go b/internal/execute/tsctests/tscbuild_test.go index 21f3a5e6dc1..1381b46d423 100644 --- a/internal/execute/tsctests/tscbuild_test.go +++ b/internal/execute/tsctests/tscbuild_test.go @@ -144,6 +144,31 @@ func TestBuildCommandLine(t *testing.T) { } testCases := slices.Concat( []*tscInput{ + { + subScenario: "included tsconfig json can be imported as json input", + files: FileMap{ + "/home/src/workspaces/project/index.ts": `import tsconfig from "./tsconfig.json" with { type: "json" }; +declare global { + interface ImportAttributes { + type: "json"; + } +} +console.log(tsconfig);`, + "/home/src/workspaces/project/tsconfig.json": stringtestutil.Dedent(` + { + "compilerOptions": { + "module": "preserve", + "moduleResolution": "bundler", + "noEmit": true, + "resolveJsonModule": true, + "strict": true, + "target": "esnext" + }, + "include": ["index.ts", "tsconfig.json"] + }`), + }, + commandLineArgs: []string{"--build"}, + }, { subScenario: "help", files: FileMap{}, diff --git a/testdata/baselines/reference/tsbuild/commandLine/included-tsconfig-json-can-be-imported-as-json-input.js b/testdata/baselines/reference/tsbuild/commandLine/included-tsconfig-json-can-be-imported-as-json-input.js new file mode 100644 index 00000000000..fd18b4ad684 --- /dev/null +++ b/testdata/baselines/reference/tsbuild/commandLine/included-tsconfig-json-can-be-imported-as-json-input.js @@ -0,0 +1,78 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +import tsconfig from "./tsconfig.json" with { type: "json" }; +declare global { + interface ImportAttributes { + type: "json"; + } +} +console.log(tsconfig); +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ + "compilerOptions": { + "module": "preserve", + "moduleResolution": "bundler", + "noEmit": true, + "resolveJsonModule": true, + "strict": true, + "target": "esnext" + }, + "include": ["index.ts", "tsconfig.json"] +} + +tsgo --build +ExitStatus:: Success +Output:: +//// [/home/src/tslibs/TS/Lib/lib.esnext.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; +//// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *new* +{"version":"FakeTSVersion","root":["./index.ts","./tsconfig.json"]} +//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *new* +{ + "version": "FakeTSVersion", + "root": [ + { + "files": [ + "./index.ts" + ], + "original": "./index.ts" + }, + { + "files": [ + "./tsconfig.json" + ], + "original": "./tsconfig.json" + } + ], + "size": 67 +} + +tsconfig.json:: +SemanticDiagnostics:: +*refresh* /home/src/tslibs/TS/Lib/lib.esnext.full.d.ts +*refresh* /home/src/workspaces/project/tsconfig.json +*refresh* /home/src/workspaces/project/index.ts +Signatures:: From 6038f11762fda4016f2916c5a16eb999ef844b9e Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 11 Jun 2026 10:54:52 -0700 Subject: [PATCH 04/49] Walk class children to find this property assignments beyond top level (#4264) --- internal/checker/emitresolver.go | 27 +++ internal/printer/emitresolver.go | 1 + .../transformers/declarations/transform.go | 168 +++++++++++------- .../conformance/controlFlowJSClassProperty.js | 1 + .../jsDeclarationEmitDoesNotRenameImport.js | 1 + ...DeclarationEmitDoesNotRenameImport.js.diff | 6 +- 6 files changed, 137 insertions(+), 67 deletions(-) diff --git a/internal/checker/emitresolver.go b/internal/checker/emitresolver.go index ac1f9d945e7..f99b11061f2 100644 --- a/internal/checker/emitresolver.go +++ b/internal/checker/emitresolver.go @@ -1259,3 +1259,30 @@ func (r *EmitResolver) TryJSTypeNodeToTypeNode(emitContext *printer.EmitContext, requestNodeBuilder := NewNodeBuilder(r.checker, emitContext) // TODO: cache per-context return requestNodeBuilder.TryJSTypeNodeToTypeNode(typeNode, enclosingDeclaration, flags, internalFlags, tracker) } + +func (r *EmitResolver) GetBaseDeclarationsForPropertyDeclaration(node *ast.Node) []*ast.Node { + if node == nil { + return nil + } + + r.checkerMu.Lock() + defer r.checkerMu.Unlock() + + s := r.checker.getSymbolOfDeclaration(node) + if s == nil || s.Parent == nil { + return nil + } + parentType := r.checker.getDeclaredTypeOfSymbol(s.Parent) + if parentType == nil { + return nil + } + bases := r.checker.getBaseTypes(parentType) + for _, b := range bases { + baseProp := r.checker.getPropertyOfObjectType(b, s.Name) + if baseProp != nil { + return baseProp.Declarations + // TODO: return base declarations from all base types if any callers actually look at the list + } + } + return nil +} diff --git a/internal/printer/emitresolver.go b/internal/printer/emitresolver.go index 4617c069b8b..285c424bb89 100644 --- a/internal/printer/emitresolver.go +++ b/internal/printer/emitresolver.go @@ -110,6 +110,7 @@ type EmitResolver interface { GetEnumMemberValue(node *ast.Node) evaluator.Result IsLateBound(node *ast.Node) bool IsOptionalParameter(node *ast.Node) bool + GetBaseDeclarationsForPropertyDeclaration(node *ast.Node) []*ast.Node // isolatedDeclarations-specific declaration emit GetPropertiesOfContainerFunction(node *ast.Node) []*ast.Symbol diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go index b1782520568..02a133459b6 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -62,12 +62,15 @@ type DeclarationTransformer struct { lateStatementReplacementMap map[ast.NodeId]*ast.Node expandoHosts map[ast.NodeId]*ast.Node // store the result of transforming expando hosts so they can be inserted later if the host is actually referenced expandoMembers map[ast.NodeId][]*ast.Node // store any found expando _members_ after transforming them so *if* the host is referenced, they can be emitted alongside it + seenProperties collections.Set[*ast.Node] + thisPropertyAssignmentsCollected []*ast.Node rawReferencedFiles []ReferencedFilePair rawTypeReferenceDirectives []*ast.FileReference rawLibReferenceDirectives []*ast.FileReference bindingNameVisitor *ast.NodeVisitor expressionVisitor *ast.NodeVisitor exportStrippingVisitor *ast.NodeVisitor + thisPropertyVisitor *ast.NodeVisitor } // TODO: Convert to transformers.TransformerFactory signature to allow more automatic composition with other transforms @@ -104,6 +107,7 @@ func NewDeclarationTransformer(host DeclarationEmitHost, context *printer.EmitCo tx.bindingNameVisitor = tx.EmitContext().NewNodeVisitor(tx.visitBindingName) tx.expressionVisitor = tx.EmitContext().NewNodeVisitor(tx.visitNestedExpression) tx.exportStrippingVisitor = tx.EmitContext().NewNodeVisitor(tx.stripExportModifiers) + tx.thisPropertyVisitor = tx.EmitContext().NewNodeVisitor(tx.visitThisPropertyAssignments) return tx } @@ -1685,10 +1689,98 @@ func (tx *DeclarationTransformer) transformClassDeclaration(input *ast.ClassDecl ) } +func (tx *DeclarationTransformer) visitThisPropertyAssignments(node *ast.Node) *ast.Node { + var thisTarget *ast.Node + isStatic := false + thisContainer := ast.GetThisContainer(node, false, false) + thisTarget = thisContainer.Parent + if thisTarget == nil { + return nil // thisContainer was source file, can't have expando-this + } + if ast.HasStaticModifier(thisContainer) || ast.IsClassStaticBlockDeclaration(thisContainer) { + isStatic = true + } + if thisTarget != tx.enclosingDeclaration { + return nil // stop searching within new `this` contexts + } +caseBlock: + switch ast.GetAssignmentDeclarationKind(node) { + case ast.JSDeclarationKindThisProperty: + name := ast.GetNameOfDeclaration(node) + base := tx.resolver.GetReferencedMemberValueDeclaration(node) + if base == nil || tx.seenProperties.Has(base) { + break + } + tx.seenProperties.Add(base) + + // problem: this prop might be overriding a prop from a base type. The checker has special bails for override compat comparisons for binary expression properties, + // but what we transform to won't - so we either need to match the base type (for example, if it's a getter/setter) or emit nothing + // See `checkKindsOfPropertyMemberOverrides` in the checker for what we're trying to satisfy here + if thisTarget.ClassLikeData().HeritageClauses != nil && len(thisTarget.ClassLikeData().HeritageClauses.Nodes) > 0 && !isClassExtendingNull(thisTarget) { + // there is a base type any assignments might be "from" + tx.tracker.ReportInferenceFallback(thisTarget) // Add an isolated declarations error on this class - we can't know how to transform this prop into an assignment without referring to type information + decls := tx.resolver.GetBaseDeclarationsForPropertyDeclaration(node) + if len(decls) > 0 { + break caseBlock // property lightly overrides a property in a base type - skip it + // TODO: If the property has an explicit `@type` annotation, we should probably emit it (maybe with an `override` modifier) instead of skipping it + } + } + + var mods *ast.ModifierList + if isStatic { + mods = tx.Factory().NewModifierList([]*ast.Node{tx.Factory().NewModifier(ast.KindStaticKeyword)}) + } + if ast.HasDynamicName(node) { + if !transformers.IsSimpleInlineableExpression(name) { + break // Member either becomes an index signature or is a reassignment + } + tx.checkName(node) + name = tx.Factory().NewComputedPropertyName(name) // Convert `this[foo] = expr` to `[foo]: Type` + } + if ast.GetTextOfPropertyName(name) == "constructor" { + break // `constructor` is a builtin class member, not allowed to redeclare it + } + if ast.IsIdentifier(name) && !scanner.IsIdentifierText(name.Text(), core.LanguageVariantStandard) { + name = tx.Factory().NewStringLiteralFromNode(name) + } + prop := tx.Factory().NewPropertyDeclaration( + mods, + name, + nil, + tx.ensureType(node, false), + nil, + ) + if ast.IsExpressionStatement(node.Parent) { + tx.preserveJsDoc(prop, node.Parent) + } + tx.thisPropertyAssignmentsCollected = append(tx.thisPropertyAssignmentsCollected, prop) + } + return tx.thisPropertyVisitor.VisitEachChild(node) +} + +func isClassExtendingNull(node *ast.Node) bool { + if node == nil { + return false + } + heritage := node.ClassLikeData().HeritageClauses + if heritage == nil { + return false + } + if len(heritage.Nodes) > 1 || len(heritage.Nodes) == 0 { + return false + } + for _, expA := range heritage.Nodes[0].AsHeritageClause().Types.Nodes { + expr := expA.AsExpressionWithTypeArguments().Expression + if expr != nil && expr.Kind == ast.KindNullKeyword { + return true + } + } + return false +} + // collectThisPropertyAssignments finds `this.x = expr` assignments in constructors, methods, and static blocks // of JS classes and synthesizes PropertyDeclaration nodes for each unique property name. func (tx *DeclarationTransformer) collectThisPropertyAssignments(input *ast.ClassDeclaration) []*ast.Node { - var result []*ast.Node seen := collections.Set[*ast.Node]{} // Pre-populate seen with existing direct member nodes to avoid duplicates for _, member := range input.Members.Nodes { @@ -1696,73 +1788,17 @@ func (tx *DeclarationTransformer) collectThisPropertyAssignments(input *ast.Clas seen.Add(member) } } - for _, member := range input.Members.Nodes { - isStatic := false - var body *ast.Node - switch member.Kind { - case ast.KindConstructor: - body = member.AsConstructorDeclaration().Body - case ast.KindMethodDeclaration: - body = member.AsMethodDeclaration().Body - if ast.HasStaticModifier(member) { - isStatic = true - } - case ast.KindClassStaticBlockDeclaration: - body = member.AsClassStaticBlockDeclaration().Body - isStatic = true - default: - continue - } - if body == nil { - continue - } - for _, stmt := range body.AsBlock().Statements.Nodes { - if stmt.Kind != ast.KindExpressionStatement { - continue - } - expr := stmt.Expression() - if expr == nil || expr.Kind != ast.KindBinaryExpression { - continue - } - if ast.GetAssignmentDeclarationKind(expr) != ast.JSDeclarationKindThisProperty { - continue - } - name := ast.GetNameOfDeclaration(expr) - base := tx.resolver.GetReferencedMemberValueDeclaration(expr) - if base == nil || seen.Has(base) { - continue - } - seen.Add(base) + tx.seenProperties = seen + defer tx.seenProperties.Clear() + tx.thisPropertyAssignmentsCollected = []*ast.Node{} + defer func() { + tx.thisPropertyAssignmentsCollected = nil + }() - var mods *ast.ModifierList - if isStatic { - mods = tx.Factory().NewModifierList([]*ast.Node{tx.Factory().NewModifier(ast.KindStaticKeyword)}) - } - if ast.HasDynamicName(expr) { - if !transformers.IsSimpleInlineableExpression(name) { - continue // Member either becomes an index signature or is a reassignment - } - tx.checkName(expr) - name = tx.Factory().NewComputedPropertyName(name) // Convert `this[foo] = expr` to `[foo]: Type` - } - if ast.GetTextOfPropertyName(name) == "constructor" { - continue // `constructor` is a builtin class member, not allowed to redeclare it - } - if ast.IsIdentifier(name) && !scanner.IsIdentifierText(name.Text(), core.LanguageVariantStandard) { - name = tx.Factory().NewStringLiteralFromNode(name) - } - prop := tx.Factory().NewPropertyDeclaration( - mods, - name, - nil, - tx.ensureType(expr, false), - nil, - ) - tx.preserveJsDoc(prop, stmt) - result = append(result, prop) - } + for _, n := range input.Members.Nodes { + tx.thisPropertyVisitor.VisitEachChild(n) } - return result + return tx.thisPropertyAssignmentsCollected } func (tx *DeclarationTransformer) walkBindingPattern(pattern *ast.BindingPattern, param *ast.Node) []*ast.Node { diff --git a/testdata/baselines/reference/conformance/controlFlowJSClassProperty.js b/testdata/baselines/reference/conformance/controlFlowJSClassProperty.js index c5b2c3103a2..857a0b5fcd5 100644 --- a/testdata/baselines/reference/conformance/controlFlowJSClassProperty.js +++ b/testdata/baselines/reference/conformance/controlFlowJSClassProperty.js @@ -35,6 +35,7 @@ c.position; //// [controlFlowJSClassProperty.d.ts] export declare class C { + position: [number, number] | undefined; name: string; /** * @param {[number, number] | undefined} position diff --git a/testdata/baselines/reference/submodule/compiler/jsDeclarationEmitDoesNotRenameImport.js b/testdata/baselines/reference/submodule/compiler/jsDeclarationEmitDoesNotRenameImport.js index 18864134524..5f0cf191f36 100644 --- a/testdata/baselines/reference/submodule/compiler/jsDeclarationEmitDoesNotRenameImport.js +++ b/testdata/baselines/reference/submodule/compiler/jsDeclarationEmitDoesNotRenameImport.js @@ -53,6 +53,7 @@ export type Options = { * @property {typeof import("./Test.js").default} [test] */ declare class X extends Test { + test: import("./Test.js").default | undefined; /** * @param {Options} options */ diff --git a/testdata/baselines/reference/submoduleAccepted/compiler/jsDeclarationEmitDoesNotRenameImport.js.diff b/testdata/baselines/reference/submoduleAccepted/compiler/jsDeclarationEmitDoesNotRenameImport.js.diff index 59880c28168..1df706b5486 100644 --- a/testdata/baselines/reference/submoduleAccepted/compiler/jsDeclarationEmitDoesNotRenameImport.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/compiler/jsDeclarationEmitDoesNotRenameImport.js.diff @@ -24,7 +24,11 @@ }; /** * @typedef {Object} Options -@@= skipped -23, +23 lines =@@ + * @property {typeof import("./Test.js").default} [test] + */ + declare class X extends Test { ++ test: import("./Test.js").default | undefined; + /** * @param {Options} options */ constructor(options: Options); From 0a1ff41b0ddb6c385e31b22448eb32d4d25ff047 Mon Sep 17 00:00:00 2001 From: Isabel Duan Date: Thu, 11 Jun 2026 11:07:29 -0700 Subject: [PATCH 05/49] add localization for extension (#3834) --- .dprint.jsonc | 1 + .github/workflows/ci.yml | 3 + Herebyfile.mjs | 8 ++ _extension/.gitignore | 2 + _extension/l10n/bundle.l10n.json | 72 ++++++++++++++++ _extension/package.json | 65 ++++++++------- _extension/package.nls.json | 26 ++++++ _extension/src/client.ts | 24 +++--- _extension/src/commands.ts | 2 +- _extension/src/extension.ts | 21 ++--- .../src/languageFeatures/sourceDefinition.ts | 8 +- _extension/src/projectStatus.ts | 12 +-- _extension/src/session.ts | 83 +++++++++---------- _extension/src/statusBar.ts | 8 +- 14 files changed, 226 insertions(+), 109 deletions(-) create mode 100644 _extension/l10n/bundle.l10n.json create mode 100644 _extension/package.nls.json diff --git a/.dprint.jsonc b/.dprint.jsonc index fc89d71e37e..97a7f142a21 100644 --- a/.dprint.jsonc +++ b/.dprint.jsonc @@ -46,6 +46,7 @@ "**/node_modules", "**/*-lock.json", "**/testdata", + "_extension/l10n/**", "_submodules/**", "internal/bundled/libs/**", "internal/lsp/lsproto/_generate/*.json", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ed14ab56d0..ed8ffbc41ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -286,6 +286,9 @@ jobs: - run: node --experimental-strip-types ./internal/lsp/lsproto/_generate/fetchModel.mts - run: node --experimental-strip-types ./internal/lsp/lsproto/_generate/generate.mts + - name: generate files for extension + run: npx hereby generate:extension + - name: Regenerate fourslash tests and update failing test list run: | set -x diff --git a/Herebyfile.mjs b/Herebyfile.mjs index c867326f3a4..a8650f7d29f 100644 --- a/Herebyfile.mjs +++ b/Herebyfile.mjs @@ -296,6 +296,14 @@ export const generate = task({ }, }); +export const generateExtension = task({ + name: "generate:extension", + description: "Generates files in the extension", + run: async () => { + await $`npm run -w _extension generateLocBundle`; + }, +}); + // ── Enum generation from Go source ────────────────────────────── /** diff --git a/_extension/.gitignore b/_extension/.gitignore index 21c68a9bf68..846f69f46a4 100644 --- a/_extension/.gitignore +++ b/_extension/.gitignore @@ -1,3 +1,5 @@ *.vsix dist/ lib/ +package.nls.qps-ploc.json +l10n/bundle.l10n.qps-ploc.json diff --git a/_extension/l10n/bundle.l10n.json b/_extension/l10n/bundle.l10n.json new file mode 100644 index 00000000000..99d88aea82c --- /dev/null +++ b/_extension/l10n/bundle.l10n.json @@ -0,0 +1,72 @@ +{ + "TypeScript Native Preview": "TypeScript Native Preview", + "$(beaker) tsgo {0}": "$(beaker) tsgo {0}", + "TypeScript Native Preview Language Server": "TypeScript Native Preview Language Server", + "Show Menu": "Show Menu", + "Language server is not running.": "Language server is not running.", + "Garbage collection triggered": "Garbage collection triggered", + "Failed to run GC: {0}": "Failed to run GC: {0}", + "Heap profile saved to: {0}": "Heap profile saved to: {0}", + "Failed to save heap profile: {0}": "Failed to save heap profile: {0}", + "Allocation profile saved to: {0}": "Allocation profile saved to: {0}", + "Failed to save allocation profile: {0}": "Failed to save allocation profile: {0}", + "CPU profiling started. Profile will be saved to: {0}": "CPU profiling started. Profile will be saved to: {0}", + "Failed to start CPU profile: {0}": "Failed to start CPU profile: {0}", + "CPU profile saved to: {0}": "CPU profile saved to: {0}", + "Failed to stop CPU profile: {0}": "Failed to stop CPU profile: {0}", + "Copy": "Copy", + "API session initialized. Listening on: {0}": "API session initialized. Listening on: {0}", + "Failed to initialize API session: {0}": "Failed to initialize API session: {0}", + "$(refresh) Restart Server": "$(refresh) Restart Server", + "Restart the TypeScript Native Preview language server": "Restart the TypeScript Native Preview language server", + "$(output) Show Output": "$(output) Show Output", + "Show the TypeScript Native Preview output log": "Show the TypeScript Native Preview output log", + "$(report) Report Issue": "$(report) Report Issue", + "Report an issue with TypeScript Native Preview": "Report an issue with TypeScript Native Preview", + "$(versions) Select Version": "$(versions) Select Version", + "Choose between bundled and workspace versions": "Choose between bundled and workspace versions", + "$(stop-circle) Disable TypeScript Native Preview": "$(stop-circle) Disable TypeScript Native Preview", + "Switch back to the built-in TypeScript extension": "Switch back to the built-in TypeScript extension", + "Executable": "Executable", + "Executable path copied to clipboard.": "Executable path copied to clipboard.", + "PID": "PID", + "Server PID copied to clipboard.": "Server PID copied to clipboard.", + "{0} Commands": "{0} Commands", + "Use Bundled Version": "Use Bundled Version", + "Use Workspace Version": "Use Workspace Version", + "$(lock) Manage Workspace Trust to select a workspace version": "$(lock) Manage Workspace Trust to select a workspace version", + "Use Custom Version": "Use Custom Version", + "Select the TypeScript Native Preview version to use": "Select the TypeScript Native Preview version to use", + "Allow": "Allow", + "Dismiss": "Dismiss", + "Never in this Workspace": "Never in this Workspace", + "This workspace has a TypeScript Native Preview tsdk configured ({0}). Would you like to use it?": "This workspace has a TypeScript Native Preview tsdk configured ({0}). Would you like to use it?", + "Enter directory path for profile output": "Enter directory path for profile output", + "Directory path is required": "Directory path is required", + "Loading project info...": "Loading project info...", + "No tsconfig": "No tsconfig", + "No jsconfig": "No jsconfig", + "No opened folders": "No opened folders", + "File is not part of opened folders": "File is not part of opened folders", + "Open Config File": "Open Config File", + "TypeScript Native Preview Project Status": "TypeScript Native Preview Project Status", + "TypeScript Native Preview setting has changed. Restart extensions to apply changes.": "TypeScript Native Preview setting has changed. Restart extensions to apply changes.", + "Restart Extensions": "Restart Extensions", + "The built-in TypeScript extension is disabled. Sync launch.json with launch.template.json to reenable.": "The built-in TypeScript extension is disabled. Sync launch.json with launch.template.json to reenable.", + "OK": "OK", + "Enable Setting": "Enable Setting", + "TypeScript Native Preview is running in development mode with \"{0}\" set to false.": "TypeScript Native Preview is running in development mode with \"{0}\" set to false.", + "Ignore": "Ignore", + "TypeScript Native Preview is disabled. Select 'Enable TypeScript Native Preview (Experimental)' in the command palette to enable it.": "TypeScript Native Preview is disabled. Select 'Enable TypeScript Native Preview (Experimental)' in the command palette to enable it.", + "Error prompting to use workspace version: {0}": "Error prompting to use workspace version: {0}", + "Unexpected number of arguments.": "Unexpected number of arguments.", + "Starting language server...": "Starting language server...", + "Language client is not initialized": "Language client is not initialized", + "Restarting language server...": "Restarting language server...", + "Graceful shutdown failed, forcing restart: {0}": "Graceful shutdown failed, forcing restart: {0}", + "The typescript.native-preview-lsp server crashed {0} times in the last 3 minutes. The server will not be restarted. See the output for more information.": "The typescript.native-preview-lsp server crashed {0} times in the last 3 minutes. The server will not be restarted. See the output for more information.", + "Go to Source Definition failed. No editor is active.": "Go to Source Definition failed. No editor is active.", + "Go to Source Definition failed. Unsupported file type.": "Go to Source Definition failed. Unsupported file type.", + "Finding source definitions": "Finding source definitions", + "No source definitions found.": "No source definitions found." +} \ No newline at end of file diff --git a/_extension/package.json b/_extension/package.json index 344d0f3235f..b9ef5eb088d 100644 --- a/_extension/package.json +++ b/_extension/package.json @@ -3,11 +3,12 @@ "displayName": "TypeScript (Native Preview)", "publisher": "TypeScriptTeam", "author": "Microsoft Corp.", - "description": "Preview of the native TypeScript language server for Visual Studio Code.", + "description": "%description%", "icon": "logo.png", "private": true, "version": "0.0.0", "type": "commonjs", + "l10n": "./l10n", "repository": { "type": "git", "url": "https://github.com/microsoft/typescript-go" @@ -38,7 +39,7 @@ "typescript.native-preview.customConfigFileName": { "type": "string", "default": "", - "description": "Custom config file name to use e.g. 'tsconfig.all.json', before defaulting to tsconfig.json/jsconfig.json.", + "description": "%native-preview.trace.customConfigFileName.description%", "tags": ["experimental"] }, "typescript.native-preview.trace.server": { @@ -49,22 +50,22 @@ "verbose" ], "default": "verbose", - "description": "Trace TypeScript Go server communication.", + "description": "%native-preview.trace.server.description%", "tags": [ "experimental" ] }, "typescript.native-preview.pprofDir": { "type": "string", - "description": "Directory to write pprof profiles to.", + "description": "%native-preview.pprofDir.description%", "tags": [ "experimental" ] }, "typescript.native-preview.tsdk": { "type": "string", + "description": "%native-preview.tsserver.tsdk.description%", "scope": "window", - "description": "Path to the @typescript/native-preview package or tsgo binary directory. If not specified, the extension will look for it in the default location.", "tags": [ "experimental" ] @@ -75,7 +76,7 @@ "type": "string" }, "default": [], - "description": "Additional paths to tsgo binary directories or @typescript/native-preview packages that should appear in the version selector.", + "description": "%native-preview.additionalTsdkLocations.description%", "tags": [ "experimental" ] @@ -83,16 +84,16 @@ "typescript.native-preview.showDebugInfo": { "type": "boolean", "default": false, - "description": "Show debug information (PID, executable path) in the server menu.", + "description": "%native-preview.showDebugInfo.description%", "tags": [ "experimental" ] }, "typescript.native-preview.goMemLimit": { "type": "string", - "description": "Set GOMEMLIMIT for the language server (e.g., '2048MiB', '4GiB'). See https://pkg.go.dev/runtime#hdr-Environment_Variables for more information.", + "description": "%native-preview.goMemLimit.description%", "pattern": "^[0-9]+(([KMGT]i)?B)?$", - "patternErrorMessage": "Must be a valid memory limit (e.g., '2048MiB', '4GiB').", + "patternErrorMessage": "%native-preview.goMemLimit.error%", "tags": [ "experimental" ] @@ -112,86 +113,86 @@ "commands": [ { "command": "typescript.native-preview.enable", - "title": "Enable (Experimental)", + "title": "%native-preview.enable.title%", "enablement": "!typescript.native-preview.serverRunning", "category": "TypeScript Native Preview" }, { "command": "typescript.native-preview.disable", - "title": "Disable", + "title": "%native-preview.disable.title%", "enablement": "typescript.native-preview.serverRunning", "category": "TypeScript Native Preview" }, { "command": "typescript.native-preview.restart", - "title": "Restart Language Server", + "title": "%native-preview.restart.title%", "enablement": "typescript.native-preview.serverRunning", "category": "TypeScript Native Preview" }, { "command": "typescript.native-preview.output.focus", - "title": "Show Output", + "title": "%native-preview.output.focus.title%", "enablement": "typescript.native-preview.serverRunning", "category": "TypeScript Native Preview" }, { "command": "typescript.native-preview.reportIssue", - "title": "Report Issue", + "title": "%native-preview.reportIssue.title%", "enablement": "typescript.native-preview.serverRunning", "category": "TypeScript Native Preview" }, { "command": "typescript.native-preview.selectVersion", - "title": "Select TypeScript Version...", + "title": "%native-preview.selectVersion.title%", "enablement": "typescript.native-preview.serverRunning", "category": "TypeScript Native Preview" }, { "command": "typescript.native-preview.goToSourceDefinition", - "title": "Go to Source Definition", + "title": "%native-preview.goToSourceDefinition.title%", "enablement": "typescript.native-preview.serverRunning", "category": "TypeScript Native Preview" }, { - "title": "Show References of CodeLens", "command": "typescript.native-preview.codeLens.showLocations", + "title": "%native-preview.codeLens.showLocations.title%", "enablement": "false" }, { "command": "typescript.native-preview.dev.runGC", - "title": "TypeScript Native Preview: Run Garbage Collection", + "title": "%native-preview.dev.runGC.title%", "enablement": "typescript.native-preview.serverRunning", - "category": "Developer" + "category": "%developer%" }, { "command": "typescript.native-preview.dev.saveHeapProfile", - "title": "TypeScript Native Preview: Save Heap Profile", + "title": "%native-preview.dev.saveHeapProfile.title%", "enablement": "typescript.native-preview.serverRunning", - "category": "Developer" + "category": "%developer%" }, { "command": "typescript.native-preview.dev.saveAllocProfile", - "title": "TypeScript Native Preview: Save Allocation Profile", + "title": "%native-preview.dev.saveAllocProfile.title%", "enablement": "typescript.native-preview.serverRunning", - "category": "Developer" + "category": "%developer%" }, { "command": "typescript.native-preview.dev.startCPUProfile", - "title": "TypeScript Native Preview: Start CPU Profile", + "title": "%native-preview.dev.startCPUProfile.title%", "enablement": "typescript.native-preview.serverRunning && !typescript.native-preview.cpuProfileRunning", - "category": "Developer" + "category": "%developer%" }, { "command": "typescript.native-preview.dev.stopCPUProfile", - "title": "TypeScript Native Preview: Stop CPU Profile", + "title": "%native-preview.dev.stopCPUProfile.title%", "enablement": "typescript.native-preview.cpuProfileRunning", - "category": "Developer" + "category": "%developer%" }, { "command": "typescript.native-preview.initializeAPIConnection.ui", - "title": "Initialize API Connection", + "title": "%native-preview.initializeAPIConnection.ui.title%", "enablement": "typescript.native-preview.serverRunning", - "category": "Developer: TypeScript Native Preview" + "category": "%developer%" } ], "menus": { @@ -216,12 +217,16 @@ "lib", "LICENSE", "NOTICE.txt", - "logo.png" + "logo.png", + "l10n/bundle.l10n.json", + "package.nls.json" ], "scripts": { "build": "tsc && npm run bundle", "bundle": "esbuild src/extension.ts --bundle --external:vscode --platform=node --format=cjs --outfile=dist/extension.bundle.js --sourcemap", "watch": "npm run bundle -- --watch", + "generateLocTest": "npx @vscode/l10n-dev generate-pseudo -o ./l10n/ ./l10n/bundle.l10n.json ./package.nls.json", + "generateLocBundle": "npx @vscode/l10n-dev export --outDir ./l10n ./src", "//vscode:prepublish": "we do not use this hook; the Herebyfile runs bundle:release once instead", "bundle:release": "npm run bundle -- --minify" }, diff --git a/_extension/package.nls.json b/_extension/package.nls.json new file mode 100644 index 00000000000..434e5452234 --- /dev/null +++ b/_extension/package.nls.json @@ -0,0 +1,26 @@ +{ + "description": "Preview of the native TypeScript language server for Visual Studio Code.", + "native-preview.trace.customConfigFileName.description": "Custom config file name to use e.g. 'tsconfig.all.json', before defaulting to tsconfig.json/jsconfig.json.", + "native-preview.trace.server.description": "Trace TypeScript Go server communication.", + "native-preview.pprofDir.description": "Directory to write pprof profiles to.", + "native-preview.tsserver.tsdk.description": "Path to the @typescript/native-preview package or tsgo binary directory. If not specified, the extension will look for it in the default location.", + "native-preview.additionalTsdkLocations.description": "Additional paths to tsgo binary directories or @typescript/native-preview packages that should appear in the version selector.", + "native-preview.showDebugInfo.description": "Show debug information (PID, executable path) in the server menu.", + "native-preview.goMemLimit.description": "Set GOMEMLIMIT for the language server (e.g., '2048MiB', '4GiB'). See https://pkg.go.dev/runtime#hdr-Environment_Variables for more information.", + "native-preview.goMemLimit.error": "Must be a valid memory limit (e.g., '2048MiB', '4GiB').", + "native-preview.enable.title": "Enable (Experimental)", + "native-preview.disable.title": "Disable", + "native-preview.restart.title": "Restart Language Server", + "native-preview.output.focus.title": "Show Output", + "native-preview.reportIssue.title": "Report Issue", + "native-preview.selectVersion.title": "Select TypeScript Version...", + "native-preview.goToSourceDefinition.title": "Go to Source Definition", + "native-preview.codeLens.showLocations.title": "Show References of CodeLens", + "native-preview.dev.runGC.title": "TypeScript Native Preview: Run Garbage Collection", + "native-preview.dev.saveHeapProfile.title": "TypeScript Native Preview: Save Heap Profile", + "native-preview.dev.saveAllocProfile.title": "TypeScript Native Preview: Save Allocation Profile", + "native-preview.dev.startCPUProfile.title": "TypeScript Native Preview: Start CPU Profile", + "native-preview.dev.stopCPUProfile.title": "TypeScript Native Preview: Stop CPU Profile", + "native-preview.initializeAPIConnection.ui.title": "TypeScript Native Preview: Initialize API Connection", + "developer": "Developer" +} diff --git a/_extension/src/client.ts b/_extension/src/client.ts index df7c8f287ec..4db89dcf47f 100644 --- a/_extension/src/client.ts +++ b/_extension/src/client.ts @@ -208,7 +208,7 @@ export class Client implements vscode.Disposable { } satisfies StaticFeature, ); - this.outputChannel.appendLine(`Starting language server...`); + this.outputChannel.appendLine(vscode.l10n.t(`Starting language server...`)); await this.client.start(); this.isInitialized = true; this.initializedEventEmitter.fire(); @@ -276,7 +276,7 @@ export class Client implements vscode.Disposable { */ async initializeAPISession(pipe?: string): Promise<{ sessionId: string; pipe: string; }> { if (!this.client) { - throw new Error("Language client is not initialized"); + throw new Error(vscode.l10n.t("Language client is not initialized")); } return this.client.sendRequest<{ sessionId: string; pipe: string; }>("custom/initializeAPISession", { pipe }); } @@ -287,7 +287,7 @@ export class Client implements vscode.Disposable { */ async tryRestart(context: vscode.ExtensionContext): Promise { if (!this.client) { - return Promise.reject(new Error("Language client is not initialized")); + return Promise.reject(new Error(vscode.l10n.t("Language client is not initialized"))); } const exe = await getExe(context); if (exe.path !== this.exe?.path) { @@ -295,12 +295,12 @@ export class Client implements vscode.Disposable { } this.isInitialized = false; - this.outputChannel.appendLine(`Restarting language server...`); + this.outputChannel.appendLine(vscode.l10n.t("Restarting language server...")); try { await this.client.restart(); } catch (err) { - this.outputChannel.appendLine(`Graceful shutdown failed, forcing restart: ${err}`); + this.outputChannel.appendLine(vscode.l10n.t(`Graceful shutdown failed, forcing restart: {0}`, String(err))); await this.client.start(); } this.isInitialized = true; @@ -321,14 +321,14 @@ export class Client implements vscode.Disposable { async runGC(): Promise { if (!this.client) { - throw new Error("Language client is not initialized"); + throw new Error(vscode.l10n.t("Language client is not initialized")); } await this.client.sendRequest("custom/runGC"); } async saveHeapProfile(dir: string): Promise { if (!this.client) { - throw new Error("Language client is not initialized"); + throw new Error(vscode.l10n.t("Language client is not initialized")); } const result = await this.client.sendRequest<{ file: string; }>("custom/saveHeapProfile", { dir }); return result.file; @@ -336,7 +336,7 @@ export class Client implements vscode.Disposable { async saveAllocProfile(dir: string): Promise { if (!this.client) { - throw new Error("Language client is not initialized"); + throw new Error(vscode.l10n.t("Language client is not initialized")); } const result = await this.client.sendRequest<{ file: string; }>("custom/saveAllocProfile", { dir }); return result.file; @@ -344,14 +344,14 @@ export class Client implements vscode.Disposable { async startCPUProfile(dir: string): Promise { if (!this.client) { - throw new Error("Language client is not initialized"); + throw new Error(vscode.l10n.t("Language client is not initialized")); } await this.client.sendRequest("custom/startCPUProfile", { dir }); } async stopCPUProfile(): Promise { if (!this.client) { - throw new Error("Language client is not initialized"); + throw new Error(vscode.l10n.t("Language client is not initialized")); } const result = await this.client.sendRequest<{ file: string; }>("custom/stopCPUProfile"); return result.file; @@ -359,7 +359,7 @@ export class Client implements vscode.Disposable { async getProjectInfo(uri: string, token?: vscode.CancellationToken): Promise<{ configFilePath: string; }> { if (!this.client) { - throw new Error("Language client is not initialized"); + throw new Error(vscode.l10n.t("Language client is not initialized")); } return this.client.sendRequest<{ configFilePath: string; }>("custom/projectInfo", { textDocument: { uri }, @@ -473,7 +473,7 @@ class ReportingErrorHandler implements ErrorHandler { if (resultingAction === CloseAction.DoNotRestart) { return { action: resultingAction, - message: `The typescript.native-preview-lsp server crashed ${this.maxRestartCount + 1} times in the last 3 minutes. The server will not be restarted. See the output for more information.`, + message: vscode.l10n.t(`The typescript.native-preview-lsp server crashed {0} times in the last 3 minutes. The server will not be restarted. See the output for more information.`, String(this.maxRestartCount + 1)), }; } diff --git a/_extension/src/commands.ts b/_extension/src/commands.ts index 37554e9116b..fcd403e1d4a 100644 --- a/_extension/src/commands.ts +++ b/_extension/src/commands.ts @@ -67,7 +67,7 @@ export function registerCodeLensShowLocationsCommand(): vscode.Disposable { function showCodeLensLocations(...args: unknown[]): void { if (args.length !== 3) { - throw new Error("Unexpected number of arguments."); + throw new Error(vscode.l10n.t("Unexpected number of arguments.")); } const lspUri = args[0] as DocumentUri; diff --git a/_extension/src/extension.ts b/_extension/src/extension.ts index 706068344c0..42064bf3e2e 100644 --- a/_extension/src/extension.ts +++ b/_extension/src/extension.ts @@ -59,7 +59,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { if (needsExtHostRestartOnChange()) { - const selected = await vscode.window.showInformationMessage("TypeScript Native Preview setting has changed. Restart extensions to apply changes.", "Restart Extensions"); + const selected = await vscode.window.showInformationMessage(vscode.l10n.t("TypeScript Native Preview setting has changed. Restart extensions to apply changes."), vscode.l10n.t("Restart Extensions")); if (selected) { vscode.commands.executeCommand("workbench.action.restartExtensionHost"); } @@ -91,20 +91,21 @@ export async function activate(context: vscode.ExtensionContext): Promise { - if (selected === "Enable Setting") { + if (selected === enableSettingString) { vscode.commands.executeCommand("typescript.native-preview.enable"); } }); @@ -112,7 +113,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { - output.appendLine(`Error prompting to use workspace version: ${err}`); + output.appendLine(vscode.l10n.t(`Error prompting to use workspace version: {0}`, String(err))); }); function onLanguageServerInitialized(listener: () => void): vscode.Disposable { diff --git a/_extension/src/languageFeatures/sourceDefinition.ts b/_extension/src/languageFeatures/sourceDefinition.ts index d1b29417154..e719e1b2f44 100644 --- a/_extension/src/languageFeatures/sourceDefinition.ts +++ b/_extension/src/languageFeatures/sourceDefinition.ts @@ -25,20 +25,20 @@ export function registerSourceDefinitionFeature(client: LanguageClient): vscode. const disposable = vscode.commands.registerCommand(sourceDefinitionCommand, async () => { const activeEditor = vscode.window.activeTextEditor; if (!activeEditor) { - vscode.window.showErrorMessage("Go to Source Definition failed. No editor is active."); + vscode.window.showErrorMessage(vscode.l10n.t("Go to Source Definition failed. No editor is active.")); return; } const { document } = activeEditor; if (!["javascript", "javascriptreact", "typescript", "typescriptreact"].includes(document.languageId)) { - vscode.window.showErrorMessage("Go to Source Definition failed. Unsupported file type."); + vscode.window.showErrorMessage(vscode.l10n.t("Go to Source Definition failed. Unsupported file type.")); return; } const position = activeEditor.selection.active; await vscode.window.withProgress({ location: vscode.ProgressLocation.Window, - title: "Finding source definitions", + title: vscode.l10n.t("Finding source definitions"), }, async (_, token) => { let response: SourceDefinitionResponse; try { @@ -70,7 +70,7 @@ export function registerSourceDefinitionFeature(client: LanguageClient): vscode. position, locations, "goto", - "No source definitions found.", + vscode.l10n.t("No source definitions found."), ); }); }); diff --git a/_extension/src/projectStatus.ts b/_extension/src/projectStatus.ts index b0590ce4461..f47fc9f4625 100644 --- a/_extension/src/projectStatus.ts +++ b/_extension/src/projectStatus.ts @@ -115,7 +115,7 @@ export class ProjectStatus implements vscode.Disposable { case ProjectInfoState.Type.Pending: { const statusItem = this.ensureStatusItem(); statusItem.severity = vscode.LanguageStatusSeverity.Information; - statusItem.text = "Loading project info..."; + statusItem.text = vscode.l10n.t("Loading project info..."); statusItem.detail = undefined; statusItem.command = undefined; statusItem.busy = true; @@ -125,15 +125,15 @@ export class ProjectStatus implements vscode.Disposable { const currentLanguageId = this.activeEditorTracker.activeJsTsEditor?.document.languageId; const isTypeScript = currentLanguageId === "typescript" || currentLanguageId === "typescriptreact"; - const noConfigFileText = isTypeScript ? "No tsconfig" : "No jsconfig"; + const noConfigFileText = isTypeScript ? vscode.l10n.t("No tsconfig") : vscode.l10n.t("No jsconfig"); const rootPath = this.getWorkspaceRootForResource(this.state.resource); if (!rootPath) { if (this.statusItem) { this.statusItem.text = noConfigFileText; this.statusItem.detail = !vscode.workspace.workspaceFolders - ? "No opened folders" - : "File is not part of opened folders"; + ? vscode.l10n.t("No opened folders") + : vscode.l10n.t("File is not part of opened folders"); this.statusItem.busy = false; } return; @@ -148,7 +148,7 @@ export class ProjectStatus implements vscode.Disposable { statusItem.text = vscode.workspace.asRelativePath(this.state.configFile); statusItem.command = { command: "vscode.open", - title: "Open Config File", + title: vscode.l10n.t("Open Config File"), arguments: [vscode.Uri.file(this.state.configFile)], }; } @@ -164,7 +164,7 @@ export class ProjectStatus implements vscode.Disposable { private ensureStatusItem(): vscode.LanguageStatusItem { if (!this.statusItem) { this.statusItem = vscode.languages.createLanguageStatusItem("typescript.native-preview.projectStatus", jsTsLanguageModes); - this.statusItem.name = "TypeScript Native Preview Project Status"; + this.statusItem.name = vscode.l10n.t("TypeScript Native Preview Project Status"); } return this.statusItem; } diff --git a/_extension/src/session.ts b/_extension/src/session.ts index fb808b03098..fe4b40d3ffe 100644 --- a/_extension/src/session.ts +++ b/_extension/src/session.ts @@ -73,7 +73,7 @@ export class SessionManager implements vscode.Disposable { async initializeAPIConnection(pipe?: string): Promise { if (!this.currentSession) { - throw new Error("Language server is not running."); + throw new Error(vscode.l10n.t("Language server is not running.")); } const result = await this.currentSession.client.initializeAPISession(pipe); return result.pipe; @@ -168,10 +168,10 @@ class Session implements vscode.Disposable { this.disposables.push(vscode.commands.registerCommand("typescript.native-preview.dev.runGC", async () => { try { await this.client.runGC(); - vscode.window.showInformationMessage("Garbage collection triggered"); + vscode.window.showInformationMessage(vscode.l10n.t(`Garbage collection triggered`)); } catch (error) { - vscode.window.showErrorMessage(`Failed to run GC: ${error}`); + vscode.window.showErrorMessage(vscode.l10n.t(`Failed to run GC: {0}`, String(error))); } })); @@ -180,10 +180,10 @@ class Session implements vscode.Disposable { if (!dir) return; try { const file = await this.client.saveHeapProfile(dir); - vscode.window.showInformationMessage(`Heap profile saved to: ${file}`); + vscode.window.showInformationMessage(vscode.l10n.t(`Heap profile saved to: {0}`, file)); } catch (error) { - vscode.window.showErrorMessage(`Failed to save heap profile: ${error}`); + vscode.window.showErrorMessage(vscode.l10n.t(`Failed to save heap profile: {0}`, String(error))); } })); @@ -192,10 +192,10 @@ class Session implements vscode.Disposable { if (!dir) return; try { const file = await this.client.saveAllocProfile(dir); - vscode.window.showInformationMessage(`Allocation profile saved to: ${file}`); + vscode.window.showInformationMessage(vscode.l10n.t(`Allocation profile saved to: {0}`, file)); } catch (error) { - vscode.window.showErrorMessage(`Failed to save allocation profile: ${error}`); + vscode.window.showErrorMessage(vscode.l10n.t(`Failed to save allocation profile: {0}`, String(error))); } })); @@ -205,10 +205,10 @@ class Session implements vscode.Disposable { try { await this.client.startCPUProfile(dir); vscode.commands.executeCommand("setContext", "typescript.native-preview.cpuProfileRunning", true); - vscode.window.showInformationMessage(`CPU profiling started. Profile will be saved to: ${dir}`); + vscode.window.showInformationMessage(vscode.l10n.t(`CPU profiling started. Profile will be saved to: {0}`, dir)); } catch (error) { - vscode.window.showErrorMessage(`Failed to start CPU profile: ${error}`); + vscode.window.showErrorMessage(vscode.l10n.t(`Failed to start CPU profile: {0}`, String(error))); vscode.commands.executeCommand("setContext", "typescript.native-preview.cpuProfileRunning", false); } })); @@ -217,10 +217,10 @@ class Session implements vscode.Disposable { try { const file = await this.client.stopCPUProfile(); vscode.commands.executeCommand("setContext", "typescript.native-preview.cpuProfileRunning", false); - vscode.window.showInformationMessage(`CPU profile saved to: ${file}`); + vscode.window.showInformationMessage(vscode.l10n.t(`CPU profile saved to: {0}`, file)); } catch (error) { - vscode.window.showErrorMessage(`Failed to stop CPU profile: ${error}`); + vscode.window.showErrorMessage(vscode.l10n.t(`Failed to stop CPU profile: {0}`, String(error))); } })); @@ -232,14 +232,15 @@ class Session implements vscode.Disposable { this.disposables.push(vscode.commands.registerCommand("typescript.native-preview.initializeAPIConnection.ui", async () => { try { const result = await this.client.initializeAPISession(); - const copy = await vscode.window.showInformationMessage(`API session initialized. Listening on: ${result.pipe}`, "Copy"); - if (copy === "Copy") { + const copyString = vscode.l10n.t("Copy"); + const copy = await vscode.window.showInformationMessage(vscode.l10n.t(`API session initialized. Listening on: {0}`, result.pipe), copyString); + if (copy === copyString) { await vscode.env.clipboard.writeText(result.pipe); } } catch (error) { const message = error instanceof Error ? error.message : String(error); - vscode.window.showErrorMessage(`Failed to initialize API session: ${message}`); + vscode.window.showErrorMessage(vscode.l10n.t(`Failed to initialize API session: {0}`, message)); } })); } @@ -261,28 +262,28 @@ async function showCommands(client: Client): Promise { } const commands: CommandItem[] = [ { - label: "$(refresh) Restart Server", - description: "Restart the TypeScript Native Preview language server", + label: vscode.l10n.t("$(refresh) Restart Server"), + description: vscode.l10n.t("Restart the TypeScript Native Preview language server"), command: "typescript.native-preview.restart", }, { - label: "$(output) Show Output", - description: "Show the TypeScript Native Preview output log", + label: vscode.l10n.t("$(output) Show Output"), + description: vscode.l10n.t("Show the TypeScript Native Preview output log"), command: "typescript.native-preview.output.focus", }, { - label: "$(report) Report Issue", - description: "Report an issue with TypeScript Native Preview", + label: vscode.l10n.t("$(report) Report Issue"), + description: vscode.l10n.t("Report an issue with TypeScript Native Preview"), command: "typescript.native-preview.reportIssue", }, { - label: "$(versions) Select Version", - description: "Choose between bundled and workspace versions", + label: vscode.l10n.t("$(versions) Select Version"), + description: vscode.l10n.t("Choose between bundled and workspace versions"), command: "typescript.native-preview.selectVersion", }, { - label: "$(stop-circle) Disable TypeScript Native Preview", - description: "Switch back to the built-in TypeScript extension", + label: vscode.l10n.t("$(stop-circle) Disable TypeScript Native Preview"), + description: vscode.l10n.t("Switch back to the built-in TypeScript extension"), command: "typescript.native-preview.disable", }, ]; @@ -294,28 +295,28 @@ async function showCommands(client: Client): Promise { commands.push({ label: "", kind: vscode.QuickPickItemKind.Separator }); if (exe) { commands.push({ - label: `Executable`, + label: vscode.l10n.t(`Executable`), description: exe.path, action: async () => { await vscode.env.clipboard.writeText(exe.path); - vscode.window.showInformationMessage("Executable path copied to clipboard."); + vscode.window.showInformationMessage(vscode.l10n.t("Executable path copied to clipboard.")); }, }); } if (pid) { commands.push({ - label: `PID`, + label: vscode.l10n.t(`PID`), description: `${pid}`, action: async () => { await vscode.env.clipboard.writeText(`${pid}`); - vscode.window.showInformationMessage("Server PID copied to clipboard."); + vscode.window.showInformationMessage(vscode.l10n.t("Server PID copied to clipboard.")); }, }); } } const selected = await vscode.window.showQuickPick(commands, { - placeHolder: "TypeScript Native Preview Commands", + placeHolder: vscode.l10n.t("{0} Commands", "TypeScript Native Preview"), }); if (selected) { @@ -394,7 +395,7 @@ async function promptSelectVersion(context: vscode.ExtensionContext, client: Cli // Bundled version items.push({ - label: (currentExePath === builtinExe.path ? "• " : "") + "Use Bundled Version", + label: (currentExePath === builtinExe.path ? "• " : "") + vscode.l10n.t("Use Bundled Version"), description: bundledVersion, detail: builtinExe.path, run: async () => { @@ -408,7 +409,7 @@ async function promptSelectVersion(context: vscode.ExtensionContext, client: Cli for (const wsVersion of workspaceVersions) { const isActive = currentExePath === wsVersion.exePath; items.push({ - label: (isActive ? "• " : "") + "Use Workspace Version", + label: (isActive ? "• " : "") + vscode.l10n.t("Use Workspace Version"), description: wsVersion.version, detail: wsVersion.tsdkPath, run: async () => { @@ -426,7 +427,7 @@ async function promptSelectVersion(context: vscode.ExtensionContext, client: Cli run: async () => {}, }); items.push({ - label: "$(lock) Manage Workspace Trust to select a workspace version", + label: vscode.l10n.t("$(lock) Manage Workspace Trust to select a workspace version"), run: async () => { await vscode.commands.executeCommand("workbench.trust.manage"); }, @@ -448,7 +449,7 @@ async function promptSelectVersion(context: vscode.ExtensionContext, client: Cli if (workspaceVersions.some(ws => ws.exePath === resolved.path)) continue; const isActive = currentExePath === resolved.path; items.push({ - label: (isActive ? "• " : "") + "Use Custom Version", + label: (isActive ? "• " : "") + vscode.l10n.t("Use Custom Version"), description: resolved.version, detail: resolved.path, run: async () => { @@ -461,7 +462,7 @@ async function promptSelectVersion(context: vscode.ExtensionContext, client: Cli } const selected = await vscode.window.showQuickPick(items, { - placeHolder: "Select the TypeScript Native Preview version to use", + placeHolder: vscode.l10n.t("Select the TypeScript Native Preview version to use"), }); if (selected) { @@ -488,19 +489,17 @@ export async function promptUseWorkspaceVersion(context: vscode.ExtensionContext const config = vscode.workspace.getConfiguration("typescript.native-preview"); const tsdkInspection = config.inspect("tsdk"); const workspaceTsdk = tsdkInspection?.workspaceValue; - if (workspaceTsdk !== undefined) { // The workspace config already specifies a tsdk location, but the // user hasn't consented to using it. Just need their approval. const resolved = await resolveTsdkPathToExe(workspaceTsdk); if (!resolved) return; - - const allow = "Allow"; - const dismiss = "Dismiss"; - const suppress = "Never in this Workspace"; + const allow = vscode.l10n.t("Allow"); + const dismiss = vscode.l10n.t("Dismiss"); + const suppress = vscode.l10n.t("Never in this Workspace"); const result = await vscode.window.showInformationMessage( - `This workspace has a TypeScript Native Preview tsdk configured (${resolved.version}). Would you like to use it?`, + vscode.l10n.t(`This workspace has a TypeScript Native Preview tsdk configured ({0}). Would you like to use it?`, resolved.version), allow, dismiss, suppress, @@ -548,11 +547,11 @@ export async function promptUseWorkspaceVersion(context: vscode.ExtensionContext async function promptForProfileDirectory(): Promise { const defaultDir = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? ""; const dir = await vscode.window.showInputBox({ - prompt: "Enter directory path for profile output", + prompt: vscode.l10n.t("Enter directory path for profile output"), value: defaultDir, validateInput: value => { if (!value.trim()) { - return "Directory path is required"; + return vscode.l10n.t("Directory path is required"); } return undefined; }, diff --git a/_extension/src/statusBar.ts b/_extension/src/statusBar.ts index 181502bd33f..7833d9557df 100644 --- a/_extension/src/statusBar.ts +++ b/_extension/src/statusBar.ts @@ -3,11 +3,11 @@ import { jsTsLanguageModes } from "./util"; export function setupStatusBar(version: string): vscode.Disposable { const statusItem = vscode.languages.createLanguageStatusItem("typescript.native-preview.status", jsTsLanguageModes); - statusItem.name = "TypeScript Native Preview"; - statusItem.text = `$(beaker) tsgo ${version}`; - statusItem.detail = "TypeScript Native Preview Language Server"; + statusItem.name = vscode.l10n.t("TypeScript Native Preview"); + statusItem.text = vscode.l10n.t(`$(beaker) tsgo {0}`, version); + statusItem.detail = vscode.l10n.t("TypeScript Native Preview Language Server"); statusItem.command = { - title: "Show Menu", + title: vscode.l10n.t("Show Menu"), command: "typescript.native-preview.showMenu", }; return statusItem; From f83eec5c1342dff9363e6718158c48cd9d974b26 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 11:11:43 -0700 Subject: [PATCH 06/49] Fix incorrect renaming of `arguments` in non-reference positions in async downlevel (#3910) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/transformers/estransforms/async.go | 26 +++-------------- .../asyncDestructuringArgumentsProperty.js | 26 +++++++++++++++++ ...syncDestructuringArgumentsProperty.symbols | 15 ++++++++++ .../asyncDestructuringArgumentsProperty.types | 18 ++++++++++++ .../asyncJsxArgumentsAttributeName.js | 28 +++++++++++++++++++ .../asyncJsxArgumentsAttributeName.symbols | 19 +++++++++++++ .../asyncJsxArgumentsAttributeName.types | 18 ++++++++++++ .../asyncDestructuringArgumentsProperty.ts | 6 ++++ .../asyncJsxArgumentsAttributeName.ts | 11 ++++++++ 9 files changed, 145 insertions(+), 22 deletions(-) create mode 100644 testdata/baselines/reference/compiler/asyncDestructuringArgumentsProperty.js create mode 100644 testdata/baselines/reference/compiler/asyncDestructuringArgumentsProperty.symbols create mode 100644 testdata/baselines/reference/compiler/asyncDestructuringArgumentsProperty.types create mode 100644 testdata/baselines/reference/compiler/asyncJsxArgumentsAttributeName.js create mode 100644 testdata/baselines/reference/compiler/asyncJsxArgumentsAttributeName.symbols create mode 100644 testdata/baselines/reference/compiler/asyncJsxArgumentsAttributeName.types create mode 100644 testdata/tests/cases/compiler/asyncDestructuringArgumentsProperty.ts create mode 100644 testdata/tests/cases/compiler/asyncJsxArgumentsAttributeName.ts diff --git a/internal/transformers/estransforms/async.go b/internal/transformers/estransforms/async.go index a91d8180d37..a7c2d0b7250 100644 --- a/internal/transformers/estransforms/async.go +++ b/internal/transformers/estransforms/async.go @@ -30,9 +30,6 @@ type asyncTransformer struct { enclosingFunctionParameterNames *collections.Set[string] lexicalArguments lexicalArgumentsInfo - parentNode *ast.Node - currentNode *ast.Node - asyncBodyVisitor *ast.NodeVisitor fallbackNodeVisitor *ast.NodeVisitor } @@ -111,7 +108,10 @@ func (tx *asyncTransformer) fallbackVisitor(node *ast.Node) *ast.Node { ast.KindVariableDeclaration: // fall through to visitEachChild case ast.KindIdentifier: - if tx.lexicalArguments.binding != nil && node.Text() == "arguments" && !isNameOfPropertyAccessOrAssignment(tx.parentNode, node) { + if tx.lexicalArguments.binding != nil && + node.Text() == "arguments" && + !ast.IsIdentifierName(node) && + !ast.IsLabelName(node) { tx.lexicalArguments.used = true return tx.lexicalArguments.binding } @@ -119,23 +119,11 @@ func (tx *asyncTransformer) fallbackVisitor(node *ast.Node) *ast.Node { return tx.fallbackNodeVisitor.VisitEachChild(node) } -func (tx *asyncTransformer) descendInto(node *ast.Node) func() { - savedParent := tx.parentNode - tx.parentNode = tx.currentNode - tx.currentNode = node - return func() { tx.currentNode = tx.parentNode; tx.parentNode = savedParent } -} - func (tx *asyncTransformer) visitFallback(node *ast.Node) *ast.Node { - cleanup := tx.descendInto(node) - defer cleanup() return tx.fallbackVisitor(node) } func (tx *asyncTransformer) visit(node *ast.Node) *ast.Node { - cleanup := tx.descendInto(node) - defer cleanup() - if tx.EmitContext().EmitFlags(node)&printer.EFNoLexicalThis != 0 && tx.inHasLexicalThisContext() { tx.setContextFlag(asyncContextHasLexicalThis, false) defer tx.setContextFlag(asyncContextHasLexicalThis, true) @@ -971,12 +959,6 @@ func (tx *asyncTransformer) getOriginalIfFunctionLike(node *ast.Node) *ast.Node return node } -func isNameOfPropertyAccessOrAssignment(parent *ast.Node, node *ast.Node) bool { - return parent != nil && - (ast.IsPropertyAccessExpression(parent) || ast.IsPropertyAssignment(parent)) && - parent.Name() == node -} - // isSimpleParameterList checks if every parameter has no initializer and an Identifier name. func isSimpleParameterList(params []*ast.Node) bool { for _, param := range params { diff --git a/testdata/baselines/reference/compiler/asyncDestructuringArgumentsProperty.js b/testdata/baselines/reference/compiler/asyncDestructuringArgumentsProperty.js new file mode 100644 index 00000000000..8dfdbbdc4a5 --- /dev/null +++ b/testdata/baselines/reference/compiler/asyncDestructuringArgumentsProperty.js @@ -0,0 +1,26 @@ +//// [tests/cases/compiler/asyncDestructuringArgumentsProperty.ts] //// + +//// [asyncDestructuringArgumentsProperty.ts] +async function f() { + const { arguments: args } = await { arguments: 42 }; + return args; +} + + +//// [asyncDestructuringArgumentsProperty.js] +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +function f() { + return __awaiter(this, void 0, void 0, function* () { + const { arguments: args } = yield { arguments: 42 }; + return args; + }); +} diff --git a/testdata/baselines/reference/compiler/asyncDestructuringArgumentsProperty.symbols b/testdata/baselines/reference/compiler/asyncDestructuringArgumentsProperty.symbols new file mode 100644 index 00000000000..b36b9d30c07 --- /dev/null +++ b/testdata/baselines/reference/compiler/asyncDestructuringArgumentsProperty.symbols @@ -0,0 +1,15 @@ +//// [tests/cases/compiler/asyncDestructuringArgumentsProperty.ts] //// + +=== asyncDestructuringArgumentsProperty.ts === +async function f() { +>f : Symbol(f, Decl(asyncDestructuringArgumentsProperty.ts, 0, 0)) + + const { arguments: args } = await { arguments: 42 }; +>arguments : Symbol(arguments, Decl(asyncDestructuringArgumentsProperty.ts, 1, 37)) +>args : Symbol(args, Decl(asyncDestructuringArgumentsProperty.ts, 1, 9)) +>arguments : Symbol(arguments, Decl(asyncDestructuringArgumentsProperty.ts, 1, 37)) + + return args; +>args : Symbol(args, Decl(asyncDestructuringArgumentsProperty.ts, 1, 9)) +} + diff --git a/testdata/baselines/reference/compiler/asyncDestructuringArgumentsProperty.types b/testdata/baselines/reference/compiler/asyncDestructuringArgumentsProperty.types new file mode 100644 index 00000000000..c563b782d02 --- /dev/null +++ b/testdata/baselines/reference/compiler/asyncDestructuringArgumentsProperty.types @@ -0,0 +1,18 @@ +//// [tests/cases/compiler/asyncDestructuringArgumentsProperty.ts] //// + +=== asyncDestructuringArgumentsProperty.ts === +async function f() { +>f : () => Promise + + const { arguments: args } = await { arguments: 42 }; +>arguments : any +>args : number +>await { arguments: 42 } : { arguments: number; } +>{ arguments: 42 } : { arguments: number; } +>arguments : number +>42 : 42 + + return args; +>args : number +} + diff --git a/testdata/baselines/reference/compiler/asyncJsxArgumentsAttributeName.js b/testdata/baselines/reference/compiler/asyncJsxArgumentsAttributeName.js new file mode 100644 index 00000000000..ba01d7dcca5 --- /dev/null +++ b/testdata/baselines/reference/compiler/asyncJsxArgumentsAttributeName.js @@ -0,0 +1,28 @@ +//// [tests/cases/compiler/asyncJsxArgumentsAttributeName.ts] //// + +//// [test.tsx] +declare namespace JSX { + interface IntrinsicElements { div: any; } +} + +async function f() { + return
; +} + + +//// [test.jsx] +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +function f() { + return __awaiter(this, void 0, void 0, function* () { + return
; + }); +} diff --git a/testdata/baselines/reference/compiler/asyncJsxArgumentsAttributeName.symbols b/testdata/baselines/reference/compiler/asyncJsxArgumentsAttributeName.symbols new file mode 100644 index 00000000000..b3c864c9c3f --- /dev/null +++ b/testdata/baselines/reference/compiler/asyncJsxArgumentsAttributeName.symbols @@ -0,0 +1,19 @@ +//// [tests/cases/compiler/asyncJsxArgumentsAttributeName.ts] //// + +=== test.tsx === +declare namespace JSX { +>JSX : Symbol(JSX, Decl(test.tsx, 0, 0)) + + interface IntrinsicElements { div: any; } +>IntrinsicElements : Symbol(IntrinsicElements, Decl(test.tsx, 0, 23)) +>div : Symbol(IntrinsicElements.div, Decl(test.tsx, 1, 31)) +} + +async function f() { +>f : Symbol(f, Decl(test.tsx, 2, 1)) + + return
; +>div : Symbol(JSX.IntrinsicElements.div, Decl(test.tsx, 1, 31)) +>arguments : Symbol(arguments, Decl(test.tsx, 5, 13)) +} + diff --git a/testdata/baselines/reference/compiler/asyncJsxArgumentsAttributeName.types b/testdata/baselines/reference/compiler/asyncJsxArgumentsAttributeName.types new file mode 100644 index 00000000000..de701a409b2 --- /dev/null +++ b/testdata/baselines/reference/compiler/asyncJsxArgumentsAttributeName.types @@ -0,0 +1,18 @@ +//// [tests/cases/compiler/asyncJsxArgumentsAttributeName.ts] //// + +=== test.tsx === +declare namespace JSX { + interface IntrinsicElements { div: any; } +>div : any +} + +async function f() { +>f : () => Promise + + return
; +>
: error +>div : any +>arguments : number +>42 : 42 +} + diff --git a/testdata/tests/cases/compiler/asyncDestructuringArgumentsProperty.ts b/testdata/tests/cases/compiler/asyncDestructuringArgumentsProperty.ts new file mode 100644 index 00000000000..fef3ebaff81 --- /dev/null +++ b/testdata/tests/cases/compiler/asyncDestructuringArgumentsProperty.ts @@ -0,0 +1,6 @@ +// @target: es2015 + +async function f() { + const { arguments: args } = await { arguments: 42 }; + return args; +} diff --git a/testdata/tests/cases/compiler/asyncJsxArgumentsAttributeName.ts b/testdata/tests/cases/compiler/asyncJsxArgumentsAttributeName.ts new file mode 100644 index 00000000000..66e15304dee --- /dev/null +++ b/testdata/tests/cases/compiler/asyncJsxArgumentsAttributeName.ts @@ -0,0 +1,11 @@ +// @target: es2015 +// @jsx: preserve + +// @filename: test.tsx +declare namespace JSX { + interface IntrinsicElements { div: any; } +} + +async function f() { + return
; +} From f0113b785213608da1da03f4475589ecc2d371fe Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 11 Jun 2026 11:18:41 -0700 Subject: [PATCH 07/49] Reparse non-identifier jsdoc names where possible, error otherwise (#4058) --- .../src/enums/nodeFlags.enum.ts | 1 + .../native-preview/src/enums/nodeFlags.ts | 1 + internal/ast/nodeflags.go | 1 + internal/parser/reparser.go | 69 ++++++++++++++++--- internal/scanner/utilities.go | 16 +++++ .../jsdocNonIdentifierPropertiesAndParams.js | 59 ++++++++++++++++ ...ocNonIdentifierPropertiesAndParams.symbols | 28 ++++++++ ...sdocNonIdentifierPropertiesAndParams.types | 29 ++++++++ .../compiler/jsEnumCrossFileExport.errors.txt | 5 +- .../jsEnumCrossFileExport.errors.txt.diff | 5 +- .../compiler/jsdocTypedefNoCrash.errors.txt | 11 +++ .../jsdocTypedefNoCrash.errors.txt.diff | 15 ++++ .../compiler/jsdocTypedefNoCrash2.errors.txt | 5 +- .../jsdocTypedefNoCrash2.errors.txt.diff | 5 +- .../misspelledJsDocTypedefTags.errors.txt | 11 +++ ...misspelledJsDocTypedefTags.errors.txt.diff | 15 ++++ .../compiler/misspelledJsDocTypedefTags.types | 4 +- .../misspelledJsDocTypedefTags.types.diff | 20 ++++++ ...checkJsdocTypedefOnlySourceFile.errors.txt | 5 +- ...JsdocTypedefOnlySourceFile.errors.txt.diff | 5 +- .../noAssertForUnparseableTypedefs.errors.txt | 5 +- ...sertForUnparseableTypedefs.errors.txt.diff | 5 +- .../typedefInnerNamepaths.errors.txt | 5 +- .../typedefInnerNamepaths.errors.txt.diff | 15 ++-- .../conformance/typedefTagWrapping.errors.txt | 15 +++- .../typedefTagWrapping.errors.txt.diff | 21 ++++-- .../jsdocNonIdentifierPropertiesAndParams.ts | 25 +++++++ 27 files changed, 368 insertions(+), 33 deletions(-) create mode 100644 testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.js create mode 100644 testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.symbols create mode 100644 testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.types create mode 100644 testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt create mode 100644 testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt.diff create mode 100644 testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt create mode 100644 testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt.diff create mode 100644 testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types.diff create mode 100644 testdata/tests/cases/compiler/jsdocNonIdentifierPropertiesAndParams.ts diff --git a/_packages/native-preview/src/enums/nodeFlags.enum.ts b/_packages/native-preview/src/enums/nodeFlags.enum.ts index 605260208a6..22a07df84d9 100644 --- a/_packages/native-preview/src/enums/nodeFlags.enum.ts +++ b/_packages/native-preview/src/enums/nodeFlags.enum.ts @@ -30,6 +30,7 @@ export enum NodeFlags { JsonFile = 1 << 25, PossiblyContainsDeprecatedTag = 1 << 26, Unreachable = 1 << 27, + ReparserTransformedLiteral = 1 << 28, BlockScoped = Let | Const | Using, Constant = Const | Using, AwaitUsing = Const | Using, diff --git a/_packages/native-preview/src/enums/nodeFlags.ts b/_packages/native-preview/src/enums/nodeFlags.ts index 0324184fefc..33b047eda81 100644 --- a/_packages/native-preview/src/enums/nodeFlags.ts +++ b/_packages/native-preview/src/enums/nodeFlags.ts @@ -30,6 +30,7 @@ export var NodeFlags: any; NodeFlags[NodeFlags["JsonFile"] = 33554432] = "JsonFile"; NodeFlags[NodeFlags["PossiblyContainsDeprecatedTag"] = 67108864] = "PossiblyContainsDeprecatedTag"; NodeFlags[NodeFlags["Unreachable"] = 134217728] = "Unreachable"; + NodeFlags[NodeFlags["ReparserTransformedLiteral"] = 268435456] = "ReparserTransformedLiteral"; NodeFlags[NodeFlags["BlockScoped"] = 7] = "BlockScoped"; NodeFlags[NodeFlags["Constant"] = 6] = "Constant"; NodeFlags[NodeFlags["AwaitUsing"] = 6] = "AwaitUsing"; diff --git a/internal/ast/nodeflags.go b/internal/ast/nodeflags.go index bba2f98d5ca..2c808601a22 100644 --- a/internal/ast/nodeflags.go +++ b/internal/ast/nodeflags.go @@ -44,6 +44,7 @@ const ( NodeFlagsJsonFile NodeFlags = 1 << 25 // If node was parsed in a Json NodeFlagsPossiblyContainsDeprecatedTag NodeFlags = 1 << 26 // Set during parse if comment text contains '@deprecated'; must confirm via JSDoc lookup NodeFlagsUnreachable NodeFlags = 1 << 27 // If node is unreachable according to the binder + NodeFlagsReparserTransformedLiteral NodeFlags = 1 << 28 // If node was transformed during parsing, making its' naive text source not match the AST NodeFlagsBlockScoped = NodeFlagsLet | NodeFlagsConst | NodeFlagsUsing NodeFlagsConstant = NodeFlagsConst | NodeFlagsUsing diff --git a/internal/parser/reparser.go b/internal/parser/reparser.go index 0e175d46a74..62348001691 100644 --- a/internal/parser/reparser.go +++ b/internal/parser/reparser.go @@ -1,8 +1,13 @@ package parser import ( + "strconv" + "strings" + "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/diagnostics" + "github.com/microsoft/typescript-go/internal/scanner" ) func (p *Parser) finishReparsedNode(node *ast.Node, locationNode *ast.Node) { @@ -26,6 +31,24 @@ func (p *Parser) addDeepCloneReparse(node *ast.Node) *ast.Node { return clone } +func (p *Parser) addTransformedReparse(newNode *ast.Node, old *ast.Node) *ast.Node { + p.finishReparsedNode(newNode, old) + newNode.Flags |= ast.NodeFlagsReparserTransformedLiteral + p.reparsedClones = append(p.reparsedClones, newNode) + return newNode +} + +func (p *Parser) checkNonIdentifierName(name *ast.Node) *ast.Node { + if ast.IsIdentifier(name) && !scanner.IsValidIdentifier(name.AsIdentifier().Text) { + errLoc := name.Loc + if errLoc.Len() == 0 { // missing name, emit error on the character before the missing name node + errLoc = core.NewTextRange(name.Loc.Pos()-1, name.Loc.Pos()) + } + p.parseErrorAtRange(errLoc, diagnostics.Identifier_expected) + } + return name +} + // Hosted tags find a host and add their children to the correct location under the host. // Unhosted tags add synthetic nodes to the reparse list. func (p *Parser) reparseTags(parent *ast.Node, jsDoc []*ast.Node) { @@ -57,7 +80,7 @@ func (p *Parser) reparseUnhosted(tag *ast.Node, parent *ast.Node, jsDoc *ast.Nod if isNamespace { modifiers = p.createExportModifier(tag) } - typeAlias := p.factory.NewJSTypeAliasDeclaration(modifiers, p.addDeepCloneReparse(p.getInnermostNameOfJSDocNamespace(fullName)), nil, nil) + typeAlias := p.factory.NewJSTypeAliasDeclaration(modifiers, p.addDeepCloneReparse(p.checkNonIdentifierName(p.getInnermostNameOfJSDocNamespace(fullName))), nil, nil) typeAlias.AsTypeAliasDeclaration().TypeParameters = p.gatherTypeParameters(jsDoc, true /*typedefOrCallback*/) var t *ast.Node switch typeExpression.Kind { @@ -121,9 +144,9 @@ func (p *Parser) reparseJSDocSignature(jsSignature *ast.Node, fun *ast.Node, jsD clonedModifiers := p.factory.DeepCloneReparseModifiers(modifiers) switch fun.Kind { case ast.KindFunctionDeclaration: - signature = p.factory.NewFunctionDeclaration(clonedModifiers, nil, p.factory.DeepCloneReparse(fun.Name()), nil, nil, nil, nil, nil) + signature = p.factory.NewFunctionDeclaration(clonedModifiers, nil, p.factory.DeepCloneReparse(p.checkNonIdentifierName(fun.Name())), nil, nil, nil, nil, nil) case ast.KindMethodDeclaration: - signature = p.factory.NewMethodDeclaration(clonedModifiers, nil, p.factory.DeepCloneReparse(fun.Name()), nil, nil, nil, nil, nil, nil) + signature = p.factory.NewMethodDeclaration(clonedModifiers, nil, p.factory.DeepCloneReparse(p.checkNonIdentifierName(fun.Name())), nil, nil, nil, nil, nil, nil) case ast.KindConstructor: signature = p.factory.NewConstructorDeclaration(clonedModifiers, nil, nil, nil, nil, nil) case ast.KindJSDocCallbackTag: @@ -136,7 +159,7 @@ func (p *Parser) reparseJSDocSignature(jsSignature *ast.Node, fun *ast.Node, jsD signature.FunctionLikeData().TypeParameters = p.gatherTypeParameters(jsDoc, false /*typedefOrCallback*/) } parameters := p.nodeSliceArena.NewSlice(0) - for _, param := range jsSignature.Parameters() { + for pi, param := range jsSignature.Parameters() { var parameter *ast.Node if param.Kind == ast.KindJSDocThisTag { thisTag := param.AsJSDocThisTag() @@ -169,8 +192,33 @@ func (p *Parser) reparseJSDocSignature(jsSignature *ast.Node, fun *ast.Node, jsD paramType = p.reparseJSDocTypeLiteral(jsparam.TypeExpression.Type()) } } - - parameter = p.factory.NewParameterDeclaration(nil, dotDotDotToken, p.addDeepCloneReparse(jsparam.Name()), p.makeQuestionIfOptional(jsparam), paramType, nil) + name := jsparam.Name() + if ast.IsIdentifier(name) && !scanner.IsValidIdentifier(name.AsIdentifier().Text) { + // drop invalid chars for _, if empty, write _0, etc., so we have a valid param name to emit later + result := strings.Builder{} + for i, ch := range name.AsIdentifier().Text { + if i == 0 { + if !scanner.IsIdentifierStart(ch) { + result.WriteRune('_') + } else { + result.WriteRune(ch) + } + continue + } else if !scanner.IsIdentifierPart(ch) { + result.WriteRune('_') + } else { + result.WriteRune(ch) + } + } + if result.Len() == 0 { + result.WriteRune('_') + result.WriteString(strconv.Itoa(pi)) + } + name = p.addTransformedReparse(p.factory.NewIdentifier(result.String()), name) + } else { + name = p.addDeepCloneReparse(name) + } + parameter = p.factory.NewParameterDeclaration(nil, dotDotDotToken, name, p.makeQuestionIfOptional(jsparam), paramType, nil) } p.finishReparsedNode(parameter, param) parameters = append(parameters, parameter) @@ -206,7 +254,12 @@ func (p *Parser) reparseJSDocTypeLiteral(t *ast.TypeNode) *ast.Node { if name.Kind == ast.KindQualifiedName { name = name.AsQualifiedName().Right } - property := p.factory.NewPropertySignatureDeclaration(nil, p.addDeepCloneReparse(name), p.makeQuestionIfOptional(jsprop), nil, nil) + if ast.IsIdentifier(name) && !scanner.IsValidIdentifier(name.AsIdentifier().Text) { + name = p.addTransformedReparse(p.factory.NewStringLiteral(name.AsIdentifier().Text, ast.TokenFlagsNone), name) + } else { + name = p.addDeepCloneReparse(name) + } + property := p.factory.NewPropertySignatureDeclaration(nil, name, p.makeQuestionIfOptional(jsprop), nil, nil) if jsprop.TypeExpression != nil { property.AsPropertySignatureDeclaration().Type = p.reparseJSDocTypeLiteral(jsprop.TypeExpression.Type()) } @@ -263,7 +316,7 @@ func (p *Parser) gatherTypeParameters(j *ast.Node, typedefOrCallback bool) *ast. if constraint != nil && firstTypeParameter { reparse = p.factory.NewTypeParameterDeclaration( p.factory.DeepCloneReparseModifiers(tp.Modifiers()), - p.addDeepCloneReparse(tp.Name()), + p.addDeepCloneReparse(p.checkNonIdentifierName(tp.Name())), p.addDeepCloneReparse(constraint.Type()), nil, // expression p.addDeepCloneReparse(tp.AsTypeParameterDeclaration().DefaultType), diff --git a/internal/scanner/utilities.go b/internal/scanner/utilities.go index 8f4f0d69790..aaf81467d31 100644 --- a/internal/scanner/utilities.go +++ b/internal/scanner/utilities.go @@ -7,6 +7,7 @@ import ( "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/debug" ) func tokenIsIdentifierOrKeyword(token ast.Kind) bool { @@ -30,6 +31,21 @@ func GetTextOfNodeFromSourceText(sourceText string, node *ast.Node, includeTrivi pos = SkipTrivia(sourceText, pos) } text := sourceText[pos:node.End()] + if node.Flags&ast.NodeFlagsReparserTransformedLiteral != 0 { + // This is similar to `getLiteralTextOfNode` in the printer, but without the context of an `emitContext` to provide overrides + if ast.IsStringLiteral(node) { + if node.AsStringLiteral().TokenFlags&ast.TokenFlagsSingleQuote != 0 { + return "'" + text + "'" + } + return "\"" + text + "\"" + } else if ast.IsIdentifier(node) { + return node.Text() + } + // Only the above node kinds are currently transformed into one another by the reparser, requiring the textual remapping. + // (Any reamppings done by emit transforms are handled by `getLiteralTextOfNode` in the printer) + // Fail on any other kinds. + debug.FailBadSyntaxKind(node, "Unexpected reparser-transformed node kind") + } // if (isJSDocTypeExpressionOrChild(node)) { // // strip space + asterisk at line start // text = text.split(/\r\n|\n|\r/).map(line => line.replace(/^\s*\*/, "").trimStart()).join("\n"); diff --git a/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.js b/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.js new file mode 100644 index 00000000000..c1b67a05e60 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.js @@ -0,0 +1,59 @@ +//// [tests/cases/compiler/jsdocNonIdentifierPropertiesAndParams.ts] //// + +//// [index.js] +/** + * @typedef {Object} ButtonProps + * @property {string} label The button label + * @property {string | null | undefined} [data-test-name] Test automation attribute + * @property {string | null | undefined} [aria-label] Accessibility label + */ + +/** + * @param {ButtonProps} props + * @returns {ButtonProps} + */ +export function Button(props) { + return { ...props } +} + +/** + * @callback ButtonPropsCallback + * @param {ButtonProps} [props-like] + * @returns {ButtonProps} + */ + + + + +//// [index.d.ts] +/** + * @typedef {Object} ButtonProps + * @property {string} label The button label + * @property {string | null | undefined} [data-test-name] Test automation attribute + * @property {string | null | undefined} [aria-label] Accessibility label + */ +export type ButtonProps = { + /** + * The button label + */ + label: string; + /** + * Test automation attribute + */ + "data-test-name"?: string | null | undefined; + /** + * Accessibility label + */ + "aria-label"?: string | null | undefined; +}; +/** + * @param {ButtonProps} props + * @returns {ButtonProps} + */ +export declare function Button(props: ButtonProps): ButtonProps; +export type ButtonPropsCallback = (props_like?: ButtonProps) => ButtonProps; +/** + * @callback ButtonPropsCallback + * @param {ButtonProps} [props-like] + * @returns {ButtonProps} + */ diff --git a/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.symbols b/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.symbols new file mode 100644 index 00000000000..99dde5ee929 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.symbols @@ -0,0 +1,28 @@ +//// [tests/cases/compiler/jsdocNonIdentifierPropertiesAndParams.ts] //// + +=== index.js === +/** + * @typedef {Object} ButtonProps + * @property {string} label The button label + * @property {string | null | undefined} [data-test-name] Test automation attribute + * @property {string | null | undefined} [aria-label] Accessibility label + */ + +/** + * @param {ButtonProps} props + * @returns {ButtonProps} + */ +export function Button(props) { +>Button : Symbol(Button, Decl(index.js, 0, 0)) +>props : Symbol(props, Decl(index.js, 11, 23)) + + return { ...props } +>props : Symbol(props, Decl(index.js, 11, 23)) +} + +/** + * @callback ButtonPropsCallback + * @param {ButtonProps} [props-like] + * @returns {ButtonProps} + */ + diff --git a/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.types b/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.types new file mode 100644 index 00000000000..18be6da01c2 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsdocNonIdentifierPropertiesAndParams.types @@ -0,0 +1,29 @@ +//// [tests/cases/compiler/jsdocNonIdentifierPropertiesAndParams.ts] //// + +=== index.js === +/** + * @typedef {Object} ButtonProps + * @property {string} label The button label + * @property {string | null | undefined} [data-test-name] Test automation attribute + * @property {string | null | undefined} [aria-label] Accessibility label + */ + +/** + * @param {ButtonProps} props + * @returns {ButtonProps} + */ +export function Button(props) { +>Button : (props: ButtonProps) => ButtonProps +>props : ButtonProps + + return { ...props } +>{ ...props } : { label: string; "data-test-name"?: string | null | undefined; "aria-label"?: string | null | undefined; } +>props : ButtonProps +} + +/** + * @callback ButtonPropsCallback + * @param {ButtonProps} [props-like] + * @returns {ButtonProps} + */ + diff --git a/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt b/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt index 9bf6de47fce..55b5e08be43 100644 --- a/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt +++ b/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt @@ -1,9 +1,10 @@ +enumDef.js(14,20): error TS1003: Identifier expected. enumDef.js(14,21): error TS1003: Identifier expected. index.js(4,17): error TS2749: 'Host.UserMetrics.Action' refers to a value, but is being used as a type here. Did you mean 'typeof Host.UserMetrics.Action'? index.js(18,11): error TS2749: 'Host.UserMetrics.Blah' refers to a value, but is being used as a type here. Did you mean 'typeof Host.UserMetrics.Blah'? -==== enumDef.js (1 errors) ==== +==== enumDef.js (2 errors) ==== var Host = {}; Host.UserMetrics = {}; /** @enum {number} */ @@ -18,6 +19,8 @@ index.js(18,11): error TS2749: 'Host.UserMetrics.Blah' refers to a value, but is */ /** * @typedef {string} + ~ +!!! error TS1003: Identifier expected. */ diff --git a/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt.diff b/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt.diff index 4ee98634e71..63327549ccd 100644 --- a/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt.diff +++ b/testdata/baselines/reference/submodule/compiler/jsEnumCrossFileExport.errors.txt.diff @@ -2,12 +2,13 @@ +++ new.jsEnumCrossFileExport.errors.txt @@= skipped -0, +0 lines =@@ - ++enumDef.js(14,20): error TS1003: Identifier expected. +enumDef.js(14,21): error TS1003: Identifier expected. +index.js(4,17): error TS2749: 'Host.UserMetrics.Action' refers to a value, but is being used as a type here. Did you mean 'typeof Host.UserMetrics.Action'? +index.js(18,11): error TS2749: 'Host.UserMetrics.Blah' refers to a value, but is being used as a type here. Did you mean 'typeof Host.UserMetrics.Blah'? + + -+==== enumDef.js (1 errors) ==== ++==== enumDef.js (2 errors) ==== + var Host = {}; + Host.UserMetrics = {}; + /** @enum {number} */ @@ -22,6 +23,8 @@ + */ + /** + * @typedef {string} ++ ~ ++!!! error TS1003: Identifier expected. + + */ + diff --git a/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt new file mode 100644 index 00000000000..7713d8f5e40 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt @@ -0,0 +1,11 @@ +export.js(3,5): error TS1003: Identifier expected. + + +==== export.js (1 errors) ==== + /** + * @typedef {{ + * }} + ~ +!!! error TS1003: Identifier expected. + */ + export const foo = 5; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt.diff b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt.diff new file mode 100644 index 00000000000..34a6c932b9f --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash.errors.txt.diff @@ -0,0 +1,15 @@ +--- old.jsdocTypedefNoCrash.errors.txt ++++ new.jsdocTypedefNoCrash.errors.txt +@@= skipped -0, +0 lines =@@ +- ++export.js(3,5): error TS1003: Identifier expected. ++ ++ ++==== export.js (1 errors) ==== ++ /** ++ * @typedef {{ ++ * }} ++ ~ ++!!! error TS1003: Identifier expected. ++ */ ++ export const foo = 5; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt index 6239c728e61..b84429e569d 100644 --- a/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt +++ b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt @@ -1,12 +1,15 @@ export.js(1,13): error TS8008: Type aliases can only be used in TypeScript files. +export.js(4,5): error TS1003: Identifier expected. -==== export.js (1 errors) ==== +==== export.js (2 errors) ==== export type foo = 5; ~~~ !!! error TS8008: Type aliases can only be used in TypeScript files. /** * @typedef {{ * }} + ~ +!!! error TS1003: Identifier expected. */ export const foo = 5; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt.diff b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt.diff index 294da8e1b74..f2a92b30b91 100644 --- a/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt.diff +++ b/testdata/baselines/reference/submodule/compiler/jsdocTypedefNoCrash2.errors.txt.diff @@ -7,9 +7,10 @@ - - -==== export.js (3 errors) ==== ++export.js(4,5): error TS1003: Identifier expected. + + -+==== export.js (1 errors) ==== ++==== export.js (2 errors) ==== export type foo = 5; ~~~ -!!! error TS2451: Cannot redeclare block-scoped variable 'foo'. @@ -18,6 +19,8 @@ /** * @typedef {{ * }} ++ ~ ++!!! error TS1003: Identifier expected. */ export const foo = 5; - ~~~ diff --git a/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt new file mode 100644 index 00000000000..cb9a68e87d2 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt @@ -0,0 +1,11 @@ +a.js(4,59): error TS1003: Identifier expected. + + +==== a.js (1 errors) ==== + /** @typedef {{ endTime: number, screenshots: number}} A.*/ + Animation.AnimationModel.ScreenshotCapture.Request; + + /** @typedef {{ endTime: number, screenshots: !B.}} */ + ~ +!!! error TS1003: Identifier expected. + Animation.AnimationModel.ScreenshotCapture.Request; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt.diff b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt.diff new file mode 100644 index 00000000000..ad39e40a72e --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.errors.txt.diff @@ -0,0 +1,15 @@ +--- old.misspelledJsDocTypedefTags.errors.txt ++++ new.misspelledJsDocTypedefTags.errors.txt +@@= skipped -0, +0 lines =@@ +- ++a.js(4,59): error TS1003: Identifier expected. ++ ++ ++==== a.js (1 errors) ==== ++ /** @typedef {{ endTime: number, screenshots: number}} A.*/ ++ Animation.AnimationModel.ScreenshotCapture.Request; ++ ++ /** @typedef {{ endTime: number, screenshots: !B.}} */ ++ ~ ++!!! error TS1003: Identifier expected. ++ Animation.AnimationModel.ScreenshotCapture.Request; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types index 3d26fba20dc..e8624ba0160 100644 --- a/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types +++ b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types @@ -3,7 +3,7 @@ === a.js === /** @typedef {{ endTime: number, screenshots: number}} A.*/ Animation.AnimationModel.ScreenshotCapture.Request; ->Animation.AnimationModel.ScreenshotCapture.Request : error +>Animation.AnimationModel.ScreenshotCapture.Request : any >Animation.AnimationModel.ScreenshotCapture : any >Animation.AnimationModel : any >Animation : { new (effect?: AnimationEffect | null, timeline?: AnimationTimeline | null): Animation; prototype: Animation; } @@ -13,7 +13,7 @@ Animation.AnimationModel.ScreenshotCapture.Request; /** @typedef {{ endTime: number, screenshots: !B.}} */ Animation.AnimationModel.ScreenshotCapture.Request; ->Animation.AnimationModel.ScreenshotCapture.Request : error +>Animation.AnimationModel.ScreenshotCapture.Request : any >Animation.AnimationModel.ScreenshotCapture : any >Animation.AnimationModel : any >Animation : { new (effect?: AnimationEffect | null, timeline?: AnimationTimeline | null): Animation; prototype: Animation; } diff --git a/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types.diff b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types.diff new file mode 100644 index 00000000000..d2e04e3fa4f --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/misspelledJsDocTypedefTags.types.diff @@ -0,0 +1,20 @@ +--- old.misspelledJsDocTypedefTags.types ++++ new.misspelledJsDocTypedefTags.types +@@= skipped -2, +2 lines =@@ + === a.js === + /** @typedef {{ endTime: number, screenshots: number}} A.*/ + Animation.AnimationModel.ScreenshotCapture.Request; +->Animation.AnimationModel.ScreenshotCapture.Request : error ++>Animation.AnimationModel.ScreenshotCapture.Request : any + >Animation.AnimationModel.ScreenshotCapture : any + >Animation.AnimationModel : any + >Animation : { new (effect?: AnimationEffect | null, timeline?: AnimationTimeline | null): Animation; prototype: Animation; } +@@= skipped -10, +10 lines =@@ + + /** @typedef {{ endTime: number, screenshots: !B.}} */ + Animation.AnimationModel.ScreenshotCapture.Request; +->Animation.AnimationModel.ScreenshotCapture.Request : error ++>Animation.AnimationModel.ScreenshotCapture.Request : any + >Animation.AnimationModel.ScreenshotCapture : any + >Animation.AnimationModel : any + >Animation : { new (effect?: AnimationEffect | null, timeline?: AnimationTimeline | null): Animation; prototype: Animation; } \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt b/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt index c58053a43c1..44262ed7546 100644 --- a/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt @@ -1,14 +1,17 @@ +0.js(6,20): error TS1003: Identifier expected. 0.js(6,21): error TS1003: Identifier expected. 0.js(10,12): error TS2503: Cannot find namespace 'exports'. -==== 0.js (2 errors) ==== +==== 0.js (3 errors) ==== // @ts-check var exports = {}; /** * @typedef {string} + ~ +!!! error TS1003: Identifier expected. */ diff --git a/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt.diff index d1155e5273a..5a9f850f871 100644 --- a/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt.diff +++ b/testdata/baselines/reference/submodule/conformance/checkJsdocTypedefOnlySourceFile.errors.txt.diff @@ -5,17 +5,20 @@ - - -==== 0.js (1 errors) ==== ++0.js(6,20): error TS1003: Identifier expected. +0.js(6,21): error TS1003: Identifier expected. +0.js(10,12): error TS2503: Cannot find namespace 'exports'. + + -+==== 0.js (2 errors) ==== ++==== 0.js (3 errors) ==== // @ts-check var exports = {}; /** * @typedef {string} ++ ~ ++!!! error TS1003: Identifier expected. + */ + diff --git a/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt b/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt index e13db6e492b..35abd0a113d 100644 --- a/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt @@ -1,12 +1,15 @@ bug26693.js(1,15): error TS2591: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. +bug26693.js(1,20): error TS1003: Identifier expected. bug26693.js(1,21): error TS1005: '}' expected. bug26693.js(2,22): error TS2307: Cannot find module 'nope' or its corresponding type declarations. -==== bug26693.js (3 errors) ==== +==== bug26693.js (4 errors) ==== /** @typedef {module:locale} hi */ ~~~~~~ !!! error TS2591: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. + ~ +!!! error TS1003: Identifier expected. ~ !!! error TS1005: '}' expected. import { nope } from 'nope'; diff --git a/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt.diff index 73abaa1f3d2..16a103ac353 100644 --- a/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt.diff +++ b/testdata/baselines/reference/submodule/conformance/noAssertForUnparseableTypedefs.errors.txt.diff @@ -2,15 +2,18 @@ +++ new.noAssertForUnparseableTypedefs.errors.txt @@= skipped -0, +0 lines =@@ +bug26693.js(1,15): error TS2591: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. ++bug26693.js(1,20): error TS1003: Identifier expected. +bug26693.js(1,21): error TS1005: '}' expected. bug26693.js(2,22): error TS2307: Cannot find module 'nope' or its corresponding type declarations. -==== bug26693.js (1 errors) ==== -+==== bug26693.js (3 errors) ==== ++==== bug26693.js (4 errors) ==== /** @typedef {module:locale} hi */ + ~~~~~~ +!!! error TS2591: Cannot find name 'module'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. ++ ~ ++!!! error TS1003: Identifier expected. + ~ +!!! error TS1005: '}' expected. import { nope } from 'nope'; diff --git a/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt b/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt index 5a5e1fedf51..fe898207288 100644 --- a/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt @@ -1,15 +1,18 @@ bug25104.js(1,7): error TS2300: Duplicate identifier 'C'. +bug25104.js(3,18): error TS1003: Identifier expected. bug25104.js(3,19): error TS1005: '}' expected. bug25104.js(4,26): error TS2300: Duplicate identifier 'C'. bug25104.js(6,18): error TS1005: '}' expected. -==== bug25104.js (4 errors) ==== +==== bug25104.js (5 errors) ==== class C { ~ !!! error TS2300: Duplicate identifier 'C'. /** * @typedef {C~A} C~B + ~ +!!! error TS1003: Identifier expected. ~ !!! error TS1005: '}' expected. * @typedef {object} C~A diff --git a/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt.diff index db8d8129a0f..44743cb5412 100644 --- a/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt.diff +++ b/testdata/baselines/reference/submodule/conformance/typedefInnerNamepaths.errors.txt.diff @@ -2,18 +2,23 @@ +++ new.typedefInnerNamepaths.errors.txt @@= skipped -0, +0 lines =@@ bug25104.js(1,7): error TS2300: Duplicate identifier 'C'. ++bug25104.js(3,18): error TS1003: Identifier expected. bug25104.js(3,19): error TS1005: '}' expected. bug25104.js(4,26): error TS2300: Duplicate identifier 'C'. -bug25104.js(6,18): error TS8024: JSDoc '@param' tag has name '', but there is no parameter with that name. bug25104.js(6,18): error TS1005: '}' expected. --==== bug25104.js (5 errors) ==== -+==== bug25104.js (4 errors) ==== - class C { - ~ +@@= skipped -10, +10 lines =@@ !!! error TS2300: Duplicate identifier 'C'. -@@= skipped -17, +16 lines =@@ + /** + * @typedef {C~A} C~B ++ ~ ++!!! error TS1003: Identifier expected. + ~ + !!! error TS1005: '}' expected. + * @typedef {object} C~A +@@= skipped -7, +9 lines =@@ !!! error TS2300: Duplicate identifier 'C'. */ /** @param {C~A} o */ diff --git a/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt b/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt index 0239a154bc7..7501b5fd05b 100644 --- a/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt @@ -1,9 +1,12 @@ +mod1.js(2,21): error TS1003: Identifier expected. mod1.js(2,22): error TS2300: Duplicate identifier '(Missing)'. mod1.js(2,22): error TS1005: '}' expected. mod1.js(9,12): error TS2552: Cannot find name 'Type1'. Did you mean 'Type2'? +mod3.js(4,21): error TS1003: Identifier expected. mod3.js(4,22): error TS2300: Duplicate identifier '(Missing)'. mod3.js(4,22): error TS1005: '}' expected. mod3.js(10,12): error TS2304: Cannot find name 'StringOrNumber1'. +mod4.js(4,21): error TS1003: Identifier expected. mod4.js(4,22): error TS2300: Duplicate identifier '(Missing)'. mod4.js(4,22): error TS1005: '}' expected. mod4.js(11,12): error TS2304: Cannot find name 'StringOrNumber2'. @@ -11,9 +14,11 @@ mod7.js(5,7): error TS1110: Type expected. mod7.js(8,4): error TS1110: Type expected. -==== mod1.js (3 errors) ==== +==== mod1.js (4 errors) ==== /** * @typedef {function(string): boolean} + ~ +!!! error TS1003: Identifier expected. !!! error TS2300: Duplicate identifier '(Missing)'. !!! related TS6203 mod3.js:4:22: '(Missing)' was also declared here. @@ -54,11 +59,13 @@ mod7.js(8,4): error TS1110: Type expected. return obj.boo ? obj.num : obj.str; } -==== mod3.js (3 errors) ==== +==== mod3.js (4 errors) ==== /** * A function whose signature is very long. * * @typedef {function(boolean, string, number): + ~ +!!! error TS1003: Identifier expected. !!! error TS2300: Duplicate identifier '(Missing)'. !!! related TS6203 mod1.js:2:22: '(Missing)' was also declared here. @@ -81,11 +88,13 @@ mod7.js(8,4): error TS1110: Type expected. return func(bool, str, num) } -==== mod4.js (3 errors) ==== +==== mod4.js (4 errors) ==== /** * A function whose signature is very long. * * @typedef {function(boolean, string, + ~ +!!! error TS1003: Identifier expected. !!! error TS2300: Duplicate identifier '(Missing)'. !!! related TS6203 mod1.js:2:22: '(Missing)' was also declared here. diff --git a/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt.diff index 5a85165b86d..5aca86955b4 100644 --- a/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt.diff +++ b/testdata/baselines/reference/submodule/conformance/typedefTagWrapping.errors.txt.diff @@ -1,12 +1,15 @@ --- old.typedefTagWrapping.errors.txt +++ new.typedefTagWrapping.errors.txt @@= skipped -0, +0 lines =@@ ++mod1.js(2,21): error TS1003: Identifier expected. +mod1.js(2,22): error TS2300: Duplicate identifier '(Missing)'. +mod1.js(2,22): error TS1005: '}' expected. +mod1.js(9,12): error TS2552: Cannot find name 'Type1'. Did you mean 'Type2'? ++mod3.js(4,21): error TS1003: Identifier expected. +mod3.js(4,22): error TS2300: Duplicate identifier '(Missing)'. +mod3.js(4,22): error TS1005: '}' expected. +mod3.js(10,12): error TS2304: Cannot find name 'StringOrNumber1'. ++mod4.js(4,21): error TS1003: Identifier expected. +mod4.js(4,22): error TS2300: Duplicate identifier '(Missing)'. +mod4.js(4,22): error TS1005: '}' expected. +mod4.js(11,12): error TS2304: Cannot find name 'StringOrNumber2'. @@ -15,9 +18,11 @@ -==== mod1.js (0 errors) ==== -+==== mod1.js (3 errors) ==== ++==== mod1.js (4 errors) ==== /** * @typedef {function(string): boolean} ++ ~ ++!!! error TS1003: Identifier expected. + +!!! error TS2300: Duplicate identifier '(Missing)'. +!!! related TS6203 mod3.js:4:22: '(Missing)' was also declared here. @@ -27,7 +32,7 @@ * Type1 */ -@@= skipped -11, +26 lines =@@ +@@= skipped -11, +31 lines =@@ * Tries to use a type whose name is on a different * line than the typedef tag. * @param {Type1} func The function to call. @@ -41,11 +46,13 @@ } -==== mod3.js (0 errors) ==== -+==== mod3.js (3 errors) ==== ++==== mod3.js (4 errors) ==== /** * A function whose signature is very long. * * @typedef {function(boolean, string, number): ++ ~ ++!!! error TS1003: Identifier expected. + +!!! error TS2300: Duplicate identifier '(Missing)'. +!!! related TS6203 mod1.js:2:22: '(Missing)' was also declared here. @@ -62,16 +69,18 @@ * @param {boolean} bool The condition. * @param {string} str The string. * @param {number} num The number. -@@= skipped -20, +27 lines =@@ +@@= skipped -20, +29 lines =@@ return func(bool, str, num) } -==== mod4.js (0 errors) ==== -+==== mod4.js (3 errors) ==== ++==== mod4.js (4 errors) ==== /** * A function whose signature is very long. * * @typedef {function(boolean, string, ++ ~ ++!!! error TS1003: Identifier expected. + +!!! error TS2300: Duplicate identifier '(Missing)'. +!!! related TS6203 mod1.js:2:22: '(Missing)' was also declared here. @@ -80,7 +89,7 @@ * number): * (string|number)} StringOrNumber2 */ -@@= skipped -12, +17 lines =@@ +@@= skipped -12, +19 lines =@@ /** * Makes use of a function type with a long signature. * @param {StringOrNumber2} func The function. diff --git a/testdata/tests/cases/compiler/jsdocNonIdentifierPropertiesAndParams.ts b/testdata/tests/cases/compiler/jsdocNonIdentifierPropertiesAndParams.ts new file mode 100644 index 00000000000..0e773820f9b --- /dev/null +++ b/testdata/tests/cases/compiler/jsdocNonIdentifierPropertiesAndParams.ts @@ -0,0 +1,25 @@ +// @checkJs: true +// @allowJs: true +// @declaration: true +// @emitDeclarationOnly: true +// @filename: index.js +/** + * @typedef {Object} ButtonProps + * @property {string} label The button label + * @property {string | null | undefined} [data-test-name] Test automation attribute + * @property {string | null | undefined} [aria-label] Accessibility label + */ + +/** + * @param {ButtonProps} props + * @returns {ButtonProps} + */ +export function Button(props) { + return { ...props } +} + +/** + * @callback ButtonPropsCallback + * @param {ButtonProps} [props-like] + * @returns {ButtonProps} + */ From a58d885a79c41d50ed5f9ec796cdc395c9384ea6 Mon Sep 17 00:00:00 2001 From: Piotr Tomiak Date: Thu, 11 Jun 2026 21:16:12 +0200 Subject: [PATCH 08/49] API: Add declaration property to IndexInfo. (#4273) --- _packages/native-preview/src/api/async/api.ts | 1 + _packages/native-preview/src/api/async/types.ts | 7 ++++++- _packages/native-preview/src/api/proto.ts | 1 + _packages/native-preview/src/api/sync/api.ts | 1 + _packages/native-preview/src/api/sync/types.ts | 7 ++++++- internal/api/proto.go | 7 ++++--- internal/api/session.go | 3 +++ internal/checker/types.go | 4 ++++ 8 files changed, 26 insertions(+), 5 deletions(-) diff --git a/_packages/native-preview/src/api/async/api.ts b/_packages/native-preview/src/api/async/api.ts index 919855198ed..3d26ec92455 100644 --- a/_packages/native-preview/src/api/async/api.ts +++ b/_packages/native-preview/src/api/async/api.ts @@ -902,6 +902,7 @@ export class Checker { keyType: this.objectRegistry.getOrCreateType(d.keyType), valueType: this.objectRegistry.getOrCreateType(d.valueType), isReadonly: d.isReadonly ?? false, + declaration: d.declaration ? new NodeHandle(d.declaration) : undefined, })); } diff --git a/_packages/native-preview/src/api/async/types.ts b/_packages/native-preview/src/api/async/types.ts index 0d344ba17be..66a5184af11 100644 --- a/_packages/native-preview/src/api/async/types.ts +++ b/_packages/native-preview/src/api/async/types.ts @@ -3,7 +3,10 @@ import type { ElementFlags } from "#enums/elementFlags"; import type { ObjectFlags } from "#enums/objectFlags"; import type { TypeFlags } from "#enums/typeFlags"; import type { TypePredicateKind } from "#enums/typePredicateKind"; -import type { Symbol } from "./api.ts"; +import type { + NodeHandle, + Symbol, +} from "./api.ts"; /** * A TypeScript type. @@ -198,6 +201,8 @@ export interface IndexInfo { readonly valueType: Type; /** Whether the index signature is readonly */ readonly isReadonly: boolean; + /** The index signature declaration, if any */ + readonly declaration?: NodeHandle; } /** diff --git a/_packages/native-preview/src/api/proto.ts b/_packages/native-preview/src/api/proto.ts index f76102aa8fd..bc501126f1e 100644 --- a/_packages/native-preview/src/api/proto.ts +++ b/_packages/native-preview/src/api/proto.ts @@ -184,6 +184,7 @@ export interface IndexInfoResponse { keyType: TypeResponse; valueType: TypeResponse; isReadonly?: boolean; + declaration?: string; } export interface ProfileParams { diff --git a/_packages/native-preview/src/api/sync/api.ts b/_packages/native-preview/src/api/sync/api.ts index 0c097aa1900..8e090615cf4 100644 --- a/_packages/native-preview/src/api/sync/api.ts +++ b/_packages/native-preview/src/api/sync/api.ts @@ -910,6 +910,7 @@ export class Checker { keyType: this.objectRegistry.getOrCreateType(d.keyType), valueType: this.objectRegistry.getOrCreateType(d.valueType), isReadonly: d.isReadonly ?? false, + declaration: d.declaration ? new NodeHandle(d.declaration) : undefined, })); } diff --git a/_packages/native-preview/src/api/sync/types.ts b/_packages/native-preview/src/api/sync/types.ts index e788818d35e..c0acae58146 100644 --- a/_packages/native-preview/src/api/sync/types.ts +++ b/_packages/native-preview/src/api/sync/types.ts @@ -11,7 +11,10 @@ import type { ElementFlags } from "#enums/elementFlags"; import type { ObjectFlags } from "#enums/objectFlags"; import type { TypeFlags } from "#enums/typeFlags"; import type { TypePredicateKind } from "#enums/typePredicateKind"; -import type { Symbol } from "./api.ts"; +import type { + NodeHandle, + Symbol, +} from "./api.ts"; /** * A TypeScript type. @@ -206,6 +209,8 @@ export interface IndexInfo { readonly valueType: Type; /** Whether the index signature is readonly */ readonly isReadonly: boolean; + /** The index signature declaration, if any */ + readonly declaration?: NodeHandle; } /** diff --git a/internal/api/proto.go b/internal/api/proto.go index c27089fdcda..5b09155d231 100644 --- a/internal/api/proto.go +++ b/internal/api/proto.go @@ -911,9 +911,10 @@ type TypePredicateResponse struct { // IndexInfoResponse represents a single index signature. type IndexInfoResponse struct { - KeyType TypeResponse `json:"keyType"` - ValueType TypeResponse `json:"valueType"` - IsReadonly bool `json:"isReadonly,omitempty"` + KeyType TypeResponse `json:"keyType"` + ValueType TypeResponse `json:"valueType"` + IsReadonly bool `json:"isReadonly,omitempty"` + Declaration NodeHandle `json:"declaration,omitempty"` } // SourceFileResponse contains the binary-encoded AST data for a source file. diff --git a/internal/api/session.go b/internal/api/session.go index bf2f2341ae9..cf5a8fda2a9 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -1932,6 +1932,9 @@ func (s *Session) handleGetIndexInfosOfType(ctx context.Context, params *Checker ValueType: *setup.sd.registerType(info.ValueType()), IsReadonly: info.IsReadonly(), } + if info.Declaration() != nil { + results[i].Declaration = setup.sd.nodeHandleFrom(info.Declaration()) + } } return results, nil diff --git a/internal/checker/types.go b/internal/checker/types.go index eeb17751cd8..bf728413f13 100644 --- a/internal/checker/types.go +++ b/internal/checker/types.go @@ -1378,6 +1378,10 @@ func (info *IndexInfo) IsReadonly() bool { return info.isReadonly } +func (info *IndexInfo) Declaration() *ast.Node { + return info.declaration +} + /** * Ternary values are defined such that * x & y picks the lesser in the order False < Unknown < Maybe < True, and From de1bb2e5b0d4511a1aedd83fe583d6d39a3fbadb Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 21:05:35 +0000 Subject: [PATCH 09/49] LSP: accept null initializationOptions (#4281) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/fourslash/fourslash.go | 6 ++- internal/lsp/lsproto/_generate/generate.mts | 12 +++++- internal/lsp/lsproto/lsp_generated.go | 35 ++++++++++++++-- internal/lsp/lsproto/lsp_json_test.go | 5 +++ internal/lsp/server.go | 44 ++++++++++++--------- 5 files changed, 76 insertions(+), 26 deletions(-) diff --git a/internal/fourslash/fourslash.go b/internal/fourslash/fourslash.go index 7401fac7825..e1b51d15438 100644 --- a/internal/fourslash/fourslash.go +++ b/internal/fourslash/fourslash.go @@ -319,8 +319,10 @@ const showCodeLensLocationsCommandName = "typescript.showCodeLensLocations" func (f *FourslashTest) initialize(t *testing.T, capabilities *lsproto.ClientCapabilities) { params := &lsproto.InitializeParams{ Locale: new("en-US"), - InitializationOptions: &lsproto.InitializationOptions{ - CodeLensShowLocationsCommandName: new(showCodeLensLocationsCommandName), + InitializationOptions: &lsproto.InitializationOptionsOrNull{ + InitializationOptions: &lsproto.InitializationOptions{ + CodeLensShowLocationsCommandName: new(showCodeLensLocationsCommandName), + }, }, } params.Capabilities = getCapabilitiesWithDefaults(capabilities) diff --git a/internal/lsp/lsproto/_generate/generate.mts b/internal/lsp/lsproto/_generate/generate.mts index d271848be35..7ca90a895b9 100755 --- a/internal/lsp/lsproto/_generate/generate.mts +++ b/internal/lsp/lsproto/_generate/generate.mts @@ -1017,9 +1017,17 @@ function patchAndPreprocessModel() { } for (const prop of structure.properties) { - // Replace initializationOptions type with custom InitializationOptions + // Replace initializationOptions type with custom InitializationOptions. + // The spec types this field as LSPAny?, which includes null, so keep + // it nullable so a null value sent by loose clients is accepted. if (prop.name === "initializationOptions" && prop.type.kind === "reference" && prop.type.name === "LSPAny") { - prop.type = { kind: "reference", name: "InitializationOptions" }; + prop.type = { + kind: "or", + items: [ + { kind: "reference", name: "InitializationOptions" }, + { kind: "base", name: "null" }, + ], + }; } // Replace Data *any fields with custom typed Data fields diff --git a/internal/lsp/lsproto/lsp_generated.go b/internal/lsp/lsproto/lsp_generated.go index 5420b773d99..ce399920a81 100644 --- a/internal/lsp/lsproto/lsp_generated.go +++ b/internal/lsp/lsproto/lsp_generated.go @@ -6353,7 +6353,7 @@ type InitializeParams struct { Capabilities *ClientCapabilities `json:"capabilities"` // User provided initialization options. - InitializationOptions *InitializationOptions `json:"initializationOptions,omitzero"` + InitializationOptions *InitializationOptionsOrNull `json:"initializationOptions,omitzero"` // The initial trace setting. If omitted trace is disabled ('off'). Trace *TraceValue `json:"trace,omitzero"` @@ -6436,9 +6436,6 @@ func (s *InitializeParams) UnmarshalJSONFrom(dec *json.Decoder) error { return err } case `"initializationOptions"`: - if dec.PeekKind() == 'n' { - return errNull("initializationOptions") - } if err := json.UnmarshalDecode(dec, &s.InitializationOptions); err != nil { return err } @@ -33114,6 +33111,36 @@ func (o *DocumentUriOrNull) UnmarshalJSONFrom(dec *json.Decoder) error { } } +type InitializationOptionsOrNull struct { + InitializationOptions *InitializationOptions +} + +var _ json.MarshalerTo = (*InitializationOptionsOrNull)(nil) + +func (o *InitializationOptionsOrNull) MarshalJSONTo(enc *json.Encoder) error { + if o.InitializationOptions != nil { + return json.MarshalEncode(enc, o.InitializationOptions) + } + return enc.WriteToken(json.Null) +} + +var _ json.UnmarshalerFrom = (*InitializationOptionsOrNull)(nil) + +func (o *InitializationOptionsOrNull) UnmarshalJSONFrom(dec *json.Decoder) error { + *o = InitializationOptionsOrNull{} + + switch dec.PeekKind() { + case 'n': + _, err := dec.ReadToken() + return err + case '{': + o.InitializationOptions = new(InitializationOptions) + return json.UnmarshalDecode(dec, o.InitializationOptions) + default: + return errInvalidKind("InitializationOptionsOrNull", dec.PeekKind()) + } +} + type WorkspaceFoldersOrNull struct { WorkspaceFolders *[]*WorkspaceFolder } diff --git a/internal/lsp/lsproto/lsp_json_test.go b/internal/lsp/lsproto/lsp_json_test.go index a4b67014fc3..f09fafab0d8 100644 --- a/internal/lsp/lsproto/lsp_json_test.go +++ b/internal/lsp/lsproto/lsp_json_test.go @@ -140,6 +140,11 @@ func TestUnmarshalAcceptsNullForNullableFields(t *testing.T) { input: `{"userPreferences": null}`, target: new(InitializationOptions), }, + { + name: "InitializeParams initializationOptions null", + input: `{"processId": null, "rootUri": null, "capabilities": {}, "initializationOptions": null}`, + target: new(InitializeParams), + }, } for _, tt := range tests { diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 5c4b93d775d..7aac5f263d5 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -170,10 +170,11 @@ type Server struct { defaultLibraryPath string typingsLocation string - initializeParams *lsproto.InitializeParams - clientCapabilities lsproto.ResolvedClientCapabilities - positionEncoding lsproto.PositionEncodingKind - locale locale.Locale + initializeParams *lsproto.InitializeParams + initializationOptions *lsproto.InitializationOptions + clientCapabilities lsproto.ResolvedClientCapabilities + positionEncoding lsproto.PositionEncodingKind + locale locale.Locale watchEnabled bool telemetryEnabled bool @@ -339,13 +340,14 @@ func (s *Server) ProgressFinish(message *diagnostics.Message, args ...any) { func (s *Server) RequestConfiguration(ctx context.Context) (lsutil.UserPreferences, error) { caps := lsproto.GetClientCapabilities(ctx) if !caps.Workspace.Configuration { - if s.initializeParams != nil && s.initializeParams.InitializationOptions != nil && s.initializeParams.InitializationOptions.UserPreferences != nil { + if opts := s.initializationOptions; opts.UserPreferences != nil { + userPrefs := *opts.UserPreferences s.logger.Logf( "received formatting options from initialization: %T\n%+v", - *s.initializeParams.InitializationOptions.UserPreferences, - *s.initializeParams.InitializationOptions.UserPreferences, + userPrefs, + userPrefs, ) - if config, ok := (*s.initializeParams.InitializationOptions.UserPreferences).(map[string]any); ok { + if config, ok := userPrefs.(map[string]any); ok { return lsutil.ParseUserPreferences(map[string]any{"js/ts": config}), nil } } @@ -994,8 +996,16 @@ func (s *Server) handleInitialize(ctx context.Context, params *lsproto.Initializ s.initStarted.Store(true) s.initializeParams = params - if params.InitializationOptions != nil && params.InitializationOptions.LogVerbosity != nil { - if v := *params.InitializationOptions.LogVerbosity; isValidLogVerbosity(v) { + // The spec types initializationOptions as nullable; treat both null and an + // absent value as empty options so the rest of the server can read fields + // off s.initializationOptions without nil-checking the container. + if params.InitializationOptions != nil && params.InitializationOptions.InitializationOptions != nil { + s.initializationOptions = params.InitializationOptions.InitializationOptions + } else { + s.initializationOptions = &lsproto.InitializationOptions{} + } + if s.initializationOptions.LogVerbosity != nil { + if v := *s.initializationOptions.LogVerbosity; isValidLogVerbosity(v) { s.logger.SetVerbosity(v) } } @@ -1175,13 +1185,11 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali var disablePushDiagnostics bool var enableTelemetry bool - if s.initializeParams != nil && s.initializeParams.InitializationOptions != nil { - if s.initializeParams.InitializationOptions.DisablePushDiagnostics != nil { - disablePushDiagnostics = *s.initializeParams.InitializationOptions.DisablePushDiagnostics - } - if s.initializeParams.InitializationOptions.EnableTelemetry != nil { - enableTelemetry = *s.initializeParams.InitializationOptions.EnableTelemetry - } + if s.initializationOptions.DisablePushDiagnostics != nil { + disablePushDiagnostics = *s.initializationOptions.DisablePushDiagnostics + } + if s.initializationOptions.EnableTelemetry != nil { + enableTelemetry = *s.initializationOptions.EnableTelemetry } s.telemetryEnabled = enableTelemetry @@ -1623,7 +1631,7 @@ func (s *Server) handleCodeLensResolve(ctx context.Context, codeLens *lsproto.Co return defaultLs.ResolveCodeLens( ctx, codeLens, - s.initializeParams.InitializationOptions.CodeLensShowLocationsCommandName, + s.initializationOptions.CodeLensShowLocationsCommandName, orchestrator, ) } From 4a461b7967fd1585447f1511b56ff0943cd9009f Mon Sep 17 00:00:00 2001 From: Piotr Tomiak Date: Thu, 11 Jun 2026 23:05:37 +0200 Subject: [PATCH 10/49] TS GO API: add getCompletionsAtPosition to the API and support includeSymbols (#4263) --- Herebyfile.mjs | 1 + _packages/native-preview/src/api/async/api.ts | 28 ++- .../native-preview/src/api/async/types.ts | 32 +++ _packages/native-preview/src/api/proto.ts | 22 ++ _packages/native-preview/src/api/sync/api.ts | 28 ++- .../native-preview/src/api/sync/types.ts | 32 +++ .../src/enums/completionItemKind.enum.ts | 29 +++ .../src/enums/completionItemKind.ts | 29 +++ .../native-preview/test/async/api.test.ts | 83 +++++++ .../native-preview/test/sync/api.test.ts | 83 +++++++ internal/api/proto.go | 38 ++++ internal/api/session.go | 55 +++++ internal/ls/completions.go | 204 ++++++++++++------ internal/ls/string_completions.go | 30 ++- 14 files changed, 609 insertions(+), 85 deletions(-) create mode 100644 _packages/native-preview/src/enums/completionItemKind.enum.ts create mode 100644 _packages/native-preview/src/enums/completionItemKind.ts diff --git a/Herebyfile.mjs b/Herebyfile.mjs index a8650f7d29f..93dee2dcc1d 100644 --- a/Herebyfile.mjs +++ b/Herebyfile.mjs @@ -331,6 +331,7 @@ const enumDefs = [ { name: "ModifierFlags", goPrefix: "ModifierFlags", goFile: "internal/ast/modifierflags.go", outDir: "_packages/native-preview/src/enums" }, { name: "TokenFlags", goPrefix: "TokenFlags", goFile: "internal/ast/tokenflags.go", outDir: "_packages/native-preview/src/enums" }, { name: "NodeBuilderFlags", goPrefix: "Flags", goFile: "internal/nodebuilder/types.go", outDir: "_packages/native-preview/src/enums" }, + { name: "CompletionItemKind", goPrefix: "CompletionItemKind", goFile: "internal/lsp/lsproto/lsp_generated.go", outDir: "_packages/native-preview/src/enums" }, ]; /** diff --git a/_packages/native-preview/src/api/async/api.ts b/_packages/native-preview/src/api/async/api.ts index 3d26ec92455..5c87a9dda4a 100644 --- a/_packages/native-preview/src/api/async/api.ts +++ b/_packages/native-preview/src/api/async/api.ts @@ -1,4 +1,5 @@ /// +import { CompletionItemKind } from "#enums/completionItemKind"; import { DiagnosticCategory } from "#enums/diagnosticCategory"; import { ElementFlags } from "#enums/elementFlags"; import { NodeBuilderFlags } from "#enums/nodeBuilderFlags"; @@ -40,6 +41,7 @@ import { toPath, } from "../path.ts"; import type { + CompletionInfoResponse, ConfigResponse, DocumentIdentifier, DocumentPosition, @@ -65,6 +67,9 @@ import { import type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, + CompletionEntry, + CompletionInfo, + CompletionOptions, ConditionalType, Diagnostic, FreshableType, @@ -91,9 +96,9 @@ import type { UnionType, } from "./types.ts"; -export { DiagnosticCategory, ElementFlags, ModifierFlags, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind }; +export { CompletionItemKind, DiagnosticCategory, ElementFlags, ModifierFlags, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind }; export type { APIOptions, ClientSocketOptions, ClientSpawnOptions, DocumentIdentifier, DocumentPosition, LSPConnectionOptions }; -export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; +export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts"; /** Type alias for the snapshot-scoped object registry */ @@ -578,6 +583,25 @@ export class Checker { })); } + async getCompletionsAtPosition(document: string, position: number, options?: CompletionOptions): Promise { + const data = await this.client.apiRequest("getCompletionsAtPosition", { + snapshot: this.snapshotId, + project: this.projectId, + file: document, + position, + triggerCharacter: options?.triggerCharacter, + includeSymbol: options?.includeSymbol, + }); + if (!data) return undefined; + return { + isIncomplete: data.isIncomplete, + entries: data.entries.map(e => ({ + ...e, + symbol: e.symbol ? this.objectRegistry.getOrCreateSymbol(e.symbol) : undefined, + })), + }; + } + getTypeAtLocation(node: Node): Promise; getTypeAtLocation(nodes: readonly Node[]): Promise<(Type | undefined)[]>; async getTypeAtLocation(nodeOrNodes: Node | readonly Node[]): Promise { diff --git a/_packages/native-preview/src/api/async/types.ts b/_packages/native-preview/src/api/async/types.ts index 66a5184af11..d5c2f97ac65 100644 --- a/_packages/native-preview/src/api/async/types.ts +++ b/_packages/native-preview/src/api/async/types.ts @@ -1,3 +1,4 @@ +import type { CompletionItemKind } from "#enums/completionItemKind"; import type { DiagnosticCategory } from "#enums/diagnosticCategory"; import type { ElementFlags } from "#enums/elementFlags"; import type { ObjectFlags } from "#enums/objectFlags"; @@ -205,6 +206,37 @@ export interface IndexInfo { readonly declaration?: NodeHandle; } +export interface CompletionEntryLabelDetails { + detail?: string; + description?: string; +} + +/** Options for {@link Checker.getCompletionsAtPosition}. */ +export interface CompletionOptions { + triggerCharacter?: string; + /** Include a `symbol` property on each completion entry. Only populated for symbol-based completions (not keywords or literals). */ + includeSymbol?: boolean; +} + +/** A single completion item returned by {@link Checker.getCompletionsAtPosition}. */ +export interface CompletionEntry { + readonly name: string; + readonly kind?: CompletionItemKind; + readonly sortText?: string; + readonly insertText?: string; + readonly filterText?: string; + readonly detail?: string; + readonly labelDetails?: CompletionEntryLabelDetails; + /** The symbol associated with this completion entry. Only set when `includeSymbol: true` is passed and a symbol is available. */ + readonly symbol?: Symbol; +} + +/** The result of {@link Checker.getCompletionsAtPosition}. */ +export interface CompletionInfo { + readonly isIncomplete: boolean; + readonly entries: readonly CompletionEntry[]; +} + /** * A diagnostic message from the TypeScript compiler. */ diff --git a/_packages/native-preview/src/api/proto.ts b/_packages/native-preview/src/api/proto.ts index bc501126f1e..706f3b4750b 100644 --- a/_packages/native-preview/src/api/proto.ts +++ b/_packages/native-preview/src/api/proto.ts @@ -1,3 +1,4 @@ +import type { CompletionItemKind } from "#enums/completionItemKind"; import { documentURIToFileName, fileNameToDocumentURI, @@ -194,3 +195,24 @@ export interface ProfileParams { export interface ProfileResult { file: string; } + +export interface CompletionEntryLabelDetailsResponse { + detail?: string; + description?: string; +} + +export interface CompletionEntryResponse { + name: string; + kind?: CompletionItemKind; + sortText?: string; + insertText?: string; + filterText?: string; + detail?: string; + labelDetails?: CompletionEntryLabelDetailsResponse; + symbol?: SymbolResponse; +} + +export interface CompletionInfoResponse { + isIncomplete: boolean; + entries: CompletionEntryResponse[]; +} diff --git a/_packages/native-preview/src/api/sync/api.ts b/_packages/native-preview/src/api/sync/api.ts index 8e090615cf4..7baa7d05d43 100644 --- a/_packages/native-preview/src/api/sync/api.ts +++ b/_packages/native-preview/src/api/sync/api.ts @@ -7,6 +7,7 @@ // Regenerate: npm run generate (from _packages/native-preview) // /// +import { CompletionItemKind } from "#enums/completionItemKind"; import { DiagnosticCategory } from "#enums/diagnosticCategory"; import { ElementFlags } from "#enums/elementFlags"; import { NodeBuilderFlags } from "#enums/nodeBuilderFlags"; @@ -48,6 +49,7 @@ import { toPath, } from "../path.ts"; import type { + CompletionInfoResponse, ConfigResponse, DocumentIdentifier, DocumentPosition, @@ -73,6 +75,9 @@ import { import type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, + CompletionEntry, + CompletionInfo, + CompletionOptions, ConditionalType, Diagnostic, FreshableType, @@ -99,9 +104,9 @@ import type { UnionType, } from "./types.ts"; -export { DiagnosticCategory, ElementFlags, ModifierFlags, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind }; +export { CompletionItemKind, DiagnosticCategory, ElementFlags, ModifierFlags, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind }; export type { APIOptions, ClientSocketOptions, ClientSpawnOptions, DocumentIdentifier, DocumentPosition, LSPConnectionOptions }; -export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; +export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts"; /** Type alias for the snapshot-scoped object registry */ @@ -586,6 +591,25 @@ export class Checker { })); } + getCompletionsAtPosition(document: string, position: number, options?: CompletionOptions): CompletionInfo | undefined { + const data = this.client.apiRequest("getCompletionsAtPosition", { + snapshot: this.snapshotId, + project: this.projectId, + file: document, + position, + triggerCharacter: options?.triggerCharacter, + includeSymbol: options?.includeSymbol, + }); + if (!data) return undefined; + return { + isIncomplete: data.isIncomplete, + entries: data.entries.map(e => ({ + ...e, + symbol: e.symbol ? this.objectRegistry.getOrCreateSymbol(e.symbol) : undefined, + })), + }; + } + getTypeAtLocation(node: Node): Type | undefined; getTypeAtLocation(nodes: readonly Node[]): (Type | undefined)[]; getTypeAtLocation(nodeOrNodes: Node | readonly Node[]): Type | (Type | undefined)[] | undefined { diff --git a/_packages/native-preview/src/api/sync/types.ts b/_packages/native-preview/src/api/sync/types.ts index c0acae58146..90226d1bb7b 100644 --- a/_packages/native-preview/src/api/sync/types.ts +++ b/_packages/native-preview/src/api/sync/types.ts @@ -6,6 +6,7 @@ // Source: src/api/async/types.ts // Regenerate: npm run generate (from _packages/native-preview) // +import type { CompletionItemKind } from "#enums/completionItemKind"; import type { DiagnosticCategory } from "#enums/diagnosticCategory"; import type { ElementFlags } from "#enums/elementFlags"; import type { ObjectFlags } from "#enums/objectFlags"; @@ -213,6 +214,37 @@ export interface IndexInfo { readonly declaration?: NodeHandle; } +export interface CompletionEntryLabelDetails { + detail?: string; + description?: string; +} + +/** Options for {@link Checker.getCompletionsAtPosition}. */ +export interface CompletionOptions { + triggerCharacter?: string; + /** Include a `symbol` property on each completion entry. Only populated for symbol-based completions (not keywords or literals). */ + includeSymbol?: boolean; +} + +/** A single completion item returned by {@link Checker.getCompletionsAtPosition}. */ +export interface CompletionEntry { + readonly name: string; + readonly kind?: CompletionItemKind; + readonly sortText?: string; + readonly insertText?: string; + readonly filterText?: string; + readonly detail?: string; + readonly labelDetails?: CompletionEntryLabelDetails; + /** The symbol associated with this completion entry. Only set when `includeSymbol: true` is passed and a symbol is available. */ + readonly symbol?: Symbol; +} + +/** The result of {@link Checker.getCompletionsAtPosition}. */ +export interface CompletionInfo { + readonly isIncomplete: boolean; + readonly entries: readonly CompletionEntry[]; +} + /** * A diagnostic message from the TypeScript compiler. */ diff --git a/_packages/native-preview/src/enums/completionItemKind.enum.ts b/_packages/native-preview/src/enums/completionItemKind.enum.ts new file mode 100644 index 00000000000..c5a4140c457 --- /dev/null +++ b/_packages/native-preview/src/enums/completionItemKind.enum.ts @@ -0,0 +1,29 @@ +// Code generated by Herebyfile.mjs generate:enums from internal/lsp/lsproto/lsp_generated.go. DO NOT EDIT. + +export enum CompletionItemKind { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25, +} diff --git a/_packages/native-preview/src/enums/completionItemKind.ts b/_packages/native-preview/src/enums/completionItemKind.ts new file mode 100644 index 00000000000..cb1e5d9aa31 --- /dev/null +++ b/_packages/native-preview/src/enums/completionItemKind.ts @@ -0,0 +1,29 @@ +// Code generated by Herebyfile.mjs generate:enums from internal/lsp/lsproto/lsp_generated.go. DO NOT EDIT. +export var CompletionItemKind: any; +(function (CompletionItemKind) { + CompletionItemKind[CompletionItemKind["Text"] = 1] = "Text"; + CompletionItemKind[CompletionItemKind["Method"] = 2] = "Method"; + CompletionItemKind[CompletionItemKind["Function"] = 3] = "Function"; + CompletionItemKind[CompletionItemKind["Constructor"] = 4] = "Constructor"; + CompletionItemKind[CompletionItemKind["Field"] = 5] = "Field"; + CompletionItemKind[CompletionItemKind["Variable"] = 6] = "Variable"; + CompletionItemKind[CompletionItemKind["Class"] = 7] = "Class"; + CompletionItemKind[CompletionItemKind["Interface"] = 8] = "Interface"; + CompletionItemKind[CompletionItemKind["Module"] = 9] = "Module"; + CompletionItemKind[CompletionItemKind["Property"] = 10] = "Property"; + CompletionItemKind[CompletionItemKind["Unit"] = 11] = "Unit"; + CompletionItemKind[CompletionItemKind["Value"] = 12] = "Value"; + CompletionItemKind[CompletionItemKind["Enum"] = 13] = "Enum"; + CompletionItemKind[CompletionItemKind["Keyword"] = 14] = "Keyword"; + CompletionItemKind[CompletionItemKind["Snippet"] = 15] = "Snippet"; + CompletionItemKind[CompletionItemKind["Color"] = 16] = "Color"; + CompletionItemKind[CompletionItemKind["File"] = 17] = "File"; + CompletionItemKind[CompletionItemKind["Reference"] = 18] = "Reference"; + CompletionItemKind[CompletionItemKind["Folder"] = 19] = "Folder"; + CompletionItemKind[CompletionItemKind["EnumMember"] = 20] = "EnumMember"; + CompletionItemKind[CompletionItemKind["Constant"] = 21] = "Constant"; + CompletionItemKind[CompletionItemKind["Struct"] = 22] = "Struct"; + CompletionItemKind[CompletionItemKind["Event"] = 23] = "Event"; + CompletionItemKind[CompletionItemKind["Operator"] = 24] = "Operator"; + CompletionItemKind[CompletionItemKind["TypeParameter"] = 25] = "TypeParameter"; +})(CompletionItemKind || (CompletionItemKind = {})); diff --git a/_packages/native-preview/test/async/api.test.ts b/_packages/native-preview/test/async/api.test.ts index 996b507acb2..ae2fe5c7af9 100644 --- a/_packages/native-preview/test/async/api.test.ts +++ b/_packages/native-preview/test/async/api.test.ts @@ -2407,6 +2407,89 @@ describe("Checker - isTypeAssignableTo", () => { }); }); +describe("Checker - getCompletionsAtPosition", () => { + test("returns member completions after a dot", async () => { + const src = `\nconst obj = { name: "hello", age: 42 };\nobj.\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + // Position right after "obj." — member completion trigger + const pos = src.indexOf("obj.") + "obj.".length; + const completions = await project.checker.getCompletionsAtPosition("/src/main.ts", pos, { triggerCharacter: "." }); + assert.ok(completions, "Expected completions to be returned"); + assert.ok(completions.entries.length > 0, "Expected at least one completion entry"); + assert.ok(completions.entries.some(e => e.name === "name"), "Expected 'name' property in completions"); + assert.ok(completions.entries.some(e => e.name === "age"), "Expected 'age' property in completions"); + assert.ok(completions.entries.every(e => e.symbol === undefined), "Expected no symbol information"); + } + finally { + await api.close(); + } + }); + + test("completion entries include sortText", async () => { + const src = `\nconst obj = { value: 1 };\nobj.\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("obj.") + "obj.".length; + const completions = await project.checker.getCompletionsAtPosition("/src/main.ts", pos, { triggerCharacter: "." }); + assert.ok(completions); + assert.ok(completions.entries.length > 0); + assert.ok(completions.entries.some(e => e.sortText !== undefined), "Expected sortText on all entries"); + } + finally { + await api.close(); + } + }); + + test("returns undefined for a non-existent file", async () => { + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": `export {};`, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const completions = await project.checker.getCompletionsAtPosition("/src/does-not-exist.ts", 0); + assert.equal(completions, undefined, "Expected undefined for non-existent file"); + } + finally { + await api.close(); + } + }); + + test("includeSymbol: true populates symbol on property completions", async () => { + const src = `\nconst obj = { name: "hello", age: 42 };\nobj.\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("obj.") + "obj.".length; + const completions = await project.checker.getCompletionsAtPosition("/src/main.ts", pos, { triggerCharacter: ".", includeSymbol: true }); + assert.ok(completions, "Expected completions"); + const nameEntry = completions.entries.find(e => e.name === "name"); + assert.ok(nameEntry, "Expected 'name' entry"); + assert.ok(nameEntry.symbol, "Expected symbol to be set on 'name' entry when includeSymbol: true"); + assert.equal(nameEntry.symbol.name, "name", "Symbol name should match completion name"); + } + finally { + await api.close(); + } + }); +}); + describe("Emitter - printNode", () => { const emitterFiles = { "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), diff --git a/_packages/native-preview/test/sync/api.test.ts b/_packages/native-preview/test/sync/api.test.ts index ef0a2430afe..8aeaa64bdb1 100644 --- a/_packages/native-preview/test/sync/api.test.ts +++ b/_packages/native-preview/test/sync/api.test.ts @@ -2415,6 +2415,89 @@ describe("Checker - isTypeAssignableTo", () => { }); }); +describe("Checker - getCompletionsAtPosition", () => { + test("returns member completions after a dot", () => { + const src = `\nconst obj = { name: "hello", age: 42 };\nobj.\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + // Position right after "obj." — member completion trigger + const pos = src.indexOf("obj.") + "obj.".length; + const completions = project.checker.getCompletionsAtPosition("/src/main.ts", pos, { triggerCharacter: "." }); + assert.ok(completions, "Expected completions to be returned"); + assert.ok(completions.entries.length > 0, "Expected at least one completion entry"); + assert.ok(completions.entries.some(e => e.name === "name"), "Expected 'name' property in completions"); + assert.ok(completions.entries.some(e => e.name === "age"), "Expected 'age' property in completions"); + assert.ok(completions.entries.every(e => e.symbol === undefined), "Expected no symbol information"); + } + finally { + api.close(); + } + }); + + test("completion entries include sortText", () => { + const src = `\nconst obj = { value: 1 };\nobj.\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("obj.") + "obj.".length; + const completions = project.checker.getCompletionsAtPosition("/src/main.ts", pos, { triggerCharacter: "." }); + assert.ok(completions); + assert.ok(completions.entries.length > 0); + assert.ok(completions.entries.some(e => e.sortText !== undefined), "Expected sortText on all entries"); + } + finally { + api.close(); + } + }); + + test("returns undefined for a non-existent file", () => { + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": `export {};`, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const completions = project.checker.getCompletionsAtPosition("/src/does-not-exist.ts", 0); + assert.equal(completions, undefined, "Expected undefined for non-existent file"); + } + finally { + api.close(); + } + }); + + test("includeSymbol: true populates symbol on property completions", () => { + const src = `\nconst obj = { name: "hello", age: 42 };\nobj.\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const pos = src.indexOf("obj.") + "obj.".length; + const completions = project.checker.getCompletionsAtPosition("/src/main.ts", pos, { triggerCharacter: ".", includeSymbol: true }); + assert.ok(completions, "Expected completions"); + const nameEntry = completions.entries.find(e => e.name === "name"); + assert.ok(nameEntry, "Expected 'name' entry"); + assert.ok(nameEntry.symbol, "Expected symbol to be set on 'name' entry when includeSymbol: true"); + assert.equal(nameEntry.symbol.name, "name", "Symbol name should match completion name"); + } + finally { + api.close(); + } + }); +}); + describe("Emitter - printNode", () => { const emitterFiles = { "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), diff --git a/internal/api/proto.go b/internal/api/proto.go index 5b09155d231..8951fb6481b 100644 --- a/internal/api/proto.go +++ b/internal/api/proto.go @@ -127,6 +127,9 @@ const ( MethodGetReferencedSymbolsForNode Method = "getReferencedSymbolsForNode" MethodGetSignatureUsages Method = "getSignatureUsages" + // Language service methods + MethodGetCompletionsAtPosition Method = "getCompletionsAtPosition" + // Diagnostic methods MethodGetSyntacticDiagnostics Method = "getSyntacticDiagnostics" MethodGetSemanticDiagnostics Method = "getSemanticDiagnostics" @@ -361,6 +364,7 @@ var unmarshalers = map[Method]func([]byte) (any, error){ MethodGetReferencesToSymbolInFile: unmarshallerFor[GetReferencesToSymbolInFileParams], MethodGetReferencedSymbolsForNode: unmarshallerFor[GetReferencedSymbolsForNodeParams], MethodGetSignatureUsages: unmarshallerFor[GetSignatureUsagesParams], + MethodGetCompletionsAtPosition: unmarshallerFor[GetCompletionsAtPositionParams], MethodPrintNode: unmarshallerFor[PrintNodeParams], MethodGetAnyType: unmarshallerFor[GetIntrinsicTypeParams], MethodGetStringType: unmarshallerFor[GetIntrinsicTypeParams], @@ -764,6 +768,40 @@ type SignatureUsageResponse struct { Call NodeHandle `json:"call,omitempty"` } +// GetCompletionsAtPositionParams are the parameters for the getCompletionsAtPosition method. +type GetCompletionsAtPositionParams struct { + Snapshot SnapshotID `json:"snapshot"` + Project ProjectID `json:"project"` + File DocumentIdentifier `json:"file"` + Position uint32 `json:"position"` + TriggerCharacter *string `json:"triggerCharacter,omitempty"` + IncludeSymbol bool `json:"includeSymbol,omitempty"` +} + +// CompletionEntryLabelDetailsResponse holds additional label display text for a completion entry. +type CompletionEntryLabelDetailsResponse struct { + Detail *string `json:"detail,omitempty"` + Description *string `json:"description,omitempty"` +} + +// CompletionEntryResponse represents a single completion item. +type CompletionEntryResponse struct { + Name string `json:"name"` + Kind uint32 `json:"kind,omitempty"` + SortText *string `json:"sortText,omitempty"` + InsertText *string `json:"insertText,omitempty"` + FilterText *string `json:"filterText,omitempty"` + Detail *string `json:"detail,omitempty"` + LabelDetails *CompletionEntryLabelDetailsResponse `json:"labelDetails,omitempty"` + Symbol *SymbolResponse `json:"symbol,omitempty"` +} + +// CompletionInfoResponse wraps a list of completion entries. +type CompletionInfoResponse struct { + IsIncomplete bool `json:"isIncomplete"` + Entries []*CompletionEntryResponse `json:"entries"` +} + // GetIntrinsicTypeParams is used for intrinsic type getters (anyType, stringType, etc.). type GetIntrinsicTypeParams struct { Snapshot SnapshotID `json:"snapshot"` diff --git a/internal/api/session.go b/internal/api/session.go index cf5a8fda2a9..b9ec4ed0d71 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -544,6 +544,8 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json. return s.handleGetReferencedSymbolsForNode(ctx, parsed.(*GetReferencedSymbolsForNodeParams)) case string(MethodGetSignatureUsages): return s.handleGetSignatureUsages(ctx, parsed.(*GetSignatureUsagesParams)) + case string(MethodGetCompletionsAtPosition): + return s.handleGetCompletionsAtPosition(ctx, parsed.(*GetCompletionsAtPositionParams)) default: return nil, fmt.Errorf("unknown method: %s", method) } @@ -2307,6 +2309,59 @@ func (s *Session) handleGetSignatureUsages(ctx context.Context, params *GetSigna return result, nil } +// handleGetCompletionsAtPosition returns completions at a position in a document. +func (s *Session) handleGetCompletionsAtPosition(ctx context.Context, params *GetCompletionsAtPositionParams) (*CompletionInfoResponse, error) { + sd, err := s.getSnapshotData(params.Snapshot) + if err != nil { + return nil, err + } + program, err := sd.getProgram(params.Project) + if err != nil { + return nil, err + } + sourceFile := program.GetSourceFile(params.File.ToFileName()) + if sourceFile == nil { + return nil, nil + } + langSvc, err := s.setupLanguageService(sd, program, params.Project, "") + if err != nil { + return nil, err + } + positionMap := sourceFile.GetPositionMap() + internalPos := positionMap.UTF16ToUTF8(int(params.Position)) + result, err := langSvc.GetCompletionsAtPosition(ctx, sourceFile, internalPos, params.TriggerCharacter, params.IncludeSymbol) + if err != nil || result == nil { + return nil, err + } + entries := make([]*CompletionEntryResponse, 0, len(result.Items)) + for _, item := range result.Items { + entry := &CompletionEntryResponse{ + Name: item.Label, + SortText: item.SortText, + InsertText: item.InsertText, + FilterText: item.FilterText, + Detail: item.Detail, + } + if item.Kind != nil { + entry.Kind = uint32(*item.Kind) + } + if item.LabelDetails != nil { + entry.LabelDetails = &CompletionEntryLabelDetailsResponse{ + Detail: item.LabelDetails.Detail, + Description: item.LabelDetails.Description, + } + } + if item.Symbol != nil { + entry.Symbol = sd.registerSymbol(item.Symbol) + } + entries = append(entries, entry) + } + return &CompletionInfoResponse{ + IsIncomplete: result.IsIncomplete, + Entries: entries, + }, nil +} + // handleGetReferencedSymbolsForNode returns node handles for all references found at a node. func (s *Session) handleGetReferencedSymbolsForNode(ctx context.Context, params *GetReferencedSymbolsForNodeParams) ([]ReferencedSymbolEntry, error) { sd, err := s.getSnapshotData(params.Snapshot) diff --git a/internal/ls/completions.go b/internal/ls/completions.go index b8d1dfeed48..5eb40157301 100644 --- a/internal/ls/completions.go +++ b/internal/ls/completions.go @@ -44,19 +44,36 @@ func (l *LanguageService) ProvideCompletion( } ctx = format.WithFormatCodeSettings(ctx, l.FormatOptions(), l.FormatOptions().NewLineCharacter) position := int(l.converters.LineAndCharacterToPosition(file, LSPPosition)) - completionList, err := l.getCompletionsAtPosition( + completionListInternal, err := l.getCompletionsAtPosition( ctx, file, position, triggerCharacter, + false, /*includeSymbols*/ ) if err != nil { return lsproto.CompletionItemsOrListOrNull{}, err } - completionList = ensureItemData(file.FileName(), position, completionList) + completionList := ensureItemData(file.FileName(), position, completionListInternal.toLSP()) return lsproto.CompletionItemsOrListOrNull{List: completionList}, nil } +func (l *LanguageService) GetCompletionsAtPosition(ctx context.Context, file *ast.SourceFile, position int, triggerCharacter *string, includeSymbols bool) (*CompletionList, error) { + return l.getCompletionsAtPosition(ctx, file, position, triggerCharacter, includeSymbols) +} + +type CompletionItem struct { + *lsproto.CompletionItem + Symbol *ast.Symbol // non-nil for symbol completions when IncludeSymbols is set; nil otherwise +} + +type CompletionList struct { + IsIncomplete bool + ItemDefaults *lsproto.CompletionItemDefaults + ApplyKind *lsproto.CompletionItemApplyKinds + Items []*CompletionItem +} + func ensureItemData(fileName string, pos int, list *lsproto.CompletionList) *lsproto.CompletionList { if list == nil { return nil @@ -106,7 +123,7 @@ type completionDataData struct { } type completionDataKeyword struct { - keywordCompletions []*lsproto.CompletionItem + keywordCompletions []*CompletionItem isNewIdentifierLocation bool } @@ -290,12 +307,31 @@ const ( globalsSearchFail ) +func (l *CompletionList) toLSP() *lsproto.CompletionList { + if l == nil { + return nil + } + items := make([]*lsproto.CompletionItem, 0, len(l.Items)) + for _, entry := range l.Items { + if entry != nil && entry.CompletionItem != nil { + items = append(items, entry.CompletionItem) + } + } + return &lsproto.CompletionList{ + IsIncomplete: l.IsIncomplete, + ItemDefaults: l.ItemDefaults, + ApplyKind: l.ApplyKind, + Items: items, + } +} + func (l *LanguageService) getCompletionsAtPosition( ctx context.Context, file *ast.SourceFile, position int, triggerCharacter *string, -) (*lsproto.CompletionList, error) { + includeSymbols bool, +) (*CompletionList, error) { _, previousToken := getRelevantTokens(position, file) if triggerCharacter != nil && !IsInString(file, position, previousToken) && !isValidTrigger(file, *triggerCharacter, previousToken, position) { return nil, nil @@ -304,9 +340,7 @@ func (l *LanguageService) getCompletionsAtPosition( if triggerCharacter != nil && *triggerCharacter == " " { // `isValidTrigger` ensures we are at `import |` if l.UserPreferences().IncludeCompletionsForImportStatements.IsTrue() { - return &lsproto.CompletionList{ - IsIncomplete: true, - }, nil + return &CompletionList{IsIncomplete: true}, nil } return nil, nil } @@ -325,6 +359,7 @@ func (l *LanguageService) getCompletionsAtPosition( previousToken, checker, compilerOptions, + includeSymbols, ) if stringCompletions != nil { return stringCompletions, nil @@ -363,6 +398,7 @@ func (l *LanguageService) getCompletionsAtPosition( data, position, optionalReplacementSpan, + includeSymbols, ) if err != nil { return nil, err @@ -530,10 +566,12 @@ func (l *LanguageService) getCompletionData( if importStatementCompletionInfo.keywordCompletion != ast.KindUnknown { if importStatementCompletionInfo.isKeywordOnlyCompletion { return &completionDataKeyword{ - keywordCompletions: []*lsproto.CompletionItem{{ - Label: scanner.TokenToString(importStatementCompletionInfo.keywordCompletion), - Kind: new(lsproto.CompletionItemKindKeyword), - SortText: new(string(SortTextGlobalsOrKeywords)), + keywordCompletions: []*CompletionItem{{ + CompletionItem: &lsproto.CompletionItem{ + Label: scanner.TokenToString(importStatementCompletionInfo.keywordCompletion), + Kind: new(lsproto.CompletionItemKindKeyword), + SortText: new(string(SortTextGlobalsOrKeywords)), + }, }}, isNewIdentifierLocation: importStatementCompletionInfo.isNewIdentifierLocation, }, nil @@ -1694,7 +1732,8 @@ func (l *LanguageService) completionInfoFromData( data *completionDataData, position int, optionalReplacementSpan *lsproto.Range, -) (*lsproto.CompletionList, error) { + includeSymbols bool, +) (*CompletionList, error) { keywordFilters := data.keywordFilters isNewIdentifierLocation := data.isNewIdentifierLocation contextToken := data.contextToken @@ -1743,6 +1782,7 @@ func (l *LanguageService) completionInfoFromData( position, file, compilerOptions, + includeSymbols, ) if data.keywordFilters != KeywordCompletionFiltersNone { @@ -1760,14 +1800,14 @@ func (l *LanguageService) completionInfoFromData( for _, keywordEntry := range getContextualKeywords(file, contextToken, position) { if !uniqueNames.Has(keywordEntry.Label) { uniqueNames.Add(keywordEntry.Label) - sortedEntries = append(sortedEntries, keywordEntry) + sortedEntries = append(sortedEntries, &CompletionItem{CompletionItem: keywordEntry}) } } for _, literal := range literals { literalEntry := createCompletionItemForLiteral(file, preferences, literal) uniqueNames.Add(literalEntry.Label) - sortedEntries = append(sortedEntries, literalEntry) + sortedEntries = append(sortedEntries, &CompletionItem{CompletionItem: literalEntry}) } if !isChecked { @@ -1795,7 +1835,7 @@ func (l *LanguageService) completionInfoFromData( return nil, err } if casesItem != nil { - sortedEntries = append(sortedEntries, casesItem) + sortedEntries = append(sortedEntries, &CompletionItem{CompletionItem: casesItem}) } } } @@ -1809,7 +1849,7 @@ func (l *LanguageService) completionInfoFromData( optionalReplacementSpan, ) - return &lsproto.CompletionList{ + return &CompletionList{ IsIncomplete: data.hasUnresolvedAutoImports, ItemDefaults: itemDefaults, Items: sortedEntries, @@ -1824,7 +1864,8 @@ func (l *LanguageService) getCompletionEntriesFromSymbols( position int, file *ast.SourceFile, compilerOptions *core.CompilerOptions, -) (uniqueNames collections.Set[string], sortedEntries []*lsproto.CompletionItem) { + includeSymbols bool, +) (uniqueNames collections.Set[string], sortedEntries []*CompletionItem) { closestSymbolDeclaration := getClosestSymbolDeclaration(data.contextToken, data.location) useSemicolons := lsutil.ProbablyUsesSemicolons(file) isMemberCompletion := isMemberCompletionKind(data.completionKind) @@ -1890,7 +1931,11 @@ func (l *LanguageService) getCompletionEntriesFromSymbols( !(symbol.Parent == nil && !core.Some(symbol.Declarations, func(d *ast.Node) bool { return ast.GetSourceFileOfNode(d) == file })) uniques[name] = shouldShadowLaterSymbols - sortedEntries = append(sortedEntries, entry) + var sym *ast.Symbol + if includeSymbols { + sym = symbol + } + sortedEntries = append(sortedEntries, &CompletionItem{CompletionItem: entry, Symbol: sym}) } for _, autoImport := range data.autoImports { @@ -1944,7 +1989,7 @@ func (l *LanguageService) getCompletionEntriesFromSymbols( if isShadowed, _ := uniques[autoImport.Fix.Name]; !isShadowed { uniques[autoImport.Fix.Name] = false - sortedEntries = append(sortedEntries, entry) + sortedEntries = append(sortedEntries, &CompletionItem{CompletionItem: entry}) } } @@ -3221,16 +3266,19 @@ var ( }) ) -func cloneItems(items []*lsproto.CompletionItem) []*lsproto.CompletionItem { - result := make([]*lsproto.CompletionItem, len(items)) +func cloneItems(items []*lsproto.CompletionItem) []*CompletionItem { + if items == nil { + return nil + } + entries := make([]*CompletionItem, len(items)) for i, item := range items { itemClone := *item - result[i] = &itemClone + entries[i] = &CompletionItem{CompletionItem: &itemClone} } - return result + return entries } -func getKeywordCompletions(keywordFilter KeywordCompletionFilters, filterOutTsOnlyKeywords bool) []*lsproto.CompletionItem { +func getKeywordCompletions(keywordFilter KeywordCompletionFilters, filterOutTsOnlyKeywords bool) []*CompletionItem { if !filterOutTsOnlyKeywords { return cloneItems(getTypescriptKeywordCompletions(keywordFilter)) } @@ -3391,8 +3439,8 @@ func (l *LanguageService) getJSCompletionEntries( file *ast.SourceFile, position int, uniqueNames *collections.Set[string], - sortedEntries []*lsproto.CompletionItem, -) []*lsproto.CompletionItem { + sortedEntries []*CompletionItem, +) []*CompletionItem { nameTable := file.GetNameTable() for name, pos := range nameTable { // Skip identifiers produced only from the current location @@ -3401,11 +3449,13 @@ func (l *LanguageService) getJSCompletionEntries( } if !uniqueNames.Has(name) && scanner.IsIdentifierText(name, core.LanguageVariantStandard) { uniqueNames.Add(name) - sortedEntries = append(sortedEntries, &lsproto.CompletionItem{ - Label: name, - Kind: new(lsproto.CompletionItemKindText), - SortText: new(string(SortTextJavascriptIdentifiers)), - CommitCharacters: new([]string{}), + sortedEntries = append(sortedEntries, &CompletionItem{ + CompletionItem: &lsproto.CompletionItem{ + Label: name, + Kind: new(lsproto.CompletionItemKindText), + SortText: new(string(SortTextJavascriptIdentifiers)), + CommitCharacters: new([]string{}), + }, }) } } @@ -4193,7 +4243,7 @@ func (l *LanguageService) setItemDefaults( ctx context.Context, position int, file *ast.SourceFile, - items []*lsproto.CompletionItem, + items []*CompletionItem, defaultCommitCharacters *[]string, optionalReplacementSpan *lsproto.Range, ) *lsproto.CompletionItemDefaults { @@ -4262,10 +4312,10 @@ func (l *LanguageService) specificKeywordCompletionInfo( ctx context.Context, position int, file *ast.SourceFile, - items []*lsproto.CompletionItem, + items []*CompletionItem, isNewIdentifierLocation bool, optionalReplacementSpan *lsproto.Range, -) *lsproto.CompletionList { +) *CompletionList { defaultCommitCharacters := getDefaultCommitCharacters(isNewIdentifierLocation) itemDefaults := l.setItemDefaults( ctx, @@ -4275,7 +4325,7 @@ func (l *LanguageService) specificKeywordCompletionInfo( &defaultCommitCharacters, optionalReplacementSpan, ) - return &lsproto.CompletionList{ + return &CompletionList{ IsIncomplete: false, ItemDefaults: itemDefaults, Items: items, @@ -4287,7 +4337,7 @@ func (l *LanguageService) getJsxClosingTagCompletion( location *ast.Node, file *ast.SourceFile, position int, -) *lsproto.CompletionList { +) *CompletionList { // We wanna walk up the tree till we find a JSX closing element. jsxClosingElement := ast.FindAncestorOrQuit(location, func(node *ast.Node) ast.FindAncestorResult { switch node.Kind { @@ -4323,7 +4373,7 @@ func (l *LanguageService) getJsxClosingTagCompletion( optionalReplacementSpan := new(l.createLspRangeFromNode(jsxClosingElement.TagName(), file)) defaultCommitCharacters := getDefaultCommitCharacters(false /*isNewIdentifierLocation*/) - item := l.createLSPCompletionItem( + lspItem := l.createLSPCompletionItem( ctx, fullClosingTag, /*name*/ "", /*insertText*/ @@ -4344,7 +4394,10 @@ func (l *LanguageService) getJsxClosingTagCompletion( nil, /*autoImportEntryData*/ // !!! jsx autoimports nil, /*detail*/ ) - items := []*lsproto.CompletionItem{item} + item := &CompletionItem{ + CompletionItem: lspItem, + } + items := []*CompletionItem{item} itemDefaults := l.setItemDefaults( ctx, position, @@ -4354,7 +4407,7 @@ func (l *LanguageService) getJsxClosingTagCompletion( optionalReplacementSpan, ) - return &lsproto.CompletionList{ + return &CompletionList{ IsIncomplete: false, ItemDefaults: itemDefaults, Items: items, @@ -4460,7 +4513,7 @@ func (l *LanguageService) getLabelCompletionsAtPosition( file *ast.SourceFile, position int, optionalReplacementSpan *lsproto.Range, -) *lsproto.CompletionList { +) *CompletionList { items := l.getLabelStatementCompletions(ctx, node, file, position) if len(items) == 0 { return nil @@ -4474,7 +4527,7 @@ func (l *LanguageService) getLabelCompletionsAtPosition( &defaultCommitCharacters, optionalReplacementSpan, ) - return &lsproto.CompletionList{ + return &CompletionList{ IsIncomplete: false, ItemDefaults: itemDefaults, Items: items, @@ -4486,9 +4539,9 @@ func (l *LanguageService) getLabelStatementCompletions( node *ast.BreakOrContinueStatement, file *ast.SourceFile, position int, -) []*lsproto.CompletionItem { +) []*CompletionItem { var uniques collections.Set[string] - var items []*lsproto.CompletionItem + var items []*CompletionItem current := node for current != nil { if ast.IsFunctionLike(current) { @@ -4498,7 +4551,7 @@ func (l *LanguageService) getLabelStatementCompletions( name := current.Label().Text() if !uniques.Has(name) { uniques.Add(name) - items = append(items, l.createLSPCompletionItem( + lspItem := l.createLSPCompletionItem( ctx, name, "", /*insertText*/ @@ -4518,7 +4571,10 @@ func (l *LanguageService) getLabelStatementCompletions( "", /*source*/ nil, /*autoImportEntryData*/ nil, /*detail*/ - )) + ) + items = append(items, &CompletionItem{ + CompletionItem: lspItem, + }) } } current = current.Parent @@ -4895,7 +4951,7 @@ func (l *LanguageService) getCompletionItemDetails( case *completionDataJSDocParameterName: return createSimpleDetails(item, data.Name, docFormat) case *completionDataKeyword: - if core.Some(request.keywordCompletions, func(c *lsproto.CompletionItem) bool { + if core.Some(request.keywordCompletions, func(c *CompletionItem) bool { return c.Label == data.Name }) { return createSimpleDetails(item, data.Name, docFormat) @@ -5300,8 +5356,8 @@ func (l *LanguageService) jsDocCompletionInfo( ctx context.Context, position int, file *ast.SourceFile, - items []*lsproto.CompletionItem, -) *lsproto.CompletionList { + items []*CompletionItem, +) *CompletionList { defaultCommitCharacters := getDefaultCommitCharacters(false /*isNewIdentifierLocation*/) itemDefaults := l.setItemDefaults( ctx, @@ -5311,7 +5367,7 @@ func (l *LanguageService) jsDocCompletionInfo( &defaultCommitCharacters, nil, /*optionalReplacementSpan*/ ) - return &lsproto.CompletionList{ + return &CompletionList{ IsIncomplete: false, ItemDefaults: itemDefaults, Items: items, @@ -5431,11 +5487,11 @@ var jsDocTagCompletionItems = sync.OnceValue(func() []*lsproto.CompletionItem { return items }) -func getJSDocTagNameCompletions() []*lsproto.CompletionItem { +func getJSDocTagNameCompletions() []*CompletionItem { return cloneItems(jsDocTagNameCompletionItems()) } -func getJSDocTagCompletions() []*lsproto.CompletionItem { +func getJSDocTagCompletions() []*CompletionItem { return cloneItems(jsDocTagCompletionItems()) } @@ -5447,7 +5503,7 @@ func getJSDocParameterCompletions( options *core.CompilerOptions, preferences lsutil.UserPreferences, tagNameOnly bool, -) []*lsproto.CompletionItem { +) []*CompletionItem { currentToken := astnav.GetTokenAtPosition(file, position) if !ast.IsJSDocTag(currentToken) && !currentToken.IsJSDoc() { return nil @@ -5482,7 +5538,7 @@ func getJSDocParameterCompletions( } } paramIndex := -1 - return core.MapNonNil(fun.Parameters(), func(param *ast.ParameterDeclarationNode) *lsproto.CompletionItem { + return core.MapNonNil(fun.Parameters(), func(param *ast.ParameterDeclarationNode) *CompletionItem { paramIndex++ if paramIndex < paramTagCount { // This parameter is already annotated. @@ -5525,12 +5581,14 @@ func getJSDocParameterCompletions( } } - return &lsproto.CompletionItem{ - Label: displayText, - Kind: new(lsproto.CompletionItemKindVariable), - SortText: new(string(SortTextLocationPriority)), - InsertText: strPtrTo(snippetText), - InsertTextFormat: core.IfElse(isSnippet, new(lsproto.InsertTextFormatSnippet), nil), + return &CompletionItem{ + CompletionItem: &lsproto.CompletionItem{ + Label: displayText, + Kind: new(lsproto.CompletionItemKindVariable), + SortText: new(string(SortTextLocationPriority)), + InsertText: strPtrTo(snippetText), + InsertTextFormat: core.IfElse(isSnippet, new(lsproto.InsertTextFormatSnippet), nil), + }, } } else if paramIndex == paramTagCount { // Destructuring parameter; do it positionally @@ -5566,12 +5624,14 @@ func getJSDocParameterCompletions( displayText = strings.TrimPrefix(displayText, "@") snippetText = strings.TrimPrefix(snippetText, "@") } - return &lsproto.CompletionItem{ - Label: displayText, - Kind: new(lsproto.CompletionItemKindVariable), - SortText: new(string(SortTextLocationPriority)), - InsertText: strPtrTo(snippetText), - InsertTextFormat: core.IfElse(isSnippet, new(lsproto.InsertTextFormatSnippet), nil), + return &CompletionItem{ + CompletionItem: &lsproto.CompletionItem{ + Label: displayText, + Kind: new(lsproto.CompletionItemKindVariable), + SortText: new(string(SortTextLocationPriority)), + InsertText: strPtrTo(snippetText), + InsertTextFormat: core.IfElse(isSnippet, new(lsproto.InsertTextFormatSnippet), nil), + }, } } return nil @@ -5836,7 +5896,7 @@ func jsDocParamElementWorker( return nil } -func getJSDocParameterNameCompletions(tag *ast.JSDocParameterOrPropertyTag) []*lsproto.CompletionItem { +func getJSDocParameterNameCompletions(tag *ast.JSDocParameterOrPropertyTag) []*CompletionItem { if !ast.IsIdentifier(tag.Name()) { return nil } @@ -5852,7 +5912,7 @@ func getJSDocParameterNameCompletions(tag *ast.JSDocParameterOrPropertyTag) []*l tags = jsDoc.AsJSDoc().Tags.Nodes } - return core.MapNonNil(fn.Parameters(), func(param *ast.ParameterDeclarationNode) *lsproto.CompletionItem { + return core.MapNonNil(fn.Parameters(), func(param *ast.ParameterDeclarationNode) *CompletionItem { if !ast.IsIdentifier(param.Name()) { return nil } @@ -5867,10 +5927,12 @@ func getJSDocParameterNameCompletions(tag *ast.JSDocParameterOrPropertyTag) []*l return nil } - return &lsproto.CompletionItem{ - Label: name, - Kind: new(lsproto.CompletionItemKindVariable), - SortText: new(string(SortTextLocationPriority)), + return &CompletionItem{ + CompletionItem: &lsproto.CompletionItem{ + Label: name, + Kind: new(lsproto.CompletionItemKindVariable), + SortText: new(string(SortTextLocationPriority)), + }, } }) } diff --git a/internal/ls/string_completions.go b/internal/ls/string_completions.go index 9bba30eb9a3..f5240bcf329 100644 --- a/internal/ls/string_completions.go +++ b/internal/ls/string_completions.go @@ -57,7 +57,8 @@ func (l *LanguageService) getStringLiteralCompletions( contextToken *ast.Node, checker *checker.Checker, compilerOptions *core.CompilerOptions, -) *lsproto.CompletionList { + includeSymbols bool, +) *CompletionList { if isInReferenceComment(file, position) { entries := l.getTripleSlashReferenceCompletions(file, position, l.GetProgram(), checker) return l.convertPathCompletions(ctx, entries, file, position) @@ -81,6 +82,7 @@ func (l *LanguageService) getStringLiteralCompletions( position, checker, compilerOptions, + includeSymbols, ) } return nil @@ -94,7 +96,8 @@ func (l *LanguageService) convertStringLiteralCompletions( position int, typeChecker *checker.Checker, options *core.CompilerOptions, -) *lsproto.CompletionList { + includeSymbols bool, +) *CompletionList { if completion == nil { return nil } @@ -121,6 +124,7 @@ func (l *LanguageService) convertStringLiteralCompletions( position, file, options, + includeSymbols, /*includeSymbols*/ ) defaultCommitCharacters := getDefaultCommitCharacters(completion.hasIndexSignature) itemDefaults := l.setItemDefaults( @@ -131,7 +135,7 @@ func (l *LanguageService) convertStringLiteralCompletions( &defaultCommitCharacters, optionalReplacementRange, ) - return &lsproto.CompletionList{ + return &CompletionList{ IsIncomplete: false, ItemDefaults: itemDefaults, Items: items, @@ -146,9 +150,9 @@ func (l *LanguageService) convertStringLiteralCompletions( } else { quoteChar = printer.QuoteCharDoubleQuote } - items := core.Map(completion.types, func(t *checker.StringLiteralType) *lsproto.CompletionItem { + items := core.Map(completion.types, func(t *checker.StringLiteralType) *CompletionItem { name := printer.EscapeString(t.AsLiteralType().Value().(string), quoteChar) - return l.createLSPCompletionItem( + lspItem := l.createLSPCompletionItem( ctx, name, "", /*insertText*/ @@ -169,6 +173,9 @@ func (l *LanguageService) convertStringLiteralCompletions( nil, /*autoImportEntryData*/ nil, /*detail*/ ) + return &CompletionItem{ + CompletionItem: lspItem, + } }) defaultCommitCharacters := getDefaultCommitCharacters(completion.isNewIdentifier) itemDefaults := l.setItemDefaults( @@ -179,7 +186,7 @@ func (l *LanguageService) convertStringLiteralCompletions( &defaultCommitCharacters, nil, /*optionalReplacementSpan*/ ) - return &lsproto.CompletionList{ + return &CompletionList{ IsIncomplete: false, ItemDefaults: itemDefaults, Items: items, @@ -194,10 +201,10 @@ func (l *LanguageService) convertPathCompletions( pathCompletions []*pathCompletion, file *ast.SourceFile, position int, -) *lsproto.CompletionList { +) *CompletionList { isNewIdentifierLocation := true // The user may type in a path that doesn't yet exist, creating a "new identifier" with respect to the collection of identifiers the server is aware of. defaultCommitCharacters := getDefaultCommitCharacters(isNewIdentifierLocation) - items := core.Map(pathCompletions, func(pathCompletion *pathCompletion) *lsproto.CompletionItem { + items := core.Map(pathCompletions, func(pathCompletion *pathCompletion) *CompletionItem { var replacementSpan *lsproto.Range if pathCompletion.textRange != nil { replacementSpan = new(l.createLspRangeFromBounds(pathCompletion.textRange.Pos(), pathCompletion.textRange.End(), file)) @@ -206,7 +213,7 @@ func (l *LanguageService) convertPathCompletions( if !strings.HasSuffix(pathCompletion.name, pathCompletion.extension) { detail += pathCompletion.extension } - return l.createLSPCompletionItem( + lspItem := l.createLSPCompletionItem( ctx, pathCompletion.name, "", /*insertText*/ @@ -227,6 +234,9 @@ func (l *LanguageService) convertPathCompletions( nil, /*autoImportEntryData*/ &detail, ) + return &CompletionItem{ + CompletionItem: lspItem, + } }) itemDefaults := l.setItemDefaults( ctx, @@ -236,7 +246,7 @@ func (l *LanguageService) convertPathCompletions( &defaultCommitCharacters, nil, /*optionalReplacementSpan*/ ) - return &lsproto.CompletionList{ + return &CompletionList{ IsIncomplete: false, ItemDefaults: itemDefaults, Items: items, From 45df665f6f7104648e99a9f002102e9e604de046 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 11 Jun 2026 14:16:39 -0700 Subject: [PATCH 11/49] Dedupe cjs export assignments (#4284) --- internal/checker/nodebuilderimpl.go | 10 ++++ .../transformers/declarations/transform.go | 10 ++++ .../compiler/cjsExportsDuplication.errors.txt | 19 ++++++++ .../compiler/cjsExportsDuplication.js | 20 ++++++++ .../compiler/cjsExportsDuplication.symbols | 36 ++++++++++++++ .../compiler/cjsExportsDuplication.types | 48 +++++++++++++++++++ .../compiler/numericExportNameDeclaration.js | 28 ----------- .../conformance/moduleExportDuplicateAlias.js | 1 - .../moduleExportDuplicateAlias2.js | 2 - .../moduleExportDuplicateAlias3.js | 4 -- .../moduleExportDuplicateAlias.js.diff | 1 - .../moduleExportDuplicateAlias2.js.diff | 2 - .../moduleExportDuplicateAlias3.js.diff | 4 -- .../cases/compiler/cjsExportsDuplication.ts | 15 ++++++ 14 files changed, 158 insertions(+), 42 deletions(-) create mode 100644 testdata/baselines/reference/compiler/cjsExportsDuplication.errors.txt create mode 100644 testdata/baselines/reference/compiler/cjsExportsDuplication.js create mode 100644 testdata/baselines/reference/compiler/cjsExportsDuplication.symbols create mode 100644 testdata/baselines/reference/compiler/cjsExportsDuplication.types create mode 100644 testdata/tests/cases/compiler/cjsExportsDuplication.ts diff --git a/internal/checker/nodebuilderimpl.go b/internal/checker/nodebuilderimpl.go index b326959b260..61aac48c694 100644 --- a/internal/checker/nodebuilderimpl.go +++ b/internal/checker/nodebuilderimpl.go @@ -2166,6 +2166,10 @@ func (b *NodeBuilderImpl) indexInfoToIndexSignatureDeclarationHelper(indexInfo * return b.f.NewIndexSignatureDeclaration(modifiers, b.f.NewNodeList([]*ast.Node{indexingParameter}), typeNode) } +func hasTypeAnnotation(declaration *ast.Declaration) bool { + return declaration != nil && declaration.Type() != nil +} + /** * Unlike `typeToTypeNodeHelper`, this handles setting up the `AllowUniqueESSymbolType` flag * so a `unique symbol` is returned when appropriate for the input symbol, rather than `typeof sym` @@ -2223,6 +2227,12 @@ func (b *NodeBuilderImpl) serializeTypeForDeclaration(declaration *ast.Declarati } else { pt = b.pc.GetTypeOfDeclaration(declaration) } + if (pt == nil || pt.Kind == pseudochecker.PseudoTypeKindNoResult) && ast.IsBinaryExpression(declaration) { + if decl := core.Find(symbol.Declarations, hasTypeAnnotation); decl != nil { + // Binary expressions have a first-in-wins type annotation system. The first one with an annotation supplies the type for the rest. + pt = b.pc.GetTypeOfDeclaration(decl) + } + } reportErrors := !b.ctx.suppressReportInferenceFallback if b.pseudoTypeEquivalentToType(pt, t, !requiresAddingUndefined && (ast.IsParameterDeclaration(declaration) || ast.IsPropertySignatureDeclaration(declaration) || ast.IsPropertyDeclaration(declaration)) && isOptionalDeclaration(declaration), reportErrors) { // !!! TODO: If annotated type node is a reference with insufficient type arguments, we should still fall back to type serialization diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go index 02a133459b6..e3a78ea690a 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -59,6 +59,7 @@ type DeclarationTransformer struct { enclosingDeclaration *ast.Node resultHasExternalModuleIndicator bool suppressNewDiagnosticContexts bool + witnessedCjsExports collections.Set[string] lateStatementReplacementMap map[ast.NodeId]*ast.Node expandoHosts map[ast.NodeId]*ast.Node // store the result of transforming expando hosts so they can be inserted later if the host is actually referenced expandoMembers map[ast.NodeId][]*ast.Node // store any found expando _members_ after transforming them so *if* the host is referenced, they can be emitted alongside it @@ -269,6 +270,7 @@ func (tx *DeclarationTransformer) visitSourceFile(node *ast.SourceFile) *ast.Nod tx.rawReferencedFiles = make([]ReferencedFilePair, 0) tx.rawTypeReferenceDirectives = make([]*ast.FileReference, 0) tx.rawLibReferenceDirectives = make([]*ast.FileReference, 0) + tx.witnessedCjsExports.Clear() tx.state.currentSourceFile = node tx.collectFileReferences(node) tx.resolver.PrecalculateDeclarationEmitVisibility(node) @@ -1128,6 +1130,14 @@ func (tx *DeclarationTransformer) transformExportAssignment(input *ast.Node, ass } func (tx *DeclarationTransformer) transformCommonJSExport(input *ast.Node, name *ast.Node) *ast.Node { + var nameText string + if ast.IsIdentifier(name) || ast.IsStringLiteral(name) { + nameText = name.Text() + } + if tx.witnessedCjsExports.Has(nameText) && nameText != "" { + return nil // Already emitted this export name + } + tx.witnessedCjsExports.Add(nameText) tx.resultHasExternalModuleIndicator = true tx.resultHasScopeMarker = true if isCommonJSAliasExport(input) { diff --git a/testdata/baselines/reference/compiler/cjsExportsDuplication.errors.txt b/testdata/baselines/reference/compiler/cjsExportsDuplication.errors.txt new file mode 100644 index 00000000000..817f67dcd61 --- /dev/null +++ b/testdata/baselines/reference/compiler/cjsExportsDuplication.errors.txt @@ -0,0 +1,19 @@ +file2.js(1,1): error TS2322: Type 'number' is not assignable to type 'string'. +file2.js(5,1): error TS2322: Type 'boolean' is not assignable to type 'string'. + + +==== file.js (0 errors) ==== + exports.foo = 42 + exports.foo = "hello" + exports.foo = true + +==== file2.js (2 errors) ==== + exports.foo = 42 + ~~~~~~~~~~~ +!!! error TS2322: Type 'number' is not assignable to type 'string'. + /** @type {string} */ + exports.foo = "hello" + /** @type {boolean} */ + exports.foo = true + ~~~~~~~~~~~ +!!! error TS2322: Type 'boolean' is not assignable to type 'string'. \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/cjsExportsDuplication.js b/testdata/baselines/reference/compiler/cjsExportsDuplication.js new file mode 100644 index 00000000000..b981d66b519 --- /dev/null +++ b/testdata/baselines/reference/compiler/cjsExportsDuplication.js @@ -0,0 +1,20 @@ +//// [tests/cases/compiler/cjsExportsDuplication.ts] //// + +//// [file.js] +exports.foo = 42 +exports.foo = "hello" +exports.foo = true + +//// [file2.js] +exports.foo = 42 +/** @type {string} */ +exports.foo = "hello" +/** @type {boolean} */ +exports.foo = true + + + +//// [file.d.ts] +export declare var foo: "hello" | 42 | true; +//// [file2.d.ts] +export declare var foo: string; diff --git a/testdata/baselines/reference/compiler/cjsExportsDuplication.symbols b/testdata/baselines/reference/compiler/cjsExportsDuplication.symbols new file mode 100644 index 00000000000..b5ee727da26 --- /dev/null +++ b/testdata/baselines/reference/compiler/cjsExportsDuplication.symbols @@ -0,0 +1,36 @@ +//// [tests/cases/compiler/cjsExportsDuplication.ts] //// + +=== file.js === +exports.foo = 42 +>exports.foo : Symbol(foo, Decl(file.js, 0, 0), Decl(file.js, 0, 16), Decl(file.js, 1, 21)) +>exports : Symbol(exports, Decl(file.js, 0, 0)) +>foo : Symbol(foo, Decl(file.js, 0, 0), Decl(file.js, 0, 16), Decl(file.js, 1, 21)) + +exports.foo = "hello" +>exports.foo : Symbol(foo, Decl(file.js, 0, 0), Decl(file.js, 0, 16), Decl(file.js, 1, 21)) +>exports : Symbol(exports, Decl(file.js, 0, 0)) +>foo : Symbol(foo, Decl(file.js, 0, 0), Decl(file.js, 0, 16), Decl(file.js, 1, 21)) + +exports.foo = true +>exports.foo : Symbol(foo, Decl(file.js, 0, 0), Decl(file.js, 0, 16), Decl(file.js, 1, 21)) +>exports : Symbol(exports, Decl(file.js, 0, 0)) +>foo : Symbol(foo, Decl(file.js, 0, 0), Decl(file.js, 0, 16), Decl(file.js, 1, 21)) + +=== file2.js === +exports.foo = 42 +>exports.foo : Symbol(foo, Decl(file2.js, 0, 0), Decl(file2.js, 0, 16), Decl(file2.js, 2, 21)) +>exports : Symbol(exports, Decl(file2.js, 0, 0)) +>foo : Symbol(foo, Decl(file2.js, 0, 0), Decl(file2.js, 0, 16), Decl(file2.js, 2, 21)) + +/** @type {string} */ +exports.foo = "hello" +>exports.foo : Symbol(foo, Decl(file2.js, 0, 0), Decl(file2.js, 0, 16), Decl(file2.js, 2, 21)) +>exports : Symbol(exports, Decl(file2.js, 0, 0)) +>foo : Symbol(foo, Decl(file2.js, 0, 0), Decl(file2.js, 0, 16), Decl(file2.js, 2, 21)) + +/** @type {boolean} */ +exports.foo = true +>exports.foo : Symbol(foo, Decl(file2.js, 0, 0), Decl(file2.js, 0, 16), Decl(file2.js, 2, 21)) +>exports : Symbol(exports, Decl(file2.js, 0, 0)) +>foo : Symbol(foo, Decl(file2.js, 0, 0), Decl(file2.js, 0, 16), Decl(file2.js, 2, 21)) + diff --git a/testdata/baselines/reference/compiler/cjsExportsDuplication.types b/testdata/baselines/reference/compiler/cjsExportsDuplication.types new file mode 100644 index 00000000000..9381e89b829 --- /dev/null +++ b/testdata/baselines/reference/compiler/cjsExportsDuplication.types @@ -0,0 +1,48 @@ +//// [tests/cases/compiler/cjsExportsDuplication.ts] //// + +=== file.js === +exports.foo = 42 +>exports.foo = 42 : 42 +>exports.foo : "hello" | 42 | true +>exports : typeof import("./file") +>foo : "hello" | 42 | true +>42 : 42 + +exports.foo = "hello" +>exports.foo = "hello" : "hello" +>exports.foo : "hello" | 42 | true +>exports : typeof import("./file") +>foo : "hello" | 42 | true +>"hello" : "hello" + +exports.foo = true +>exports.foo = true : true +>exports.foo : "hello" | 42 | true +>exports : typeof import("./file") +>foo : "hello" | 42 | true +>true : true + +=== file2.js === +exports.foo = 42 +>exports.foo = 42 : 42 +>exports.foo : string +>exports : typeof import("./file2") +>foo : string +>42 : 42 + +/** @type {string} */ +exports.foo = "hello" +>exports.foo = "hello" : "hello" +>exports.foo : string +>exports : typeof import("./file2") +>foo : string +>"hello" : "hello" + +/** @type {boolean} */ +exports.foo = true +>exports.foo = true : true +>exports.foo : string +>exports : typeof import("./file2") +>foo : string +>true : true + diff --git a/testdata/baselines/reference/compiler/numericExportNameDeclaration.js b/testdata/baselines/reference/compiler/numericExportNameDeclaration.js index c8f94b58bc1..c3ebc677323 100644 --- a/testdata/baselines/reference/compiler/numericExportNameDeclaration.js +++ b/testdata/baselines/reference/compiler/numericExportNameDeclaration.js @@ -11,31 +11,3 @@ Object.defineProperty(exports, 1, {}); //// [bug.d.ts] declare const _exported: any; export { _exported as "1" }; -declare const _exported_1: any; -export { _exported_1 as "1" }; -declare const _exported_2: any; -export { _exported_2 as "1" }; - - -//// [DtsFileErrors] - - -bug.d.ts(2,23): error TS2300: Duplicate identifier '"1"'. -bug.d.ts(4,25): error TS2300: Duplicate identifier '"1"'. -bug.d.ts(6,25): error TS2300: Duplicate identifier '"1"'. - - -==== bug.d.ts (3 errors) ==== - declare const _exported: any; - export { _exported as "1" }; - ~~~ -!!! error TS2300: Duplicate identifier '"1"'. - declare const _exported_1: any; - export { _exported_1 as "1" }; - ~~~ -!!! error TS2300: Duplicate identifier '"1"'. - declare const _exported_2: any; - export { _exported_2 as "1" }; - ~~~ -!!! error TS2300: Duplicate identifier '"1"'. - \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/moduleExportDuplicateAlias.js b/testdata/baselines/reference/submodule/conformance/moduleExportDuplicateAlias.js index 9ac69bfce72..a6c581158b8 100644 --- a/testdata/baselines/reference/submodule/conformance/moduleExportDuplicateAlias.js +++ b/testdata/baselines/reference/submodule/conformance/moduleExportDuplicateAlias.js @@ -28,6 +28,5 @@ apply(); //// [moduleExportAliasDuplicateAlias.d.ts] export declare var apply: typeof a; declare function a(): void; -export declare var apply: typeof a; //// [test.d.ts] export {}; diff --git a/testdata/baselines/reference/submodule/conformance/moduleExportDuplicateAlias2.js b/testdata/baselines/reference/submodule/conformance/moduleExportDuplicateAlias2.js index d6db92f560a..4303afd7a42 100644 --- a/testdata/baselines/reference/submodule/conformance/moduleExportDuplicateAlias2.js +++ b/testdata/baselines/reference/submodule/conformance/moduleExportDuplicateAlias2.js @@ -28,7 +28,5 @@ apply(); //// [moduleExportAliasDuplicateAlias.d.ts] export declare var apply: typeof a; declare function a(): void; -export declare var apply: typeof a; -export declare var apply: typeof a; //// [test.d.ts] export {}; diff --git a/testdata/baselines/reference/submodule/conformance/moduleExportDuplicateAlias3.js b/testdata/baselines/reference/submodule/conformance/moduleExportDuplicateAlias3.js index 070ade755f5..1611b199c8c 100644 --- a/testdata/baselines/reference/submodule/conformance/moduleExportDuplicateAlias3.js +++ b/testdata/baselines/reference/submodule/conformance/moduleExportDuplicateAlias3.js @@ -33,10 +33,6 @@ const result = apply.toFixed(); //// [moduleExportAliasDuplicateAlias.d.ts] export declare var apply: "ok" | 1 | typeof a | undefined; -export declare var apply: "ok" | 1 | typeof a | undefined; declare function a(): void; -export declare var apply: "ok" | 1 | typeof a | undefined; -export declare var apply: "ok" | 1 | typeof a | undefined; -export declare var apply: "ok" | 1 | typeof a | undefined; //// [test.d.ts] export {}; diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportDuplicateAlias.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportDuplicateAlias.js.diff index d99a0efd35a..eecff1193c6 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportDuplicateAlias.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportDuplicateAlias.js.diff @@ -7,6 +7,5 @@ -export { a as apply }; +export declare var apply: typeof a; declare function a(): void; -+export declare var apply: typeof a; //// [test.d.ts] export {}; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportDuplicateAlias2.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportDuplicateAlias2.js.diff index a2c0475d9a3..cc720bee7d8 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportDuplicateAlias2.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportDuplicateAlias2.js.diff @@ -7,7 +7,5 @@ -export { a as apply }; +export declare var apply: typeof a; declare function a(): void; -+export declare var apply: typeof a; -+export declare var apply: typeof a; //// [test.d.ts] export {}; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportDuplicateAlias3.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportDuplicateAlias3.js.diff index c7d36bcdb54..2a4e467fce1 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportDuplicateAlias3.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportDuplicateAlias3.js.diff @@ -6,11 +6,7 @@ //// [moduleExportAliasDuplicateAlias.d.ts] -export const apply: "ok" | 1 | typeof a | undefined; -export { a as apply }; -+export declare var apply: "ok" | 1 | typeof a | undefined; +export declare var apply: "ok" | 1 | typeof a | undefined; declare function a(): void; -+export declare var apply: "ok" | 1 | typeof a | undefined; -+export declare var apply: "ok" | 1 | typeof a | undefined; -+export declare var apply: "ok" | 1 | typeof a | undefined; //// [test.d.ts] export {}; \ No newline at end of file diff --git a/testdata/tests/cases/compiler/cjsExportsDuplication.ts b/testdata/tests/cases/compiler/cjsExportsDuplication.ts new file mode 100644 index 00000000000..423afc3a6b7 --- /dev/null +++ b/testdata/tests/cases/compiler/cjsExportsDuplication.ts @@ -0,0 +1,15 @@ +// @allowJs: true +// @checkJs: true +// @declaration: true +// @emitDeclarationOnly: true +// @filename: file.js +exports.foo = 42 +exports.foo = "hello" +exports.foo = true + +// @filename: file2.js +exports.foo = 42 +/** @type {string} */ +exports.foo = "hello" +/** @type {boolean} */ +exports.foo = true \ No newline at end of file From 42eb4d43baf81852d6676ef3981f21d9af8c9ed3 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 11 Jun 2026 14:53:51 -0700 Subject: [PATCH 12/49] Don't provide auto-imports for non-identifiers (#4287) --- ...nsForArbitraryNonIdentifierExports_test.go | 37 +++++++++++++++++++ internal/ls/autoimport/view.go | 4 ++ 2 files changed, 41 insertions(+) create mode 100644 internal/fourslash/tests/autoImportCompletionsForArbitraryNonIdentifierExports_test.go diff --git a/internal/fourslash/tests/autoImportCompletionsForArbitraryNonIdentifierExports_test.go b/internal/fourslash/tests/autoImportCompletionsForArbitraryNonIdentifierExports_test.go new file mode 100644 index 00000000000..53e6508dd34 --- /dev/null +++ b/internal/fourslash/tests/autoImportCompletionsForArbitraryNonIdentifierExports_test.go @@ -0,0 +1,37 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + util "github.com/microsoft/typescript-go/internal/fourslash/tests/util" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestAutoImportCompletionsForArbitraryNonIdentifierExports(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + const content = ` +// @module: esnext +// @Filename: /a.ts +const foo = 0; +export { foo as "foo-bar" }; +export const fooBar = 1; + +// @Filename: /b.ts +foo/**/ +` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + + f.VerifyCompletions(t, "", &fourslash.CompletionsExpectedList{ + Items: &fourslash.CompletionsExpectedItems{ + Excludes: []string{"foo-bar"}, + Includes: []fourslash.CompletionsExpectedItem{"fooBar"}, + }, + ItemDefaults: &fourslash.CompletionsExpectedItemDefaults{ + CommitCharacters: &util.DefaultCommitCharacters, + EditRange: util.Ignored, + }, + }) +} diff --git a/internal/ls/autoimport/view.go b/internal/ls/autoimport/view.go index 35441eeded6..9f8b144fa5c 100644 --- a/internal/ls/autoimport/view.go +++ b/internal/ls/autoimport/view.go @@ -14,6 +14,7 @@ import ( "github.com/microsoft/typescript-go/internal/lsp/lsproto" "github.com/microsoft/typescript-go/internal/module" "github.com/microsoft/typescript-go/internal/modulespecifiers" + "github.com/microsoft/typescript-go/internal/scanner" "github.com/microsoft/typescript-go/internal/tspath" ) @@ -180,6 +181,9 @@ func (v *View) GetCompletions(ctx context.Context, prefix string, position lspro outer: for _, e := range results { name := e.Name() + if !scanner.IsIdentifierText(name, core.LanguageVariantStandard) { + continue + } if forJSX && !(unicode.IsUpper(rune(name[0])) || e.IsRenameable()) { continue } From 79fe60a804acfa49d81e67c499bd7509d70aa8ce Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:41:33 -0700 Subject: [PATCH 13/49] fix: release speculative parse-cache acquire using the acquired file's own key (#4285) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> --- internal/compiler/program.go | 12 ++++++--- internal/project/logging/logger.go | 6 +++++ internal/project/project.go | 11 ++++++--- internal/project/session.go | 6 ++++- internal/project/snapshot_test.go | 39 ++++++++++++++++++++++++++++++ 5 files changed, 65 insertions(+), 9 deletions(-) diff --git a/internal/compiler/program.go b/internal/compiler/program.go index fc9434e3e1d..4a6b6e253d6 100644 --- a/internal/compiler/program.go +++ b/internal/compiler/program.go @@ -281,7 +281,11 @@ func NewProgram(opts ProgramOptions) *Program { // In addition to a new program, return a boolean indicating whether the data of the old program was reused. // createCheckerPool, if non-nil, overrides the CreateCheckerPool stored in the old program's options, // ensuring each caller uses a fresh closure and avoiding data races on captured variables. -func (p *Program) UpdateProgram(changedFilePath tspath.Path, newHost CompilerHost, createCheckerPool func(*Program) CheckerPool) (*Program, bool) { +// The returned *ast.SourceFile is the changed file as acquired through newHost; it is nil +// only if the host cannot locate the file (e.g. it was deleted). Callers that manage +// host-side parse caches must release this exact pointer when the old program could not be +// reused, since it was acquired speculatively before that decision was made. +func (p *Program) UpdateProgram(changedFilePath tspath.Path, newHost CompilerHost, createCheckerPool func(*Program) CheckerPool) (*Program, *ast.SourceFile, bool) { newOpts := p.opts newOpts.Host = newHost if createCheckerPool != nil { @@ -297,11 +301,11 @@ func (p *Program) UpdateProgram(changedFilePath tspath.Path, newHost CompilerHos _, inRedirectFiles := p.redirectFilesByPath[changedFilePath] _, isRedirectTarget := p.redirectTargetsMap[changedFilePath] if inRedirectFiles || isRedirectTarget { - return NewProgram(newOpts), false + return NewProgram(newOpts), newFile, false } if !canReplaceFileInProgram(oldFile, newFile) { - return NewProgram(newOpts), false + return NewProgram(newOpts), newFile, false } // TODO: reverify compiler options when config has changed? result := &Program{ @@ -322,7 +326,7 @@ func (p *Program) UpdateProgram(changedFilePath tspath.Path, newHost CompilerHos result.filesByPath = maps.Clone(result.filesByPath) result.filesByPath[newFile.Path()] = newFile updateFileIncludeProcessor(result) - return result, true + return result, newFile, true } func (p *Program) initCheckerPool() { diff --git a/internal/project/logging/logger.go b/internal/project/logging/logger.go index 6cf3bb1187f..592649fd05b 100644 --- a/internal/project/logging/logger.go +++ b/internal/project/logging/logger.go @@ -124,6 +124,12 @@ func NewLogger(output io.Writer) Logger { } } +// NewNopLogger returns a no-op Logger that discards all log messages. +// It is safe to call any method on the returned Logger. +func NewNopLogger() Logger { + return (*logger)(nil) +} + func formatTime(t time.Time) string { return fmt.Sprintf("[%s]", t.Format("15:04:05.000")) } diff --git a/internal/project/project.go b/internal/project/project.go index b945980beb6..37abaff97ae 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -366,11 +366,14 @@ func (p *Project) CreateProgram() CreateProgramResult { commandLine := p.getCommandLineWithTypingsFiles() if p.dirtyFilePath != "" && p.Program != nil && p.Program.CommandLine() == commandLine { - newProgram, programCloned = p.Program.UpdateProgram(p.dirtyFilePath, p.host, createCheckerPool) + var dirtyFile *ast.SourceFile + newProgram, dirtyFile, programCloned = p.Program.UpdateProgram(p.dirtyFilePath, p.host, createCheckerPool) if programCloned { updateKind = ProgramUpdateKindCloned for _, file := range newProgram.SourceFiles() { - if file.Path() != p.dirtyFilePath { + // Use pointer identity: dirtyFile is the exact instance UpdateProgram acquired, + // and it is the only file whose refcount is already accounted for. + if file != dirtyFile { // UpdateProgram acquired the changed file only, so we need to ref everything else p.host.builder.parseCache.Ref(NewParseCacheKey(file.ParseOptions(), file.Hash, file.ScriptKind)) } @@ -378,11 +381,11 @@ func (p *Project) CreateProgram() CreateProgramResult { for _, file := range newProgram.DuplicateSourceFiles() { p.host.builder.parseCache.Ref(NewParseCacheKey(file.ParseOptions, file.Hash, file.ScriptKind)) } - } else if newFile := newProgram.GetSourceFileByPath(p.dirtyFilePath); newFile != nil { + } else if dirtyFile != nil { // UpdateProgram always acquires the dirty file before deciding whether it can // reuse the old program. If it falls back to a full rebuild, release that // speculative acquire so the rebuilt program is the only remaining owner. - p.host.builder.parseCache.Deref(NewParseCacheKey(newFile.ParseOptions(), newFile.Hash, newFile.ScriptKind)) + p.host.builder.parseCache.Deref(NewParseCacheKey(dirtyFile.ParseOptions(), dirtyFile.Hash, dirtyFile.ScriptKind)) } } else { var typingsLocation string diff --git a/internal/project/session.go b/internal/project/session.go index bd037b1a051..4fb513460ba 100644 --- a/internal/project/session.go +++ b/internal/project/session.go @@ -189,12 +189,16 @@ func NewSession(init *SessionInit) *Session { } extendedConfigCache := NewExtendedConfigCache() + sessionLogger := init.Logger + if sessionLogger == nil { + sessionLogger = logging.NewNopLogger() + } session := &Session{ backgroundCtx: init.BackgroundCtx, options: init.Options, toPath: toPath, client: init.Client, - logger: init.Logger, + logger: sessionLogger, npmExecutor: init.NpmExecutor, fs: overlayFS, parseCache: parseCache, diff --git a/internal/project/snapshot_test.go b/internal/project/snapshot_test.go index 1e4e19eb553..99d613f53fe 100644 --- a/internal/project/snapshot_test.go +++ b/internal/project/snapshot_test.go @@ -171,6 +171,45 @@ func TestSnapshot(t *testing.T) { _, err := session.GetCurrentLanguageServiceWithAutoImports(ctx, otherIndexURI) assert.NilError(t, err) }) + + t.Run("fallback rebuild with recomputed parse options is safe for later clone", func(t *testing.T) { + t.Parallel() + + testFiles := map[string]any{ + "/project/node_modules/pkg/index.ts": `export const pkg = 0;`, + "/project/src/other.ts": `export const other = 1;`, + } + session := setup(testFiles) + pkgURI := lsproto.DocumentUri("file:///project/node_modules/pkg/index.ts") + otherURI := lsproto.DocumentUri("file:///project/src/other.ts") + + session.DidOpenFile(context.Background(), pkgURI, 1, testFiles["/project/node_modules/pkg/index.ts"].(string), lsproto.LanguageKindTypeScript) + session.DidOpenFile(context.Background(), otherURI, 1, testFiles["/project/src/other.ts"].(string), lsproto.LanguageKindTypeScript) + _, err := session.GetLanguageService(context.Background(), pkgURI) + assert.NilError(t, err) + + err = session.fs.fs.WriteFile("/project/node_modules/pkg/package.json", `{ "type": "module" }`) + assert.NilError(t, err) + session.DidChangeFile(context.Background(), pkgURI, 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{ + { + WholeDocument: &lsproto.TextDocumentContentChangeWholeDocument{ + Text: `import "./missing"; export const pkg = 1;`, + }, + }, + }) + _, err = session.GetLanguageService(context.Background(), pkgURI) + assert.NilError(t, err) + + session.DidChangeFile(context.Background(), otherURI, 2, []lsproto.TextDocumentContentChangePartialOrWholeDocument{ + { + WholeDocument: &lsproto.TextDocumentContentChangeWholeDocument{ + Text: `export const other = 2;`, + }, + }, + }) + _, err = session.GetLanguageService(context.Background(), otherURI) + assert.NilError(t, err) + }) } func BenchmarkSnapshotCloneRefCost(b *testing.B) { From 52a0fe638151bb2be3d2af2963f239af5881351a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 12 Jun 2026 06:50:53 -0700 Subject: [PATCH 14/49] Fix stack overflow caused by simplification of recursive type (#4260) --- internal/checker/checker.go | 17 ++++--- ...siveIndexedAccessSimplification.errors.txt | 42 ++++++++++++++++ ...cursiveIndexedAccessSimplification.symbols | 49 +++++++++++++++++++ ...recursiveIndexedAccessSimplification.types | 33 +++++++++++++ .../recursiveIndexedAccessSimplification.ts | 15 ++++++ 5 files changed, 150 insertions(+), 6 deletions(-) create mode 100644 testdata/baselines/reference/compiler/recursiveIndexedAccessSimplification.errors.txt create mode 100644 testdata/baselines/reference/compiler/recursiveIndexedAccessSimplification.symbols create mode 100644 testdata/baselines/reference/compiler/recursiveIndexedAccessSimplification.types create mode 100644 testdata/tests/cases/compiler/recursiveIndexedAccessSimplification.ts diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 5a66fd5cee2..4766e7da980 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -27716,6 +27716,16 @@ func (c *Checker) getSimplifiedIndexedAccessType(t *Type, writing bool) *Type { return core.IfElse(cached == c.circularConstraintType, t, cached) } c.cachedTypes[key] = t + result := c.getSimplifiedIndexedAccessTypeWorker(t, writing) + if result != t { + // If the simplification is a union type that includes t, remove t from the type. + result = c.removeType(result, t) + c.cachedTypes[key] = result + } + return result +} + +func (c *Checker) getSimplifiedIndexedAccessTypeWorker(t *Type, writing bool) *Type { // We recursively simplify the object type as it may in turn be an indexed access type. For example, with // '{ [P in T]: { [Q in U]: number } }[T][U]' we want to first simplify the inner indexed access type. objectType := c.getSimplifiedType(t.AsIndexedAccessType().objectType, writing) @@ -27724,7 +27734,6 @@ func (c *Checker) getSimplifiedIndexedAccessType(t *Type, writing bool) *Type { // T[A | B] -> T[A] & T[B] (writing) distributedOverIndex := c.distributeObjectOverIndexType(objectType, indexType, writing) if distributedOverIndex != nil { - c.cachedTypes[key] = distributedOverIndex return distributedOverIndex } // Only do the inner distributions if the index can no longer be instantiated to cause index distribution again @@ -27734,7 +27743,6 @@ func (c *Checker) getSimplifiedIndexedAccessType(t *Type, writing bool) *Type { // (T & U)[K] -> T[K] & U[K] distributedOverObject := c.distributeIndexOverObjectType(objectType, indexType, writing) if distributedOverObject != nil { - c.cachedTypes[key] = distributedOverObject return distributedOverObject } } @@ -27746,7 +27754,6 @@ func (c *Checker) getSimplifiedIndexedAccessType(t *Type, writing bool) *Type { if c.isGenericTupleType(objectType) && indexType.flags&TypeFlagsNumberLike != 0 { elementType := c.getElementTypeOfSliceOfTupleType(objectType, core.IfElse(indexType.flags&TypeFlagsNumber != 0, 0, objectType.TargetTupleType().fixedLength), 0 /*endSkipCount*/, writing, false) if elementType != nil { - c.cachedTypes[key] = elementType return elementType } } @@ -27755,11 +27762,9 @@ func (c *Checker) getSimplifiedIndexedAccessType(t *Type, writing bool) *Type { // For example, for an index access { [P in K]: Box }[X], we construct the type Box. if c.isGenericMappedType(objectType) { if c.getMappedTypeNameTypeKind(objectType) != MappedTypeNameTypeKindRemapping { - result := c.mapType(c.substituteIndexedMappedType(objectType, t.AsIndexedAccessType().indexType), func(t *Type) *Type { + return c.mapType(c.substituteIndexedMappedType(objectType, t.AsIndexedAccessType().indexType), func(t *Type) *Type { return c.getSimplifiedType(t, writing) }) - c.cachedTypes[key] = result - return result } } return t diff --git a/testdata/baselines/reference/compiler/recursiveIndexedAccessSimplification.errors.txt b/testdata/baselines/reference/compiler/recursiveIndexedAccessSimplification.errors.txt new file mode 100644 index 00000000000..3567f2da71f --- /dev/null +++ b/testdata/baselines/reference/compiler/recursiveIndexedAccessSimplification.errors.txt @@ -0,0 +1,42 @@ +recursiveIndexedAccessSimplification.ts(5,5): error TS4109: Type arguments for 'Array' circularly reference themselves. +recursiveIndexedAccessSimplification.ts(5,9): error TS2536: Type 'number' cannot be used to index type 'Recur'. +recursiveIndexedAccessSimplification.ts(8,5): error TS2322: Type '[string, ...Recur[]]' is not assignable to type 'Recur'. + Type '[string, ...Recur[]]' is not assignable to type 'Recur[number][]'. + Type 'string | Recur' is not assignable to type 'Recur[number] & (T extends unknown[] ? {} : { [K in keyof T]?: Recur | undefined; })[number]'. + Type 'string' is not assignable to type 'Recur[number] & (T extends unknown[] ? {} : { [K in keyof T]?: Recur | undefined; })[number]'. + Type 'string' is not assignable to type 'Recur[number] & (T extends unknown[] ? {} : { [K in keyof T]?: Recur | undefined; })[number]'. + Type 'string' is not assignable to type '(T extends unknown[] ? {} : { [K in keyof T]?: Recur | undefined; })[number]'. +recursiveIndexedAccessSimplification.ts(8,12): error TS2589: Type instantiation is excessively deep and possibly infinite. +recursiveIndexedAccessSimplification.ts(12,37): error TS2589: Type instantiation is excessively deep and possibly infinite. + + +==== recursiveIndexedAccessSimplification.ts (5 errors) ==== + // https://github.com/microsoft/TypeScript/issues/63270 + + type Recur = + (T extends (unknown[]) ? {} : { [K in keyof T]?: Recur}) | + [...Recur[number][]]; + ~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS4109: Type arguments for 'Array' circularly reference themselves. + ~~~~~~~~~~~~~~~~ +!!! error TS2536: Type 'number' cannot be used to index type 'Recur'. + + function join(l: Recur[]): Recur { + return ['marker', ...l]; + ~~~~~~ +!!! error TS2322: Type '[string, ...Recur[]]' is not assignable to type 'Recur'. +!!! error TS2322: Type '[string, ...Recur[]]' is not assignable to type 'Recur[number][]'. +!!! error TS2322: Type 'string | Recur' is not assignable to type 'Recur[number] & (T extends unknown[] ? {} : { [K in keyof T]?: Recur | undefined; })[number]'. +!!! error TS2322: Type 'string' is not assignable to type 'Recur[number] & (T extends unknown[] ? {} : { [K in keyof T]?: Recur | undefined; })[number]'. +!!! error TS2322: Type 'string' is not assignable to type 'Recur[number] & (T extends unknown[] ? {} : { [K in keyof T]?: Recur | undefined; })[number]'. +!!! error TS2322: Type 'string' is not assignable to type '(T extends unknown[] ? {} : { [K in keyof T]?: Recur | undefined; })[number]'. + ~~~~~~~~~~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + } + + function a(l: Recur[]): void { + const x: Recur | undefined = join(l); + ~~~~~~~ +!!! error TS2589: Type instantiation is excessively deep and possibly infinite. + } + \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/recursiveIndexedAccessSimplification.symbols b/testdata/baselines/reference/compiler/recursiveIndexedAccessSimplification.symbols new file mode 100644 index 00000000000..51c5018c730 --- /dev/null +++ b/testdata/baselines/reference/compiler/recursiveIndexedAccessSimplification.symbols @@ -0,0 +1,49 @@ +//// [tests/cases/compiler/recursiveIndexedAccessSimplification.ts] //// + +=== recursiveIndexedAccessSimplification.ts === +// https://github.com/microsoft/TypeScript/issues/63270 + +type Recur = +>Recur : Symbol(Recur, Decl(recursiveIndexedAccessSimplification.ts, 0, 0)) +>T : Symbol(T, Decl(recursiveIndexedAccessSimplification.ts, 2, 11)) + + (T extends (unknown[]) ? {} : { [K in keyof T]?: Recur}) | +>T : Symbol(T, Decl(recursiveIndexedAccessSimplification.ts, 2, 11)) +>K : Symbol(K, Decl(recursiveIndexedAccessSimplification.ts, 3, 38)) +>T : Symbol(T, Decl(recursiveIndexedAccessSimplification.ts, 2, 11)) +>Recur : Symbol(Recur, Decl(recursiveIndexedAccessSimplification.ts, 0, 0)) +>T : Symbol(T, Decl(recursiveIndexedAccessSimplification.ts, 2, 11)) +>K : Symbol(K, Decl(recursiveIndexedAccessSimplification.ts, 3, 38)) + + [...Recur[number][]]; +>Recur : Symbol(Recur, Decl(recursiveIndexedAccessSimplification.ts, 0, 0)) +>T : Symbol(T, Decl(recursiveIndexedAccessSimplification.ts, 2, 11)) + +function join(l: Recur[]): Recur { +>join : Symbol(join, Decl(recursiveIndexedAccessSimplification.ts, 4, 28)) +>T : Symbol(T, Decl(recursiveIndexedAccessSimplification.ts, 6, 14)) +>l : Symbol(l, Decl(recursiveIndexedAccessSimplification.ts, 6, 17)) +>Recur : Symbol(Recur, Decl(recursiveIndexedAccessSimplification.ts, 0, 0)) +>T : Symbol(T, Decl(recursiveIndexedAccessSimplification.ts, 6, 14)) +>Recur : Symbol(Recur, Decl(recursiveIndexedAccessSimplification.ts, 0, 0)) +>T : Symbol(T, Decl(recursiveIndexedAccessSimplification.ts, 6, 14)) + + return ['marker', ...l]; +>l : Symbol(l, Decl(recursiveIndexedAccessSimplification.ts, 6, 17)) +} + +function a(l: Recur[]): void { +>a : Symbol(a, Decl(recursiveIndexedAccessSimplification.ts, 8, 1)) +>T : Symbol(T, Decl(recursiveIndexedAccessSimplification.ts, 10, 11)) +>l : Symbol(l, Decl(recursiveIndexedAccessSimplification.ts, 10, 14)) +>Recur : Symbol(Recur, Decl(recursiveIndexedAccessSimplification.ts, 0, 0)) +>T : Symbol(T, Decl(recursiveIndexedAccessSimplification.ts, 10, 11)) + + const x: Recur | undefined = join(l); +>x : Symbol(x, Decl(recursiveIndexedAccessSimplification.ts, 11, 9)) +>Recur : Symbol(Recur, Decl(recursiveIndexedAccessSimplification.ts, 0, 0)) +>T : Symbol(T, Decl(recursiveIndexedAccessSimplification.ts, 10, 11)) +>join : Symbol(join, Decl(recursiveIndexedAccessSimplification.ts, 4, 28)) +>l : Symbol(l, Decl(recursiveIndexedAccessSimplification.ts, 10, 14)) +} + diff --git a/testdata/baselines/reference/compiler/recursiveIndexedAccessSimplification.types b/testdata/baselines/reference/compiler/recursiveIndexedAccessSimplification.types new file mode 100644 index 00000000000..8ca64cf95a6 --- /dev/null +++ b/testdata/baselines/reference/compiler/recursiveIndexedAccessSimplification.types @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/recursiveIndexedAccessSimplification.ts] //// + +=== recursiveIndexedAccessSimplification.ts === +// https://github.com/microsoft/TypeScript/issues/63270 + +type Recur = +>Recur : Recur + + (T extends (unknown[]) ? {} : { [K in keyof T]?: Recur}) | + [...Recur[number][]]; + +function join(l: Recur[]): Recur { +>join : (l: Recur[]) => Recur +>l : Recur[] + + return ['marker', ...l]; +>['marker', ...l] : [string, ...Recur[]] +>'marker' : "marker" +>...l : Recur +>l : Recur[] +} + +function a(l: Recur[]): void { +>a : (l: Recur[]) => void +>l : Recur[] + + const x: Recur | undefined = join(l); +>x : Recur | undefined +>join(l) : Recur +>join : (l: Recur[]) => Recur +>l : Recur[] +} + diff --git a/testdata/tests/cases/compiler/recursiveIndexedAccessSimplification.ts b/testdata/tests/cases/compiler/recursiveIndexedAccessSimplification.ts new file mode 100644 index 00000000000..981b67e29d2 --- /dev/null +++ b/testdata/tests/cases/compiler/recursiveIndexedAccessSimplification.ts @@ -0,0 +1,15 @@ +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/63270 + +type Recur = + (T extends (unknown[]) ? {} : { [K in keyof T]?: Recur}) | + [...Recur[number][]]; + +function join(l: Recur[]): Recur { + return ['marker', ...l]; +} + +function a(l: Recur[]): void { + const x: Recur | undefined = join(l); +} From 4cf361f5cec332a3398600733d2ede1ec5aef27c Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 12 Jun 2026 11:11:02 -0700 Subject: [PATCH 15/49] Fix stack overflow crash in base constraint resolution (#4242) --- internal/checker/checker.go | 15 ++++- .../compiler/infiniteConstraints2.symbols | 58 +++++++++++++++++++ .../compiler/infiniteConstraints2.types | 31 ++++++++++ .../compiler/infiniteConstraints.errors.txt | 8 ++- .../infiniteConstraints.errors.txt.diff | 23 ++++++++ .../cases/compiler/infiniteConstraints2.ts | 14 +++++ 6 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 testdata/baselines/reference/compiler/infiniteConstraints2.symbols create mode 100644 testdata/baselines/reference/compiler/infiniteConstraints2.types create mode 100644 testdata/baselines/reference/submodule/compiler/infiniteConstraints.errors.txt.diff create mode 100644 testdata/tests/cases/compiler/infiniteConstraints2.ts diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 4766e7da980..18fcff71d83 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -27401,7 +27401,20 @@ func (c *Checker) computeBaseConstraint(t *Type, stack []RecursionId) *Type { } return c.getNextBaseConstraint(c.getIndexedAccessTypeOrUndefined(baseObjectType, baseIndexType, t.AsIndexedAccessType().accessFlags, nil, nil), stack) case t.flags&TypeFlagsConditional != 0: - return c.getNextBaseConstraint(c.getConstraintFromConditionalType(t), stack) + d := t.AsConditionalType() + if d.root.isDistributive && c.cachedTypes[CachedTypeKey{kind: CachedTypeKindRestrictiveInstantiation, typeId: t.id}] != t { + constraint := c.getSimplifiedType(d.checkType, false /*writing*/) + if constraint == d.checkType { + constraint = c.getNextBaseConstraint(constraint, stack) + } + if constraint != nil && constraint != d.checkType { + instantiated := c.getConditionalTypeInstantiation(t, prependTypeMapping(d.root.checkType, constraint, d.mapper), true /*forConstraint*/, nil) + if instantiated.flags&TypeFlagsNever == 0 { + return c.getNextBaseConstraint(instantiated, stack) + } + } + } + return c.getNextBaseConstraint(c.getDefaultConstraintOfConditionalType(t), stack) case t.flags&TypeFlagsSubstitution != 0: return c.getNextBaseConstraint(c.getSubstitutionIntersection(t), stack) case c.isGenericTupleType(t): diff --git a/testdata/baselines/reference/compiler/infiniteConstraints2.symbols b/testdata/baselines/reference/compiler/infiniteConstraints2.symbols new file mode 100644 index 00000000000..6c034e09428 --- /dev/null +++ b/testdata/baselines/reference/compiler/infiniteConstraints2.symbols @@ -0,0 +1,58 @@ +//// [tests/cases/compiler/infiniteConstraints2.ts] //// + +=== infiniteConstraints2.ts === +// https://github.com/microsoft/TypeScript/issues/63269 + +export type Prepend = +>Prepend : Symbol(Prepend, Decl(infiniteConstraints2.ts, 0, 0)) +>Elm : Symbol(Elm, Decl(infiniteConstraints2.ts, 2, 20)) +>T : Symbol(T, Decl(infiniteConstraints2.ts, 2, 24)) + + T extends unknown ? +>T : Symbol(T, Decl(infiniteConstraints2.ts, 2, 24)) + + ((arg: Elm, ...rest: T) => void) extends ((...args: infer T2) => void) ? T2 : +>arg : Symbol(arg, Decl(infiniteConstraints2.ts, 4, 3)) +>Elm : Symbol(Elm, Decl(infiniteConstraints2.ts, 2, 20)) +>rest : Symbol(rest, Decl(infiniteConstraints2.ts, 4, 12)) +>T : Symbol(T, Decl(infiniteConstraints2.ts, 2, 24)) +>args : Symbol(args, Decl(infiniteConstraints2.ts, 4, 44)) +>T2 : Symbol(T2, Decl(infiniteConstraints2.ts, 4, 58)) +>T2 : Symbol(T2, Decl(infiniteConstraints2.ts, 4, 58)) + + never : + never; +export type ExactExtract = (T extends U ? U extends T ? T : never : never) & string; +>ExactExtract : Symbol(ExactExtract, Decl(infiniteConstraints2.ts, 6, 7)) +>T : Symbol(T, Decl(infiniteConstraints2.ts, 7, 25)) +>U : Symbol(U, Decl(infiniteConstraints2.ts, 7, 27)) +>T : Symbol(T, Decl(infiniteConstraints2.ts, 7, 25)) +>U : Symbol(U, Decl(infiniteConstraints2.ts, 7, 27)) +>U : Symbol(U, Decl(infiniteConstraints2.ts, 7, 27)) +>T : Symbol(T, Decl(infiniteConstraints2.ts, 7, 25)) +>T : Symbol(T, Decl(infiniteConstraints2.ts, 7, 25)) + +type Conv = { +>Conv : Symbol(Conv, Decl(infiniteConstraints2.ts, 7, 90)) +>T : Symbol(T, Decl(infiniteConstraints2.ts, 8, 10)) +>U : Symbol(U, Decl(infiniteConstraints2.ts, 8, 12)) +>T : Symbol(T, Decl(infiniteConstraints2.ts, 8, 10)) + + 0: [T]; +>0 : Symbol(0, Decl(infiniteConstraints2.ts, 8, 23)) +>T : Symbol(T, Decl(infiniteConstraints2.ts, 8, 10)) + + 1: Prepend, {a: number}>>; +>1 : Symbol(1, Decl(infiniteConstraints2.ts, 9, 11)) +>Prepend : Symbol(Prepend, Decl(infiniteConstraints2.ts, 0, 0)) +>T : Symbol(T, Decl(infiniteConstraints2.ts, 8, 10)) +>Conv : Symbol(Conv, Decl(infiniteConstraints2.ts, 7, 90)) +>ExactExtract : Symbol(ExactExtract, Decl(infiniteConstraints2.ts, 6, 7)) +>U : Symbol(U, Decl(infiniteConstraints2.ts, 8, 12)) +>T : Symbol(T, Decl(infiniteConstraints2.ts, 8, 10)) +>a : Symbol(a, Decl(infiniteConstraints2.ts, 10, 44)) + +}[U extends T ? 0 : 1]; +>U : Symbol(U, Decl(infiniteConstraints2.ts, 8, 12)) +>T : Symbol(T, Decl(infiniteConstraints2.ts, 8, 10)) + diff --git a/testdata/baselines/reference/compiler/infiniteConstraints2.types b/testdata/baselines/reference/compiler/infiniteConstraints2.types new file mode 100644 index 00000000000..585be72f87c --- /dev/null +++ b/testdata/baselines/reference/compiler/infiniteConstraints2.types @@ -0,0 +1,31 @@ +//// [tests/cases/compiler/infiniteConstraints2.ts] //// + +=== infiniteConstraints2.ts === +// https://github.com/microsoft/TypeScript/issues/63269 + +export type Prepend = +>Prepend : Prepend + + T extends unknown ? + ((arg: Elm, ...rest: T) => void) extends ((...args: infer T2) => void) ? T2 : +>arg : Elm +>rest : T +>args : T2 + + never : + never; +export type ExactExtract = (T extends U ? U extends T ? T : never : never) & string; +>ExactExtract : ExactExtract + +type Conv = { +>Conv : Conv + + 0: [T]; +>0 : [T] + + 1: Prepend, {a: number}>>; +>1 : Prepend, { a: number; }>> +>a : number + +}[U extends T ? 0 : 1]; + diff --git a/testdata/baselines/reference/submodule/compiler/infiniteConstraints.errors.txt b/testdata/baselines/reference/submodule/compiler/infiniteConstraints.errors.txt index 7dbe760461e..fb0eb7c97fc 100644 --- a/testdata/baselines/reference/submodule/compiler/infiniteConstraints.errors.txt +++ b/testdata/baselines/reference/submodule/compiler/infiniteConstraints.errors.txt @@ -1,13 +1,19 @@ +infiniteConstraints.ts(3,37): error TS2321: Excessive stack depth comparing types '"val"' and 'keyof Extract], { val: string; }>'. +infiniteConstraints.ts(3,37): error TS2536: Type '"val"' cannot be used to index type 'Extract], { val: string; }>'. infiniteConstraints.ts(4,37): error TS2536: Type '"val"' cannot be used to index type 'B[Exclude]'. infiniteConstraints.ts(31,43): error TS2322: Type 'Value<"dup">' is not assignable to type 'never'. infiniteConstraints.ts(31,63): error TS2322: Type 'Value<"dup">' is not assignable to type 'never'. infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'. -==== infiniteConstraints.ts (4 errors) ==== +==== infiniteConstraints.ts (6 errors) ==== // Both of the following types trigger the recursion limiter in getImmediateBaseConstraint type T1], { val: string }>["val"] }> = B; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2321: Excessive stack depth comparing types '"val"' and 'keyof Extract], { val: string; }>'. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2536: Type '"val"' cannot be used to index type 'Extract], { val: string; }>'. type T2]["val"] }> = B; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS2536: Type '"val"' cannot be used to index type 'B[Exclude]'. diff --git a/testdata/baselines/reference/submodule/compiler/infiniteConstraints.errors.txt.diff b/testdata/baselines/reference/submodule/compiler/infiniteConstraints.errors.txt.diff new file mode 100644 index 00000000000..262b7d316aa --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/infiniteConstraints.errors.txt.diff @@ -0,0 +1,23 @@ +--- old.infiniteConstraints.errors.txt ++++ new.infiniteConstraints.errors.txt +@@= skipped -0, +0 lines =@@ ++infiniteConstraints.ts(3,37): error TS2321: Excessive stack depth comparing types '"val"' and 'keyof Extract], { val: string; }>'. ++infiniteConstraints.ts(3,37): error TS2536: Type '"val"' cannot be used to index type 'Extract], { val: string; }>'. + infiniteConstraints.ts(4,37): error TS2536: Type '"val"' cannot be used to index type 'B[Exclude]'. + infiniteConstraints.ts(31,43): error TS2322: Type 'Value<"dup">' is not assignable to type 'never'. + infiniteConstraints.ts(31,63): error TS2322: Type 'Value<"dup">' is not assignable to type 'never'. + infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'. + + +-==== infiniteConstraints.ts (4 errors) ==== ++==== infiniteConstraints.ts (6 errors) ==== + // Both of the following types trigger the recursion limiter in getImmediateBaseConstraint + + type T1], { val: string }>["val"] }> = B; ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS2321: Excessive stack depth comparing types '"val"' and 'keyof Extract], { val: string; }>'. ++ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS2536: Type '"val"' cannot be used to index type 'Extract], { val: string; }>'. + type T2]["val"] }> = B; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + !!! error TS2536: Type '"val"' cannot be used to index type 'B[Exclude]'. \ No newline at end of file diff --git a/testdata/tests/cases/compiler/infiniteConstraints2.ts b/testdata/tests/cases/compiler/infiniteConstraints2.ts new file mode 100644 index 00000000000..ce68fb43e90 --- /dev/null +++ b/testdata/tests/cases/compiler/infiniteConstraints2.ts @@ -0,0 +1,14 @@ +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/63269 + +export type Prepend = + T extends unknown ? + ((arg: Elm, ...rest: T) => void) extends ((...args: infer T2) => void) ? T2 : + never : + never; +export type ExactExtract = (T extends U ? U extends T ? T : never : never) & string; +type Conv = { + 0: [T]; + 1: Prepend, {a: number}>>; +}[U extends T ? 0 : 1]; From bb2240ee4d32a4ac23ab133d2a61a73568478103 Mon Sep 17 00:00:00 2001 From: John Favret <64748847+johnfav03@users.noreply.github.com> Date: Fri, 12 Jun 2026 16:25:49 -0500 Subject: [PATCH 16/49] Rebuilt CLI watcher around new `fswatch` package (#4026) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cmd/tsgo/main.go | 7 +- internal/execute/build/orchestrator.go | 18 +- internal/execute/tsc.go | 17 +- .../execute/tsctests/mock_watch_backend.go | 197 +++++++ internal/execute/tsctests/runner.go | 13 +- internal/execute/tsctests/sys.go | 10 +- internal/execute/tsctests/tscwatch_test.go | 70 ++- .../execute/tsctests/watcher_race_test.go | 32 +- internal/execute/watcher.go | 527 ++++++++++++++++-- internal/testutil/fsbaselineutil/differ.go | 38 ++ internal/vfs/trackingvfs/trackingvfs.go | 5 +- internal/vfs/vfswatch/vfswatch.go | 267 --------- internal/vfs/vfswatch/vfswatch_race_test.go | 319 ----------- internal/vfs/vfswatch/vfswatch_test.go | 64 --- .../Parse-watch-interval-option.js | 4 + ...etects-at-types-package-installed-later.js | 8 + ...-change-in-symlinked-node_modules-file.js} | 26 +- ...deeply-nested-non-existent-include-path.js | 80 +++ ...tiple-new-subdirectories-simultaneously.js | 10 + ...s-file-added-in-new-nested-subdirectory.js | 10 + ...to-previously-non-existent-include-path.js | 9 + ...leted-and-new-file-added-simultaneously.js | 8 + ...h-detects-file-renamed-and-renamed-back.js | 12 + .../watch-detects-import-path-restructured.js | 8 + ...atch-detects-imported-directory-removed.js | 8 + ...ts-imported-file-added-in-new-directory.js | 8 + ...s-module-going-missing-then-coming-back.js | 12 + ...sted-subdirectory-removed-and-recreated.js | 14 + ...-new-file-in-existing-include-directory.js | 10 + ...etects-new-file-resolving-failed-import.js | 8 + ...atch-detects-node-modules-package-added.js | 8 + ...ch-detects-node-modules-package-removed.js | 8 + ...node-modules-reinstalled-after-deletion.js | 119 ++++ ...detects-package-json-types-field-edited.js | 8 + .../watch-detects-scoped-package-installed.js | 8 + .../watch-handles-file-rapidly-recreated.js | 8 + .../watch-handles-tsconfig-deleted.js | 8 + ...les-tsconfig-with-extends-base-modified.js | 8 + .../watch-rebuilds-when-file-is-modified.js | 8 + ...ch-rebuilds-when-source-file-is-deleted.js | 8 + ...when-tsconfig-include-pattern-adds-file.js | 9 + ...n-tsconfig-is-modified-to-change-strict.js | 8 + ...config-is-touched-but-content-unchanged.js | 8 + .../watch-skips-build-when-no-files-change.js | 8 + ...bility-with-ancestor-directory-fallback.js | 79 +++ .../watch-with-no-tsconfig.js | 4 + .../watch-with-tsconfig-and-incremental.js | 4 + ...-with-tsconfig-files-list-entry-deleted.js | 8 + .../noEmit/dts-errors-without-dts-enabled.js | 28 + .../reference/tscWatch/noEmit/dts-errors.js | 28 + .../tscWatch/noEmit/semantic-errors.js | 28 + .../tscWatch/noEmit/syntax-errors.js | 28 + 52 files changed, 1526 insertions(+), 724 deletions(-) create mode 100644 internal/execute/tsctests/mock_watch_backend.go delete mode 100644 internal/vfs/vfswatch/vfswatch.go delete mode 100644 internal/vfs/vfswatch/vfswatch_race_test.go delete mode 100644 internal/vfs/vfswatch/vfswatch_test.go rename testdata/baselines/reference/tscWatch/commandLineWatch/{watch-detects-change-in-symlinked-file.js => watch-detects-change-in-symlinked-node_modules-file.js} (76%) create mode 100644 testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-deeply-nested-non-existent-include-path.js create mode 100644 testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-reinstalled-after-deletion.js create mode 100644 testdata/baselines/reference/tscWatch/commandLineWatch/watch-stability-with-ancestor-directory-fallback.js diff --git a/cmd/tsgo/main.go b/cmd/tsgo/main.go index 894d3982986..8d6816fa3d4 100644 --- a/cmd/tsgo/main.go +++ b/cmd/tsgo/main.go @@ -1,7 +1,10 @@ package main import ( + "context" "os" + "os/signal" + "syscall" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/execute" @@ -22,6 +25,8 @@ func runMain() int { return runAPI(args[1:]) } } - result := execute.CommandLine(newSystem(), args, nil) + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + result := execute.CommandLine(ctx, newSystem(), args, nil) return int(result.Status) } diff --git a/internal/execute/build/orchestrator.go b/internal/execute/build/orchestrator.go index fc8c5ac3d67..99a2e1179cb 100644 --- a/internal/execute/build/orchestrator.go +++ b/internal/execute/build/orchestrator.go @@ -1,6 +1,7 @@ package build import ( + "context" "io" "strings" "sync/atomic" @@ -208,30 +209,35 @@ func (o *Orchestrator) GenerateGraph(oldTasks *collections.SyncMap[tspath.Path, } } -func (o *Orchestrator) Start() tsc.CommandLineResult { +func (o *Orchestrator) Start(ctx context.Context) tsc.CommandLineResult { if o.opts.Command.CompilerOptions.Watch.IsTrue() { o.watchStatusReporter(ast.NewCompilerDiagnostic(diagnostics.Starting_compilation_in_watch_mode)) } o.GenerateGraph(nil) result := o.buildOrClean() if o.opts.Command.CompilerOptions.Watch.IsTrue() { - o.Watch() + o.Watch(ctx) result.Watcher = o } return result } -func (o *Orchestrator) Watch() { +func (o *Orchestrator) Watch(ctx context.Context) { o.updateWatch() o.resetCaches() // Start watching for file changes if o.opts.Testing == nil { watchInterval := o.opts.Command.WatchOptions.WatchInterval() + ticker := time.NewTicker(watchInterval) + defer ticker.Stop() for { - // Testing mode: run a single cycle and exit - time.Sleep(watchInterval) - o.DoCycle() + select { + case <-ctx.Done(): + return + case <-ticker.C: + o.DoCycle() + } } } } diff --git a/internal/execute/tsc.go b/internal/execute/tsc.go index 385ceba1876..17baf4e51c5 100644 --- a/internal/execute/tsc.go +++ b/internal/execute/tsc.go @@ -49,17 +49,17 @@ func stopTracing(sys tsc.System, tr *tracing.Tracing) { } } -func CommandLine(sys tsc.System, commandLineArgs []string, testing tsc.CommandLineTesting) tsc.CommandLineResult { +func CommandLine(ctx context.Context, sys tsc.System, commandLineArgs []string, testing tsc.CommandLineTesting) tsc.CommandLineResult { if len(commandLineArgs) > 0 { switch strings.ToLower(commandLineArgs[0]) { case "-b", "--b", "-build", "--build": - return tscBuildCompilation(sys, tsoptions.ParseBuildCommandLine(commandLineArgs, sys), testing) + return tscBuildCompilation(ctx, sys, tsoptions.ParseBuildCommandLine(commandLineArgs, sys), testing) // case "-f": // return fmtMain(sys, commandLineArgs[1], commandLineArgs[1]) } } - return tscCompilation(sys, tsoptions.ParseCommandLine(commandLineArgs, sys), testing) + return tscCompilation(ctx, sys, tsoptions.ParseCommandLine(commandLineArgs, sys), testing) } func fmtMain(sys tsc.System, input, output string) tsc.ExitStatus { @@ -87,7 +87,7 @@ func fmtMain(sys tsc.System, input, output string) tsc.ExitStatus { return tsc.ExitStatusSuccess } -func tscBuildCompilation(sys tsc.System, buildCommand *tsoptions.ParsedBuildCommandLine, testing tsc.CommandLineTesting) tsc.CommandLineResult { +func tscBuildCompilation(ctx context.Context, sys tsc.System, buildCommand *tsoptions.ParsedBuildCommandLine, testing tsc.CommandLineTesting) tsc.CommandLineResult { locale := buildCommand.Locale() reportDiagnostic := tsc.CreateDiagnosticReporter(sys, sys.Writer(), locale, buildCommand.CompilerOptions) @@ -115,10 +115,10 @@ func tscBuildCompilation(sys tsc.System, buildCommand *tsoptions.ParsedBuildComm Command: buildCommand, Testing: testing, }) - return orchestrator.Start() + return orchestrator.Start(ctx) } -func tscCompilation(sys tsc.System, commandLine *tsoptions.ParsedCommandLine, testing tsc.CommandLineTesting) tsc.CommandLineResult { +func tscCompilation(ctx context.Context, sys tsc.System, commandLine *tsoptions.ParsedCommandLine, testing tsc.CommandLineTesting) tsc.CommandLineResult { configFileName := "" locale := commandLine.Locale() reportDiagnostic := tsc.CreateDiagnosticReporter(sys, sys.Writer(), locale, commandLine.CompilerOptions()) @@ -201,9 +201,9 @@ func tscCompilation(sys tsc.System, commandLine *tsoptions.ParsedCommandLine, te configForCompilation := commandLine extendedConfigCache := &tsc.ExtendedConfigCache{} var compileTimes tsc.CompileTimes + var commandLineRaw *collections.OrderedMap[string, any] if configFileName != "" { configStart := sys.Now() - var commandLineRaw *collections.OrderedMap[string, any] if raw, ok := commandLine.Raw.(*collections.OrderedMap[string, any]); ok { // Wrap command line options in a "compilerOptions" key to match tsconfig.json structure wrapped := &collections.OrderedMap[string, any]{} @@ -234,11 +234,12 @@ func tscCompilation(sys tsc.System, commandLine *tsoptions.ParsedCommandLine, te sys, configForCompilation, compilerOptionsFromCommandLine, + commandLineRaw, reportDiagnostic, reportErrorSummary, testing, ) - watcher.start() + watcher.start(ctx) return tsc.CommandLineResult{Status: tsc.ExitStatusSuccess, Watcher: watcher} } else if configForCompilation.CompilerOptions().IsIncremental() { return performIncrementalCompilation( diff --git a/internal/execute/tsctests/mock_watch_backend.go b/internal/execute/tsctests/mock_watch_backend.go new file mode 100644 index 00000000000..6d549994b39 --- /dev/null +++ b/internal/execute/tsctests/mock_watch_backend.go @@ -0,0 +1,197 @@ +package tsctests + +import ( + "fmt" + "io" + "path" + "sort" + "strings" + "sync" + + "github.com/microsoft/typescript-go/internal/execute" + "github.com/microsoft/typescript-go/internal/fswatch" + "github.com/microsoft/typescript-go/internal/testutil/fsbaselineutil" +) + +// MockWatchBackend implements execute.WatchBackend for testing. It +// records all WatchDirectory calls so tests can verify that +// the correct watches are registered. Events can be delivered through +// SendEvents, which routes them only through watches whose paths +// match, enforcing that tests fail if the wrong watches are set up. +type MockWatchBackend struct { + mu sync.Mutex + Dirs map[string]*MockWatch + DirectoryExists func(string) bool // if set, WatchDirectory fails for non-existent dirs +} + +var _ execute.WatchBackend = (*MockWatchBackend)(nil) + +// NewMockWatchBackend creates a ready-to-use mock backend. +func NewMockWatchBackend() *MockWatchBackend { + return &MockWatchBackend{ + Dirs: make(map[string]*MockWatch), + } +} + +// HasWatches reports whether any watches have been registered. +func (m *MockWatchBackend) HasWatches() bool { + m.mu.Lock() + defer m.mu.Unlock() + return len(m.Dirs) > 0 +} + +// MockWatch records a single registered watch. +type MockWatch struct { + Path string + Callback fswatch.WatchCallback + Recursive bool + Ignore func(string) bool + Closed bool +} + +func (w *MockWatch) Close() error { + w.Closed = true + return nil +} + +func (m *MockWatchBackend) WatchDirectory(dir string, fn fswatch.WatchCallback, recursive bool, ignore func(string) bool) (io.Closer, error) { + m.mu.Lock() + defer m.mu.Unlock() + if m.DirectoryExists != nil && !m.DirectoryExists(dir) { + return nil, fmt.Errorf("directory does not exist: %s", dir) + } + w := &MockWatch{Path: dir, Callback: fn, Recursive: recursive, Ignore: ignore} + m.Dirs[dir] = w + return w, nil +} + +// SendEvents routes events through the registered watch callbacks +// that match each event's path. Directory watches match if the event +// path is a child (or recursive descendant) of the watched directory. +// Events that match no watch are silently dropped — this is by design +// so that tests fail when the production code doesn't register the +// needed watches. +func (m *MockWatchBackend) SendEvents(events []fswatch.Event) { + // Snapshot callbacks under the lock, then invoke outside the lock + // to avoid deadlock if the callback re-enters the mock. + m.mu.Lock() + type target struct { + cb fswatch.WatchCallback + events []fswatch.Event + } + targets := make(map[*MockWatch]*target) + + for _, e := range events { + // Check directory watches. + for _, w := range m.Dirs { + if w.Closed { + continue + } + if w.Ignore != nil && w.Ignore(e.Path) { + continue + } + if !pathIsUnder(e.Path, w.Path, w.Recursive) { + continue + } + if t, ok := targets[w]; ok { + t.events = append(t.events, e) + } else { + targets[w] = &target{cb: w.Callback, events: []fswatch.Event{e}} + } + } + } + m.mu.Unlock() + + for _, t := range targets { + t.cb(t.events, nil) + } +} + +// SendChangedPaths converts a list of file changes into fswatch +// events with appropriate event kinds and routes them through +// registered watches via SendEvents. For new/modified files, it also +// emits update events for their parent directories, simulating how +// real filesystem watchers report directory events. +func (m *MockWatchBackend) SendChangedPaths(changes []fsbaselineutil.FileChange) { + events := make([]fswatch.Event, 0, len(changes)*2) + seenDirs := make(map[string]struct{}) + for _, c := range changes { + kind := fswatch.EventUpdate + if c.Deleted { + kind = fswatch.EventDelete + } + events = append(events, fswatch.Event{Kind: kind, Path: c.Path}) + // Emit update events for parent directories of changed files. + // Real filesystem watchers deliver events to non-recursive watches + // when a child directory is created, which the mock must replicate. + dir := path.Dir(c.Path) + for dir != "" && dir != "/" && dir != "." { + if _, seen := seenDirs[dir]; seen { + break + } + seenDirs[dir] = struct{}{} + events = append(events, fswatch.Event{Kind: fswatch.EventUpdate, Path: dir}) + parent := path.Dir(dir) + if parent == dir { + break + } + dir = parent + } + } + m.SendEvents(events) +} + +// pathIsUnder reports whether eventPath is inside dir. If recursive is +// false, only direct children match. +func pathIsUnder(eventPath, dir string, recursive bool) bool { + if !strings.HasPrefix(eventPath, dir) { + return false + } + rest := eventPath[len(dir):] + if len(rest) == 0 { + return false // exact match = the dir itself, not a child + } + if rest[0] != '/' { + return false // e.g. dir="/foo", path="/foobar" + } + if !recursive { + // Direct child only: no further '/' after the separator. + return !strings.Contains(rest[1:], "/") + } + return true +} + +// WatchState returns a deterministic, human-readable summary of all +// active watches. This is intended to be included in test baselines +// so that watch registration correctness is verified via snapshot diffs. +func (m *MockWatchBackend) WatchState() string { + m.mu.Lock() + defer m.mu.Unlock() + + var b strings.Builder + b.WriteString("Watch Registrations::\n") + + // Directory watches, sorted by path. + var dirs []string + for dir, w := range m.Dirs { + if !w.Closed { + dirs = append(dirs, dir) + } + } + sort.Strings(dirs) + + b.WriteString("Directory watches::\n") + if len(dirs) == 0 { + b.WriteString(" (none)\n") + } + for _, d := range dirs { + w := m.Dirs[d] + if w.Recursive { + fmt.Fprintf(&b, " %s (recursive)\n", d) + } else { + fmt.Fprintf(&b, " %s\n", d) + } + } + + return b.String() +} diff --git a/internal/execute/tsctests/runner.go b/internal/execute/tsctests/runner.go index 8adfca02233..38c7e9c2089 100644 --- a/internal/execute/tsctests/runner.go +++ b/internal/execute/tsctests/runner.go @@ -1,6 +1,7 @@ package tsctests import ( + "context" "fmt" "path/filepath" "slices" @@ -42,7 +43,7 @@ type tscInput struct { func (test *tscInput) executeCommand(sys *TestSys, baselineBuilder *strings.Builder, commandLineArgs []string) tsc.CommandLineResult { fmt.Fprint(baselineBuilder, "tsgo ", strings.Join(commandLineArgs, " "), "\n") - result := execute.CommandLine(sys, commandLineArgs, sys) + result := execute.CommandLine(context.Background(), sys, commandLineArgs, sys) switch result.Status { case tsc.ExitStatusSuccess: baselineBuilder.WriteString("ExitStatus:: Success") @@ -80,6 +81,9 @@ func (test *tscInput) run(t *testing.T, scenario string) { sys.baselineFSwithDiff(baselineBuilder) result := test.executeCommand(sys, baselineBuilder, test.commandLineArgs) sys.serializeState(baselineBuilder) + if result.Watcher != nil && sys.mockWatchBackend.HasWatches() { + baselineBuilder.WriteString(sys.mockWatchBackend.WatchState()) + } var unexpectedDiff strings.Builder unexpectedDiff.WriteString(sys.baselinePrograms(baselineBuilder, "Initial build")) @@ -93,14 +97,19 @@ func (test *tscInput) run(t *testing.T, scenario string) { if do.edit != nil { do.edit(sys) } + changedPaths := sys.fsDiffer.ChangedPaths() sys.baselineFSwithDiff(baselineBuilder) if result.Watcher == nil { test.executeCommand(sys, baselineBuilder, commandLineArgs) } else { + sys.mockWatchBackend.SendChangedPaths(changedPaths) result.Watcher.DoCycle() } sys.serializeState(baselineBuilder) + if result.Watcher != nil && sys.mockWatchBackend.HasWatches() { + baselineBuilder.WriteString(sys.mockWatchBackend.WatchState()) + } unexpectedDiff.WriteString(sys.baselinePrograms(baselineBuilder, fmt.Sprintf("Edit [%d]:: %s\n", index, do.caption))) }) wg.Queue(func() { @@ -111,7 +120,7 @@ func (test *tscInput) run(t *testing.T, scenario string) { test.edits[i].edit(nonIncrementalSys) } } - execute.CommandLine(nonIncrementalSys, commandLineArgs, nonIncrementalSys) + execute.CommandLine(context.Background(), nonIncrementalSys, commandLineArgs, nonIncrementalSys) }) wg.RunAndWait() diff --git a/internal/execute/tsctests/sys.go b/internal/execute/tsctests/sys.go index c18d8d54478..6f7fe60ffbf 100644 --- a/internal/execute/tsctests/sys.go +++ b/internal/execute/tsctests/sys.go @@ -1,6 +1,7 @@ package tsctests import ( + "context" "fmt" "io" "maps" @@ -102,7 +103,7 @@ func GetFileMapWithBuild(files FileMap, commandLineArgs []string) FileMap { sys := newTestSys(&tscInput{ files: maps.Clone(files), }, false) - execute.CommandLine(sys, commandLineArgs, sys) + execute.CommandLine(context.Background(), sys, commandLineArgs, sys) sys.fs.writtenFiles.Range(func(key string) bool { if text, ok := sys.fsFromFileMap().ReadFile(key); ok { files[key] = text @@ -131,6 +132,8 @@ func newTestSys(tscInput *tscInput, forIncrementalCorrectness bool) *TestSys { }, currentWrite) sys.env = tscInput.env sys.forIncrementalCorrectness = forIncrementalCorrectness + sys.mockWatchBackend = NewMockWatchBackend() + sys.mockWatchBackend.DirectoryExists = sys.fs.FS.DirectoryExists sys.fsDiffer = &fsbaselineutil.FSDiffer{ FS: sys.fs.FS.(iovfs.FsWithSys), DefaultLibs: func() *collections.SyncSet[string] { return sys.fs.defaultLibs }, @@ -155,6 +158,7 @@ type TestSys struct { tracer *harnessutil.TracerForBaselining fsDiffer *fsbaselineutil.FSDiffer forIncrementalCorrectness bool + mockWatchBackend *MockWatchBackend fs *testFs defaultLibraryPath string @@ -313,6 +317,10 @@ func (s *TestSys) writeHeaderToBaseline(builder *strings.Builder, program *incre } } +func (s *TestSys) WatchBackend() execute.WatchBackend { + return s.mockWatchBackend +} + func (s *TestSys) OnProgram(program *incremental.Program) { s.writeHeaderToBaseline(&s.programBaselines, program) diff --git a/internal/execute/tsctests/tscwatch_test.go b/internal/execute/tsctests/tscwatch_test.go index f11fe018b80..7650fcea7e9 100644 --- a/internal/execute/tsctests/tscwatch_test.go +++ b/internal/execute/tsctests/tscwatch_test.go @@ -290,6 +290,33 @@ func TestWatch(t *testing.T) { }, }, }, + // Path resolution: node_modules removed then reinstalled (npm ci after rm -rf) + { + subScenario: "watch detects node modules reinstalled after deletion", + files: FileMap{ + "/home/src/workspaces/project/index.ts": `import { lib } from "mylib";`, + "/home/src/workspaces/project/tsconfig.json": `{}`, + "/home/src/workspaces/project/node_modules/mylib/package.json": `{"name": "mylib", "main": "index.js", "types": "index.d.ts"}`, + "/home/src/workspaces/project/node_modules/mylib/index.js": `exports.lib = "hello";`, + "/home/src/workspaces/project/node_modules/mylib/index.d.ts": `export declare const lib: string;`, + }, + commandLineArgs: []string{"--watch"}, + edits: []*tscEdit{ + { + caption: "delete node_modules entirely", + edit: func(sys *TestSys) { + sys.removeNoError("/home/src/workspaces/project/node_modules/mylib/index.d.ts") + sys.removeNoError("/home/src/workspaces/project/node_modules/mylib/index.js") + sys.removeNoError("/home/src/workspaces/project/node_modules/mylib/package.json") + }, + }, + newTscEdit("reinstall node_modules", func(sys *TestSys) { + sys.writeFileNoError("/home/src/workspaces/project/node_modules/mylib/package.json", `{"name": "mylib", "main": "index.js", "types": "index.d.ts"}`) + sys.writeFileNoError("/home/src/workspaces/project/node_modules/mylib/index.js", `exports.lib = "hello";`) + sys.writeFileNoError("/home/src/workspaces/project/node_modules/mylib/index.d.ts", `export declare const lib: string;`) + }), + }, + }, // Config file lifecycle { subScenario: "watch handles tsconfig deleted", @@ -479,14 +506,15 @@ func TestWatch(t *testing.T) { }), }, }, - // Symlinks + // Symlinks — only node_modules symlinks are resolved via Realpath, + // matching the TypeScript compiler's behavior (see program.ts:2119). { - subScenario: "watch detects change in symlinked file", + subScenario: "watch detects change in symlinked node_modules file", files: FileMap{ - "/home/src/workspaces/project/index.ts": `import { shared } from "./link";`, - "/home/src/workspaces/shared/index.ts": `export const shared = "v1";`, - "/home/src/workspaces/project/link.ts": vfstest.Symlink("/home/src/workspaces/shared/index.ts"), - "/home/src/workspaces/project/tsconfig.json": `{}`, + "/home/src/workspaces/project/index.ts": `import { shared } from "shared";`, + "/home/src/workspaces/shared/index.ts": `export const shared = "v1";`, + "/home/src/workspaces/project/node_modules/shared/index.ts": vfstest.Symlink("/home/src/workspaces/shared/index.ts"), + "/home/src/workspaces/project/tsconfig.json": `{}`, }, commandLineArgs: []string{"--watch"}, edits: []*tscEdit{ @@ -495,6 +523,36 @@ func TestWatch(t *testing.T) { }), }, }, + // Ancestor fallback stability — when a tsconfig include references a + // directory that doesn't exist + { + subScenario: "watch stability with ancestor directory fallback", + files: FileMap{ + "/home/src/workspaces/project/index.ts": `const x: number = 1;`, + "/home/src/workspaces/project/tsconfig.json": `{ "include": ["*.ts", "missing/**/*"] }`, + }, + commandLineArgs: []string{"--watch"}, + edits: []*tscEdit{ + newTscEdit("trivial file change", func(sys *TestSys) { + sys.writeFileNoError("/home/src/workspaces/project/index.ts", `const x: number = 2;`) + }), + }, + }, + // Ancestor fallback: creating deeply nested directories that didn't + // exist at initial build time should trigger a rebuild and re-watch. + { + subScenario: "watch detects file added in deeply nested non-existent include path", + files: FileMap{ + "/home/src/workspaces/project/index.ts": `const x: number = 1;`, + "/home/src/workspaces/project/tsconfig.json": `{ "include": ["*.ts", "deep/nested/dir/**/*"] }`, + }, + commandLineArgs: []string{"--watch"}, + edits: []*tscEdit{ + newTscEdit("create deeply nested file matching include", func(sys *TestSys) { + sys.writeFileNoError("/home/src/workspaces/project/deep/nested/dir/added.ts", `export const added = 1;`) + }), + }, + }, } for _, test := range testCases { diff --git a/internal/execute/tsctests/watcher_race_test.go b/internal/execute/tsctests/watcher_race_test.go index 90206d498ca..d917e666fa8 100644 --- a/internal/execute/tsctests/watcher_race_test.go +++ b/internal/execute/tsctests/watcher_race_test.go @@ -1,11 +1,15 @@ package tsctests import ( + "context" "fmt" "sync" "testing" + "time" "github.com/microsoft/typescript-go/internal/execute" + "github.com/microsoft/typescript-go/internal/execute/tsc" + "gotest.tools/v3/assert" ) // createTestWatcher sets up a minimal project with a tsconfig and @@ -22,7 +26,7 @@ func createTestWatcher(t *testing.T) (*execute.Watcher, *TestSys) { commandLineArgs: []string{"--watch"}, } sys := newTestSys(input, false) - result := execute.CommandLine(sys, []string{"--watch"}, sys) + result := execute.CommandLine(context.Background(), sys, []string{"--watch"}, sys) if result.Watcher == nil { t.Fatal("expected Watcher to be non-nil in watch mode") } @@ -260,3 +264,29 @@ func TestWatcherAlternatingModifyAndDoCycle(t *testing.T) { wg.Wait() } + +func TestBuildWatchStopsWhenContextIsCancelled(t *testing.T) { + t.Parallel() + + sys := newTestSys(&tscInput{ + files: FileMap{ + "/home/src/workspaces/project/tsconfig.json": `{"compilerOptions":{"composite":true},"files":["index.ts"]}`, + "/home/src/workspaces/project/index.ts": `export const x = 1;`, + }, + }, false) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + resultCh := make(chan tsc.CommandLineResult, 1) + go func() { + resultCh <- execute.CommandLine(ctx, sys, []string{"--build", "--watch", "--watchInterval", "60000"}, nil) + }() + + select { + case result := <-resultCh: + assert.Equal(t, result.Status, tsc.ExitStatusSuccess) + assert.Assert(t, result.Watcher != nil) + case <-time.After(2 * time.Second): + t.Fatal("build watch did not stop after context cancellation") + } +} diff --git a/internal/execute/watcher.go b/internal/execute/watcher.go index 1502a9aedd1..7ea8ee9aa5b 100644 --- a/internal/execute/watcher.go +++ b/internal/execute/watcher.go @@ -1,7 +1,12 @@ package execute import ( + "context" + "errors" + "fmt" + "io" "reflect" + "strings" "sync" "time" @@ -12,13 +17,40 @@ import ( "github.com/microsoft/typescript-go/internal/diagnostics" "github.com/microsoft/typescript-go/internal/execute/incremental" "github.com/microsoft/typescript-go/internal/execute/tsc" + "github.com/microsoft/typescript-go/internal/fswatch" "github.com/microsoft/typescript-go/internal/tsoptions" "github.com/microsoft/typescript-go/internal/tspath" "github.com/microsoft/typescript-go/internal/vfs/cachedvfs" "github.com/microsoft/typescript-go/internal/vfs/trackingvfs" - "github.com/microsoft/typescript-go/internal/vfs/vfswatch" ) +// WatchBackend abstracts fswatch.Watcher for testing +type WatchBackend interface { + WatchDirectory(dir string, fn fswatch.WatchCallback, recursive bool, ignore func(string) bool) (io.Closer, error) +} + +type commandLineTestingWithWatchBackend interface { + WatchBackend() WatchBackend +} + +type fswatchBackend struct{ inner fswatch.Watcher } + +func (b *fswatchBackend) WatchDirectory(dir string, fn fswatch.WatchCallback, recursive bool, ignore func(string) bool) (io.Closer, error) { + var opts []fswatch.WatchOption + if recursive { + opts = append(opts, fswatch.WithRecursive()) + } + if ignore != nil { + opts = append(opts, fswatch.WithIgnore(ignore)) + } + return b.inner.WatchDirectory(dir, fn, opts...) +} + +type watchedDir struct { + closer io.Closer + recursive bool +} + type cachedSourceFile struct { file *ast.SourceFile modTime time.Time @@ -58,6 +90,7 @@ type Watcher struct { configFileName string config *tsoptions.ParsedCommandLine compilerOptionsFromCommandLine *core.CompilerOptions + commandLineRaw *collections.OrderedMap[string, any] reportDiagnostic tsc.DiagnosticReporter reportErrorSummary tsc.DiagnosticsReporter reportWatchStatus tsc.DiagnosticReporter @@ -70,7 +103,17 @@ type Watcher struct { configFilePaths []string sourceFileCache *collections.SyncMap[tspath.Path, *cachedSourceFile] - fileWatcher *vfswatch.FileWatcher + + backend WatchBackend + watchedDirs map[string]*watchedDir // dir path → watch state + seenFiles *collections.Set[tspath.Path] // all build dependencies (for event filtering) + configMtimes map[string]time.Time + doCycleCh chan struct{} + debugLog io.Writer // nil = silent; set via TS_WATCH_DEBUG + + changedMu sync.Mutex + changedPaths map[string]fswatch.EventKind // event path → last event kind + changedOverflow bool // true on ErrOverflow; forces full scan fallback } var _ tsc.Watcher = (*Watcher)(nil) @@ -79,6 +122,7 @@ func createWatcher( sys tsc.System, configParseResult *tsoptions.ParsedCommandLine, compilerOptionsFromCommandLine *core.CompilerOptions, + commandLineRaw *collections.OrderedMap[string, any], reportDiagnostic tsc.DiagnosticReporter, reportErrorSummary tsc.DiagnosticsReporter, testing tsc.CommandLineTesting, @@ -87,25 +131,25 @@ func createWatcher( sys: sys, config: configParseResult, compilerOptionsFromCommandLine: compilerOptionsFromCommandLine, + commandLineRaw: commandLineRaw, reportDiagnostic: reportDiagnostic, reportErrorSummary: reportErrorSummary, reportWatchStatus: tsc.CreateWatchStatusReporter(sys, configParseResult.Locale(), configParseResult.CompilerOptions(), testing), testing: testing, sourceFileCache: &collections.SyncMap[tspath.Path, *cachedSourceFile]{}, + doCycleCh: make(chan struct{}, 1), + watchedDirs: make(map[string]*watchedDir), } if configParseResult.ConfigFile != nil { w.configFileName = configParseResult.ConfigFile.SourceFile.FileName() } - w.fileWatcher = vfswatch.NewFileWatcher( - sys.FS(), - w.config.ParsedConfig.WatchOptions.WatchInterval(), - testing != nil, - w.DoCycle, - ) + if t, ok := testing.(commandLineTestingWithWatchBackend); ok { + w.backend = t.WatchBackend() + } return w } -func (w *Watcher) start() { +func (w *Watcher) start(ctx context.Context) { w.mu.Lock() w.extendedConfigCache = &tsc.ExtendedConfigCache{} host := compiler.NewCompilerHost(w.sys.GetCurrentDirectory(), w.sys.FS(), w.sys.DefaultLibraryPath(), w.extendedConfigCache, getTraceFromSys(w.sys, w.config.Locale(), w.testing)) @@ -116,7 +160,15 @@ func (w *Watcher) start() { } if w.sys.GetEnvironmentVariable("TS_WATCH_DEBUG") != "" { - w.fileWatcher.SetDebugLog(w.sys.Writer()) + w.debugLog = w.sys.Writer() + } + + if w.testing == nil && w.backend == nil { + fsw := fswatch.Default() + w.backend = &fswatchBackend{inner: fsw} + if w.debugLog != nil { + fmt.Fprintf(w.debugLog, "[watch] using %s backend\n", fsw.Name()) + } } w.reportWatchStatus(ast.NewCompilerDiagnostic(diagnostics.Starting_compilation_in_watch_mode)) @@ -124,17 +176,356 @@ func (w *Watcher) start() { w.mu.Unlock() if w.testing == nil { - w.fileWatcher.Run(w.sys.Now) + for { + select { + case <-ctx.Done(): + w.closeAllWatches() + return + case <-w.doCycleCh: + w.DoCycle() + } + } + } +} + +func (w *Watcher) computeDesiredWatches(seenFilePaths []string) map[string]bool { + cwd := w.sys.GetCurrentDirectory() + + desiredDirs := make(map[string]bool) // dir → recursive + + // Wildcard directories from tsconfig (recursive or non-recursive) + if w.config.ConfigFile != nil { + for dir, recursive := range w.config.WildcardDirectories() { + realDir := w.sys.FS().Realpath(dir) + desiredDirs[realDir] = recursive + } + } + + // For no-config CLI mode, ensure CWD is watched + if w.config.ConfigFile == nil && len(desiredDirs) == 0 { + dir := w.sys.FS().Realpath(cwd) + desiredDirs[dir] = false + } + + // Config file parent directories as non-recursive watches + for _, cfgPath := range w.configFilePaths { + realPath := w.sys.FS().Realpath(cfgPath) + dir := tspath.GetDirectoryPath(realPath) + if _, has := desiredDirs[dir]; !has { + desiredDirs[dir] = false + } + } + + // For no-config CLI mode, also watch the CLI-specified files' directories + if w.config.ConfigFile == nil { + for _, fileName := range w.config.FileNames() { + absPath := tspath.GetNormalizedAbsolutePath(fileName, cwd) + realPath := w.sys.FS().Realpath(absPath) + dir := tspath.GetDirectoryPath(realPath) + if _, has := desiredDirs[dir]; !has { + desiredDirs[dir] = false + } + } + } + + // Add parent directories for seen files not covered by existing dir watches. + // Resolve ancestor fallbacks first so coverage checks use final dirs. + resolvedDirs := w.resolveDesiredDirs(desiredDirs) + + opts := w.comparePathsOptions() + for _, filePath := range seenFilePaths { + dir := tspath.GetDirectoryPath(filePath) + covered := false + for wdir, recursive := range resolvedDirs { + if recursive { + if tspath.ContainsPath(wdir, dir, opts) { + covered = true + break + } + } else if dir == wdir { + covered = true + break + } + } + if !covered { + if canWatchDirectory(dir) { + resolvedDirs[dir] = false + } + } + } + + // Re-resolve in case newly added dirs don't exist + return w.resolveDesiredDirs(resolvedDirs) +} + +func (w *Watcher) reconcileWatches(seenFilePaths []string) { + if w.backend == nil { + return + } + + desiredDirs := w.computeDesiredWatches(seenFilePaths) + + // Reconcile directory watches using DiffMaps, performing effects inline + core.DiffMapsFunc( + w.watchedDirs, + desiredDirs, + func(wd *watchedDir, recursive bool) bool { return wd.recursive == recursive }, + func(dir string, recursive bool) { + if w.debugLog != nil { + fmt.Fprintf(w.debugLog, "[watch] watching directory %s (recursive=%v)\n", dir, recursive) + } + w.createDirWatch(dir, recursive) + }, + func(dir string, wd *watchedDir) { + if w.debugLog != nil { + fmt.Fprintf(w.debugLog, "[watch] closing stale dir watch: %s\n", dir) + } + wd.closer.Close() + delete(w.watchedDirs, dir) + }, + func(dir string, wd *watchedDir, recursive bool) { + if w.debugLog != nil { + fmt.Fprintf(w.debugLog, "[watch] recreating dir watch %s (recursive %v→%v)\n", dir, wd.recursive, recursive) + } + wd.closer.Close() + delete(w.watchedDirs, dir) + w.createDirWatch(dir, recursive) + }, + ) +} + +func (w *Watcher) comparePathsOptions() tspath.ComparePathsOptions { + return tspath.ComparePathsOptions{ + UseCaseSensitiveFileNames: w.sys.FS().UseCaseSensitiveFileNames(), + CurrentDirectory: w.sys.GetCurrentDirectory(), + } +} + +func (w *Watcher) resolveDesiredDirs(desiredDirs map[string]bool) map[string]bool { + resolved := make(map[string]bool, len(desiredDirs)) + for dir, recursive := range desiredDirs { + watchDir := dir + watchRecursive := recursive + for !w.sys.FS().DirectoryExists(watchDir) { + parent := tspath.GetDirectoryPath(watchDir) + if parent == watchDir { + break + } + watchDir = parent + watchRecursive = false // ancestor fallbacks are always non-recursive + } + if !w.sys.FS().DirectoryExists(watchDir) || !canWatchDirectory(watchDir) { + if w.debugLog != nil { + fmt.Fprintf(w.debugLog, "[watch] no watchable ancestor for %s\n", dir) + } + continue + } + if watchDir != dir && w.debugLog != nil { + fmt.Fprintf(w.debugLog, "[watch] resolved %s to ancestor %s\n", dir, watchDir) + } + if existing, has := resolved[watchDir]; has { + resolved[watchDir] = existing || watchRecursive + } else { + resolved[watchDir] = watchRecursive + } + } + return resolved +} + +func canWatchDirectory(dir string) bool { + components := tspath.GetPathComponents(dir, "") + length := len(components) + if length <= 2 { + return false + } + rootLength := perceivedOsRootLengthForWatching(components) + return length > rootLength+1 +} + +func perceivedOsRootLengthForWatching(components []string) int { + length := len(components) + if length <= 1 { + return 1 + } + root := components[0] + indexAfterOsRoot := 1 + isDosStyle := len(root) >= 2 && tspath.IsVolumeCharacter(root[0]) && root[1] == ':' + + if root != "/" && !isDosStyle && len(components) > 1 { + // Check for UNC-like paths: //server/c$/... + if len(components[1]) >= 2 && tspath.IsVolumeCharacter(components[1][0]) && strings.HasSuffix(components[1], "$") { + if length == 2 { + return 2 + } + indexAfterOsRoot = 2 + isDosStyle = true + } + } + + if isDosStyle && (indexAfterOsRoot >= length || !strings.EqualFold(components[indexAfterOsRoot], "users")) { + return indexAfterOsRoot + } + + if indexAfterOsRoot < length && strings.EqualFold(components[indexAfterOsRoot], "workspaces") { + // Codespaces: /workspaces repos are hoisted here + return indexAfterOsRoot + 1 + } + + // /home/username or C:/Users/username + return indexAfterOsRoot + 2 +} + +func (w *Watcher) createDirWatch(dir string, recursive bool) { + entry := &watchedDir{recursive: recursive} + cb := func(events []fswatch.Event, err error) { + if err != nil && errors.Is(err, fswatch.ErrWatchTerminated) { + w.handleWatchTerminated(dir, entry) + return + } + w.onWatchEvents(events, err) + } + watch, err := w.backend.WatchDirectory(dir, cb, recursive, shouldIgnoreWatchPath) + if err != nil { + if w.debugLog != nil { + fmt.Fprintf(w.debugLog, "[watch] failed to watch directory %s: %v\n", dir, err) + } + return + } + entry.closer = watch + w.watchedDirs[dir] = entry +} + +func (w *Watcher) closeAllWatches() { + w.mu.Lock() + dirs := make([]io.Closer, 0, len(w.watchedDirs)) + for dir, wd := range w.watchedDirs { + dirs = append(dirs, wd.closer) + delete(w.watchedDirs, dir) + } + w.mu.Unlock() + for _, c := range dirs { + c.Close() + } +} + +func (w *Watcher) handleWatchTerminated(dir string, identity *watchedDir) { + if w.debugLog != nil { + fmt.Fprintf(w.debugLog, "[watch] watch terminated: %s\n", dir) + } + var staleCloser io.Closer + w.mu.Lock() + if wd, ok := w.watchedDirs[dir]; ok && wd == identity { + staleCloser = wd.closer + delete(w.watchedDirs, dir) + } + w.mu.Unlock() + if staleCloser != nil { + staleCloser.Close() + } + w.changedMu.Lock() + w.changedOverflow = true + w.changedMu.Unlock() + w.signalDoCycle() +} + +func shouldIgnoreWatchPath(path string) bool { + p := tspath.NormalizeSlashes(path) + return strings.HasSuffix(p, "/.git") || + strings.Contains(p, "/.git/") || + strings.Contains(p, "/node_modules/.") || + strings.Contains(p, "/.#") +} + +func (w *Watcher) onWatchEvents(events []fswatch.Event, err error) { + if err != nil { + if errors.Is(err, fswatch.ErrOverflow) { + if w.debugLog != nil { + fmt.Fprintf(w.debugLog, "[watch] event overflow, triggering rebuild\n") + } + w.changedMu.Lock() + w.changedOverflow = true + w.changedMu.Unlock() + w.signalDoCycle() + return + } + fmt.Fprintf(w.sys.Writer(), "Warning: File watch error: %v\n", err) + return + } + + if len(events) > 0 { + if w.debugLog != nil { + fmt.Fprintf(w.debugLog, "[watch] %d event(s): ", len(events)) + for i, e := range events { + if i > 0 { + fmt.Fprint(w.debugLog, ", ") + } + if i >= 5 { + fmt.Fprintf(w.debugLog, "... and %d more", len(events)-i) + break + } + fmt.Fprintf(w.debugLog, "%s %s", e.Kind, e.Path) + } + fmt.Fprintln(w.debugLog) + } + w.changedMu.Lock() + if w.changedPaths == nil { + w.changedPaths = make(map[string]fswatch.EventKind, len(events)) + } + for _, e := range events { + w.changedPaths[e.Path] = e.Kind + } + w.changedMu.Unlock() + w.signalDoCycle() + } +} + +func (w *Watcher) signalDoCycle() { + select { + case w.doCycleCh <- struct{}{}: + // Signal sent; the DoCycle loop will pick it up. + default: + // A signal is already pending; coalesced. } } func (w *Watcher) DoCycle() { w.mu.Lock() defer w.mu.Unlock() + + w.changedMu.Lock() + changedPaths := w.changedPaths + overflow := w.changedOverflow + w.changedPaths = nil + w.changedOverflow = false + w.changedMu.Unlock() + + hasEvents := len(changedPaths) > 0 || overflow + if w.recheckTsConfig() { return } - if !w.fileWatcher.WatchStateUninitialized() && !w.configModified && !w.fileWatcher.HasChangesFromWatchState() { + + if hasEvents && !overflow && !w.configModified { + // Filter fswatch events against known dependencies + if w.isRelevantChange(changedPaths) { + w.evictChangedSourceFiles(changedPaths) + } else { + if w.debugLog != nil { + fmt.Fprintf(w.debugLog, "[watch] DoCycle: %d event(s) not relevant to compilation, skipping rebuild\n", len(changedPaths)) + } + if w.testing != nil { + w.testing.OnProgram(w.program) + } + return + } + } else if overflow { + // Overflow: evict the entire source file cache to force re-build + w.sourceFileCache = &collections.SyncMap[tspath.Path, *cachedSourceFile]{} + } else if !hasEvents && !w.configModified { + // No events and no config change + if w.debugLog != nil { + fmt.Fprintf(w.debugLog, "[watch] DoCycle: no events, skipping\n") + } if w.testing != nil { w.testing.OnProgram(w.program) } @@ -145,6 +536,37 @@ func (w *Watcher) DoCycle() { w.doBuild() } +func (w *Watcher) isRelevantChange(changedPaths map[string]fswatch.EventKind) bool { + caseSensitive := w.sys.FS().UseCaseSensitiveFileNames() + cwd := w.sys.GetCurrentDirectory() + opts := w.comparePathsOptions() + for eventPath := range changedPaths { + p := tspath.ToPath(eventPath, cwd, caseSensitive) + if w.seenFiles.Has(p) { + return true + } + if w.config.ConfigFile != nil && w.config.PossiblyMatchesFileName(eventPath) { + return true + } + if w.config.ConfigFile != nil && w.config.PossiblyMatchesDirectoryName(p) { + return true + } + // If a directory was created under an ancestor fallback watch, + // treat it as relevant — it may be on the path to a previously + // non-existent directory we want to watch. Err on the side of + // false positives (unnecessary rebuild) over false negatives + // (missed rebuild). + if w.sys.FS().DirectoryExists(eventPath) { + for dir := range w.watchedDirs { + if tspath.ContainsPath(dir, eventPath, opts) { + return true + } + } + } + } + return false +} + func (w *Watcher) doBuild() { if w.configModified { w.sourceFileCache = &collections.SyncMap[tspath.Path, *cachedSourceFile]{} @@ -176,8 +598,23 @@ func (w *Watcher) doBuild() { result := w.compileAndEmit() cached.DisableAndClearCache() - w.fileWatcher.UpdateWatchState(tfs.SeenFiles.ToSlice(), wildcardDirs) - w.fileWatcher.SetPollInterval(w.config.ParsedConfig.WatchOptions.WatchInterval()) + + caseSensitive := w.sys.FS().UseCaseSensitiveFileNames() + cwd := w.sys.GetCurrentDirectory() + seenSlice := tfs.SeenFiles.ToSlice() + w.seenFiles = collections.NewSetWithSizeHint[tspath.Path](len(seenSlice)) + for _, p := range seenSlice { + w.seenFiles.Add(tspath.ToPath(p, cwd, caseSensitive)) + } + + w.configMtimes = make(map[string]time.Time, len(w.configFilePaths)) + for _, cfgPath := range w.configFilePaths { + if s := w.sys.FS().Stat(cfgPath); s != nil { + w.configMtimes[cfgPath] = s.ModTime() + } + } + + w.reconcileWatches(seenSlice) w.configModified = false programFiles := w.program.GetProgram().FilesByPath() @@ -200,6 +637,20 @@ func (w *Watcher) doBuild() { } } +func (w *Watcher) evictChangedSourceFiles(changedPaths map[string]fswatch.EventKind) { + caseSensitive := w.sys.FS().UseCaseSensitiveFileNames() + cwd := w.sys.GetCurrentDirectory() + for eventPath := range changedPaths { + p := tspath.ToPath(eventPath, cwd, caseSensitive) + if _, ok := w.sourceFileCache.Load(p); ok { + if w.debugLog != nil { + fmt.Fprintf(w.debugLog, "[watch] evicting cached source file: %s\n", p) + } + w.sourceFileCache.Delete(p) + } + } +} + func (w *Watcher) compileAndEmit() tsc.CompileAndEmitResult { return tsc.EmitFilesAndReportErrors(tsc.EmitInput{ Sys: w.sys, @@ -222,22 +673,16 @@ func (w *Watcher) recheckTsConfig() bool { if !w.configHasErrors && len(w.configFilePaths) > 0 { changed := false for _, path := range w.configFilePaths { - old, ok := w.fileWatcher.WatchStateEntry(path) - if !ok { - changed = true - break - } + oldMtime, ok := w.configMtimes[path] s := w.sys.FS().Stat(path) - if !old.Exists { + if !ok { if s != nil { changed = true break } - } else { - if s == nil || !s.ModTime().Equal(old.ModTime) { - changed = true - break - } + } else if s == nil || !s.ModTime().Equal(oldMtime) { + changed = true + break } } if !changed { @@ -245,8 +690,25 @@ func (w *Watcher) recheckTsConfig() bool { } } + configParseResult := w.parseConfigFile() + if configParseResult == nil { + return true + } + if w.configHasErrors { + w.configModified = true + } + w.configHasErrors = false + w.configFilePaths = append([]string{w.configFileName}, configParseResult.ExtendedSourceFiles()...) + if !reflect.DeepEqual(w.config.ParsedConfig, configParseResult.ParsedConfig) { + w.configModified = true + } + w.config = configParseResult + return false +} + +func (w *Watcher) parseConfigFile() *tsoptions.ParsedCommandLine { extendedConfigCache := &tsc.ExtendedConfigCache{} - configParseResult, errors := tsoptions.GetParsedCommandLineOfConfigFile(w.configFileName, w.compilerOptionsFromCommandLine, nil, w.sys, extendedConfigCache) + configParseResult, errors := tsoptions.GetParsedCommandLineOfConfigFile(w.configFileName, w.compilerOptionsFromCommandLine, w.commandLineRaw, w.sys, extendedConfigCache) if len(errors) > 0 { for _, e := range errors { w.reportDiagnostic(e) @@ -258,17 +720,8 @@ func (w *Watcher) recheckTsConfig() bool { } else { w.reportWatchStatus(ast.NewCompilerDiagnostic(diagnostics.Found_0_errors_Watching_for_file_changes, errorCount)) } - return true - } - if w.configHasErrors { - w.configModified = true - } - w.configHasErrors = false - w.configFilePaths = append([]string{w.configFileName}, configParseResult.ExtendedSourceFiles()...) - if !reflect.DeepEqual(w.config.ParsedConfig, configParseResult.ParsedConfig) { - w.configModified = true + return nil } - w.config = configParseResult w.extendedConfigCache = extendedConfigCache - return false + return configParseResult } diff --git a/internal/testutil/fsbaselineutil/differ.go b/internal/testutil/fsbaselineutil/differ.go index 4fd44003df7..bb01153fed1 100644 --- a/internal/testutil/fsbaselineutil/differ.go +++ b/internal/testutil/fsbaselineutil/differ.go @@ -137,3 +137,41 @@ func (d *FSDiffer) addFsEntryDiff(diffs map[string]string, newDirContent *DiffEn diffs[path] = "*Lib*\n" + newDirContent.Content } } + +// FileChange represents a filesystem change detected between snapshots. +type FileChange struct { + Path string + Deleted bool +} + +func (d *FSDiffer) ChangedPaths() []FileChange { + if d.serializedDiff == nil { + return nil + } + + var changes []FileChange + oldSnap := d.serializedDiff + + // Check current files against previous snapshot. + for path, file := range d.MapFs().Entries() { + if file.Mode&fs.ModeSymlink != 0 || !file.Mode.IsRegular() { + continue + } + if old, ok := oldSnap.Snap[path]; !ok { + // New file. + changes = append(changes, FileChange{Path: path}) + } else if string(file.Data) != old.Content || file.ModTime != old.MTime { + // Modified or touched file. + changes = append(changes, FileChange{Path: path}) + } + } + + // Check for deleted files. + for path := range oldSnap.Snap { + if fileInfo := d.MapFs().GetFileInfo(path); fileInfo == nil { + changes = append(changes, FileChange{Path: path, Deleted: true}) + } + } + + return changes +} diff --git a/internal/vfs/trackingvfs/trackingvfs.go b/internal/vfs/trackingvfs/trackingvfs.go index 133af98b880..cd0436b3b3a 100644 --- a/internal/vfs/trackingvfs/trackingvfs.go +++ b/internal/vfs/trackingvfs/trackingvfs.go @@ -70,4 +70,7 @@ func (fs *FS) WalkDir(root string, walkFn vfs.WalkDirFunc) error { }) } -func (fs *FS) Realpath(path string) string { return fs.Inner.Realpath(path) } +func (fs *FS) Realpath(path string) string { + fs.SeenFiles.Add(path) + return fs.Inner.Realpath(path) +} diff --git a/internal/vfs/vfswatch/vfswatch.go b/internal/vfs/vfswatch/vfswatch.go deleted file mode 100644 index 3189d961b09..00000000000 --- a/internal/vfs/vfswatch/vfswatch.go +++ /dev/null @@ -1,267 +0,0 @@ -// This package implements a polling-based file watcher designed -// for use by both the CLI watcher and the language server. -package vfswatch - -import ( - "fmt" - "io" - "slices" - "sync" - "time" - - "github.com/microsoft/typescript-go/internal/vfs" - "github.com/zeebo/xxh3" -) - -const debounceWait = 250 * time.Millisecond - -type WatchEntry struct { - ModTime time.Time - Exists bool - ChildrenHash uint64 // 0 if not tracked -} - -type FileWatcher struct { - fs vfs.FS - pollInterval time.Duration - testing bool - callback func() - watchState map[string]WatchEntry - wildcardDirectories map[string]bool - mu sync.Mutex - debugLog io.Writer // nil = silent; non-nil = write timing lines here -} - -func NewFileWatcher(fs vfs.FS, pollInterval time.Duration, testing bool, callback func()) *FileWatcher { - return &FileWatcher{ - fs: fs, - pollInterval: pollInterval, - testing: testing, - callback: callback, - } -} - -// SetDebugLog enables per-scan timing output written to w. -// Pass nil to disable. Safe to call at any time. -func (fw *FileWatcher) SetDebugLog(w io.Writer) { - fw.mu.Lock() - defer fw.mu.Unlock() - fw.debugLog = w -} - -func (fw *FileWatcher) SetPollInterval(d time.Duration) { - fw.mu.Lock() - defer fw.mu.Unlock() - fw.pollInterval = d -} - -func (fw *FileWatcher) WatchStateEntry(path string) (WatchEntry, bool) { - fw.mu.Lock() - defer fw.mu.Unlock() - e, ok := fw.watchState[path] - return e, ok -} - -func (fw *FileWatcher) WatchStateUninitialized() bool { - fw.mu.Lock() - defer fw.mu.Unlock() - return fw.watchState == nil -} - -func (fw *FileWatcher) UpdateWatchState(paths []string, wildcardDirs map[string]bool) { - state := snapshotPaths(fw.fs, paths, wildcardDirs) - fw.mu.Lock() - defer fw.mu.Unlock() - fw.watchState = state - fw.wildcardDirectories = wildcardDirs -} - -func (fw *FileWatcher) WaitForSettled(now func() time.Time) { - if fw.testing { - return - } - fw.mu.Lock() - pollInterval := fw.pollInterval - fw.mu.Unlock() - current := fw.currentState() - settledAt := now() - tick := min(pollInterval, debounceWait) - for now().Sub(settledAt) < debounceWait { - time.Sleep(tick) - if fw.hasChanges(current) { - current = fw.currentState() - settledAt = now() - } - } -} - -func (fw *FileWatcher) currentState() map[string]WatchEntry { - fw.mu.Lock() - watchState := fw.watchState - wildcardDirs := fw.wildcardDirectories - fw.mu.Unlock() - state := make(map[string]WatchEntry, len(watchState)) - for fn := range watchState { - if s := fw.fs.Stat(fn); s != nil { - state[fn] = WatchEntry{ModTime: s.ModTime(), Exists: true} - } else { - state[fn] = WatchEntry{Exists: false} - } - } - for dir, recursive := range wildcardDirs { - if !recursive { - snapshotDirEntry(fw.fs, state, dir) - continue - } - _ = fw.fs.WalkDir(dir, func(path string, d vfs.DirEntry, err error) error { - if err != nil || !d.IsDir() { - return nil - } - snapshotDirEntry(fw.fs, state, path) - return nil - }) - } - return state -} - -func snapshotPaths(fs vfs.FS, paths []string, wildcardDirs map[string]bool) map[string]WatchEntry { - state := make(map[string]WatchEntry, len(paths)) - for _, fn := range paths { - if s := fs.Stat(fn); s != nil { - state[fn] = WatchEntry{ModTime: s.ModTime(), Exists: true} - } else { - state[fn] = WatchEntry{Exists: false} - } - } - for dir, recursive := range wildcardDirs { - if !recursive { - snapshotDirEntry(fs, state, dir) - continue - } - _ = fs.WalkDir(dir, func(path string, d vfs.DirEntry, err error) error { - if err != nil || !d.IsDir() { - return nil - } - snapshotDirEntry(fs, state, path) - return nil - }) - } - return state -} - -func snapshotDirEntry(fs vfs.FS, state map[string]WatchEntry, dir string) { - entries := fs.GetAccessibleEntries(dir) - h := hashEntries(entries) - if existing, ok := state[dir]; ok { - existing.ChildrenHash = h - state[dir] = existing - } else { - if s := fs.Stat(dir); s != nil { - state[dir] = WatchEntry{ModTime: s.ModTime(), Exists: true, ChildrenHash: h} - } - } -} - -func hashEntries(entries vfs.Entries) uint64 { - dirs := slices.Clone(entries.Directories) - files := slices.Clone(entries.Files) - slices.Sort(dirs) - slices.Sort(files) - var h xxh3.Hasher - for _, name := range dirs { - _, _ = h.WriteString("d:") - _, _ = h.WriteString(name) - _, _ = h.Write([]byte{0}) - } - for _, name := range files { - _, _ = h.WriteString("f:") - _, _ = h.WriteString(name) - _, _ = h.Write([]byte{0}) - } - return h.Sum64() -} - -// hasChanges compares the current filesystem state against baseline. -// -// Tracked entries fall into two categories: -// -// - Explicit paths (files the compiler depends on, plus directory paths -// accessed via DirectoryExists/Stat/etc. during compilation). For these -// we only need to know whether the path exists and, if it does, whether -// its mtime has changed. We never depend on *what's inside* a directory -// in this category — any specific file we care about is tracked -// independently in this same map. -// -// - Wildcard tree directories. snapshotPaths walks every directory under -// each recursive wildcard root and stores it with a ChildrenHash that -// covers the directory's listing. Re-hashing here detects any new, -// deleted, or renamed file or subdirectory in those trees. -// -// Iterating baseline once therefore covers both: a single fs.Stat per entry, -// plus a fs.GetAccessibleEntries only for entries with ChildrenHash != 0 -// (i.e. wildcard tree members). -func (fw *FileWatcher) hasChanges(baseline map[string]WatchEntry) bool { - for path, old := range baseline { - s := fw.fs.Stat(path) - if !old.Exists { - if s != nil { - return true - } - } else { - if s == nil || !s.ModTime().Equal(old.ModTime) { - return true - } - if old.ChildrenHash != 0 { - entries := fw.fs.GetAccessibleEntries(path) - if hashEntries(entries) != old.ChildrenHash { - return true - } - } - } - } - return false -} - -// HasChangesFromWatchState compares the current filesystem against the -// stored watch state. Safe for concurrent use: watchState is snapshotted -// under lock; the map itself is never mutated after creation -// (UpdateWatchState replaces it). -func (fw *FileWatcher) HasChangesFromWatchState() bool { - fw.mu.Lock() - ws := fw.watchState - fw.mu.Unlock() - return fw.hasChanges(ws) -} - -func (fw *FileWatcher) Run(now func() time.Time) { - for { - fw.mu.Lock() - interval := fw.pollInterval - ws := fw.watchState - log := fw.debugLog - fw.mu.Unlock() - time.Sleep(interval) - start := now() - changed := ws == nil || fw.hasChanges(ws) - if log != nil { - elapsed := now().Sub(start) - files, dirs, missing := 0, 0, 0 - for _, e := range ws { - switch { - case !e.Exists: - missing++ - case e.ChildrenHash != 0: - dirs++ - default: - files++ - } - } - fmt.Fprintf(log, "[vfswatch] scan: %d paths (%d files, %d dirs, %d missing), %.1fms, changed=%v\n", - len(ws), files, dirs, missing, float64(elapsed.Microseconds())/1000.0, changed) - } - if changed { - fw.WaitForSettled(now) - fw.callback() - } - } -} diff --git a/internal/vfs/vfswatch/vfswatch_race_test.go b/internal/vfs/vfswatch/vfswatch_race_test.go deleted file mode 100644 index 31a7cdf7267..00000000000 --- a/internal/vfs/vfswatch/vfswatch_race_test.go +++ /dev/null @@ -1,319 +0,0 @@ -package vfswatch_test - -import ( - "fmt" - "sync" - "testing" - "time" - - "github.com/microsoft/typescript-go/internal/vfs" - "github.com/microsoft/typescript-go/internal/vfs/vfstest" - "github.com/microsoft/typescript-go/internal/vfs/vfswatch" -) - -var defaultPaths = []string{ - "/src/a.ts", - "/src/b.ts", - "/src/c.ts", - "/src/sub/d.ts", - "/tsconfig.json", -} - -func newTestFS() vfs.FS { - return vfstest.FromMap(map[string]string{ - "/src/a.ts": "const a = 1;", - "/src/b.ts": "const b = 2;", - "/src/c.ts": "const c = 3;", - "/src/sub/d.ts": "const d = 4;", - "/tsconfig.json": `{}`, - }, true) -} - -func newWatcherWithState(fs vfs.FS) *vfswatch.FileWatcher { - fw := vfswatch.NewFileWatcher(fs, 10*time.Millisecond, true, func() {}) - fw.UpdateWatchState(defaultPaths, nil) - return fw -} - -// TestRaceHasChangesVSUpdateWatchState tests for data races between -// concurrent HasChanges reads and UpdateWatchState writes on the -// WatchState map. -func TestRaceHasChangesVSUpdateWatchState(t *testing.T) { - t.Parallel() - fs := newTestFS() - fw := newWatcherWithState(fs) - - var wg sync.WaitGroup - - for range 10 { - wg.Go(func() { - for range 200 { - fw.HasChangesFromWatchState() - } - }) - } - - for range 5 { - wg.Go(func() { - for range 100 { - fw.UpdateWatchState([]string{"/src/a.ts", "/src/b.ts"}, nil) - } - }) - } - - wg.Wait() -} - -// TestRaceWildcardDirectoriesAccess tests for data races when -// WildcardDirectories is read internally by HasChanges while being -// replaced concurrently via UpdateWatchState. -func TestRaceWildcardDirectoriesAccess(t *testing.T) { - t.Parallel() - fs := newTestFS() - fw := newWatcherWithState(fs) - fw.UpdateWatchState(defaultPaths, map[string]bool{"/src": true}) - - var wg sync.WaitGroup - - for range 10 { - wg.Go(func() { - for range 200 { - fw.HasChangesFromWatchState() - } - }) - } - - for range 5 { - wg.Go(func() { - for range 100 { - fw.UpdateWatchState(defaultPaths, map[string]bool{"/src": true}) - } - }) - } - - wg.Wait() -} - -// TestRacePollIntervalAccess tests for data races on the PollInterval -// field when it is read and written from multiple goroutines. -func TestRacePollIntervalAccess(t *testing.T) { - t.Parallel() - fs := newTestFS() - fw := newWatcherWithState(fs) - - var wg sync.WaitGroup - - for range 10 { - wg.Go(func() { - for range 500 { - fw.HasChangesFromWatchState() - } - }) - } - - for i := range 5 { - wg.Add(1) - go func(i int) { - defer wg.Done() - for j := range 200 { - fw.SetPollInterval(time.Duration(i*200+j) * time.Millisecond) - } - }(i) - } - - wg.Wait() -} - -// TestRaceMixedOperations hammers all FileWatcher operations -// concurrently: HasChanges, UpdateWatchState, FS mutations, -// and PollInterval writes. -func TestRaceMixedOperations(t *testing.T) { - t.Parallel() - fs := newTestFS() - fw := newWatcherWithState(fs) - fw.UpdateWatchState(defaultPaths, map[string]bool{"/src": true}) - - var wg sync.WaitGroup - - // HasChanges readers - for range 8 { - wg.Go(func() { - for range 100 { - fw.HasChangesFromWatchState() - } - }) - } - - // UpdateWatchState writers - for i := range 4 { - wg.Add(1) - go func(i int) { - defer wg.Done() - for j := range 50 { - paths := []string{"/src/a.ts", fmt.Sprintf("/src/new_%d_%d.ts", i, j)} - fw.UpdateWatchState(paths, map[string]bool{"/src": true}) - } - }(i) - } - - // FS modifiers - for i := range 4 { - wg.Add(1) - go func(i int) { - defer wg.Done() - for j := range 50 { - path := fmt.Sprintf("/src/gen_%d_%d.ts", i, j) - _ = fs.WriteFile(path, fmt.Sprintf("const x = %d;", j)) - if j%3 == 0 { - _ = fs.Remove(path) - } - } - }(i) - } - - // PollInterval writers - for i := range 2 { - wg.Add(1) - go func(i int) { - defer wg.Done() - for j := range 100 { - fw.SetPollInterval(time.Duration(50+j) * time.Millisecond) - } - }(i) - } - - wg.Wait() -} - -// TestRaceUpdateWithConcurrentFileModifications creates and deletes -// files on the FS while UpdateWatchState is scanning the same FS, -// testing for races between the FS walker and concurrent mutations. -func TestRaceUpdateWithConcurrentFileModifications(t *testing.T) { - t.Parallel() - fs := newTestFS() - fw := newWatcherWithState(fs) - fw.UpdateWatchState(defaultPaths, map[string]bool{"/src": true}) - - var wg sync.WaitGroup - - // Rapid file creation/deletion - for i := range 6 { - wg.Add(1) - go func(i int) { - defer wg.Done() - for j := range 100 { - path := fmt.Sprintf("/src/churn_%d_%d.ts", i, j) - _ = fs.WriteFile(path, fmt.Sprintf("export const v = %d;", j)) - _ = fs.Remove(path) - } - }(i) - } - - // Concurrent UpdateWatchState (walks the FS tree via WildcardDirectories) - for range 4 { - wg.Go(func() { - for range 50 { - fw.UpdateWatchState([]string{"/src/a.ts", "/tsconfig.json"}, map[string]bool{"/src": true}) - } - }) - } - - wg.Wait() -} - -// FuzzFileWatcherOperations fuzzes random sequences of file operations -// and watcher state management to find panics and edge cases. -// Run with -race to also detect data races. -func FuzzFileWatcherOperations(f *testing.F) { - f.Add([]byte{0, 1, 2, 3, 0, 1, 2, 3}) - f.Add([]byte{2, 2, 2, 0, 0, 1, 3, 3}) - f.Add([]byte{3, 3, 3, 3, 0, 0, 0, 0}) - f.Add([]byte{4, 4, 4, 0, 2, 1, 3, 2}) - f.Add([]byte{5, 5, 5, 5, 5, 5, 5, 5}) - f.Add([]byte{0, 0, 0, 0, 0, 0, 0, 0}) - f.Add([]byte{1, 1, 1, 1, 1, 1, 1, 1}) - - f.Fuzz(func(t *testing.T, ops []byte) { - if len(ops) == 0 { - return - } - - fs := newTestFS() - fw := newWatcherWithState(fs) - - files := []string{"/src/a.ts", "/src/b.ts", "/src/c.ts", "/src/new.ts", "/src/sub/new.ts"} - - for i, op := range ops { - path := files[i%len(files)] - - switch op % 6 { - case 0: // Write/modify a file - _ = fs.WriteFile(path, fmt.Sprintf("const x = %d;", i)) - case 1: // Remove a file - _ = fs.Remove(path) - case 2: // Check for changes against current state - fw.HasChangesFromWatchState() - case 3: // Rebuild watch state - fw.UpdateWatchState(files, nil) - case 4: // Set wildcard directories and check for changes - fw.UpdateWatchState(files, map[string]bool{"/src": true}) - fw.HasChangesFromWatchState() - case 5: // Modify PollInterval - fw.SetPollInterval(time.Duration(i*10) * time.Millisecond) - } - } - }) -} - -// FuzzFileWatcherConcurrent is a fuzz test that runs random operations -// from multiple goroutines to find concurrency bugs. -func FuzzFileWatcherConcurrent(f *testing.F) { - f.Add([]byte{0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5}) - f.Add([]byte{0, 0, 0, 3, 3, 3, 2, 2, 2, 1, 1, 1}) - f.Add([]byte{2, 3, 2, 3, 2, 3, 0, 0, 0, 0, 0, 0}) - - f.Fuzz(func(t *testing.T, ops []byte) { - if len(ops) < 4 { - return - } - - fs := newTestFS() - fw := newWatcherWithState(fs) - fw.UpdateWatchState(defaultPaths, map[string]bool{"/src": true}) - - files := []string{"/src/a.ts", "/src/b.ts", "/src/c.ts", "/src/new.ts"} - - // Split ops into chunks for different goroutines - chunkSize := len(ops) / 2 - if chunkSize == 0 { - chunkSize = 1 - } - - var wg sync.WaitGroup - - for start := 0; start < len(ops); start += chunkSize { - end := min(start+chunkSize, len(ops)) - chunk := ops[start:end] - - wg.Add(1) - go func(chunk []byte, goroutineID int) { - defer wg.Done() - for i, op := range chunk { - path := files[(goroutineID*len(chunk)+i)%len(files)] - switch op % 4 { - case 0: - _ = fs.WriteFile(path, fmt.Sprintf("const g%d = %d;", goroutineID, i)) - case 1: - _ = fs.Remove(path) - case 2: - fw.HasChangesFromWatchState() - case 3: - fw.UpdateWatchState([]string{path}, map[string]bool{"/src": true}) - } - } - }(chunk, start/chunkSize) - } - - wg.Wait() - }) -} diff --git a/internal/vfs/vfswatch/vfswatch_test.go b/internal/vfs/vfswatch/vfswatch_test.go deleted file mode 100644 index f7220e461ee..00000000000 --- a/internal/vfs/vfswatch/vfswatch_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package vfswatch_test - -import ( - "sync/atomic" - "testing" - "time" - - "github.com/microsoft/typescript-go/internal/vfs" - "github.com/microsoft/typescript-go/internal/vfs/vfstest" - "github.com/microsoft/typescript-go/internal/vfs/vfswatch" -) - -// countingFS wraps a vfs.FS and counts calls to GetAccessibleEntries. -type countingFS struct { - vfs.FS - n atomic.Int64 -} - -func (c *countingFS) GetAccessibleEntries(path string) vfs.Entries { - c.n.Add(1) - return c.FS.GetAccessibleEntries(path) -} - -// TestHasChangesNoRedundantGetAccessibleEntries verifies that -// HasChangesFromWatchState calls GetAccessibleEntries only for directories -// that are part of a recursive wildcard tree, and exactly once each — never -// for directories that merely happened to be in the explicit paths list. -// -// Setup: /src is a recursive wildcard root containing /src and /src/sub. -// The explicit paths list also contains /node_modules (a directory accessed -// during compilation but with no wildcard scope) and the tsconfig file. -// A single HasChangesFromWatchState call should call GetAccessibleEntries -// exactly twice: once for /src and once for /src/sub. /node_modules is a -// directory but is *not* a wildcard tree member, so it gets only a Stat — -// the watcher does not depend on its listing. -func TestHasChangesNoRedundantGetAccessibleEntries(t *testing.T) { - t.Parallel() - - inner := vfstest.FromMap(map[string]string{ - "/src/a.ts": "const a = 1;", - "/src/b.ts": "const b = 2;", - "/src/sub/c.ts": "const c = 3;", - "/node_modules/x.js": "", - "/tsconfig.json": "{}", - }, true) - cfs := &countingFS{FS: inner} - - fw := vfswatch.NewFileWatcher(cfs, 10*time.Millisecond, true, func() {}) - fw.UpdateWatchState( - []string{"/src/a.ts", "/src/b.ts", "/src/sub/c.ts", "/node_modules", "/tsconfig.json"}, - map[string]bool{"/src": true}, - ) - - cfs.n.Store(0) // reset counter after baseline snapshot - - fw.HasChangesFromWatchState() - - // Only /src and /src/sub (the wildcard tree) are tracked with ChildrenHash. - // /node_modules is a directory in the explicit paths list but should NOT be - // hashed: its listing is not something the watcher depends on. - if got := cfs.n.Load(); got != 2 { - t.Errorf("GetAccessibleEntries called %d times, want 2 (once per wildcard-tree dir)", got) - } -} diff --git a/testdata/baselines/reference/tscWatch/commandLine/Parse-watch-interval-option.js b/testdata/baselines/reference/tscWatch/commandLine/Parse-watch-interval-option.js index c4931ed7907..fe70403840d 100644 --- a/testdata/baselines/reference/tscWatch/commandLine/Parse-watch-interval-option.js +++ b/testdata/baselines/reference/tscWatch/commandLine/Parse-watch-interval-option.js @@ -42,6 +42,10 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-at-types-package-installed-later.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-at-types-package-installed-later.js index 01adb0dc40d..aa4ab101cd0 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-at-types-package-installed-later.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-at-types-package-installed-later.js @@ -50,6 +50,10 @@ declare const console: { log(msg: any): void; }; export {}; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -71,6 +75,10 @@ Output:: //// [/home/src/workspaces/project/index.js] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-change-in-symlinked-file.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-change-in-symlinked-node_modules-file.js similarity index 76% rename from testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-change-in-symlinked-file.js rename to testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-change-in-symlinked-node_modules-file.js index 5aa1b5ad18e..de6faa404f9 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-change-in-symlinked-file.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-change-in-symlinked-node_modules-file.js @@ -2,8 +2,8 @@ currentDirectory::/home/src/workspaces/project useCaseSensitiveFileNames::true Input:: //// [/home/src/workspaces/project/index.ts] *new* -import { shared } from "./link"; -//// [/home/src/workspaces/project/link.ts] -> /home/src/workspaces/shared/index.ts *new* +import { shared } from "shared"; +//// [/home/src/workspaces/project/node_modules/shared/index.ts] -> /home/src/workspaces/shared/index.ts *new* //// [/home/src/workspaces/project/tsconfig.json] *new* {} //// [/home/src/workspaces/shared/index.ts] *new* @@ -42,14 +42,16 @@ declare const console: { log(msg: any): void; }; //// [/home/src/workspaces/project/index.js] *new* export {}; -//// [/home/src/workspaces/project/link.js] *new* -export const shared = "v1"; - +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) + /home/src/workspaces/shared tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts -*refresh* /home/src/workspaces/project/link.ts +*refresh* /home/src/workspaces/shared/index.ts *refresh* /home/src/workspaces/project/index.ts Signatures:: @@ -65,14 +67,16 @@ Output:: [HH:MM:SS AM] Found 0 errors. Watching for file changes. //// [/home/src/workspaces/project/index.js] *rewrite with same content* -//// [/home/src/workspaces/project/link.js] *modified* -export const shared = "v2"; - +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) + /home/src/workspaces/shared tsconfig.json:: SemanticDiagnostics:: -*refresh* /home/src/workspaces/project/link.ts +*refresh* /home/src/workspaces/shared/index.ts *refresh* /home/src/workspaces/project/index.ts Signatures:: -(computed .d.ts) /home/src/workspaces/project/link.ts +(used version) /home/src/workspaces/shared/index.ts (computed .d.ts) /home/src/workspaces/project/index.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-deeply-nested-non-existent-include-path.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-deeply-nested-non-existent-include-path.js new file mode 100644 index 00000000000..0427dcf2d89 --- /dev/null +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-deeply-nested-non-existent-include-path.js @@ -0,0 +1,80 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: number = 1; +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ "include": ["*.ts", "deep/nested/dir/**/*"] } + +tsgo --watch +ExitStatus:: Success +Output:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] Found 0 errors. Watching for file changes. + +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; +//// [/home/src/workspaces/project/index.js] *new* +"use strict"; +const x = 1; + + +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project +tsconfig.json:: +SemanticDiagnostics:: +*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts +*refresh* /home/src/workspaces/project/index.ts +Signatures:: + + +Edit [0]:: create deeply nested file matching include +//// [/home/src/workspaces/project/deep/nested/dir/added.ts] *new* +export const added = 1; + + +Output:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] Found 0 errors. Watching for file changes. + +//// [/home/src/workspaces/project/deep/nested/dir/added.js] *new* +export const added = 1; + + +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project + /home/src/workspaces/project/deep + /home/src/workspaces/project/deep/nested + /home/src/workspaces/project/deep/nested/dir (recursive) +tsconfig.json:: +SemanticDiagnostics:: +*refresh* /home/src/workspaces/project/deep/nested/dir/added.ts +Signatures:: +(computed .d.ts) /home/src/workspaces/project/deep/nested/dir/added.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-multiple-new-subdirectories-simultaneously.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-multiple-new-subdirectories-simultaneously.js index 6d26fe6cc46..9a1e9314d1a 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-multiple-new-subdirectories-simultaneously.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-multiple-new-subdirectories-simultaneously.js @@ -43,6 +43,11 @@ declare const console: { log(msg: any): void; }; export const a = 1; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project + /home/src/workspaces/project/src (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -77,6 +82,11 @@ export {}; export function format(s) { return s.trim(); } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project + /home/src/workspaces/project/src (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/src/models/user.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-new-nested-subdirectory.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-new-nested-subdirectory.js index 71da18e80e1..a7bd09160b4 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-new-nested-subdirectory.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-in-new-nested-subdirectory.js @@ -43,6 +43,11 @@ declare const console: { log(msg: any): void; }; export const a = 1; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project + /home/src/workspaces/project/src (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -64,6 +69,11 @@ Output:: export const util = "nested"; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project + /home/src/workspaces/project/src (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/src/deep/nested/util.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-to-previously-non-existent-include-path.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-to-previously-non-existent-include-path.js index 9130ae0e4bf..8a43e3892ac 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-to-previously-non-existent-include-path.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-added-to-previously-non-existent-include-path.js @@ -44,6 +44,10 @@ declare const console: { log(msg: any): void; }; const x = 1; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -65,6 +69,11 @@ Output:: export const helper = "added"; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project + /home/src/workspaces/project/src (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/src/helper.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-deleted-and-new-file-added-simultaneously.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-deleted-and-new-file-added-simultaneously.js index b9f2d5c8481..36a2685038e 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-deleted-and-new-file-added-simultaneously.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-deleted-and-new-file-added-simultaneously.js @@ -45,6 +45,10 @@ export {}; export const b = 1; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -71,6 +75,10 @@ Output:: export const c = 2; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/c.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-renamed-and-renamed-back.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-renamed-and-renamed-back.js index 0496d95a210..711c91de0f4 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-renamed-and-renamed-back.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-file-renamed-and-renamed-back.js @@ -45,6 +45,10 @@ export const helper = 1; export {}; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -77,6 +81,10 @@ export const helper = 1; //// [/home/src/workspaces/project/index.js] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/helper2.ts @@ -110,6 +118,10 @@ Output:: //// [/home/src/workspaces/project/helper.js] *rewrite with same content* //// [/home/src/workspaces/project/index.js] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/helper.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-import-path-restructured.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-import-path-restructured.js index 3211131af98..8374953226b 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-import-path-restructured.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-import-path-restructured.js @@ -45,6 +45,10 @@ export {}; export const util = "v1"; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -71,6 +75,10 @@ Output:: export const util = "v2"; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/src/util.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-imported-directory-removed.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-imported-directory-removed.js index 5c7c582f53b..50b4d36de5a 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-imported-directory-removed.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-imported-directory-removed.js @@ -45,6 +45,10 @@ export {}; export const util = "hello"; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -72,6 +76,10 @@ Found 1 error in index.ts:1 //// [/home/src/workspaces/project/index.js] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/index.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-imported-file-added-in-new-directory.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-imported-file-added-in-new-directory.js index e003eba9749..bc5cc3c1ea7 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-imported-file-added-in-new-directory.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-imported-file-added-in-new-directory.js @@ -48,6 +48,10 @@ declare const console: { log(msg: any): void; }; export {}; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -70,6 +74,10 @@ Output:: export const util = "hello"; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/lib/util.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-module-going-missing-then-coming-back.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-module-going-missing-then-coming-back.js index 5e3ddd32d8e..d461ccc9673 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-module-going-missing-then-coming-back.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-module-going-missing-then-coming-back.js @@ -45,6 +45,10 @@ export {}; export const util = "v1"; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -72,6 +76,10 @@ Found 1 error in index.ts:1 //// [/home/src/workspaces/project/index.js] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/index.ts @@ -104,6 +112,10 @@ Output:: export const util = "v2"; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/util.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-nested-subdirectory-removed-and-recreated.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-nested-subdirectory-removed-and-recreated.js index 8627df293ba..4cdb0864eaa 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-nested-subdirectory-removed-and-recreated.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-nested-subdirectory-removed-and-recreated.js @@ -43,6 +43,11 @@ declare const console: { log(msg: any): void; }; export const helper = "v1"; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project + /home/src/workspaces/project/src (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -60,6 +65,10 @@ Output:: [HH:MM:SS AM] Found 0 errors. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/workspaces/project + /home/src/workspaces/project/src (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -88,6 +97,11 @@ Output:: export const helper = "v2"; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project + /home/src/workspaces/project/src (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-new-file-in-existing-include-directory.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-new-file-in-existing-include-directory.js index fb174d116d7..ecf2664a6cf 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-new-file-in-existing-include-directory.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-new-file-in-existing-include-directory.js @@ -43,6 +43,11 @@ declare const console: { log(msg: any): void; }; export const a = 1; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project + /home/src/workspaces/project/src (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -64,6 +69,11 @@ Output:: export const b = 2; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project + /home/src/workspaces/project/src (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/src/b.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-new-file-resolving-failed-import.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-new-file-resolving-failed-import.js index c5115bfe0f5..a8e313335a1 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-new-file-resolving-failed-import.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-new-file-resolving-failed-import.js @@ -48,6 +48,10 @@ declare const console: { log(msg: any): void; }; export {}; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -70,6 +74,10 @@ Output:: export const b = 1; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/b.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-package-added.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-package-added.js index 2c08f665024..d8c95c635f4 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-package-added.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-package-added.js @@ -48,6 +48,10 @@ declare const console: { log(msg: any): void; }; export {}; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -71,6 +75,10 @@ Output:: //// [/home/src/workspaces/project/index.js] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/node_modules/mylib/index.d.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-package-removed.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-package-removed.js index 744b4cfbcf5..8c6a0fa103e 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-package-removed.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-package-removed.js @@ -46,6 +46,10 @@ declare const console: { log(msg: any): void; }; export {}; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -75,6 +79,10 @@ Found 1 error in index.ts:1 //// [/home/src/workspaces/project/index.js] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/index.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-reinstalled-after-deletion.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-reinstalled-after-deletion.js new file mode 100644 index 00000000000..c493aa78133 --- /dev/null +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-node-modules-reinstalled-after-deletion.js @@ -0,0 +1,119 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +import { lib } from "mylib"; +//// [/home/src/workspaces/project/node_modules/mylib/index.d.ts] *new* +export declare const lib: string; +//// [/home/src/workspaces/project/node_modules/mylib/index.js] *new* +exports.lib = "hello"; +//// [/home/src/workspaces/project/node_modules/mylib/package.json] *new* +{"name": "mylib", "main": "index.js", "types": "index.d.ts"} +//// [/home/src/workspaces/project/tsconfig.json] *new* +{} + +tsgo --watch +ExitStatus:: Success +Output:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] Found 0 errors. Watching for file changes. + +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; +//// [/home/src/workspaces/project/index.js] *new* +export {}; + + +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) +tsconfig.json:: +SemanticDiagnostics:: +*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts +*refresh* /home/src/workspaces/project/node_modules/mylib/index.d.ts +*refresh* /home/src/workspaces/project/index.ts +Signatures:: + + +Edit [0]:: delete node_modules entirely +//// [/home/src/workspaces/project/node_modules/mylib/index.d.ts] *deleted* +//// [/home/src/workspaces/project/node_modules/mylib/index.js] *deleted* +//// [/home/src/workspaces/project/node_modules/mylib/package.json] *deleted* + + +Output:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +index.ts:1:21 - error TS2307: Cannot find module 'mylib' or its corresponding type declarations. + +1 import { lib } from "mylib"; +   ~~~~~~~ + + +Found 1 error in index.ts:1 + +[HH:MM:SS AM] Found 1 error. Watching for file changes. + +//// [/home/src/workspaces/project/index.js] *rewrite with same content* + +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) +tsconfig.json:: +SemanticDiagnostics:: +*refresh* /home/src/workspaces/project/index.ts +Signatures:: +(computed .d.ts) /home/src/workspaces/project/index.ts + + +Edit [1]:: reinstall node_modules +//// [/home/src/workspaces/project/node_modules/mylib/index.d.ts] *new* +export declare const lib: string; +//// [/home/src/workspaces/project/node_modules/mylib/index.js] *new* +exports.lib = "hello"; +//// [/home/src/workspaces/project/node_modules/mylib/package.json] *new* +{"name": "mylib", "main": "index.js", "types": "index.d.ts"} + + +Output:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] Found 0 errors. Watching for file changes. + +//// [/home/src/workspaces/project/index.js] *rewrite with same content* + +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) +tsconfig.json:: +SemanticDiagnostics:: +*refresh* /home/src/workspaces/project/node_modules/mylib/index.d.ts +*refresh* /home/src/workspaces/project/index.ts +Signatures:: +(used version) /home/src/workspaces/project/node_modules/mylib/index.d.ts +(computed .d.ts) /home/src/workspaces/project/index.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-package-json-types-field-edited.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-package-json-types-field-edited.js index 9f8e5d02a37..84449ad58ba 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-package-json-types-field-edited.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-package-json-types-field-edited.js @@ -46,6 +46,10 @@ declare const console: { log(msg: any): void; }; export {}; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -66,6 +70,10 @@ Output:: //// [/home/src/workspaces/project/index.js] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/node_modules/mylib/new.d.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-scoped-package-installed.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-scoped-package-installed.js index 9f17d930f1a..28f9759b825 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-scoped-package-installed.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-detects-scoped-package-installed.js @@ -48,6 +48,10 @@ declare const console: { log(msg: any): void; }; export {}; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -69,6 +73,10 @@ Output:: //// [/home/src/workspaces/project/index.js] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/node_modules/@scope/mylib/index.d.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-file-rapidly-recreated.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-file-rapidly-recreated.js index 3cf8b77fd36..3b8816bcea4 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-file-rapidly-recreated.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-file-rapidly-recreated.js @@ -45,6 +45,10 @@ export const val = "original"; export {}; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -68,6 +72,10 @@ export const val = "recreated"; //// [/home/src/workspaces/project/index.js] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/data.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-tsconfig-deleted.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-tsconfig-deleted.js index 1d1ac5045a6..7c3ab632e03 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-tsconfig-deleted.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-tsconfig-deleted.js @@ -41,6 +41,10 @@ declare const console: { log(msg: any): void; }; const x = 1; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -57,6 +61,10 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) Diff:: incremental reports config read error while clean build without tsconfig prints usage help diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-tsconfig-with-extends-base-modified.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-tsconfig-with-extends-base-modified.js index 4abbf691989..ccebd695f2d 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-tsconfig-with-extends-base-modified.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-tsconfig-with-extends-base-modified.js @@ -48,6 +48,10 @@ const x = null; const y = x; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -76,6 +80,10 @@ Found 1 error in index.ts:1 [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-file-is-modified.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-file-is-modified.js index ad0d4250854..36b078e8a22 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-file-is-modified.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-file-is-modified.js @@ -41,6 +41,10 @@ declare const console: { log(msg: any): void; }; const x = 1; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -63,6 +67,10 @@ Output:: const x = 2; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-source-file-is-deleted.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-source-file-is-deleted.js index 9f72fb77745..50df1eed439 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-source-file-is-deleted.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-source-file-is-deleted.js @@ -45,6 +45,10 @@ export {}; export const b = 1; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -72,6 +76,10 @@ Found 1 error in a.ts:1 //// [/home/src/workspaces/project/a.js] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/a.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-include-pattern-adds-file.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-include-pattern-adds-file.js index 959ae56259e..da1837c82ba 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-include-pattern-adds-file.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-include-pattern-adds-file.js @@ -44,6 +44,10 @@ declare const console: { log(msg: any): void; }; const x = 1; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -70,6 +74,11 @@ Output:: export const extra = 2; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project + /home/src/workspaces/project/src (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/src/extra.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-is-modified-to-change-strict.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-is-modified-to-change-strict.js index 0e5e0cce9ae..e314d25dfa9 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-is-modified-to-change-strict.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-is-modified-to-change-strict.js @@ -50,6 +50,10 @@ const x = null; const y = x; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -76,6 +80,10 @@ Found 1 error in index.ts:1 [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-is-touched-but-content-unchanged.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-is-touched-but-content-unchanged.js index a9a3639536a..8d68fd3f384 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-is-touched-but-content-unchanged.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-rebuilds-when-tsconfig-is-touched-but-content-unchanged.js @@ -41,6 +41,10 @@ declare const console: { log(msg: any): void; }; const x = 1; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -58,6 +62,10 @@ Output:: [HH:MM:SS AM] Found 0 errors. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-skips-build-when-no-files-change.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-skips-build-when-no-files-change.js index d79f8a92954..5363a3d671b 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-skips-build-when-no-files-change.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-skips-build-when-no-files-change.js @@ -41,6 +41,10 @@ declare const console: { log(msg: any): void; }; const x = 1; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -53,6 +57,10 @@ Edit [0]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-stability-with-ancestor-directory-fallback.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-stability-with-ancestor-directory-fallback.js new file mode 100644 index 00000000000..a4f93241aaa --- /dev/null +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-stability-with-ancestor-directory-fallback.js @@ -0,0 +1,79 @@ +currentDirectory::/home/src/workspaces/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/workspaces/project/index.ts] *new* +const x: number = 1; +//// [/home/src/workspaces/project/tsconfig.json] *new* +{ "include": ["*.ts", "missing/**/*"] } + +tsgo --watch +ExitStatus:: Success +Output:: +[HH:MM:SS AM] Starting compilation in watch mode... + +[HH:MM:SS AM] Found 0 errors. Watching for file changes. + +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; +//// [/home/src/workspaces/project/index.js] *new* +"use strict"; +const x = 1; + + +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project +tsconfig.json:: +SemanticDiagnostics:: +*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts +*refresh* /home/src/workspaces/project/index.ts +Signatures:: + + +Edit [0]:: trivial file change +//// [/home/src/workspaces/project/index.ts] *modified* +const x: number = 2; + + +Output:: +[HH:MM:SS AM] File change detected. Starting incremental compilation... + +[HH:MM:SS AM] Found 0 errors. Watching for file changes. + +//// [/home/src/workspaces/project/index.js] *modified* +"use strict"; +const x = 2; + + +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project +tsconfig.json:: +SemanticDiagnostics:: +*refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts +*refresh* /home/src/workspaces/project/index.ts +Signatures:: +(computed .d.ts) /home/src/workspaces/project/index.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-no-tsconfig.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-no-tsconfig.js index f85233069c8..943c29fb8ce 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-no-tsconfig.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-no-tsconfig.js @@ -38,6 +38,10 @@ declare const console: { log(msg: any): void; }; "use strict"; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts *refresh* /home/src/workspaces/project/index.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-tsconfig-and-incremental.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-tsconfig-and-incremental.js index 1bbfe39551f..aa1ffcd87da 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-tsconfig-and-incremental.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-tsconfig-and-incremental.js @@ -79,6 +79,10 @@ declare const console: { log(msg: any): void; }; "size": 908 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-tsconfig-files-list-entry-deleted.js b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-tsconfig-files-list-entry-deleted.js index fc24ffca706..1b091736595 100644 --- a/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-tsconfig-files-list-entry-deleted.js +++ b/testdata/baselines/reference/tscWatch/commandLineWatch/watch-with-tsconfig-files-list-entry-deleted.js @@ -48,6 +48,10 @@ export const a = 1; export const b = 2; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -76,6 +80,10 @@ Found 1 error. [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project tsconfig.json:: SemanticDiagnostics:: Signatures:: diff --git a/testdata/baselines/reference/tscWatch/noEmit/dts-errors-without-dts-enabled.js b/testdata/baselines/reference/tscWatch/noEmit/dts-errors-without-dts-enabled.js index 8c7009954f9..78002ec5f3c 100644 --- a/testdata/baselines/reference/tscWatch/noEmit/dts-errors-without-dts-enabled.js +++ b/testdata/baselines/reference/tscWatch/noEmit/dts-errors-without-dts-enabled.js @@ -41,6 +41,10 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -59,6 +63,10 @@ Output:: [HH:MM:SS AM] Found 0 errors. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -86,6 +94,10 @@ Output:: const a = "hello"; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -107,6 +119,10 @@ Output:: [HH:MM:SS AM] Found 0 errors. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -123,6 +139,10 @@ Output:: [HH:MM:SS AM] Found 0 errors. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -152,6 +172,10 @@ const a = class { }; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -173,6 +197,10 @@ Output:: [HH:MM:SS AM] Found 0 errors. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: diff --git a/testdata/baselines/reference/tscWatch/noEmit/dts-errors.js b/testdata/baselines/reference/tscWatch/noEmit/dts-errors.js index bb88cdbc167..c0a2db979bf 100644 --- a/testdata/baselines/reference/tscWatch/noEmit/dts-errors.js +++ b/testdata/baselines/reference/tscWatch/noEmit/dts-errors.js @@ -54,6 +54,10 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -72,6 +76,10 @@ Output:: [HH:MM:SS AM] Found 0 errors. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -102,6 +110,10 @@ declare const a = "hello"; const a = "hello"; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -123,6 +135,10 @@ Output:: [HH:MM:SS AM] Found 0 errors. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -151,6 +167,10 @@ Found 1 error in a.ts:1 [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -192,6 +212,10 @@ const a = class { }; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -225,6 +249,10 @@ Found 1 error in a.ts:1 [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: diff --git a/testdata/baselines/reference/tscWatch/noEmit/semantic-errors.js b/testdata/baselines/reference/tscWatch/noEmit/semantic-errors.js index dbee71768b2..7ebf96b0135 100644 --- a/testdata/baselines/reference/tscWatch/noEmit/semantic-errors.js +++ b/testdata/baselines/reference/tscWatch/noEmit/semantic-errors.js @@ -49,6 +49,10 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -67,6 +71,10 @@ Output:: [HH:MM:SS AM] Found 0 errors. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -94,6 +102,10 @@ Output:: const a = "hello"; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -115,6 +127,10 @@ Output:: [HH:MM:SS AM] Found 0 errors. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -139,6 +155,10 @@ Found 1 error in a.ts:1 [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -171,6 +191,10 @@ Found 1 error in a.ts:1 //// [/home/src/workspaces/project/a.js] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -200,6 +224,10 @@ Found 1 error in a.ts:1 [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: diff --git a/testdata/baselines/reference/tscWatch/noEmit/syntax-errors.js b/testdata/baselines/reference/tscWatch/noEmit/syntax-errors.js index b7d623008b5..d357d27a10f 100644 --- a/testdata/baselines/reference/tscWatch/noEmit/syntax-errors.js +++ b/testdata/baselines/reference/tscWatch/noEmit/syntax-errors.js @@ -49,6 +49,10 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -67,6 +71,10 @@ Output:: [HH:MM:SS AM] Found 0 errors. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -94,6 +102,10 @@ Output:: const a = "hello"; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -115,6 +127,10 @@ Output:: [HH:MM:SS AM] Found 0 errors. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -139,6 +155,10 @@ Found 1 error in a.ts:1 [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/workspaces/project/a.ts @@ -172,6 +192,10 @@ Found 1 error in a.ts:1 const a = "hello; +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/workspaces/project/a.ts @@ -203,6 +227,10 @@ Found 1 error in a.ts:1 [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/workspaces/project/a.ts From 3e6ed733c1596944b0c80f356359600508836095 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 12 Jun 2026 14:42:06 -0700 Subject: [PATCH 17/49] Allow LSP server to use builtin watcher on Windows and macOS (#4288) Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- internal/fswatch/watcher.go | 15 + internal/lsp/lspwatcher/lspwatcher.go | 617 ++++++++++++++++ internal/lsp/lspwatcher/lspwatcher_test.go | 813 +++++++++++++++++++++ internal/lsp/server.go | 63 +- 4 files changed, 1499 insertions(+), 9 deletions(-) create mode 100644 internal/lsp/lspwatcher/lspwatcher.go create mode 100644 internal/lsp/lspwatcher/lspwatcher_test.go diff --git a/internal/fswatch/watcher.go b/internal/fswatch/watcher.go index f1510583aa3..e17d5012904 100644 --- a/internal/fswatch/watcher.go +++ b/internal/fswatch/watcher.go @@ -48,6 +48,11 @@ type Watcher interface { Name() string // Available reports whether this watcher works on the current OS. Available() bool + // HasFastRecursiveBackend reports whether this watcher supports efficient + // recursive watching without requiring a full userspace tree walk. This is + // true for Windows (ReadDirectoryChangesW subtree mode) and macOS FSEvents + // (inherently recursive), and false for all other backends. + HasFastRecursiveBackend() bool // WatchDirectory watches dir for changes, calling fn with batched // events. By default, only direct children are watched. Use // [WithRecursive] to watch the entire directory tree. @@ -214,6 +219,16 @@ func (w *watcher) String() string { return w.name } func (w *watcher) Available() bool { return w.factory != nil } func (w *watcher) unexported() {} +// HasFastRecursiveBackend implements [Watcher.HasFastRecursiveBackend]. +func (w *watcher) HasFastRecursiveBackend() bool { + switch w.name { + case "windows", "fsevents": + return true + default: + return false + } +} + func (w *watcher) getImpl() (watcherImpl, error) { w.mu.Lock() if w.impl != nil { diff --git a/internal/lsp/lspwatcher/lspwatcher.go b/internal/lsp/lspwatcher/lspwatcher.go new file mode 100644 index 00000000000..24366a3f747 --- /dev/null +++ b/internal/lsp/lspwatcher/lspwatcher.go @@ -0,0 +1,617 @@ +// Package lspwatcher implements an in-process file watcher used as a +// drop-in replacement for LSP-based file watching when the client does not +// support dynamic registration of file watchers. +package lspwatcher + +import ( + "errors" + "fmt" + "io" + "strings" + "sync" + "time" + + "github.com/microsoft/typescript-go/internal/fswatch" + "github.com/microsoft/typescript-go/internal/ls/lsconv" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/project/logging" + "github.com/microsoft/typescript-go/internal/tspath" + "github.com/microsoft/typescript-go/internal/vfs" +) + +// throttleWindow mirrors VS Code's parcel watcher integration: give the +// first batch a short grace window so adjacent filesystem bursts coalesce. +const throttleWindow = 75 * time.Millisecond + +type watcherBackend interface { + WatchDirectory(dir string, fn fswatch.WatchCallback, opts ...fswatch.WatchOption) (io.Closer, error) +} + +type defaultWatcherBackend struct { + watcher fswatch.Watcher +} + +func (d defaultWatcherBackend) WatchDirectory(dir string, fn fswatch.WatchCallback, opts ...fswatch.WatchOption) (io.Closer, error) { + return d.watcher.WatchDirectory(dir, fn, opts...) +} + +// Watcher manages a set of file system subscriptions identified by +// WatcherID strings (matching the LSP server's project.WatcherID type). +// Events are delivered to onChanges in batches as `*lsproto.FileEvent`, +// shaped exactly like a `workspace/didChangeWatchedFiles` notification. +type Watcher struct { + fs vfs.FS + backend watcherBackend + onChanges func(changes []*lsproto.FileEvent) + logger logging.Logger + + mu sync.Mutex + // watches holds the watches associated with each LSP WatcherID. A single id + // may map to more than one watch because each FileSystemWatcher in the + // registration becomes its own watch (different roots and kinds). + watches map[string][]*watch + closed bool + + // Pending batch state, protected by mu. + pending map[string]*lsproto.FileEvent + flushTimer *time.Timer +} + +// watch represents one FileSystemWatcher from the LSP registration. +// +// The directory the session asks to watch may not exist yet (common in +// granular mode, where each probed-but-missing package directory becomes a +// watch) or may be deleted while watched. To honor the watch across those +// transitions, a watch maintains either: +// +// - a "target" subscription rooted directly at the requested directory, once +// it exists, or +// - an "ancestor" subscription on the nearest existing ancestor +// (non-recursive), used to detect the requested directory — or an +// intermediate path component — being created, after which the watch +// descends toward and eventually promotes to the target. +// +// When the target materializes, synthetic create events are emitted for it +// (and, depending on whether the watch is recursive, its immediate children or +// its whole subtree) so the session re-resolves files that appeared in the gap +// before the real subscription was installed. +// +// All path fields are tspath-style (forward-slash) absolute paths. +type watch struct { + watcher *Watcher + requestedDirectory string // directory requested by the LSP layer (possibly a symlink) + kind lsproto.WatchKind + recursive bool // whether the target subscription should be recursive + + mu sync.Mutex + subscription io.Closer // current subscription (target or ancestor); nil if none + watchedDirectory string // canonicalized directory 'subscription' is rooted at + watchingTarget bool // whether 'subscription' is rooted at the target directory + closed bool +} + +// New constructs a Watcher backed by internal/fswatch's platform-default +// watcher implementation. +func New(fs vfs.FS, onChanges func(changes []*lsproto.FileEvent), logger logging.Logger) *Watcher { + return NewWithFSWatcher(fs, fswatch.Default(), onChanges, logger) +} + +// NewWithFSWatcher constructs a Watcher backed by the provided fswatch.Watcher. +// Use this to select a specific backend (e.g. fswatch.Kqueue()) instead of the +// platform default. +func NewWithFSWatcher(fs vfs.FS, watcher fswatch.Watcher, onChanges func(changes []*lsproto.FileEvent), logger logging.Logger) *Watcher { + return newWithBackend(fs, defaultWatcherBackend{watcher: watcher}, onChanges, logger) +} + +func newWithBackend(fs vfs.FS, backend watcherBackend, onChanges func(changes []*lsproto.FileEvent), logger logging.Logger) *Watcher { + return &Watcher{ + fs: fs, + backend: backend, + onChanges: onChanges, + logger: logger, + watches: make(map[string][]*watch), + } +} + +// WatchFiles subscribes to each FileSystemWatcher under the given id. +// +// A watcher whose directory does not exist yet is not an error: an ancestor +// watch is installed on the nearest existing ancestor and the subscription is +// reported as successful, so the session's notion of "this watcher is alive" +// stays true for the subscription's whole lifetime. Only a genuine backend +// failure (e.g. resource exhaustion while watching an existing directory) +// causes WatchFiles to roll back the whole id and return an error, so the +// session's pending/retry path re-registers it on the next reevaluation. +func (w *Watcher) WatchFiles(id string, fileSystemWatchers []*lsproto.FileSystemWatcher) error { + w.mu.Lock() + if w.closed { + w.mu.Unlock() + return errors.New("lspwatcher: closed") + } + if _, exists := w.watches[id]; exists { + w.mu.Unlock() + return fmt.Errorf("lspwatcher: watcher %q already exists", id) + } + // Mark the id as existing before installing any watches so a concurrent + // WatchFiles for the same id is rejected above. + w.watches[id] = nil + w.mu.Unlock() + + var failed bool + for _, fileSystemWatcher := range fileSystemWatchers { + directory, ok := watchRoot(fileSystemWatcher) + if !ok || directory == "" { + w.logger.Logf("lspwatcher: skipping watcher %q: unrecognized pattern %q", id, watchPatternString(fileSystemWatcher)) + continue + } + newWatch := &watch{ + watcher: w, + requestedDirectory: directory, + kind: effectiveKind(fileSystemWatcher), + recursive: isRecursiveGlob(fileSystemWatcher), + } + if err := newWatch.reconcile(false /*emitSynthetic*/); err != nil { + w.logger.Logf("lspwatcher: failed to register watcher %q for %q: %v", id, directory, err) + newWatch.close() + failed = true + break + } + w.mu.Lock() + w.watches[id] = append(w.watches[id], newWatch) + w.mu.Unlock() + } + + if failed { + // Roll back the whole id so the session's retry (MarkPending) can + // cleanly re-register it. The session treats an id as a single unit. + _ = w.UnwatchFiles(id) + return fmt.Errorf("lspwatcher: failed to register one or more watchers for %q", id) + } + return nil +} + +// UnwatchFiles tears down all subscriptions associated with id. +func (w *Watcher) UnwatchFiles(id string) error { + w.mu.Lock() + watches, ok := w.watches[id] + if !ok { + w.mu.Unlock() + return fmt.Errorf("lspwatcher: no watcher with id %q", id) + } + delete(w.watches, id) + w.mu.Unlock() + for _, watch := range watches { + watch.close() + } + return nil +} + +// Close removes every subscription. Safe to call multiple times. +func (w *Watcher) Close() { + w.mu.Lock() + if w.closed { + w.mu.Unlock() + return + } + w.closed = true + watchesByID := w.watches + w.watches = nil + if w.flushTimer != nil { + w.flushTimer.Stop() + w.flushTimer = nil + } + w.pending = nil + w.mu.Unlock() + for _, watches := range watchesByID { + for _, watch := range watches { + watch.close() + } + } +} + +// close tears down the watch's current subscription and prevents any in-flight +// reconcile from reinstalling one. +func (w *watch) close() { + w.mu.Lock() + w.closed = true + subscription := w.subscription + w.subscription = nil + w.watchedDirectory = "" + w.mu.Unlock() + if subscription != nil { + _ = subscription.Close() + } +} + +// reconcile installs or advances this watch toward the target directory based +// on the current filesystem state. It is called at registration, whenever a +// ancestor watch observes activity, and after a target watch is terminated by +// deletion. +// +// emitSynthetic controls whether promoting to the target emits synthetic +// create events: false for the initial install when the target already exists +// (the session already knows about those files), true for any missing→present +// recovery. +// +// It returns a non-nil error only on a genuine backend failure to install a +// watch; a missing target directory is handled by installing an ancestor watch +// and returns nil. +func (w *watch) reconcile(emitSyntheticCreates bool) error { + w.mu.Lock() + defer w.mu.Unlock() + watcher := w.watcher + for { + if w.closed { + return nil + } + if watcher.fs.DirectoryExists(w.requestedDirectory) { + if w.watchingTarget && w.subscription != nil { + return nil // already watching the target + } + targetDirectory := watcher.fs.Realpath(w.requestedDirectory) + var options []fswatch.WatchOption + if w.recursive { + options = append(options, fswatch.WithRecursive()) + } + subscription, err := watcher.backend.WatchDirectory(targetDirectory, w.targetCallback(targetDirectory), options...) + if err != nil { + return err + } + previous := w.subscription + w.subscription = subscription + w.watchedDirectory = targetDirectory + w.watchingTarget = true + if previous != nil { + _ = previous.Close() + } + if emitSyntheticCreates { + watcher.emitSyntheticCreates(targetDirectory, w.requestedDirectory, w.kind, w.recursive) + } + return nil + } + + ancestor, ok := nearestExistingAncestor(watcher.fs, w.requestedDirectory) + if !ok { + // Nothing exists to watch (even the root is gone); drop any subscription. + if w.subscription != nil { + previous := w.subscription + w.subscription = nil + w.watchedDirectory = "" + w.watchingTarget = false + _ = previous.Close() + } + return nil + } + ancestorDirectory := watcher.fs.Realpath(ancestor) + if !w.watchingTarget && w.subscription != nil && w.watchedDirectory == ancestorDirectory { + return nil // already watching the correct ancestor + } + subscription, err := watcher.backend.WatchDirectory(ancestorDirectory, w.ancestorCallback()) + if err != nil { + return err + } + previous := w.subscription + w.subscription = subscription + w.watchedDirectory = ancestorDirectory + w.watchingTarget = false + if previous != nil { + _ = previous.Close() + } + // The target may have appeared between the DirectoryExists check above + // and installing this ancestor subscription (e.g. an atomic tree + // creation), so loop to descend further or promote immediately. Any + // promotion from here on is a missing→present transition, so synthesize + // creates. + emitSyntheticCreates = true + } +} + +// targetCallback returns the fswatch callback for a target watch rooted at +// watchedReal. It forwards events to the session and, on ErrWatchTerminated +// (the watched directory was deleted), falls back to watching the nearest +// existing ancestor so the watch re-attaches when the directory is recreated. +func (w *watch) targetCallback(watchedDirectory string) fswatch.WatchCallback { + watcher := w.watcher + return func(events []fswatch.Event, err error) { + terminated := false + if err != nil { + switch { + case errors.Is(err, fswatch.ErrOverflow): + watcher.logger.Logf("lspwatcher: watch overflow in %q (some events may have been dropped): %v", watchedDirectory, err) + case errors.Is(err, fswatch.ErrWatchTerminated): + terminated = true + watcher.logger.Logf("lspwatcher: watch terminated in %q (directory removed): %v", watchedDirectory, err) + default: + watcher.logger.Logf("lspwatcher: watch error in %q: %v", watchedDirectory, err) + } + } + if len(events) > 0 { + watcher.forwardEvents(watchedDirectory, w.requestedDirectory, w.kind, events) + } + if terminated { + // The delete event for the directory was forwarded above; now + // re-attach to the nearest existing ancestor. + w.handleTerminated() + } + } +} + +// handleTerminated clears the dead target watch (the backend has already +// removed it) and re-evaluates, falling back to an ancestor watch on the nearest +// existing ancestor so the watch re-attaches when the directory reappears. +// Clearing the state first is essential: reconcile would otherwise see +// watchingTarget && subscription != nil and conclude the target is already +// watched, even though the subscription is dead — losing recovery if the +// directory is recreated before reconcile runs. +func (w *watch) handleTerminated() { + w.mu.Lock() + if w.closed { + w.mu.Unlock() + return + } + previous := w.subscription + w.subscription = nil + w.watchedDirectory = "" + w.watchingTarget = false + w.mu.Unlock() + if previous != nil { + _ = previous.Close() + } + _ = w.reconcile(true /*emitSyntheticCreates*/) +} + +// ancestorCallback returns the fswatch callback for an ancestor watch. Ancestor +// watches exist only to detect the target — or an intermediate path component — +// being created; their events are about ancestor directories the session +// doesn't track, so they are ignored and the watch is simply re-evaluated. +func (w *watch) ancestorCallback() fswatch.WatchCallback { + return func(events []fswatch.Event, err error) { + _ = w.reconcile(true /*emitSyntheticCreates*/) + } +} + +// nearestExistingAncestor returns the deepest existing directory that is dir or +// an ancestor of dir, walking upward. ok is false only if nothing in the chain +// (including the root) exists. +func nearestExistingAncestor(fs vfs.FS, dir string) (string, bool) { + for { + if fs.DirectoryExists(dir) { + return dir, true + } + parent := tspath.GetDirectoryPath(dir) + if parent == dir { + return "", false + } + dir = parent + } +} + +// forwardEvents translates fswatch events rooted at watchedDirectory into LSP +// file events (remapping paths back into the requestedDirectory namespace) and +// enqueues them for the next debounced flush. +func (w *Watcher) forwardEvents(watchedDirectory, requestedDirectory string, kind lsproto.WatchKind, events []fswatch.Event) { + w.mu.Lock() + if w.closed { + w.mu.Unlock() + return + } + if w.pending == nil { + w.pending = make(map[string]*lsproto.FileEvent, len(events)) + } + comparePathsOptions := tspath.ComparePathsOptions{UseCaseSensitiveFileNames: w.fs.UseCaseSensitiveFileNames()} + for _, event := range events { + var changeType lsproto.FileChangeType + switch event.Kind { + case fswatch.EventUpdate: + // fswatch intentionally doesn't distinguish create vs update. + // For LSP consumers this is fine: callers infer create/update + // from their own cache and both should invalidate stale state. + if kind&(lsproto.WatchKindCreate|lsproto.WatchKindChange) == 0 { + continue + } + changeType = lsproto.FileChangeTypeChanged + case fswatch.EventDelete: + if kind&lsproto.WatchKindDelete == 0 { + continue + } + changeType = lsproto.FileChangeTypeDeleted + default: + continue + } + + path := remapEventPath(watchedDirectory, requestedDirectory, tspath.NormalizeSlashes(event.Path), comparePathsOptions) + uri := lsconv.FileNameToDocumentURI(path) + w.pending[string(uri)] = &lsproto.FileEvent{ + Uri: uri, + Type: changeType, + } + } + w.scheduleFlushLocked() + w.mu.Unlock() +} + +// emitSyntheticCreates enqueues synthetic create events after a target watch is +// (re)installed following a missing→present transition, so the session +// re-resolves files that appeared before the real watch existed. The target +// directory itself is always included; for a non-recursive watch its immediate +// children are added, and for a recursive watch its whole subtree is walked. +// Nothing is emitted if the watch doesn't request create notifications. +func (w *Watcher) emitSyntheticCreates(watchedDirectory, requestedDirectory string, kind lsproto.WatchKind, recursive bool) { + if kind&lsproto.WatchKindCreate == 0 { + return + } + comparePathsOptions := tspath.ComparePathsOptions{UseCaseSensitiveFileNames: w.fs.UseCaseSensitiveFileNames()} + paths := []string{requestedDirectory} + if recursive { + _ = w.fs.WalkDir(watchedDirectory, func(path string, entry vfs.DirEntry, err error) error { + if err != nil { + return nil + } + normalizedPath := tspath.NormalizeSlashes(path) + if normalizedPath == watchedDirectory { + return nil + } + paths = append(paths, remapEventPath(watchedDirectory, requestedDirectory, normalizedPath, comparePathsOptions)) + return nil + }) + } else { + entries := w.fs.GetAccessibleEntries(watchedDirectory) + for _, name := range entries.Files { + paths = append(paths, tspath.CombinePaths(requestedDirectory, name)) + } + for _, name := range entries.Directories { + paths = append(paths, tspath.CombinePaths(requestedDirectory, name)) + } + } + w.enqueueSyntheticCreates(paths) +} + +// enqueueSyntheticCreates adds synthetic create events for paths, without +// clobbering a more specific event already pending for the same path (e.g. a +// real delete). +func (w *Watcher) enqueueSyntheticCreates(paths []string) { + w.mu.Lock() + if w.closed { + w.mu.Unlock() + return + } + if w.pending == nil { + w.pending = make(map[string]*lsproto.FileEvent, len(paths)) + } + for _, path := range paths { + uri := lsconv.FileNameToDocumentURI(path) + if _, ok := w.pending[string(uri)]; ok { + continue + } + w.pending[string(uri)] = &lsproto.FileEvent{ + Uri: uri, + Type: lsproto.FileChangeTypeCreated, + } + } + w.scheduleFlushLocked() + w.mu.Unlock() +} + +// scheduleFlushLocked arms the debounce flush timer if it isn't already armed. +// Callers must hold w.mu. +func (w *Watcher) scheduleFlushLocked() { + if w.flushTimer == nil { + w.flushTimer = time.AfterFunc(throttleWindow, w.flush) + } +} + +// remapEventPath translates an absolute path observed under watchedDirectory +// (the canonicalized, symlink-resolved directory actually watched) back into +// the requestedDirectory namespace the session requested. When the two +// directories are identical, the path is returned unchanged. +func remapEventPath(watchedDirectory, requestedDirectory, path string, comparePathsOptions tspath.ComparePathsOptions) string { + if watchedDirectory == requestedDirectory { + return path + } + if tspath.ContainsPath(watchedDirectory, path, comparePathsOptions) { + relative := tspath.GetRelativePathFromDirectory(watchedDirectory, path, comparePathsOptions) + if relative == "" || relative == "." { + return requestedDirectory + } + return tspath.CombinePaths(requestedDirectory, relative) + } + return path +} + +func (w *Watcher) flush() { + w.mu.Lock() + if w.closed { + w.mu.Unlock() + return + } + pending := w.pending + w.pending = nil + w.flushTimer = nil + w.mu.Unlock() + + if len(pending) == 0 { + return + } + changes := make([]*lsproto.FileEvent, 0, len(pending)) + for _, event := range pending { + changes = append(changes, event) + } + w.onChanges(changes) +} + +// watchRoot extracts the directory the fswatch subscription should be +// rooted at from a FileSystemWatcher. The patterns the project layer +// produces are of the form `/**/*` (recursive) or `/*` +// (non-recursive, used by granular watch mode), either as a Pattern +// with a fully-qualified directory or as a RelativePattern with a +// file:// BaseUri, so the heuristic of "everything before the first +// glob meta character" is reliable. Use [isRecursiveGlob] to determine +// whether the subscription should be recursive. +// +// Returned roots are tspath-normalized (forward-slash) absolute paths. +func watchRoot(fileSystemWatcher *lsproto.FileSystemWatcher) (string, bool) { + if fileSystemWatcher.GlobPattern.Pattern != nil { + return rootFromGlob(*fileSystemWatcher.GlobPattern.Pattern), true + } + if relativePattern := fileSystemWatcher.GlobPattern.RelativePattern; relativePattern != nil { + var base string + if relativePattern.BaseUri.URI != nil { + base = lsproto.DocumentUri(*relativePattern.BaseUri.URI).FileName() + } else { + return "", false + } + pattern := tspath.CombinePaths(base, relativePattern.Pattern) + return rootFromGlob(pattern), true + } + return "", false +} + +func rootFromGlob(pattern string) string { + pattern = tspath.NormalizeSlashes(pattern) + metaIndex := -1 + for i := range len(pattern) { + switch pattern[i] { + case '*', '?', '[', '{': + metaIndex = i + } + if metaIndex != -1 { + break + } + } + if metaIndex == -1 { + return tspath.NormalizePath(strings.TrimRight(pattern, "/")) + } + directory := strings.TrimRight(pattern[:metaIndex], "/") + if directory == "" { + return "" + } + return tspath.NormalizePath(directory) +} + +func watchPatternString(fileSystemWatcher *lsproto.FileSystemWatcher) string { + if fileSystemWatcher.GlobPattern.Pattern != nil { + return *fileSystemWatcher.GlobPattern.Pattern + } + if relativePattern := fileSystemWatcher.GlobPattern.RelativePattern; relativePattern != nil { + var base string + if relativePattern.BaseUri.URI != nil { + base = string(*relativePattern.BaseUri.URI) + } + return base + "/" + relativePattern.Pattern + } + return "" +} + +// isRecursiveGlob reports whether a FileSystemWatcher's pattern requests +// recursive watching (contains a `**` segment). Granular watch mode emits +// non-recursive `/*` patterns, which watch only the immediate directory. +func isRecursiveGlob(fileSystemWatcher *lsproto.FileSystemWatcher) bool { + return strings.Contains(watchPatternString(fileSystemWatcher), "**") +} + +func effectiveKind(fileSystemWatcher *lsproto.FileSystemWatcher) lsproto.WatchKind { + if fileSystemWatcher.Kind != nil { + return *fileSystemWatcher.Kind + } + return lsproto.WatchKindCreate | lsproto.WatchKindChange | lsproto.WatchKindDelete +} diff --git a/internal/lsp/lspwatcher/lspwatcher_test.go b/internal/lsp/lspwatcher/lspwatcher_test.go new file mode 100644 index 00000000000..d514ba89792 --- /dev/null +++ b/internal/lsp/lspwatcher/lspwatcher_test.go @@ -0,0 +1,813 @@ +package lspwatcher + +import ( + "errors" + "io" + "os" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + "github.com/microsoft/typescript-go/internal/bundled" + "github.com/microsoft/typescript-go/internal/fswatch" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/project/logging" + "github.com/microsoft/typescript-go/internal/tspath" + "github.com/microsoft/typescript-go/internal/vfs/osvfs" +) + +func waitFor(t *testing.T, cond func() bool, msg string) { + t.Helper() + deadline := time.Now().Add(5 * time.Second) + for time.Now().Before(deadline) { + if cond() { + return + } + time.Sleep(20 * time.Millisecond) + } + t.Fatalf("timed out waiting for %s", msg) +} + +func TestWatcher_CreateChangeDelete(t *testing.T) { + t.Parallel() + dir := t.TempDir() + + var ( + mu sync.Mutex + batches [][]*lsproto.FileEvent + ) + w := New(bundled.WrapFS(osvfs.FS()), func(changes []*lsproto.FileEvent) { + mu.Lock() + defer mu.Unlock() + batches = append(batches, changes) + }, logging.NewLogger(os.Stderr)) + t.Cleanup(w.Close) + + pattern := tspath.NormalizeSlashes(dir) + "/**/*" + kind := lsproto.WatchKindCreate | lsproto.WatchKindChange | lsproto.WatchKindDelete + if err := w.WatchFiles("test", []*lsproto.FileSystemWatcher{{ + GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &pattern}, + Kind: &kind, + }}); err != nil { + t.Fatal(err) + } + + time.Sleep(200 * time.Millisecond) + + file := filepath.Join(dir, "a.ts") + if err := os.WriteFile(file, []byte("export {}"), 0o644); err != nil { + t.Fatal(err) + } + + collected := func() []*lsproto.FileEvent { + mu.Lock() + defer mu.Unlock() + var all []*lsproto.FileEvent + for _, b := range batches { + all = append(all, b...) + } + return all + } + + waitFor(t, func() bool { + for _, e := range collected() { + if e.Type == lsproto.FileChangeTypeChanged { + return true + } + } + return false + }, "update event") + + if err := os.Remove(file); err != nil { + t.Fatal(err) + } + waitFor(t, func() bool { + for _, e := range collected() { + if e.Type == lsproto.FileChangeTypeDeleted { + return true + } + } + return false + }, "delete event") + + if err := w.UnwatchFiles("test"); err != nil { + t.Fatal(err) + } +} + +func TestWatcher_KindFilter(t *testing.T) { + t.Parallel() + dir := t.TempDir() + dirNorm := tspath.NormalizeSlashes(dir) + + var ( + mu sync.Mutex + got []*lsproto.FileEvent + ) + backend := newFakeBackend() + w := newWithBackend(bundled.WrapFS(osvfs.FS()), backend, func(changes []*lsproto.FileEvent) { + mu.Lock() + defer mu.Unlock() + got = append(got, changes...) + }, logging.NewLogger(os.Stderr)) + t.Cleanup(w.Close) + + pattern := dirNorm + "/**/*" + kind := lsproto.WatchKindDelete + if err := w.WatchFiles("test", []*lsproto.FileSystemWatcher{{ + GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &pattern}, + Kind: &kind, + }}); err != nil { + t.Fatal(err) + } + backend.emitAll([]fswatch.Event{ + {Kind: fswatch.EventUpdate, Path: filepath.FromSlash(filepath.Join(dirNorm, "x.ts"))}, + {Kind: fswatch.EventDelete, Path: filepath.FromSlash(filepath.Join(dirNorm, "x.ts"))}, + }, nil) + + waitFor(t, func() bool { + mu.Lock() + defer mu.Unlock() + for _, e := range got { + if e.Type == lsproto.FileChangeTypeDeleted { + return true + } + } + return false + }, "delete event") + + mu.Lock() + for _, e := range got { + if e.Type != lsproto.FileChangeTypeDeleted { + t.Errorf("unexpected non-delete event: %+v", e) + } + } + mu.Unlock() +} + +func TestRootFromGlob(t *testing.T) { + t.Parallel() + cases := []struct { + pattern string + want string + }{ + {"/abs/path/**/*", "/abs/path"}, + {"/abs/path/", "/abs/path"}, + {"/abs/path/?.ts", "/abs/path"}, + {"/abs/path/{a,b}/*", "/abs/path"}, + } + for _, c := range cases { + if got := rootFromGlob(c.pattern); got != c.want { + t.Errorf("rootFromGlob(%q) = %q, want %q", c.pattern, got, c.want) + } + } +} + +type fakeBackend struct { + mu sync.Mutex + byDir map[string]fswatch.WatchCallback + closed map[string]int + optCount map[string]int + failDirs map[string]error +} + +func newFakeBackend() *fakeBackend { + return &fakeBackend{ + byDir: make(map[string]fswatch.WatchCallback), + closed: make(map[string]int), + optCount: make(map[string]int), + failDirs: make(map[string]error), + } +} + +func (f *fakeBackend) WatchDirectory(dir string, fn fswatch.WatchCallback, opts ...fswatch.WatchOption) (io.Closer, error) { + f.mu.Lock() + defer f.mu.Unlock() + if err := f.failDirs[dir]; err != nil { + return nil, err + } + f.byDir[dir] = fn + f.optCount[dir] = len(opts) + return fakeWatch{closeFn: func() error { + f.mu.Lock() + defer f.mu.Unlock() + delete(f.byDir, dir) + f.closed[dir]++ + return nil + }}, nil +} + +// watchedDirs returns the directories currently subscribed, for assertions. +func (f *fakeBackend) watchedDirs() []string { + f.mu.Lock() + defer f.mu.Unlock() + dirs := make([]string, 0, len(f.byDir)) + for d := range f.byDir { + dirs = append(dirs, d) + } + return dirs +} + +func (f *fakeBackend) isWatching(dir string) bool { + f.mu.Lock() + defer f.mu.Unlock() + _, ok := f.byDir[dir] + return ok +} + +func (f *fakeBackend) emit(dir string, events []fswatch.Event, err error) { + f.mu.Lock() + cb := f.byDir[dir] + f.mu.Unlock() + if cb != nil { + cb(events, err) + } +} + +func (f *fakeBackend) emitAll(events []fswatch.Event, err error) { + f.mu.Lock() + cbs := make([]fswatch.WatchCallback, 0, len(f.byDir)) + for _, cb := range f.byDir { + cbs = append(cbs, cb) + } + f.mu.Unlock() + for _, cb := range cbs { + cb(events, err) + } +} + +type fakeWatch struct{ closeFn func() error } + +func (w fakeWatch) Close() error { return w.closeFn() } + +func TestWatcher_BookkeepingAndOverflow(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + dirNorm := tspath.NormalizeSlashes(dir) + pattern := dirNorm + "/**/*" + + fs := bundled.WrapFS(osvfs.FS()) + backend := newFakeBackend() + var ( + mu sync.Mutex + got []*lsproto.FileEvent + ) + w := newWithBackend(fs, backend, func(changes []*lsproto.FileEvent) { + mu.Lock() + defer mu.Unlock() + got = append(got, changes...) + }, logging.NewLogger(os.Stderr)) + + if err := w.WatchFiles("id", []*lsproto.FileSystemWatcher{{ + GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &pattern}, + }}); err != nil { + t.Fatal(err) + } + if err := w.WatchFiles("id", []*lsproto.FileSystemWatcher{{ + GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &pattern}, + }}); err == nil { + t.Fatal("expected duplicate-id error") + } + + backend.emitAll([]fswatch.Event{ + {Kind: fswatch.EventUpdate, Path: filepath.FromSlash(filepath.Join(dirNorm, "a.ts"))}, + }, fswatch.ErrOverflow) + waitFor(t, func() bool { + mu.Lock() + defer mu.Unlock() + return len(got) > 0 + }, "events after overflow") + + if err := w.UnwatchFiles("missing"); err == nil { + t.Fatal("expected unknown-id error") + } + if err := w.UnwatchFiles("id"); err != nil { + t.Fatal(err) + } + if err := w.WatchFiles("id2", nil); err != nil { + t.Fatal(err) + } + w.Close() + if err := w.WatchFiles("id3", nil); err == nil { + t.Fatal("expected closed error") + } +} + +func TestWatcher_NonRecursiveGlobIsNotRecursive(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + dirNorm := tspath.NormalizeSlashes(dir) + if err := os.MkdirAll(filepath.Join(dir, "sub"), 0o755); err != nil { + t.Fatal(err) + } + subNorm := tspath.NormalizeSlashes(filepath.Join(dir, "sub")) + + fs := bundled.WrapFS(osvfs.FS()) + backend := newFakeBackend() + w := newWithBackend(fs, backend, func([]*lsproto.FileEvent) {}, logging.NewLogger(os.Stderr)) + t.Cleanup(w.Close) + + recursive := dirNorm + "/**/*" + nonRecursive := subNorm + "/*" + if err := w.WatchFiles("id", []*lsproto.FileSystemWatcher{ + {GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &recursive}}, + {GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &nonRecursive}}, + }); err != nil { + t.Fatal(err) + } + + realDir := tspath.NormalizeSlashes(fs.Realpath(dirNorm)) + realSub := tspath.NormalizeSlashes(fs.Realpath(subNorm)) + + backend.mu.Lock() + defer backend.mu.Unlock() + if got := backend.optCount[realDir]; got != 1 { + t.Errorf("recursive glob %q: expected 1 watch option (WithRecursive), got %d", recursive, got) + } + if got := backend.optCount[realSub]; got != 0 { + t.Errorf("non-recursive glob %q: expected 0 watch options, got %d", nonRecursive, got) + } +} + +func TestWatcher_RealBackend_MissingThenCreate(t *testing.T) { + t.Parallel() + base := t.TempDir() + fs := bundled.WrapFS(osvfs.FS()) + + var ( + mu sync.Mutex + batches [][]*lsproto.FileEvent + ) + w := New(fs, func(changes []*lsproto.FileEvent) { + mu.Lock() + defer mu.Unlock() + batches = append(batches, changes) + }, logging.NewLogger(os.Stderr)) + t.Cleanup(w.Close) + + // Watch a directory that does not exist yet. + target := tspath.NormalizeSlashes(filepath.Join(base, "pkg")) + pattern := target + "/*" + kind := lsproto.WatchKindCreate | lsproto.WatchKindChange | lsproto.WatchKindDelete + if err := w.WatchFiles("test", []*lsproto.FileSystemWatcher{{ + GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &pattern}, + Kind: &kind, + }}); err != nil { + t.Fatal(err) + } + + // Give the ancestor watch time to install. + time.Sleep(200 * time.Millisecond) + + // Create the target directory and a file inside it. The real backend's + // ancestor watch should fire, promote to the target, and + // the file should ultimately surface. + if err := os.MkdirAll(filepath.Join(base, "pkg"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(base, "pkg", "index.ts"), []byte("export {}"), 0o644); err != nil { + t.Fatal(err) + } + + collected := func() []*lsproto.FileEvent { + mu.Lock() + defer mu.Unlock() + var all []*lsproto.FileEvent + for _, b := range batches { + all = append(all, b...) + } + return all + } + + waitFor(t, func() bool { + for _, e := range collected() { + if strings.HasSuffix(string(e.Uri), "/pkg/index.ts") { + return true + } + } + return false + }, "event for file created in a previously-missing directory") +} + +func TestWatcher_MissingDirectoryTracksAncestor(t *testing.T) { + t.Parallel() + + fs := bundled.WrapFS(osvfs.FS()) + backend := newFakeBackend() + w := newWithBackend(fs, backend, func([]*lsproto.FileEvent) {}, logging.NewLogger(os.Stderr)) + t.Cleanup(w.Close) + + base := t.TempDir() + baseReal := tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(base))) + target := tspath.NormalizeSlashes(filepath.Join(base, "pkg")) + pattern := target + "/*" + + if err := w.WatchFiles("id", []*lsproto.FileSystemWatcher{{ + GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &pattern}, + }}); err != nil { + t.Fatal(err) + } + + // A missing target directory installs an ancestor watch on the nearest + // existing ancestor (the base dir), not on the target. + if !backend.isWatching(baseReal) { + t.Fatalf("expected ancestor watch on ancestor %q, watched: %v", baseReal, backend.watchedDirs()) + } + if dirs := backend.watchedDirs(); len(dirs) != 1 { + t.Fatalf("expected exactly one (ancestor) watch, got %v", dirs) + } + + if err := w.UnwatchFiles("id"); err != nil { + t.Fatal(err) + } + if dirs := backend.watchedDirs(); len(dirs) != 0 { + t.Fatalf("expected all watches closed after unwatch, got %v", dirs) + } +} + +func TestWatcher_MissingDirectoryPromotesOnCreate(t *testing.T) { + t.Parallel() + + fs := bundled.WrapFS(osvfs.FS()) + backend := newFakeBackend() + var ( + mu sync.Mutex + got []*lsproto.FileEvent + ) + w := newWithBackend(fs, backend, func(changes []*lsproto.FileEvent) { + mu.Lock() + got = append(got, changes...) + mu.Unlock() + }, logging.NewLogger(os.Stderr)) + t.Cleanup(w.Close) + + base := t.TempDir() + baseReal := tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(base))) + target := tspath.NormalizeSlashes(filepath.Join(base, "pkg")) + pattern := target + "/*" + kind := lsproto.WatchKindCreate | lsproto.WatchKindChange | lsproto.WatchKindDelete + + if err := w.WatchFiles("id", []*lsproto.FileSystemWatcher{{ + GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &pattern}, + Kind: &kind, + }}); err != nil { + t.Fatal(err) + } + + // Create the target directory with a file, then notify the ancestor watch. + if err := os.MkdirAll(filepath.Join(base, "pkg"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(base, "pkg", "index.ts"), []byte("export {}"), 0o644); err != nil { + t.Fatal(err) + } + backend.emit(baseReal, []fswatch.Event{ + {Kind: fswatch.EventUpdate, Path: filepath.Join(base, "pkg")}, + }, nil) + + targetReal := tspath.NormalizeSlashes(fs.Realpath(target)) + waitFor(t, func() bool { return backend.isWatching(targetReal) }, "promotion to target watch") + + // Synthetic creates must cover the target dir and its immediate child so + // the session re-resolves files created before the watch was installed. + waitFor(t, func() bool { + mu.Lock() + defer mu.Unlock() + var sawDir, sawChild bool + for _, e := range got { + if e.Type != lsproto.FileChangeTypeCreated { + continue + } + s := string(e.Uri) + if strings.HasSuffix(s, "/pkg") { + sawDir = true + } + if strings.HasSuffix(s, "/pkg/index.ts") { + sawChild = true + } + } + return sawDir && sawChild + }, "synthetic create events for target and child") +} + +func TestWatcher_MultiLevelDescend(t *testing.T) { + t.Parallel() + + fs := bundled.WrapFS(osvfs.FS()) + backend := newFakeBackend() + w := newWithBackend(fs, backend, func([]*lsproto.FileEvent) {}, logging.NewLogger(os.Stderr)) + t.Cleanup(w.Close) + + base := t.TempDir() + baseReal := tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(base))) + target := tspath.NormalizeSlashes(filepath.Join(base, "a", "b", "c")) + pattern := target + "/*" + + if err := w.WatchFiles("id", []*lsproto.FileSystemWatcher{{ + GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &pattern}, + }}); err != nil { + t.Fatal(err) + } + if !backend.isWatching(baseReal) { + t.Fatalf("expected initial ancestor watch on %q, got %v", baseReal, backend.watchedDirs()) + } + + // Reveal one path component at a time; the ancestor watch should descend. + mkdirAndRealpath := func(rel string) string { + p := filepath.Join(base, rel) + if err := os.MkdirAll(p, 0o755); err != nil { + t.Fatal(err) + } + return tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(p))) + } + + aReal := mkdirAndRealpath("a") + backend.emit(baseReal, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "a")}}, nil) + waitFor(t, func() bool { return backend.isWatching(aReal) }, "descend to a") + + abReal := mkdirAndRealpath(filepath.Join("a", "b")) + backend.emit(aReal, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "a", "b")}}, nil) + waitFor(t, func() bool { return backend.isWatching(abReal) }, "descend to a/b") + + abcReal := mkdirAndRealpath(filepath.Join("a", "b", "c")) + backend.emit(abReal, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "a", "b", "c")}}, nil) + waitFor(t, func() bool { return backend.isWatching(abcReal) }, "promote to target a/b/c") +} + +func TestWatcher_AtomicTreeCreateRace(t *testing.T) { + t.Parallel() + + fs := bundled.WrapFS(osvfs.FS()) + backend := newFakeBackend() + w := newWithBackend(fs, backend, func([]*lsproto.FileEvent) {}, logging.NewLogger(os.Stderr)) + t.Cleanup(w.Close) + + base := t.TempDir() + baseReal := tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(base))) + target := tspath.NormalizeSlashes(filepath.Join(base, "a", "b", "c")) + pattern := target + "/*" + + if err := w.WatchFiles("id", []*lsproto.FileSystemWatcher{{ + GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &pattern}, + }}); err != nil { + t.Fatal(err) + } + + // The whole tree appears at once (e.g. an extraction/symlink). A single + // notification on the base ancestor watch must descend all the way and + // promote to the target in one reconcile pass. + if err := os.MkdirAll(filepath.Join(base, "a", "b", "c"), 0o755); err != nil { + t.Fatal(err) + } + backend.emit(baseReal, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "a")}}, nil) + + targetReal := tspath.NormalizeSlashes(fs.Realpath(target)) + waitFor(t, func() bool { return backend.isWatching(targetReal) }, "promote to target in one pass") +} + +func TestWatcher_SyntheticCreateDepth(t *testing.T) { + t.Parallel() + + for _, recursive := range []bool{false, true} { + t.Run(map[bool]string{false: "non-recursive", true: "recursive"}[recursive], func(t *testing.T) { + t.Parallel() + fs := bundled.WrapFS(osvfs.FS()) + backend := newFakeBackend() + var ( + mu sync.Mutex + got []*lsproto.FileEvent + ) + w := newWithBackend(fs, backend, func(changes []*lsproto.FileEvent) { + mu.Lock() + got = append(got, changes...) + mu.Unlock() + }, logging.NewLogger(os.Stderr)) + t.Cleanup(w.Close) + + base := t.TempDir() + baseReal := tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(base))) + target := tspath.NormalizeSlashes(filepath.Join(base, "pkg")) + kind := lsproto.WatchKindCreate | lsproto.WatchKindChange | lsproto.WatchKindDelete + var pattern string + if recursive { + pattern = target + "/**/*" + } else { + pattern = target + "/*" + } + + if err := w.WatchFiles("id", []*lsproto.FileSystemWatcher{{ + GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &pattern}, + Kind: &kind, + }}); err != nil { + t.Fatal(err) + } + + // Materialize the target with a nested file under a subdirectory. + if err := os.MkdirAll(filepath.Join(base, "pkg", "sub"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(base, "pkg", "top.ts"), []byte("export {}"), 0o644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(base, "pkg", "sub", "deep.ts"), []byte("export {}"), 0o644); err != nil { + t.Fatal(err) + } + backend.emit(baseReal, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "pkg")}}, nil) + + targetReal := tspath.NormalizeSlashes(fs.Realpath(target)) + waitFor(t, func() bool { return backend.isWatching(targetReal) }, "promotion") + + created := func() map[string]bool { + mu.Lock() + defer mu.Unlock() + m := map[string]bool{} + for _, e := range got { + if e.Type == lsproto.FileChangeTypeCreated { + m[string(e.Uri)] = true + } + } + return m + } + + // Both modes must synthesize the immediate child. + waitFor(t, func() bool { + for s := range created() { + if strings.HasSuffix(s, "/pkg/top.ts") { + return true + } + } + return false + }, "synthetic create for immediate child") + + // Only the recursive watch should synthesize the deep descendant. + deadline := time.Now().Add(1 * time.Second) + sawDeep := func() bool { + for s := range created() { + if strings.HasSuffix(s, "/pkg/sub/deep.ts") { + return true + } + } + return false + } + if recursive { + for time.Now().Before(deadline) && !sawDeep() { + time.Sleep(20 * time.Millisecond) + } + if !sawDeep() { + t.Errorf("recursive watch should synthesize deep descendant; got %v", created()) + } + } else { + time.Sleep(300 * time.Millisecond) // allow any erroneous deep event to arrive + if sawDeep() { + t.Errorf("non-recursive watch must not synthesize deep descendant; got %v", created()) + } + } + }) + } +} + +func TestWatcher_TerminatedFallsBackAndRecovers(t *testing.T) { + t.Parallel() + + fs := bundled.WrapFS(osvfs.FS()) + backend := newFakeBackend() + var ( + mu sync.Mutex + got []*lsproto.FileEvent + ) + w := newWithBackend(fs, backend, func(changes []*lsproto.FileEvent) { + mu.Lock() + got = append(got, changes...) + mu.Unlock() + }, logging.NewLogger(os.Stderr)) + t.Cleanup(w.Close) + + base := t.TempDir() + baseReal := tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(base))) + target := tspath.NormalizeSlashes(filepath.Join(base, "pkg")) + if err := os.MkdirAll(filepath.Join(base, "pkg"), 0o755); err != nil { + t.Fatal(err) + } + targetReal := tspath.NormalizeSlashes(fs.Realpath(target)) + pattern := target + "/*" + kind := lsproto.WatchKindCreate | lsproto.WatchKindChange | lsproto.WatchKindDelete + + if err := w.WatchFiles("id", []*lsproto.FileSystemWatcher{{ + GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &pattern}, + Kind: &kind, + }}); err != nil { + t.Fatal(err) + } + if !backend.isWatching(targetReal) { + t.Fatalf("expected target watch on %q, got %v", targetReal, backend.watchedDirs()) + } + + // Delete the directory and deliver ErrWatchTerminated together with the + // directory's own delete event (as the real backends do). + if err := os.RemoveAll(filepath.Join(base, "pkg")); err != nil { + t.Fatal(err) + } + backend.emit(targetReal, []fswatch.Event{ + {Kind: fswatch.EventDelete, Path: filepath.Join(base, "pkg")}, + }, errors.Join(fswatch.ErrWatchTerminated, errors.New("removed"))) + + // The delete must be forwarded, and the watch must fall back to watching + // the ancestor. + waitFor(t, func() bool { + mu.Lock() + defer mu.Unlock() + for _, e := range got { + if e.Type == lsproto.FileChangeTypeDeleted && strings.HasSuffix(string(e.Uri), "/pkg") { + return true + } + } + return false + }, "forwarded delete of terminated dir") + waitFor(t, func() bool { return backend.isWatching(baseReal) && !backend.isWatching(targetReal) }, "fallback to ancestor watch") + + // Recreate the directory; the ancestor watch must promote back to target. + if err := os.MkdirAll(filepath.Join(base, "pkg"), 0o755); err != nil { + t.Fatal(err) + } + recreatedReal := tspath.NormalizeSlashes(fs.Realpath(target)) + backend.emit(baseReal, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "pkg")}}, nil) + waitFor(t, func() bool { return backend.isWatching(recreatedReal) }, "recovery to target watch after recreation") +} + +func TestWatcher_GenuineFailureRollsBackForRetry(t *testing.T) { + t.Parallel() + + fs := bundled.WrapFS(osvfs.FS()) + backend := newFakeBackend() + w := newWithBackend(fs, backend, func([]*lsproto.FileEvent) {}, logging.NewLogger(os.Stderr)) + t.Cleanup(w.Close) + + dir := t.TempDir() + dirReal := tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(dir))) + pattern := tspath.NormalizeSlashes(dir) + "/*" + + // Inject a genuine backend failure for the existing directory. + backend.mu.Lock() + backend.failDirs[dirReal] = errors.New("too many open files") + backend.mu.Unlock() + + err := w.WatchFiles("id", []*lsproto.FileSystemWatcher{{ + GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &pattern}, + }}) + if err == nil { + t.Fatal("expected error from genuine backend failure") + } + + // The id must have been rolled back so a retry can re-register cleanly + // (rather than hitting the duplicate-id error). Clear the injected failure + // to simulate the resource pressure easing on retry. + backend.mu.Lock() + delete(backend.failDirs, dirReal) + backend.mu.Unlock() + + if err := w.WatchFiles("id", []*lsproto.FileSystemWatcher{{ + GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &pattern}, + }}); err != nil { + t.Fatalf("retry after rollback should succeed, got %v", err) + } + if !backend.isWatching(dirReal) { + t.Fatalf("expected watch on %q after successful retry, got %v", dirReal, backend.watchedDirs()) + } +} + +func TestWatcher_WatchTerminatedDoesNotDropEvents(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + dirNorm := tspath.NormalizeSlashes(dir) + fs := bundled.WrapFS(osvfs.FS()) + backend := newFakeBackend() + var got []*lsproto.FileEvent + var mu sync.Mutex + w := newWithBackend(fs, backend, func(changes []*lsproto.FileEvent) { + mu.Lock() + got = append(got, changes...) + mu.Unlock() + }, logging.NewLogger(os.Stderr)) + + pattern := dirNorm + "/**/*" + if err := w.WatchFiles("id", []*lsproto.FileSystemWatcher{{ + GlobPattern: lsproto.PatternOrRelativePattern{Pattern: &pattern}, + }}); err != nil { + t.Fatal(err) + } + + backend.emitAll([]fswatch.Event{ + {Kind: fswatch.EventUpdate, Path: filepath.FromSlash(filepath.Join(dirNorm, "b.ts"))}, + }, errors.Join(fswatch.ErrWatchTerminated, errors.New("simulated"))) + + waitFor(t, func() bool { + mu.Lock() + defer mu.Unlock() + return len(got) > 0 + }, "events with watch-terminated error") +} diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 7aac5f263d5..6f6b8851e1e 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -18,6 +18,7 @@ import ( "github.com/microsoft/typescript-go/internal/collections" "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/diagnostics" + "github.com/microsoft/typescript-go/internal/fswatch" "github.com/microsoft/typescript-go/internal/json" "github.com/microsoft/typescript-go/internal/jsonrpc" "github.com/microsoft/typescript-go/internal/locale" @@ -25,6 +26,7 @@ import ( "github.com/microsoft/typescript-go/internal/ls/lsconv" "github.com/microsoft/typescript-go/internal/ls/lsutil" "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/lsp/lspwatcher" "github.com/microsoft/typescript-go/internal/pprof" "github.com/microsoft/typescript-go/internal/project" "github.com/microsoft/typescript-go/internal/project/ata" @@ -180,6 +182,12 @@ type Server struct { telemetryEnabled bool watcherID atomic.Uint32 watchers collections.SyncSet[project.WatcherID] + // builtinWatcher is non-nil when the server is running its own + // in-process file watcher instead of using LSP-based watching. It + // is enabled when the client lacks DynamicRegistration for + // workspace/didChangeWatchedFiles and the builtin watcher backend + // supports efficient recursive watching (Windows or FSEvents). + builtinWatcher *lspwatcher.Watcher lastRequestTimeMs atomic.Int64 @@ -220,6 +228,13 @@ func (s *Server) InitComplete() <-chan struct{} { return s.initComplete } // WatchFiles implements project.Client. func (s *Server) WatchFiles(ctx context.Context, id project.WatcherID, watchers []*lsproto.FileSystemWatcher) error { + if s.builtinWatcher != nil { + if err := s.builtinWatcher.WatchFiles(string(id), watchers); err != nil { + return fmt.Errorf("failed to register file watcher: %w", err) + } + s.watchers.Add(id) + return nil + } _, err := sendClientRequest(ctx, s, lsproto.ClientRegisterCapabilityInfo, &lsproto.RegistrationParams{ Registrations: []*lsproto.Registration{ { @@ -242,6 +257,16 @@ func (s *Server) WatchFiles(ctx context.Context, id project.WatcherID, watchers // UnwatchFiles implements project.Client. func (s *Server) UnwatchFiles(ctx context.Context, id project.WatcherID) error { + if s.builtinWatcher != nil { + if !s.watchers.Has(id) { + return fmt.Errorf("no file watcher exists with ID %s", id) + } + if err := s.builtinWatcher.UnwatchFiles(string(id)); err != nil { + return fmt.Errorf("failed to unregister file watcher: %w", err) + } + s.watchers.Delete(id) + return nil + } if s.watchers.Has(id) { _, err := sendClientRequest(ctx, s, lsproto.ClientUnregisterCapabilityInfo, &lsproto.UnregistrationParams{ Unregisterations: []*lsproto.Unregistration{ @@ -1164,8 +1189,33 @@ func (s *Server) handleInitialize(ctx context.Context, params *lsproto.Initializ } func (s *Server) handleInitialized(ctx context.Context, params *lsproto.InitializedParams) error { - if s.clientCapabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration { + var disablePushDiagnostics bool + var enableTelemetry bool + if s.initializationOptions.DisablePushDiagnostics != nil { + disablePushDiagnostics = *s.initializationOptions.DisablePushDiagnostics + } + if s.initializationOptions.EnableTelemetry != nil { + enableTelemetry = *s.initializationOptions.EnableTelemetry + } + hasDynamicWatchRegistration := s.clientCapabilities.Workspace.DidChangeWatchedFiles.DynamicRegistration + if hasDynamicWatchRegistration { + s.logger.Logf("file watching: using LSP client-side watching (client supports dynamic registration)") s.watchEnabled = true + } else if fswatch.Default().HasFastRecursiveBackend() { + // The client cannot watch files itself, but the builtin watcher has a + // backend with efficient recursive watching (Windows or FSEvents), so + // fall back to watching files in-process. + s.logger.Logf("file watching: using builtin in-process watcher (client lacks dynamic watch registration)") + s.watchEnabled = true + s.builtinWatcher = lspwatcher.New(s.fs, func(changes []*lsproto.FileEvent) { + if s.session != nil { + s.session.DidChangeWatchedFiles(s.backgroundCtx, changes) + } + }, s.logger) + } else { + // The client cannot watch files and the builtin watcher backend lacks + // efficient recursive watching, so file watching is disabled. + s.logger.Logf("file watching: disabled (client lacks dynamic watch registration and builtin watcher backend is not fast-recursive)") } cwd := s.cwd @@ -1183,14 +1233,6 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali cwd = s.cwd } - var disablePushDiagnostics bool - var enableTelemetry bool - if s.initializationOptions.DisablePushDiagnostics != nil { - disablePushDiagnostics = *s.initializationOptions.DisablePushDiagnostics - } - if s.initializationOptions.EnableTelemetry != nil { - enableTelemetry = *s.initializationOptions.EnableTelemetry - } s.telemetryEnabled = enableTelemetry s.session = project.NewSession(&project.SessionInit{ @@ -1252,6 +1294,9 @@ func (s *Server) handleInitialized(ctx context.Context, params *lsproto.Initiali } func (s *Server) handleShutdown(ctx context.Context, _ lsproto.NoParams, _ *lsproto.RequestMessage) (lsproto.ShutdownResponse, error) { + if s.builtinWatcher != nil { + s.builtinWatcher.Close() + } s.session.Close() return lsproto.ShutdownResponse{}, nil } From b1e0c7e9f56529db4194cf227347b57f24800ec0 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Jun 2026 22:17:39 +0000 Subject: [PATCH 18/49] Fix external helper crash after module-status updates (#3994) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> Co-authored-by: Jake Bailey <5341706+jakebailey@users.noreply.github.com> --- internal/compiler/program.go | 17 +++++++ ...sAfterScriptBecomesDecoratedModule_test.go | 47 +++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go diff --git a/internal/compiler/program.go b/internal/compiler/program.go index 4a6b6e253d6..e6069d66b04 100644 --- a/internal/compiler/program.go +++ b/internal/compiler/program.go @@ -307,6 +307,9 @@ func (p *Program) UpdateProgram(changedFilePath tspath.Path, newHost CompilerHos if !canReplaceFileInProgram(oldFile, newFile) { return NewProgram(newOpts), newFile, false } + if oldNeedsImportHelpers := p.importHelpersImportSpecifiers[oldFile.Path()] != nil; oldNeedsImportHelpers != p.needsImportHelpersImportSpecifier(newFile) { + return NewProgram(newOpts), newFile, false + } // TODO: reverify compiler options when config has changed? result := &Program{ opts: newOpts, @@ -356,6 +359,20 @@ func canReplaceFileInProgram(file1 *ast.SourceFile, file2 *ast.SourceFile) bool equalCheckJSDirectives(file1.CheckJsDirective, file2.CheckJsDirective) } +func (p *Program) needsImportHelpersImportSpecifier(file *ast.SourceFile) bool { + redirect, _ := p.projectReferenceFileMapper.getRedirectForResolution(file) + optionsForFile := module.GetCompilerOptionsWithRedirect(p.opts.Config.CompilerOptions(), redirect) + if !optionsForFile.ImportHelpers.IsTrue() { + return false + } + isJavaScriptFile := ast.IsSourceFileJS(file) + isExternalModuleFile := ast.IsExternalModule(file) + if !isJavaScriptFile && (file.IsDeclarationFile || (!optionsForFile.GetIsolatedModules() && !isExternalModuleFile)) { + return false + } + return true +} + func equalModuleSpecifiers(n1 *ast.Node, n2 *ast.Node) bool { return n1.Kind == n2.Kind && (!ast.IsStringLiteral(n1) || n1.Text() == n2.Text()) } diff --git a/internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go b/internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go new file mode 100644 index 00000000000..1928537f424 --- /dev/null +++ b/internal/fourslash/tests/importHelpersAfterScriptBecomesDecoratedModule_test.go @@ -0,0 +1,47 @@ +package fourslash_test + +import ( + "testing" + + "github.com/microsoft/typescript-go/internal/fourslash" + "github.com/microsoft/typescript-go/internal/testutil" +) + +func TestImportHelpersAfterScriptBecomesDecoratedModule(t *testing.T) { + t.Parallel() + defer testutil.RecoverAndFail(t, "Panic on fourslash test") + + const content = `// @Filename: /tsconfig.json +{ + "compilerOptions": { + "target": "es2015", + "module": "commonjs", + "experimentalDecorators": true, + "importHelpers": true + }, + "files": ["foo.ts"] +} + +// @Filename: /foo.ts +declare function dec(value: Function): void; +/*insert*/class C {} + +// @Filename: /node_modules/tslib/package.json +{ "name": "tslib", "typings": "tslib.d.ts" } + +// @Filename: /node_modules/tslib/tslib.d.ts +export declare function __decorate(...args: any[]): any; + +// @Filename: /node_modules/tslib/tslib.js +exports.__decorate = function () {}; +` + f, done := fourslash.NewFourslash(t, nil /*capabilities*/, content) + defer done() + + f.GoToFile(t, "/foo.ts") + f.VerifyNumberOfErrorsInCurrentFile(t, 0) + f.Replace(t, f.MarkerByName(t, "insert").Position, 0, `@dec +export `) + // The second diagnostics request forces external helper resolution after the edit. + f.VerifyNumberOfErrorsInCurrentFile(t, 0) +} From 7ce96c85690b63837f6626d539564304c4c4a512 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 12 Jun 2026 15:44:16 -0700 Subject: [PATCH 19/49] Make LS checkers time out after being idle, segment based on use (take 2) (#3435) --- internal/api/session.go | 6 +- internal/checker/checker.go | 9 +- internal/compiler/program.go | 5 + internal/core/context.go | 20 + internal/lsp/server.go | 1 + internal/project/checkerpool.go | 534 ++++++-- internal/project/checkerpool_test.go | 1295 ++++++++++++++++++ internal/project/project.go | 16 +- internal/project/projectcollectionbuilder.go | 6 +- internal/project/session.go | 1 + internal/project/snapshot.go | 7 + 11 files changed, 1768 insertions(+), 132 deletions(-) create mode 100644 internal/project/checkerpool_test.go diff --git a/internal/api/session.go b/internal/api/session.go index b9ec4ed0d71..7652967c5b0 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -333,7 +333,7 @@ func (s *Session) setupChecker(ctx context.Context, snapshot SnapshotID, project return checkerSetup{}, err } - c, done := program.GetTypeChecker(ctx) + c, done := program.GetTypeChecker(core.WithCheckerLifetime(ctx, core.CheckerLifetimeAPI)) return checkerSetup{ sd: sd, program: program, @@ -2128,6 +2128,7 @@ func (s *Session) toFileChangeSummary(changes *APIFileChanges) project.FileChang // handleGetSyntacticDiagnostics returns syntactic diagnostics for a file or all files. func (s *Session) handleGetSyntacticDiagnostics(ctx context.Context, params *GetDiagnosticsParams) ([]*DiagnosticResponse, error) { + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) sd, err := s.getSnapshotData(params.Snapshot) if err != nil { return nil, err @@ -2149,6 +2150,7 @@ func (s *Session) handleGetSyntacticDiagnostics(ctx context.Context, params *Get // handleGetSemanticDiagnostics returns semantic diagnostics for a file or all files. func (s *Session) handleGetSemanticDiagnostics(ctx context.Context, params *GetDiagnosticsParams) ([]*DiagnosticResponse, error) { + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) sd, err := s.getSnapshotData(params.Snapshot) if err != nil { return nil, err @@ -2170,6 +2172,7 @@ func (s *Session) handleGetSemanticDiagnostics(ctx context.Context, params *GetD // handleGetSuggestionDiagnostics returns suggestion diagnostics for a file or all files. func (s *Session) handleGetSuggestionDiagnostics(ctx context.Context, params *GetDiagnosticsParams) ([]*DiagnosticResponse, error) { + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) sd, err := s.getSnapshotData(params.Snapshot) if err != nil { return nil, err @@ -2191,6 +2194,7 @@ func (s *Session) handleGetSuggestionDiagnostics(ctx context.Context, params *Ge // handleGetDeclarationDiagnostics returns declaration diagnostics for a file or all files. func (s *Session) handleGetDeclarationDiagnostics(ctx context.Context, params *GetDiagnosticsParams) ([]*DiagnosticResponse, error) { + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) sd, err := s.getSnapshotData(params.Snapshot) if err != nil { return nil, err diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 18fcff71d83..32b7abe1995 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -14289,7 +14289,12 @@ func (c *Checker) recordMergedSymbol(target *ast.Symbol, source *ast.Symbol) { } func (c *Checker) getSymbolIfSameReference(s1 *ast.Symbol, s2 *ast.Symbol) *ast.Symbol { - if c.getMergedSymbol(c.resolveSymbol(c.getMergedSymbol(s1))) == c.getMergedSymbol(c.resolveSymbol(c.getMergedSymbol(s2))) { + // An instantiated symbol (e.g. a parameter of a signature instantiated to substitute `this`) + // refers to the same declaration as its instantiation target. Whether a given context yields the + // original symbol or an instantiated copy can depend on incidental checker cache state (see + // instantiateSymbol, whose reuse shortcut is gated on the type already being resolved), so unwrap + // instantiations here to keep "same reference" answers deterministic. + if c.getMergedSymbol(c.resolveSymbol(c.getMergedSymbol(c.getTargetSymbol(s1)))) == c.getMergedSymbol(c.resolveSymbol(c.getMergedSymbol(c.getTargetSymbol(s2)))) { return s1 } return nil @@ -21544,7 +21549,7 @@ func (c *Checker) getTargetSymbol(s *ast.Symbol) *ast.Symbol { // if symbol is instantiated its flags are not copied from the 'target' // so we'll need to get back original 'target' symbol to work with correct set of flags // NOTE: cast to TransientSymbol should be safe because only TransientSymbols have CheckFlags.Instantiated - if s.CheckFlags&ast.CheckFlagsInstantiated != 0 { + if s != nil && s.CheckFlags&ast.CheckFlagsInstantiated != 0 { return c.valueSymbolLinks.Get(s).target } return s diff --git a/internal/compiler/program.go b/internal/compiler/program.go index e6069d66b04..35093fab681 100644 --- a/internal/compiler/program.go +++ b/internal/compiler/program.go @@ -346,6 +346,11 @@ func (p *Program) initCheckerPool() { } } +// GetCheckerPool returns the checker pool associated with this program. +func (p *Program) GetCheckerPool() CheckerPool { + return p.checkerPool +} + func canReplaceFileInProgram(file1 *ast.SourceFile, file2 *ast.SourceFile) bool { return file2 != nil && file1.ParseOptions() == file2.ParseOptions() && diff --git a/internal/core/context.go b/internal/core/context.go index 394fcf45619..6747a03e2cc 100644 --- a/internal/core/context.go +++ b/internal/core/context.go @@ -8,6 +8,7 @@ type key int const ( requestIDKey key = iota + checkerLifetimeKey ) func WithRequestID(ctx context.Context, id string) context.Context { @@ -20,3 +21,22 @@ func GetRequestID(ctx context.Context) string { } return "" } + +type CheckerLifetime int + +const ( + CheckerLifetimeTemporary CheckerLifetime = iota + CheckerLifetimeDiagnostics + CheckerLifetimeAPI +) + +func WithCheckerLifetime(ctx context.Context, lifetime CheckerLifetime) context.Context { + return context.WithValue(ctx, checkerLifetimeKey, lifetime) +} + +func GetCheckerLifetime(ctx context.Context) CheckerLifetime { + if lifetime, ok := ctx.Value(checkerLifetimeKey).(CheckerLifetime); ok { + return lifetime + } + return CheckerLifetimeTemporary +} diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 6f6b8851e1e..4e5e00989db 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -1355,6 +1355,7 @@ func (s *Server) handleSetLogVerbosity(_ context.Context, params *lsproto.SetLog } func (s *Server) handleDocumentDiagnostic(ctx context.Context, ls *ls.LanguageService, params *lsproto.DocumentDiagnosticParams) (lsproto.DocumentDiagnosticResponse, error) { + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) return ls.ProvideDiagnostics(ctx, params.TextDocument.Uri) } diff --git a/internal/project/checkerpool.go b/internal/project/checkerpool.go index f3738b18646..32f68d413f0 100644 --- a/internal/project/checkerpool.go +++ b/internal/project/checkerpool.go @@ -5,180 +5,480 @@ import ( "fmt" "slices" "sync" + "time" "github.com/microsoft/typescript-go/internal/ast" "github.com/microsoft/typescript-go/internal/checker" "github.com/microsoft/typescript-go/internal/compiler" "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/debug" ) -type CheckerPool struct { - maxCheckers int - program *compiler.Program - - mu sync.Mutex - cond *sync.Cond - createCheckersOnce sync.Once - checkers []*checker.Checker - inUse map[*checker.Checker]bool - fileAssociations map[*ast.SourceFile]int - requestAssociations map[string]int +// checkerHeldAnonymous is a sentinel stored in heldBy when a checker is held +// by a caller that has no request ID (e.g., context.Background()). This +// distinguishes "held without ID" from "not held" (empty string). +const checkerHeldAnonymous = "" + +type CheckerPoolOptions struct { + // MaxCheckers controls the total number of checker slots per project + // (1 dedicated diagnostics checker + N-1 query checkers). Minimum 2. + // Zero uses the default (4). + MaxCheckers int + // IdleTimeout controls how long an idle checker is kept + // before being disposed. Zero uses the default (30s). + IdleTimeout time.Duration +} + +// checkerPool manages three categories of type checkers for a project: +// +// - Diagnostics (index 0): A single checker for LSP diagnostics, providing +// consistent walk order. Idle-cleaned. +// - Temporary (indices 1+): Ephemeral query checkers for LSP operations. +// Idle-cleaned after a configurable timeout. +// - API: A single checker for API operations, providing stable +// instance identity for reference equality on type/symbol handles. +// Never idle-cleaned. +type checkerPool struct { + opts CheckerPoolOptions + program *compiler.Program + + mu sync.Mutex + + // discarded is set when the pool's program has been replaced. The pool + // remains fully functional but stops its idle-cleanup timer so that + // query checkers are not disposed until the pool is GC'd. + discarded bool + + // checkers[0] is the diagnostics checker. + // checkers[1:] are ephemeral query checkers. + // All are idle-cleaned. + checkers []*checker.Checker + heldBy []string // heldBy[i] is the requestID holding checker i, checkerHeldAnonymous, or "" if not held + fileAssociations map[*ast.SourceFile]int // file → query checker index (1+) + requestAssociations map[string]int // requestID → checker index + + // lastReleased tracks when each checker was last released. + lastReleased []time.Time + + // cleanupTimer is reset each time a checker is released. + // When it fires, idle checkers are disposed. + cleanupTimer *time.Timer + + // persistentChecker is the API checker. It is never idle-cleaned, + // providing stable instance identity for API clients. + persistentChecker *checker.Checker + persistentHeld bool + + diagSem chan struct{} + querySem chan struct{} + persistentSem chan struct{} + log func(msg string) globalDiagAccumulated []*ast.Diagnostic globalDiagChanged bool globalDiagCheckerCount []int // per-checker count of globals last seen } -var _ compiler.CheckerPool = (*CheckerPool)(nil) +var _ compiler.CheckerPool = (*checkerPool)(nil) -func newCheckerPool(maxCheckers int, program *compiler.Program, log func(msg string)) *CheckerPool { - pool := &CheckerPool{ +func newCheckerPool(opts CheckerPoolOptions, program *compiler.Program, log func(msg string)) *checkerPool { + if opts.MaxCheckers <= 0 { + opts.MaxCheckers = 4 + } else if opts.MaxCheckers < 2 { + opts.MaxCheckers = 2 // at least 1 diagnostics + 1 query checker + } + if opts.IdleTimeout <= 0 { + opts.IdleTimeout = 30 * time.Second + } + querySlots := opts.MaxCheckers - 1 + pool := &checkerPool{ program: program, - maxCheckers: maxCheckers, - checkers: make([]*checker.Checker, maxCheckers), - inUse: make(map[*checker.Checker]bool), + opts: opts, + checkers: make([]*checker.Checker, opts.MaxCheckers), + heldBy: make([]string, opts.MaxCheckers), + fileAssociations: make(map[*ast.SourceFile]int), requestAssociations: make(map[string]int), + lastReleased: make([]time.Time, opts.MaxCheckers), + diagSem: make(chan struct{}, 1), + querySem: make(chan struct{}, querySlots), + persistentSem: make(chan struct{}, 1), log: log, - globalDiagCheckerCount: make([]int, maxCheckers), + globalDiagCheckerCount: make([]int, opts.MaxCheckers), } if pool.log == nil { pool.log = func(msg string) {} } - pool.cond = sync.NewCond(&pool.mu) return pool } -func (p *CheckerPool) GetChecker(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) { +// holdTag returns the value to store in heldBy for the given request ID. +func holdTag(requestID string) string { + if requestID == "" { + return checkerHeldAnonymous + } + return requestID +} + +func (p *checkerPool) GetChecker(ctx context.Context, file *ast.SourceFile) (*checker.Checker, func()) { + lifetime := core.GetCheckerLifetime(ctx) + requestID := core.GetRequestID(ctx) + + // Request affinity is cleaned up via context.AfterFunc when the request + // context is done. If the context can never be canceled (ctx.Done() == nil, + // e.g. context.Background()), that cleanup would never run and + // requestAssociations would grow unboundedly, so disable affinity entirely. + if ctx.Done() == nil { + requestID = "" + } + + switch lifetime { + case core.CheckerLifetimeDiagnostics: + return p.getDiagnosticsChecker(ctx, requestID) + case core.CheckerLifetimeAPI: + return p.getPersistentChecker() + default: + return p.getQueryChecker(ctx, requestID, file) + } +} + +// tryReacquireForRequest checks whether the given request already has an +// associated checker. If so, it either returns the checker directly (still held) +// or reacquires it by claiming a semaphore slot. The caller must provide the +// appropriate semaphore channel and indicate whether this is a diagnostics +// request (isDiag). If the associated checker is in the wrong category +// (e.g. a diagnostics index for a query request), the association is deleted +// and normal acquisition proceeds. +// +// Returns (checker, release, true) if the request was served (either still held +// or reclaimed). Returns (nil, nil, false) if the caller must proceed with +// normal acquisition — in this case, a semaphore slot has already been claimed. +// Must NOT be called with p.mu held. +func (p *checkerPool) tryReacquireForRequest(requestID string, sem chan<- struct{}, isDiag bool) (*checker.Checker, func(), bool) { + if requestID == "" { + sem <- struct{}{} + return nil, nil, false + } + + p.mu.Lock() + index, ok := p.requestAssociations[requestID] + if !ok { + p.mu.Unlock() + sem <- struct{}{} + return nil, nil, false + } + + // Validate that the associated index matches the expected category. + // Index 0 is for diagnostics; indices 1+ are for queries. + if (isDiag && index != 0) || (!isDiag && index == 0) { + delete(p.requestAssociations, requestID) + p.mu.Unlock() + sem <- struct{}{} + return nil, nil, false + } + + c := p.checkers[index] + if c == nil { + delete(p.requestAssociations, requestID) + p.mu.Unlock() + sem <- struct{}{} + return nil, nil, false + } + + held := p.heldBy[index] + if held == requestID { + // Same request, checker still held — return without claiming a slot. + p.mu.Unlock() + return c, noop, true + } + + if held == "" { + // Same request reacquiring after release — need a semaphore slot. + p.mu.Unlock() + sem <- struct{}{} + p.mu.Lock() + // Re-check: checker may have been disposed while waiting for the slot. + if cc := p.checkers[index]; cc == c && p.heldBy[index] == "" { + p.heldBy[index] = requestID + p.mu.Unlock() + return c, p.createRelease(requestID, index, c), true + } + p.mu.Unlock() + // Checker was replaced/disposed while waiting for the slot. + // The slot is still claimed; the caller will use it for normal acquisition. + return nil, nil, false + } + + // Checker held by another request — claim a slot normally. + p.mu.Unlock() + sem <- struct{}{} + return nil, nil, false +} + +// getDiagnosticsChecker returns the dedicated diagnostics checker (index 0). +// Creates it on first use. Blocks on diagSem if it's currently in use. +func (p *checkerPool) getDiagnosticsChecker(ctx context.Context, requestID string) (*checker.Checker, func()) { + const diagIndex = 0 + + if c, release, ok := p.tryReacquireForRequest(requestID, p.diagSem, true); ok { + return c, release + } + + // Token consumed — proceed with normal acquisition. p.mu.Lock() defer p.mu.Unlock() - requestID := core.GetRequestID(ctx) + if p.checkers[diagIndex] == nil { + p.log("checkerpool: Creating diagnostics checker") + c, _ := checker.NewChecker(p.program, nil) + p.checkers[diagIndex] = c + } + + c := p.checkers[diagIndex] + p.heldBy[diagIndex] = holdTag(requestID) + p.log("checkerpool: Acquired diagnostics checker for request " + holdTag(requestID)) if requestID != "" { - if checker, release := p.getRequestCheckerLocked(requestID); checker != nil { - return checker, release + if _, alreadyRegistered := p.requestAssociations[requestID]; !alreadyRegistered { + p.requestAssociations[requestID] = diagIndex + p.registerRequestCleanup(ctx, requestID) } } + return c, p.createRelease(requestID, diagIndex, c) +} - if file != nil { - if p.fileAssociations == nil { - p.fileAssociations = make(map[*ast.SourceFile]int) - } +// getQueryChecker returns an ephemeral query checker from indices 1+. +// Uses request affinity, then file affinity, then finds/creates. +// Blocks on querySem if all query slots are in use. +func (p *checkerPool) getQueryChecker(ctx context.Context, requestID string, file *ast.SourceFile) (*checker.Checker, func()) { + if c, release, ok := p.tryReacquireForRequest(requestID, p.querySem, false); ok { + return c, release + } + + // Token consumed — proceed with normal acquisition. + p.mu.Lock() + defer p.mu.Unlock() - if index, ok := p.fileAssociations[file]; ok { - checker := p.checkers[index] - if checker != nil { - if inUse := p.inUse[checker]; !inUse { - p.inUse[checker] = true - if requestID != "" { + // Try file affinity. + if file != nil { + if index, ok := p.fileAssociations[file]; ok && index > 0 { + if c := p.checkers[index]; c != nil && p.heldBy[index] == "" { + p.heldBy[index] = holdTag(requestID) + if requestID != "" { + if _, alreadyRegistered := p.requestAssociations[requestID]; !alreadyRegistered { p.requestAssociations[requestID] = index + p.registerRequestCleanup(ctx, requestID) } - return checker, p.createRelease(requestID, index, checker) } + return c, p.createRelease(requestID, index, c) } } } - checker, index := p.getCheckerLocked(requestID) - if file != nil { - if p.fileAssociations == nil { - p.fileAssociations = make(map[*ast.SourceFile]int) + // Find any available query checker or create one. + c, index := p.findOrCreateQueryCheckerLocked() + p.heldBy[index] = holdTag(requestID) + p.log(fmt.Sprintf("checkerpool: Acquired query checker %d for request %s", index, holdTag(requestID))) + if requestID != "" { + if _, alreadyRegistered := p.requestAssociations[requestID]; !alreadyRegistered { + p.requestAssociations[requestID] = index + p.registerRequestCleanup(ctx, requestID) } + } + if file != nil { p.fileAssociations[file] = index } - return checker, p.createRelease(requestID, index, checker) + return c, p.createRelease(requestID, index, c) } -func (p *CheckerPool) getCheckerLocked(requestID string) (*checker.Checker, int) { - if checker, index := p.getImmediatelyAvailableChecker(); checker != nil { - p.inUse[checker] = true - if requestID != "" { - p.requestAssociations[requestID] = index +// findOrCreateQueryCheckerLocked returns an idle query checker or creates one +// in the first empty slot. The semaphore guarantees at least one slot is +// available. Must be called with p.mu held. +func (p *checkerPool) findOrCreateQueryCheckerLocked() (*checker.Checker, int) { + // Prefer an existing idle checker. + for i := 1; i < len(p.checkers); i++ { + if c := p.checkers[i]; c != nil && p.heldBy[i] == "" { + return c, i } - return checker, index } - - if !p.isFullLocked() { - checker, index := p.createCheckerLocked() - p.inUse[checker] = true - if requestID != "" { - p.requestAssociations[requestID] = index + // Create in the first empty slot. + for i := 1; i < len(p.checkers); i++ { + if p.checkers[i] == nil { + p.log(fmt.Sprintf("checkerpool: Creating query checker %d", i)) + c, _ := checker.NewChecker(p.program, nil) + p.checkers[i] = c + return c, i } - return checker, index } + panic("checkerpool: no available query slot despite holding semaphore token") +} - checker, index := p.waitForAvailableChecker() - p.inUse[checker] = true - if requestID != "" { - p.requestAssociations[requestID] = index +func (p *checkerPool) getPersistentChecker() (*checker.Checker, func()) { + p.persistentSem <- struct{}{} + p.mu.Lock() + + if p.persistentChecker == nil { + p.log("checkerpool: Creating persistent checker") + c, _ := checker.NewChecker(p.program, nil) + p.persistentChecker = c } - return checker, index + + c := p.persistentChecker + p.persistentHeld = true + p.mu.Unlock() + + return c, sync.OnceFunc(func() { + p.mu.Lock() + p.persistentHeld = false + if c.WasCanceled() { + // A canceled checker panics on reuse, so drop it; the next API + // acquisition will create a fresh persistent checker. + p.log("checkerpool: Persistent checker was canceled, disposing") + if p.persistentChecker == c { + p.persistentChecker = nil + } + } + p.mu.Unlock() + <-p.persistentSem + }) } -func (p *CheckerPool) getRequestCheckerLocked(requestID string) (*checker.Checker, func()) { - if index, ok := p.requestAssociations[requestID]; ok { - checker := p.checkers[index] - if checker != nil { - if inUse := p.inUse[checker]; !inUse { - p.inUse[checker] = true - return checker, p.createRelease(requestID, index, checker) +func (p *checkerPool) createRelease(requestID string, index int, c *checker.Checker) func() { + return sync.OnceFunc(func() { + p.mu.Lock() + + if c.WasCanceled() { + // Canceled checkers must be disposed. + p.log(fmt.Sprintf("checkerpool: Checker %d for request %s was canceled, disposing", index, holdTag(requestID))) + p.disposeCheckerLocked(index, c) + } else { + p.mergeGlobalDiagnosticsFromCheckerLocked(index, c) + p.heldBy[index] = "" + p.lastReleased[index] = time.Now() + if !p.discarded { + p.scheduleCleanupLocked() } - // Checker is in use, but by the same request - assume it's the - // same goroutine or is managing its own synchronization - return checker, noop + // If discarded, skip scheduling cleanup — checkers stay alive + // until the pool is garbage collected so that API clients can + // continue resolving type/symbol handles. } - } - return nil, noop + + // Unlock before releasing the semaphore slot. If we received from + // the channel while holding p.mu, a woken goroutine could immediately + // try to acquire p.mu, risking priority inversion or unnecessary + // contention. + p.mu.Unlock() + + // Release the semaphore slot. + if index == 0 { + <-p.diagSem + } else { + <-p.querySem + } + }) +} + +// registerRequestCleanup uses context.AfterFunc to delete the request +// association when the request context is done. This prevents the map +// from growing unboundedly with completed request IDs. +// Must be called with p.mu held; the cleanup runs asynchronously. +func (p *checkerPool) registerRequestCleanup(ctx context.Context, requestID string) { + context.AfterFunc(ctx, func() { + p.mu.Lock() + defer p.mu.Unlock() + delete(p.requestAssociations, requestID) + }) } -func (p *CheckerPool) getImmediatelyAvailableChecker() (*checker.Checker, int) { - for i, checker := range p.checkers { - if checker == nil { +// scheduleCleanupLocked resets (or starts) the cleanup timer so it fires at +// the earliest pending checker-expiration deadline among all currently idle, +// unheld checkers. +// Must be called with p.mu held. Must NOT be called on discarded pools. +func (p *checkerPool) scheduleCleanupLocked() { + var earliestDeadline time.Time + for i := range p.checkers { + if p.checkers[i] == nil || p.heldBy[i] != "" || p.lastReleased[i].IsZero() { continue } - if inUse := p.inUse[checker]; !inUse { - return checker, i + deadline := p.lastReleased[i].Add(p.opts.IdleTimeout) + if earliestDeadline.IsZero() || deadline.Before(earliestDeadline) { + earliestDeadline = deadline } } - - return nil, -1 + if earliestDeadline.IsZero() { + // No idle checkers remain — stop the timer if it exists. + if p.cleanupTimer != nil { + p.cleanupTimer.Stop() + p.cleanupTimer = nil + } + return + } + delay := time.Until(earliestDeadline) + if delay <= 0 { + delay = time.Millisecond + } + if p.cleanupTimer != nil { + p.cleanupTimer.Reset(delay) + } else { + p.cleanupTimer = time.AfterFunc(delay, p.cleanupIdleCheckers) + } } -func (p *CheckerPool) waitForAvailableChecker() (*checker.Checker, int) { - p.log("checkerpool: Waiting for an available checker") - for { - p.cond.Wait() - checker, index := p.getImmediatelyAvailableChecker() - if checker != nil { - return checker, index +// cleanupIdleCheckers disposes checkers that have been idle for longer than +// the idle timeout. The API checker is separate and never idle-cleaned. +func (p *checkerPool) cleanupIdleCheckers() { + p.mu.Lock() + defer p.mu.Unlock() + // The timer callback may already have been in flight when Discard() called + // Stop() (which does not guarantee the callback won't run). Bail out without + // rescheduling so a discarded pool doesn't keep itself alive via a new timer. + if p.discarded { + return + } + now := time.Now() + for i := range p.checkers { + c := p.checkers[i] + if c == nil || p.heldBy[i] != "" { + continue + } + if p.lastReleased[i].IsZero() { + continue + } + idle := now.Sub(p.lastReleased[i]) + if idle >= p.opts.IdleTimeout { + p.log(fmt.Sprintf("checkerpool: Disposing idle checker %d (idle %v)", i, idle)) + p.disposeCheckerLocked(i, c) } } + // Reschedule for any remaining idle-but-not-yet-expired checkers. + // scheduleCleanupLocked will Reset the existing timer rather than + // creating a new one, avoiding goroutine leaks. + p.scheduleCleanupLocked() } -func (p *CheckerPool) createRelease(requestId string, index int, checker *checker.Checker) func() { - return func() { - p.mu.Lock() - defer p.mu.Unlock() - - delete(p.requestAssociations, requestId) - if checker.WasCanceled() { - // Canceled checkers must be disposed - p.log(fmt.Sprintf("checkerpool: Checker for request %s was canceled, disposing it", requestId)) - p.checkers[index] = nil - delete(p.inUse, checker) - p.globalDiagCheckerCount[index] = 0 - } else { - p.mergeGlobalDiagnosticsFromCheckerLocked(index, checker) - p.inUse[checker] = false +// disposeCheckerLocked removes a checker from the pool and clears all associations +// (file and request) that reference it. Must be called with p.mu held. +func (p *checkerPool) disposeCheckerLocked(index int, c *checker.Checker) { + debug.Assert(p.checkers[index] == c) + p.checkers[index] = nil + p.heldBy[index] = "" + p.globalDiagCheckerCount[index] = 0 + p.lastReleased[index] = time.Time{} + for file, idx := range p.fileAssociations { + if idx == index { + delete(p.fileAssociations, file) + } + } + for req, idx := range p.requestAssociations { + if idx == index { + delete(p.requestAssociations, req) } - p.cond.Signal() } } // mergeGlobalDiagnosticsFromCheckerLocked checks if the given checker has produced new global // diagnostics since the last time we looked, and if so merges them into the accumulated set. // Must be called with p.mu held. -func (p *CheckerPool) mergeGlobalDiagnosticsFromCheckerLocked(index int, c *checker.Checker) { +func (p *checkerPool) mergeGlobalDiagnosticsFromCheckerLocked(index int, c *checker.Checker) { globals := c.GetGlobalDiagnostics() if len(globals) == p.globalDiagCheckerCount[index] { return @@ -193,7 +493,7 @@ func (p *CheckerPool) mergeGlobalDiagnosticsFromCheckerLocked(index int, c *chec // GetGlobalDiagnostics returns the accumulated global diagnostics collected from // all checkers that have been used so far in this pool's lifetime. -func (p *CheckerPool) GetGlobalDiagnostics() []*ast.Diagnostic { +func (p *checkerPool) GetGlobalDiagnostics() []*ast.Diagnostic { p.mu.Lock() defer p.mu.Unlock() return slices.Clone(p.globalDiagAccumulated) @@ -201,7 +501,7 @@ func (p *CheckerPool) GetGlobalDiagnostics() []*ast.Diagnostic { // TakeNewGlobalDiagnostics reports whether new global diagnostics have been // accumulated since the last call, and resets the flag. -func (p *CheckerPool) TakeNewGlobalDiagnostics() bool { +func (p *checkerPool) TakeNewGlobalDiagnostics() bool { p.mu.Lock() defer p.mu.Unlock() changed := p.globalDiagChanged @@ -209,24 +509,22 @@ func (p *CheckerPool) TakeNewGlobalDiagnostics() bool { return changed } -func (p *CheckerPool) isFullLocked() bool { - for _, checker := range p.checkers { - if checker == nil { - return false - } +// Discard signals that this pool's program has been replaced. The pool +// remains functional but stops its idle-cleanup timer so that checkers +// are not disposed until the pool is GC'd. The API checker is unaffected +// since it is never idle-cleaned. +func (p *checkerPool) Discard() { + p.mu.Lock() + defer p.mu.Unlock() + if p.discarded { + return // already discarded } - return true -} - -func (p *CheckerPool) createCheckerLocked() (*checker.Checker, int) { - for i, existing := range p.checkers { - if existing == nil { - checker, _ := checker.NewChecker(p.program, nil) - p.checkers[i] = checker - return checker, i - } + p.log("checkerpool: Discarding pool, stopping idle cleanup") + p.discarded = true + if p.cleanupTimer != nil { + p.cleanupTimer.Stop() + p.cleanupTimer = nil } - panic("called createCheckerLocked when pool is full") } func noop() {} diff --git a/internal/project/checkerpool_test.go b/internal/project/checkerpool_test.go new file mode 100644 index 00000000000..01cb9c164a6 --- /dev/null +++ b/internal/project/checkerpool_test.go @@ -0,0 +1,1295 @@ +package project + +import ( + "context" + "sync/atomic" + "testing" + "testing/synctest" + "time" + + "github.com/microsoft/typescript-go/internal/bundled" + "github.com/microsoft/typescript-go/internal/checker" + "github.com/microsoft/typescript-go/internal/compiler" + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/lsp/lsproto" + "github.com/microsoft/typescript-go/internal/project/logging" + "github.com/microsoft/typescript-go/internal/vfs/vfstest" + "gotest.tools/v3/assert" +) + +func setupCheckerPoolSession(t *testing.T, opts CheckerPoolOptions) (*Session, *checkerPool) { + t.Helper() + if !bundled.Embedded { + t.Skip("bundled files are not embedded") + } + + files := map[string]any{ + "/src/tsconfig.json": `{ "compilerOptions": { "noLib": true } }`, + "/src/index.ts": "export const x: number = 1;", + } + fs := bundled.WrapFS(vfstest.FromMap(files, false)) + session := NewSession(&SessionInit{ + BackgroundCtx: context.Background(), + Options: &SessionOptions{ + CurrentDirectory: "/", + DefaultLibraryPath: bundled.LibPath(), + PositionEncoding: lsproto.PositionEncodingKindUTF8, + WatchEnabled: false, + LoggingEnabled: true, + CheckerPoolOptions: opts, + }, + FS: fs, + Logger: logging.NewTestLogger(), + }) + session.DidOpenFile(context.Background(), "file:///src/index.ts", 1, "export const x: number = 1;", lsproto.LanguageKindTypeScript) + + snapshot := session.Snapshot() + project := snapshot.ProjectCollection.ConfiguredProject("/src/tsconfig.json") + assert.Assert(t, project != nil, "expected configured project") + assert.Assert(t, project.checkerPool != nil, "expected checker pool") + return session, project.checkerPool +} + +// newTestCheckerPool creates a checker pool inside the current goroutine context +// (suitable for use inside synctest.Test) using the given program. +func newTestCheckerPool(program *compiler.Program, opts CheckerPoolOptions) *checkerPool { + return newCheckerPool(opts, program, func(string) {}) +} + +func TestCheckerPoolDiagnosticsRouting(t *testing.T) { + t.Parallel() + _, pool := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + + // Diagnostics requests should get checker at index 0. + ctx := core.WithRequestID(context.Background(), "diag-req-1") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) + c, release := pool.GetChecker(ctx, nil) + assert.Assert(t, c != nil) + assert.Assert(t, pool.checkers[0] == c, "diagnostics should use checker index 0") + release() +} + +func TestCheckerPoolQueryRouting(t *testing.T) { + t.Parallel() + _, pool := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + + // Query requests should get a checker at index > 0. + ctx := core.WithRequestID(context.Background(), "query-req-1") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeTemporary) + c, release := pool.GetChecker(ctx, nil) + assert.Assert(t, c != nil) + + // Verify it's not the diagnostics checker slot. + assert.Assert(t, pool.checkers[0] != c, "query should not use checker index 0") + release() +} + +func TestCheckerPoolRequestAffinity(t *testing.T) { + t.Parallel() + _, pool := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + ctx = core.WithRequestID(ctx, "req-affinity") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeTemporary) + + // First call acquires. + c1, release1 := pool.GetChecker(ctx, nil) + + // Second call with same request ID while still held returns same checker (noop release). + c2, release2 := pool.GetChecker(ctx, nil) + release2() + release1() + + assert.Assert(t, c1 == c2, "same request ID should return the same checker while held") + + // After release, same request should still get the same checker (cross-release affinity). + c3, release3 := pool.GetChecker(ctx, nil) + release3() + + assert.Assert(t, c1 == c3, "same request ID should return the same checker after release") +} + +func TestCheckerPoolIdleCleanup(t *testing.T) { + t.Parallel() + // Get a real program to use for checker creation, then test the pool + // with fake time via synctest. + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 5 * time.Second}) + + // Create a checker via a diagnostics request. + ctx := core.WithRequestID(context.Background(), "diag-cleanup") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) + c, release := pool.GetChecker(ctx, nil) + assert.Assert(t, c != nil) + release() + synctest.Wait() + + // Create a query checker as well. + ctx2 := core.WithRequestID(context.Background(), "query-cleanup") + ctx2 = core.WithCheckerLifetime(ctx2, core.CheckerLifetimeTemporary) + c2, release2 := pool.GetChecker(ctx2, nil) + assert.Assert(t, c2 != nil) + release2() + synctest.Wait() + + // Both checkers should exist. + pool.mu.Lock() + assert.Assert(t, pool.checkers[0] != nil, "diagnostics checker should exist") + var queryIdx int + for i := 1; i < len(pool.checkers); i++ { + if pool.checkers[i] != nil { + queryIdx = i + break + } + } + assert.Assert(t, queryIdx > 0, "query checker should exist") + pool.mu.Unlock() + + // Advance past idle timeout. + time.Sleep(5 * time.Second) + synctest.Wait() + + // After cleanup, both checkers should be disposed. + pool.mu.Lock() + assert.Assert(t, pool.checkers[0] == nil, "diagnostics checker should be disposed after idle timeout") + assert.Assert(t, pool.checkers[queryIdx] == nil, "query checker should be disposed after idle timeout") + pool.mu.Unlock() + }) +} + +func TestCheckerPoolFileAssociationCleanup(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + sourceFile := program.GetSourceFile("/src/index.ts") + assert.Assert(t, sourceFile != nil) + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 5 * time.Second}) + + // Create a query checker with file affinity. + ctx := core.WithRequestID(context.Background(), "file-assoc-req") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeTemporary) + c, release := pool.GetChecker(ctx, sourceFile) + assert.Assert(t, c != nil) + release() + synctest.Wait() + + // File association should exist. + pool.mu.Lock() + _, hasAssoc := pool.fileAssociations[sourceFile] + pool.mu.Unlock() + assert.Assert(t, hasAssoc, "file should have a checker association") + + // Advance past idle timeout. + time.Sleep(5 * time.Second) + synctest.Wait() + + // File association should be cleared. + pool.mu.Lock() + _, hasAssoc = pool.fileAssociations[sourceFile] + pool.mu.Unlock() + assert.Assert(t, !hasAssoc, "file association should be cleared after checker disposal") + }) +} + +func TestCheckerPoolMinCheckers(t *testing.T) { + t.Parallel() + // Requesting maxCheckers=1 should be clamped to 2. + _, pool := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 1, IdleTimeout: 10 * time.Second}) + assert.Equal(t, pool.opts.MaxCheckers, 2) + assert.Equal(t, len(pool.checkers), 2) +} + +func TestCheckerPoolDefaultIdleTimeout(t *testing.T) { + t.Parallel() + // Zero idle timeout should default to 30s. + _, pool := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4}) + assert.Equal(t, pool.opts.IdleTimeout, 30*time.Second) +} + +func TestCheckerPoolQueryContention(t *testing.T) { + t.Parallel() + // maxCheckers=2 means 1 diagnostics + 1 query checker slot. + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 30 * time.Second}) + + // Acquire the only query checker slot. + ctx1 := core.WithRequestID(context.Background(), "query-hold") + ctx1 = core.WithCheckerLifetime(ctx1, core.CheckerLifetimeTemporary) + c1, release1 := pool.GetChecker(ctx1, nil) + assert.Assert(t, c1 != nil) + + // A second query request from a different request ID should block. + var c2Got atomic.Bool + go func() { + ctx2 := core.WithRequestID(context.Background(), "query-wait") + ctx2 = core.WithCheckerLifetime(ctx2, core.CheckerLifetimeTemporary) + c2, release2 := pool.GetChecker(ctx2, nil) + _ = c2 // verified via c2Got flag + c2Got.Store(c2 != nil) + release2() + }() + + // Wait for goroutine to reach the semaphore send. + synctest.Wait() + assert.Assert(t, !c2Got.Load(), "second query should be blocked while first holds the checker") + + // Release the first checker — second should unblock. + release1() + synctest.Wait() + assert.Assert(t, c2Got.Load(), "second query should have acquired the checker after release") + }) +} + +func TestCheckerPoolDiagnosticsContention(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 30 * time.Second}) + + // Acquire the diagnostics checker. + ctx1 := core.WithRequestID(context.Background(), "diag-hold") + ctx1 = core.WithCheckerLifetime(ctx1, core.CheckerLifetimeDiagnostics) + c1, release1 := pool.GetChecker(ctx1, nil) + assert.Assert(t, c1 != nil) + + // A second diagnostics request should block since there's only one diag checker. + var c2Got atomic.Bool + go func() { + ctx2 := core.WithRequestID(context.Background(), "diag-wait") + ctx2 = core.WithCheckerLifetime(ctx2, core.CheckerLifetimeDiagnostics) + c2, release2 := pool.GetChecker(ctx2, nil) + _ = c2 // verified via c2Got flag + c2Got.Store(c2 != nil) + release2() + }() + + synctest.Wait() + assert.Assert(t, !c2Got.Load(), "second diagnostics request should be blocked") + + // A query request should NOT be blocked (separate slot). + ctx3 := core.WithRequestID(context.Background(), "query-concurrent") + ctx3 = core.WithCheckerLifetime(ctx3, core.CheckerLifetimeTemporary) + c3, release3 := pool.GetChecker(ctx3, nil) + assert.Assert(t, c3 != nil) + assert.Assert(t, c3 != c1, "query checker should be different from diagnostics checker") + release3() + + // Release the diagnostics checker — second diag request should unblock. + release1() + synctest.Wait() + assert.Assert(t, c2Got.Load(), "second diagnostics request should have acquired the checker after release") + }) +} + +func TestCheckerPoolCanceledCheckerDisposal(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + sourceFile := program.GetSourceFile("/src/index.ts") + assert.Assert(t, sourceFile != nil) + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + // Acquire a query checker and cancel it. + ctx := core.WithRequestID(context.Background(), "cancel-test") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeTemporary) + c, release := pool.GetChecker(ctx, nil) + assert.Assert(t, c != nil) + + canceledCtx, cancel := context.WithCancel(context.Background()) + cancel() + c.GetDiagnostics(canceledCtx, sourceFile) + assert.Assert(t, c.WasCanceled()) + + // Release should dispose the canceled checker. + release() + synctest.Wait() + + // Next request should get a fresh checker. + ctx2 := core.WithRequestID(context.Background(), "after-cancel") + ctx2 = core.WithCheckerLifetime(ctx2, core.CheckerLifetimeTemporary) + c2, release2 := pool.GetChecker(ctx2, nil) + assert.Assert(t, c2 != c, "should get a new checker, not the canceled one") + release2() + }) +} + +func TestCheckerPoolRequestAssociationCleanupOnDisposal(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 5 * time.Second}) + + // Create a query checker with a request association. + reqCtx, reqCancel := context.WithCancel(context.Background()) + defer reqCancel() + ctx := core.WithRequestID(reqCtx, "assoc-cleanup-req") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeTemporary) + c, release := pool.GetChecker(ctx, nil) + assert.Assert(t, c != nil) + + // Cancel the checker to trigger disposal on release. + canceledCtx, cancel := context.WithCancel(context.Background()) + cancel() + sourceFile := program.GetSourceFile("/src/index.ts") + c.GetDiagnostics(canceledCtx, sourceFile) + assert.Assert(t, c.WasCanceled()) + + release() + synctest.Wait() + + // Request association should be cleared after checker disposal. + pool.mu.Lock() + _, hasReqAssoc := pool.requestAssociations["assoc-cleanup-req"] + pool.mu.Unlock() + assert.Assert(t, !hasReqAssoc, "request association should be cleared after checker disposal") + }) +} + +func TestCheckerPoolRequestAssociationCleanupOnContextDone(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + // Create a cancellable context to simulate request lifecycle. + reqCtx, reqCancel := context.WithCancel(context.Background()) + ctx := core.WithRequestID(reqCtx, "ctx-cleanup-req") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeTemporary) + + c, release := pool.GetChecker(ctx, nil) + assert.Assert(t, c != nil) + release() + synctest.Wait() + + // Association should still exist after release. + pool.mu.Lock() + _, hasAssoc := pool.requestAssociations["ctx-cleanup-req"] + pool.mu.Unlock() + assert.Assert(t, hasAssoc, "request association should persist after release") + + // Cancel the request context — association should be cleaned up. + reqCancel() + synctest.Wait() + + pool.mu.Lock() + _, hasAssoc = pool.requestAssociations["ctx-cleanup-req"] + pool.mu.Unlock() + assert.Assert(t, !hasAssoc, "request association should be cleaned up after context cancellation") + }) +} + +func TestCheckerPoolDiagnosticsRecreatedAfterIdleDisposal(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 5 * time.Second}) + + // Create and release diagnostics checker. + ctx := core.WithRequestID(context.Background(), "diag-recreate-1") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) + c1, release1 := pool.GetChecker(ctx, nil) + assert.Assert(t, c1 != nil) + release1() + synctest.Wait() + + // Advance past idle timeout — diagnostics checker should be disposed. + time.Sleep(5 * time.Second) + synctest.Wait() + + pool.mu.Lock() + assert.Assert(t, pool.checkers[0] == nil, "diagnostics checker should be disposed") + pool.mu.Unlock() + + // Request diagnostics checker again — should get a fresh one. + ctx2 := core.WithRequestID(context.Background(), "diag-recreate-2") + ctx2 = core.WithCheckerLifetime(ctx2, core.CheckerLifetimeDiagnostics) + c2, release2 := pool.GetChecker(ctx2, nil) + assert.Assert(t, c2 != nil, "diagnostics checker should be re-created") + assert.Assert(t, c2 != c1, "should be a new checker instance") + release2() + }) +} + +func TestCheckerPoolCrossReleaseAffinityWithContention(t *testing.T) { + t.Parallel() + // maxCheckers=2: 1 diagnostics + 1 query slot. + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 30 * time.Second}) + + reqCtx, reqCancel := context.WithCancel(context.Background()) + defer reqCancel() + + // Request A acquires the only query slot. + ctxA := core.WithRequestID(reqCtx, "req-A") + ctxA = core.WithCheckerLifetime(ctxA, core.CheckerLifetimeTemporary) + cA, releaseA := pool.GetChecker(ctxA, nil) + assert.Assert(t, cA != nil) + releaseA() + synctest.Wait() + + // Request B takes the query slot while A is released. + ctxB := core.WithRequestID(context.Background(), "req-B") + ctxB = core.WithCheckerLifetime(ctxB, core.CheckerLifetimeTemporary) + cB, releaseB := pool.GetChecker(ctxB, nil) + assert.Assert(t, cB != nil) + + // Request A reacquires — should block because B holds the slot. + var reacquired atomic.Bool + cA2Ch := make(chan *checker.Checker, 1) + go func() { + c, release := pool.GetChecker(ctxA, nil) + cA2Ch <- c + reacquired.Store(true) + release() + }() + + synctest.Wait() + assert.Assert(t, !reacquired.Load(), "request A should block while B holds the slot") + + // Release B — A should unblock and get the same checker. + releaseB() + synctest.Wait() + assert.Assert(t, reacquired.Load(), "request A should unblock after B releases") + select { + case cA2 := <-cA2Ch: + assert.Assert(t, cA2 == cA, "request A should get the same checker on reacquire") + case <-t.Context().Done(): + t.Fatal("timed out waiting for reacquired checker") + } + }) +} + +func TestCheckerPoolLifetimeMismatchIgnoresAssociation(t *testing.T) { + t.Parallel() + // Verify that if a request first uses a diagnostics checker, then switches + // to a temporary lifetime (or vice versa), the stale association is ignored + // rather than returning a checker from the wrong category. + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + reqCtx, reqCancel := context.WithCancel(context.Background()) + defer reqCancel() + + // Acquire a diagnostics checker with request ID "mixed". + ctxDiag := core.WithRequestID(reqCtx, "mixed") + ctxDiag = core.WithCheckerLifetime(ctxDiag, core.CheckerLifetimeDiagnostics) + cDiag, releaseDiag := pool.GetChecker(ctxDiag, nil) + assert.Assert(t, cDiag != nil) + assert.Assert(t, pool.checkers[0] == cDiag, "diagnostics checker should be at index 0") + releaseDiag() + synctest.Wait() + + // Now use the same request ID but with query purpose. + // The old association points to index 0 (diagnostics), which should + // be rejected — the returned checker must be a query checker (index > 0). + ctxQuery := core.WithRequestID(reqCtx, "mixed") + ctxQuery = core.WithCheckerLifetime(ctxQuery, core.CheckerLifetimeTemporary) + cQuery, releaseQuery := pool.GetChecker(ctxQuery, nil) + assert.Assert(t, cQuery != nil) + assert.Assert(t, cQuery != cDiag, "query should not reuse the diagnostics checker") + + pool.mu.Lock() + assert.Assert(t, pool.checkers[0] != cQuery, "query checker should not be at diagnostics index 0") + pool.mu.Unlock() + releaseQuery() + }) +} + +func TestCheckerPoolNoRequestID(t *testing.T) { + t.Parallel() + _, pool := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + + // Calls without a request ID should still work (e.g., callhierarchy uses context.Background()). + ctx := context.Background() + + c1, release1 := pool.GetChecker(ctx, nil) + assert.Assert(t, c1 != nil) + release1() + + c2, release2 := pool.GetChecker(ctx, nil) + assert.Assert(t, c2 != nil) + release2() + + // Without request ID, no affinity guarantee — just verify it doesn't crash. +} + +func TestCheckerPoolDiagnosticsCrossReleaseAffinity(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + reqCtx, reqCancel := context.WithCancel(context.Background()) + defer reqCancel() + ctx := core.WithRequestID(reqCtx, "diag-affinity") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) + + c1, release1 := pool.GetChecker(ctx, nil) + assert.Assert(t, c1 != nil) + assert.Assert(t, pool.checkers[0] == c1, "should be the diagnostics checker") + release1() + synctest.Wait() + + // Same request reacquiring diagnostics should get the same checker. + c2, release2 := pool.GetChecker(ctx, nil) + assert.Assert(t, c2 == c1, "same diagnostics request should get the same checker after release") + release2() + }) +} + +func TestCheckerPoolDiscardKeepsIdleCheckers(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + // Create both a diagnostics and a query checker. + ctx1 := core.WithRequestID(context.Background(), "obs-diag") + ctx1 = core.WithCheckerLifetime(ctx1, core.CheckerLifetimeDiagnostics) + c1, release1 := pool.GetChecker(ctx1, nil) + assert.Assert(t, c1 != nil) + release1() + synctest.Wait() + + ctx2 := core.WithRequestID(context.Background(), "obs-query") + ctx2 = core.WithCheckerLifetime(ctx2, core.CheckerLifetimeTemporary) + c2, release2 := pool.GetChecker(ctx2, nil) + assert.Assert(t, c2 != nil) + release2() + synctest.Wait() + + // Both checkers should exist before Discard. + pool.mu.Lock() + assert.Assert(t, pool.checkers[0] != nil, "diagnostics checker should exist") + pool.mu.Unlock() + + // Discard should keep idle checkers alive (they may be referenced by + // API type handles) and just stop the cleanup timer. + pool.Discard() + + pool.mu.Lock() + assert.Assert(t, pool.checkers[0] == c1, "diagnostics checker should survive Discard") + hasQuery := false + for i := 1; i < len(pool.checkers); i++ { + if pool.checkers[i] == c2 { + hasQuery = true + break + } + } + assert.Assert(t, hasQuery, "query checker should survive Discard") + assert.Assert(t, pool.cleanupTimer == nil, "cleanup timer should be stopped after Discard") + pool.mu.Unlock() + + // Even after a long wait, checkers should not be disposed (no timer running). + time.Sleep(60 * time.Second) + synctest.Wait() + + pool.mu.Lock() + assert.Assert(t, pool.checkers[0] == c1, "diagnostics checker should persist indefinitely on discarded pool") + pool.mu.Unlock() + }) +} + +func TestCheckerPoolDiscardHeldCheckerSurvivesRelease(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + // Acquire a checker and hold it. + ctx := core.WithRequestID(context.Background(), "held-obs") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeTemporary) + c, release := pool.GetChecker(ctx, nil) + assert.Assert(t, c != nil) + + // Find which slot it's in. + pool.mu.Lock() + var heldIndex int + for i := 1; i < len(pool.checkers); i++ { + if pool.checkers[i] == c { + heldIndex = i + break + } + } + pool.mu.Unlock() + assert.Assert(t, heldIndex > 0, "should find the held checker") + + // Discard while checker is held — should NOT dispose it. + pool.Discard() + + pool.mu.Lock() + assert.Assert(t, pool.checkers[heldIndex] == c, "held checker should survive Discard") + pool.mu.Unlock() + + // Release — checker should remain alive on a discarded pool. + release() + synctest.Wait() + + pool.mu.Lock() + assert.Assert(t, pool.checkers[heldIndex] == c, "checker should persist after release on discarded pool") + pool.mu.Unlock() + + // Even after a long wait, checker persists (no cleanup timer running). + time.Sleep(60 * time.Second) + synctest.Wait() + + pool.mu.Lock() + assert.Assert(t, pool.checkers[heldIndex] == c, "checker should persist indefinitely on discarded pool") + pool.mu.Unlock() + }) +} + +func TestCheckerPoolDiscardStillFunctional(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + pool.Discard() + + // Pool should still work — GetChecker should create a fresh checker. + ctx := core.WithRequestID(context.Background(), "post-obs") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeTemporary) + c, release := pool.GetChecker(ctx, nil) + assert.Assert(t, c != nil, "should still create checkers after Discard") + + // Find the slot. + pool.mu.Lock() + var idx int + for i := 1; i < len(pool.checkers); i++ { + if pool.checkers[i] == c { + idx = i + break + } + } + pool.mu.Unlock() + assert.Assert(t, idx > 0, "checker should be in a query slot") + + // Release — checker should persist on discarded pool (no cleanup timer). + release() + synctest.Wait() + + pool.mu.Lock() + assert.Assert(t, pool.checkers[idx] == c, "checker should persist after release on discarded pool") + pool.mu.Unlock() + + // Re-acquire — should get the same checker back. + ctx2 := core.WithRequestID(context.Background(), "post-obs-2") + ctx2 = core.WithCheckerLifetime(ctx2, core.CheckerLifetimeTemporary) + c2, release2 := pool.GetChecker(ctx2, nil) + assert.Assert(t, c2 == c, "should get the same checker on discarded pool") + release2() + }) +} + +func TestCheckerPoolDiagnosticsCheckerStableIdentity(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + // Acquire the diagnostics checker. + ctx := core.WithRequestID(context.Background(), "diag-stable-1") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) + c1, release1 := pool.GetChecker(ctx, nil) + assert.Assert(t, c1 != nil, "diagnostics checker should be created") + release1() + synctest.Wait() + + // Re-acquire before idle timeout — should be the same instance. + ctx2 := core.WithRequestID(context.Background(), "diag-stable-2") + ctx2 = core.WithCheckerLifetime(ctx2, core.CheckerLifetimeDiagnostics) + c2, release2 := pool.GetChecker(ctx2, nil) + assert.Assert(t, c2 == c1, "diagnostics checker should be the same instance before idle timeout") + release2() + }) +} + +func TestCheckerPoolDiagnosticsCheckerSurvivesDiscard(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + // Create the diagnostics checker. + ctx := core.WithRequestID(context.Background(), "diag-discard") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeDiagnostics) + c, release := pool.GetChecker(ctx, nil) + assert.Assert(t, c != nil) + release() + synctest.Wait() + + pool.Discard() + + // Diagnostics checker should survive Discard. + pool.mu.Lock() + assert.Assert(t, pool.checkers[0] == c, "diagnostics checker should survive Discard") + pool.mu.Unlock() + + // Should still be acquirable and be the same instance. + ctx2 := core.WithRequestID(context.Background(), "diag-discard-2") + ctx2 = core.WithCheckerLifetime(ctx2, core.CheckerLifetimeDiagnostics) + c2, release2 := pool.GetChecker(ctx2, nil) + assert.Assert(t, c2 == c, "diagnostics checker identity should be stable after Discard") + release2() + }) +} + +func TestCheckerPoolDiagnosticsCheckerIndependentFromQuery(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + // Acquire diagnostics and query checkers. + diagCtx := core.WithRequestID(context.Background(), "diag-indep") + diagCtx = core.WithCheckerLifetime(diagCtx, core.CheckerLifetimeDiagnostics) + queryCtx := core.WithRequestID(context.Background(), "query-indep") + queryCtx = core.WithCheckerLifetime(queryCtx, core.CheckerLifetimeTemporary) + + diagC, diagRelease := pool.GetChecker(diagCtx, nil) + queryC, queryRelease := pool.GetChecker(queryCtx, nil) + + // They should be different checker instances. + assert.Assert(t, diagC != queryC, "diagnostics and query checkers should be different") + + diagRelease() + queryRelease() + }) +} + +func TestCheckerPoolAPICheckerStableIdentity(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + ctx := core.WithCheckerLifetime(context.Background(), core.CheckerLifetimeAPI) + c1, release1 := pool.GetChecker(ctx, nil) + assert.Assert(t, c1 != nil) + release1() + + c2, release2 := pool.GetChecker(ctx, nil) + assert.Assert(t, c2 == c1, "API checker should be the same instance") + release2() + + // Should survive idle timeout. + time.Sleep(60 * time.Second) + synctest.Wait() + + c3, release3 := pool.GetChecker(ctx, nil) + assert.Assert(t, c3 == c1, "API checker should survive idle timeout") + release3() + }) +} + +func TestCheckerPoolAPICheckerSurvivesDiscard(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + ctx := core.WithCheckerLifetime(context.Background(), core.CheckerLifetimeAPI) + c, release := pool.GetChecker(ctx, nil) + assert.Assert(t, c != nil) + release() + + pool.Discard() + + pool.mu.Lock() + assert.Assert(t, pool.persistentChecker == c, "API checker should survive Discard") + pool.mu.Unlock() + + c2, release2 := pool.GetChecker(ctx, nil) + assert.Assert(t, c2 == c, "API checker identity should be stable after Discard") + release2() + }) +} + +func TestCheckerPoolAllThreeIndependent(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + dedCtx := core.WithRequestID(context.Background(), "ded-req") + dedCtx = core.WithCheckerLifetime(dedCtx, core.CheckerLifetimeDiagnostics) + tmpCtx := core.WithRequestID(context.Background(), "tmp-req") + tmpCtx = core.WithCheckerLifetime(tmpCtx, core.CheckerLifetimeTemporary) + perCtx := core.WithCheckerLifetime(context.Background(), core.CheckerLifetimeAPI) + + dedC, dedRelease := pool.GetChecker(dedCtx, nil) + tmpC, tmpRelease := pool.GetChecker(tmpCtx, nil) + perC, perRelease := pool.GetChecker(perCtx, nil) + + assert.Assert(t, dedC != tmpC, "diagnostics and temporary should be different") + assert.Assert(t, dedC != perC, "diagnostics and API should be different") + assert.Assert(t, tmpC != perC, "temporary and API should be different") + + dedRelease() + tmpRelease() + perRelease() + }) +} + +func TestCheckerPoolFileAffinity(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + sourceFile := program.GetSourceFile("/src/index.ts") + assert.Assert(t, sourceFile != nil) + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + // First query with a file should create a checker and associate it. + ctx1 := core.WithRequestID(context.Background(), "file-aff-1") + ctx1 = core.WithCheckerLifetime(ctx1, core.CheckerLifetimeTemporary) + c1, release1 := pool.GetChecker(ctx1, sourceFile) + assert.Assert(t, c1 != nil) + release1() + synctest.Wait() + + // Second query with the same file (different request) should get the same checker via file affinity. + ctx2 := core.WithRequestID(context.Background(), "file-aff-2") + ctx2 = core.WithCheckerLifetime(ctx2, core.CheckerLifetimeTemporary) + c2, release2 := pool.GetChecker(ctx2, sourceFile) + assert.Assert(t, c2 != nil) + assert.Assert(t, c2 == c1, "same file should return the same checker via file affinity") + release2() + }) +} + +func TestCheckerPoolMultipleConcurrentQueryCheckers(t *testing.T) { + t.Parallel() + // maxCheckers=4: 1 diagnostics + 3 query slots. + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + // Acquire 3 query checkers concurrently (all slots). + ctx1 := core.WithRequestID(context.Background(), "multi-q-1") + ctx1 = core.WithCheckerLifetime(ctx1, core.CheckerLifetimeTemporary) + c1, release1 := pool.GetChecker(ctx1, nil) + assert.Assert(t, c1 != nil) + + ctx2 := core.WithRequestID(context.Background(), "multi-q-2") + ctx2 = core.WithCheckerLifetime(ctx2, core.CheckerLifetimeTemporary) + c2, release2 := pool.GetChecker(ctx2, nil) + assert.Assert(t, c2 != nil) + + ctx3 := core.WithRequestID(context.Background(), "multi-q-3") + ctx3 = core.WithCheckerLifetime(ctx3, core.CheckerLifetimeTemporary) + c3, release3 := pool.GetChecker(ctx3, nil) + assert.Assert(t, c3 != nil) + + // All three should be distinct checkers. + assert.Assert(t, c1 != c2, "concurrent query checkers should be distinct (1 vs 2)") + assert.Assert(t, c1 != c3, "concurrent query checkers should be distinct (1 vs 3)") + assert.Assert(t, c2 != c3, "concurrent query checkers should be distinct (2 vs 3)") + + // None should be the diagnostics checker at index 0. + pool.mu.Lock() + assert.Assert(t, pool.checkers[0] != c1 && pool.checkers[0] != c2 && pool.checkers[0] != c3, + "query checkers should not occupy the diagnostics slot") + pool.mu.Unlock() + + // A 4th query request should block since all 3 slots are full. + var c4Got atomic.Bool + go func() { + ctx4 := core.WithRequestID(context.Background(), "multi-q-4") + ctx4 = core.WithCheckerLifetime(ctx4, core.CheckerLifetimeTemporary) + c4, release4 := pool.GetChecker(ctx4, nil) + _ = c4 // verified via c4Got flag + c4Got.Store(c4 != nil) + release4() + }() + + synctest.Wait() + assert.Assert(t, !c4Got.Load(), "4th query should block when all 3 query slots are held") + + release1() + synctest.Wait() + assert.Assert(t, c4Got.Load(), "4th query should unblock after one slot is released") + + release2() + release3() + }) +} + +func TestCheckerPoolDoubleReleaseSafe(t *testing.T) { + t.Parallel() + _, pool := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + + ctx := core.WithRequestID(context.Background(), "double-release") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeTemporary) + c, release := pool.GetChecker(ctx, nil) + assert.Assert(t, c != nil) + + // First release should work normally. + release() + // Second release should be a no-op (sync.OnceFunc). + release() + + // Pool should still be functional after double release. + ctx2 := core.WithRequestID(context.Background(), "after-double") + ctx2 = core.WithCheckerLifetime(ctx2, core.CheckerLifetimeTemporary) + c2, release2 := pool.GetChecker(ctx2, nil) + assert.Assert(t, c2 != nil) + release2() +} + +func TestCheckerPoolDefaultMaxCheckers(t *testing.T) { + t.Parallel() + // Zero MaxCheckers should default to 4. + _, pool := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 0, IdleTimeout: 10 * time.Second}) + assert.Equal(t, pool.opts.MaxCheckers, 4) + assert.Equal(t, len(pool.checkers), 4) + assert.Equal(t, cap(pool.querySem), 3, "querySem capacity should be MaxCheckers-1") +} + +func TestCheckerPoolStaggeredIdleCleanup(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + + // Acquire checker A and hold it. + ctxA := core.WithRequestID(context.Background(), "stagger-A") + ctxA = core.WithCheckerLifetime(ctxA, core.CheckerLifetimeTemporary) + cA, releaseA := pool.GetChecker(ctxA, nil) + assert.Assert(t, cA != nil) + + // While A is held, acquire a second checker B. + ctxB := core.WithRequestID(context.Background(), "stagger-B") + ctxB = core.WithCheckerLifetime(ctxB, core.CheckerLifetimeTemporary) + cB, releaseB := pool.GetChecker(ctxB, nil) + assert.Assert(t, cB != nil) + assert.Assert(t, cB != cA, "B should be a different checker since A is held") + + // Find their indices. + pool.mu.Lock() + var idxA, idxB int + for i := 1; i < len(pool.checkers); i++ { + if pool.checkers[i] == cA { + idxA = i + } + if pool.checkers[i] == cB { + idxB = i + } + } + pool.mu.Unlock() + assert.Assert(t, idxA > 0) + assert.Assert(t, idxB > 0) + + // Release A first. Timer is set for t=10. + releaseA() + synctest.Wait() + + // Release B 6 seconds later. Timer is reset for t=16. + time.Sleep(6 * time.Second) + releaseB() + synctest.Wait() + + // At t < 16, both should still exist (timer hasn't fired). + pool.mu.Lock() + assert.Assert(t, pool.checkers[idxA] != nil, "checker A should still exist before timer fires") + assert.Assert(t, pool.checkers[idxB] != nil, "checker B should still exist before timer fires") + pool.mu.Unlock() + + // Advance past t=16 (when the timer fires). Both should be disposed + // because A has been idle 16s and B has been idle 10s. + time.Sleep(11 * time.Second) + synctest.Wait() + + pool.mu.Lock() + assert.Assert(t, pool.checkers[idxA] == nil, "checker A should be disposed after timer fires") + assert.Assert(t, pool.checkers[idxB] == nil, "checker B should be disposed after timer fires") + pool.mu.Unlock() + }) +} + +func TestCheckerPoolDiscardIdempotent(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 2, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + // Create a checker so there's something to discard. + ctx := core.WithRequestID(context.Background(), "idem-q") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeTemporary) + c, release := pool.GetChecker(ctx, nil) + assert.Assert(t, c != nil) + release() + synctest.Wait() + + // First discard should keep idle checkers alive. + pool.Discard() + pool.mu.Lock() + hasChecker := false + for _, c := range pool.checkers { + if c != nil { + hasChecker = true + break + } + } + pool.mu.Unlock() + assert.Assert(t, hasChecker, "first Discard should keep idle checkers alive") + + // Second discard should be a no-op (no panic, no state corruption). + pool.Discard() + + // Pool should still be functional after double Discard. + ctx2 := core.WithRequestID(context.Background(), "post-idem") + ctx2 = core.WithCheckerLifetime(ctx2, core.CheckerLifetimeTemporary) + c2, release2 := pool.GetChecker(ctx2, nil) + assert.Assert(t, c2 != nil, "pool should still work after double Discard") + release2() + }) +} + +func TestCheckerPoolGetGlobalDiagnosticsEmpty(t *testing.T) { + t.Parallel() + _, pool := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + + // Before any checker is used, global diagnostics should be empty. + diags := pool.GetGlobalDiagnostics() + assert.Equal(t, len(diags), 0, "global diagnostics should be empty initially") +} + +func TestCheckerPoolTakeNewGlobalDiagnostics(t *testing.T) { + t.Parallel() + _, pool := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + + // Initially, no new globals. + assert.Assert(t, !pool.TakeNewGlobalDiagnostics(), "should report no new globals initially") + + // Use a checker and trigger diagnostics, then release to run the merge. + ctx := core.WithRequestID(context.Background(), "global-diag-req") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeTemporary) + sourceFile := pool.program.GetSourceFile("/src/index.ts") + c, release := pool.GetChecker(ctx, sourceFile) + assert.Assert(t, c != nil) + c.GetDiagnostics(ctx, sourceFile) + release() + + // Whether globals were produced depends on the program, but the flag + // should reflect the merge result. + firstTake := pool.TakeNewGlobalDiagnostics() + + // After taking, a second call should always return false (flag is reset). + assert.Assert(t, !pool.TakeNewGlobalDiagnostics(), "TakeNewGlobalDiagnostics should reset after first call") + + // Releasing the same checker again with the same state should not set the flag. + ctx2 := core.WithRequestID(context.Background(), "global-diag-req-2") + ctx2 = core.WithCheckerLifetime(ctx2, core.CheckerLifetimeTemporary) + c2, release2 := pool.GetChecker(ctx2, sourceFile) + assert.Assert(t, c2 != nil) + c2.GetDiagnostics(ctx2, sourceFile) + release2() + + // If first call produced globals, the count is now stable, so no new change. + // If first call produced no globals, still no change. + _ = firstTake + assert.Assert(t, !pool.TakeNewGlobalDiagnostics(), "should not report new globals when checker state is unchanged") +} + +func TestCheckerPoolAPICheckerDisposedOnCancel(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + sourceFile := program.GetSourceFile("/src/index.ts") + assert.Assert(t, sourceFile != nil) + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + ctx := core.WithCheckerLifetime(context.Background(), core.CheckerLifetimeAPI) + c, release := pool.GetChecker(ctx, nil) + assert.Assert(t, c != nil) + + // Cancel the API checker. + canceledCtx, cancel := context.WithCancel(context.Background()) + cancel() + c.GetDiagnostics(canceledCtx, sourceFile) + assert.Assert(t, c.WasCanceled()) + + // Releasing a canceled API checker must drop it so it isn't reused. + release() + synctest.Wait() + + pool.mu.Lock() + assert.Assert(t, pool.persistentChecker == nil, "canceled API checker should be dropped on release") + pool.mu.Unlock() + + // Next API acquisition gets a fresh, usable checker rather than panicking. + c2, release2 := pool.GetChecker(ctx, nil) + assert.Assert(t, c2 != c, "should get a fresh API checker after cancellation") + release2() + }) +} + +func TestCheckerPoolNonCancelableContextNoAffinity(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + // A context that carries a request ID but can never be canceled + // (ctx.Done() == nil) must not register a request association, since + // there would be no way to clean it up. + ctx := core.WithRequestID(context.Background(), "uncancelable-req") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeTemporary) + assert.Assert(t, ctx.Done() == nil, "test precondition: context must be non-cancelable") + + c, release := pool.GetChecker(ctx, nil) + assert.Assert(t, c != nil) + release() + synctest.Wait() + + pool.mu.Lock() + assert.Equal(t, len(pool.requestAssociations), 0, "non-cancelable context must not grow requestAssociations") + pool.mu.Unlock() + }) +} + +func TestCheckerPoolCleanupAfterDiscardIsNoop(t *testing.T) { + t.Parallel() + session, _ := setupCheckerPoolSession(t, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 10 * time.Second}) + ls, err := session.GetLanguageService(context.Background(), "file:///src/index.ts") + assert.NilError(t, err) + program := ls.GetProgram() + + synctest.Test(t, func(t *testing.T) { + pool := newTestCheckerPool(program, CheckerPoolOptions{MaxCheckers: 4, IdleTimeout: 30 * time.Second}) + + ctx := core.WithRequestID(context.Background(), "discard-cleanup") + ctx = core.WithCheckerLifetime(ctx, core.CheckerLifetimeTemporary) + c, release := pool.GetChecker(ctx, nil) + assert.Assert(t, c != nil) + release() + synctest.Wait() + + pool.Discard() + + // Simulate the timer callback firing after Discard() (Stop() does not + // guarantee the callback won't run). It must be a no-op and must not + // re-arm the cleanup timer, which would keep the discarded pool alive. + pool.cleanupIdleCheckers() + + pool.mu.Lock() + assert.Assert(t, pool.cleanupTimer == nil, "cleanup must not reschedule a timer on a discarded pool") + hasChecker := false + for _, cc := range pool.checkers { + if cc != nil { + hasChecker = true + } + } + assert.Assert(t, hasChecker, "idle checkers must survive cleanup on a discarded pool") + pool.mu.Unlock() + }) +} diff --git a/internal/project/project.go b/internal/project/project.go index 37abaff97ae..0fb1c6f2a2b 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -77,7 +77,7 @@ type Project struct { programFilesWatch *WatchedFiles[*collections.SyncSet[tspath.Path]] typingsWatch *WatchedFiles[PatternsAndIgnored] - checkerPool *CheckerPool + checkerPool *checkerPool // installedTypingsInfo is the value of `project.ComputeTypingsInfo()` that was // used during the most recently completed typings installation. @@ -342,15 +342,13 @@ func (p *Project) hasPotentialProjectReference(projectTreeRequest *ProjectTreeRe } type CreateProgramResult struct { - Program *compiler.Program - UpdateKind ProgramUpdateKind - CheckerPool *CheckerPool + Program *compiler.Program + UpdateKind ProgramUpdateKind } func (p *Project) CreateProgram() CreateProgramResult { updateKind := ProgramUpdateKindNewFiles var programCloned bool - var checkerPool *CheckerPool var newProgram *compiler.Program // Define a fresh CreateCheckerPool closure for this call. Each invocation of @@ -358,8 +356,7 @@ func (p *Project) CreateProgram() CreateProgramResult { // the same project never share a captured variable through a stale closure // stored in the old program's options. createCheckerPool := func(program *compiler.Program) compiler.CheckerPool { - checkerPool = newCheckerPool(4, program, nil) - return checkerPool + return newCheckerPool(p.host.sessionOptions.CheckerPoolOptions, program, p.log) } // Create the command line, potentially augmented with typing files @@ -410,9 +407,8 @@ func (p *Project) CreateProgram() CreateProgramResult { newProgram.BindSourceFiles() return CreateProgramResult{ - Program: newProgram, - UpdateKind: updateKind, - CheckerPool: checkerPool, + Program: newProgram, + UpdateKind: updateKind, } } diff --git a/internal/project/projectcollectionbuilder.go b/internal/project/projectcollectionbuilder.go index 32009d52617..22e5aad6eb3 100644 --- a/internal/project/projectcollectionbuilder.go +++ b/internal/project/projectcollectionbuilder.go @@ -1055,10 +1055,11 @@ func (b *ProjectCollectionBuilder) updateProgram(entry dirty.Value[*Project], lo entry.Locked(func(entry dirty.Value[*Project]) { entry.Change(func(project *Project) { oldHost := project.host + oldCheckerPool := project.checkerPool project.host = newCompilerHost(project.currentDirectory, project, b, logger.Fork("CompilerHost")) result := project.CreateProgram() project.Program = result.Program - project.checkerPool = result.CheckerPool + project.checkerPool = result.Program.GetCheckerPool().(*checkerPool) project.ProgramUpdateKind = result.UpdateKind project.ProgramLastUpdate = b.newSnapshotID if result.UpdateKind == ProgramUpdateKindCloned { @@ -1070,6 +1071,9 @@ func (b *ProjectCollectionBuilder) updateProgram(entry dirty.Value[*Project], lo } project.dirty = false project.dirtyFilePath = "" + if oldCheckerPool != nil { + oldCheckerPool.Discard() + } }) }) } diff --git a/internal/project/session.go b/internal/project/session.go index 4fb513460ba..cd04d9817ab 100644 --- a/internal/project/session.go +++ b/internal/project/session.go @@ -63,6 +63,7 @@ type SessionOptions struct { PushDiagnosticsEnabled bool DebounceDelay time.Duration Locale locale.Locale + CheckerPoolOptions CheckerPoolOptions } type SessionInit struct { diff --git a/internal/project/snapshot.go b/internal/project/snapshot.go index b3e5fc6b1aa..fde56333707 100644 --- a/internal/project/snapshot.go +++ b/internal/project/snapshot.go @@ -535,6 +535,13 @@ func (s *Snapshot) Deref(session *Session) { func (s *Snapshot) dispose(session *Session) { for _, project := range s.ProjectCollection.Projects() { if project.Program != nil && session.programCounter.Deref(project.Program) { + // This program is no longer referenced by any snapshot. + // Mark its checker pool as discarded so its idle-cleanup timer stops + // keeping the pool alive, allowing the pool and any idle checkers it + // still references to be reclaimed when the pool is garbage-collected. + if project.checkerPool != nil { + project.checkerPool.Discard() + } for _, file := range project.Program.SourceFiles() { session.parseCache.Deref(NewParseCacheKey(file.ParseOptions(), file.Hash, file.ScriptKind)) } From 889378b308c0a8b6ed74c2c1282875bf2dd4b787 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Jun 2026 17:18:17 -0700 Subject: [PATCH 20/49] Reclassify `nodeModulesAllowJsImportAssignment` JS emit diffs as accepted baselines (#3872) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com> Co-authored-by: Jake Bailey <5341706+jakebailey@users.noreply.github.com> --- ...deModulesAllowJsImportAssignment(module=node16).js.diff | 0 ...deModulesAllowJsImportAssignment(module=node18).js.diff | 0 ...deModulesAllowJsImportAssignment(module=node20).js.diff | 0 ...ModulesAllowJsImportAssignment(module=nodenext).js.diff | 0 testdata/submoduleAccepted.txt | 7 +++++++ testdata/submoduleTriaged.txt | 7 ------- 6 files changed, 7 insertions(+), 7 deletions(-) rename testdata/baselines/reference/{submoduleTriaged => submoduleAccepted}/conformance/nodeModulesAllowJsImportAssignment(module=node16).js.diff (100%) rename testdata/baselines/reference/{submoduleTriaged => submoduleAccepted}/conformance/nodeModulesAllowJsImportAssignment(module=node18).js.diff (100%) rename testdata/baselines/reference/{submoduleTriaged => submoduleAccepted}/conformance/nodeModulesAllowJsImportAssignment(module=node20).js.diff (100%) rename testdata/baselines/reference/{submoduleTriaged => submoduleAccepted}/conformance/nodeModulesAllowJsImportAssignment(module=nodenext).js.diff (100%) diff --git a/testdata/baselines/reference/submoduleTriaged/conformance/nodeModulesAllowJsImportAssignment(module=node16).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsImportAssignment(module=node16).js.diff similarity index 100% rename from testdata/baselines/reference/submoduleTriaged/conformance/nodeModulesAllowJsImportAssignment(module=node16).js.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsImportAssignment(module=node16).js.diff diff --git a/testdata/baselines/reference/submoduleTriaged/conformance/nodeModulesAllowJsImportAssignment(module=node18).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsImportAssignment(module=node18).js.diff similarity index 100% rename from testdata/baselines/reference/submoduleTriaged/conformance/nodeModulesAllowJsImportAssignment(module=node18).js.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsImportAssignment(module=node18).js.diff diff --git a/testdata/baselines/reference/submoduleTriaged/conformance/nodeModulesAllowJsImportAssignment(module=node20).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsImportAssignment(module=node20).js.diff similarity index 100% rename from testdata/baselines/reference/submoduleTriaged/conformance/nodeModulesAllowJsImportAssignment(module=node20).js.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsImportAssignment(module=node20).js.diff diff --git a/testdata/baselines/reference/submoduleTriaged/conformance/nodeModulesAllowJsImportAssignment(module=nodenext).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsImportAssignment(module=nodenext).js.diff similarity index 100% rename from testdata/baselines/reference/submoduleTriaged/conformance/nodeModulesAllowJsImportAssignment(module=nodenext).js.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsImportAssignment(module=nodenext).js.diff diff --git a/testdata/submoduleAccepted.txt b/testdata/submoduleAccepted.txt index d70be765565..ddfbb34d3fd 100644 --- a/testdata/submoduleAccepted.txt +++ b/testdata/submoduleAccepted.txt @@ -925,6 +925,13 @@ conformance/nodeModulesAllowJsTopLevelAwait(module=node18).js.diff conformance/nodeModulesAllowJsTopLevelAwait(module=node20).js.diff conformance/nodeModulesAllowJsTopLevelAwait(module=nodenext).js.diff +## JS declaration emit adds `export import` modifier for require-style import assignments +## https://github.com/microsoft/typescript-go/issues/3542 +conformance/nodeModulesAllowJsImportAssignment(module=node16).js.diff +conformance/nodeModulesAllowJsImportAssignment(module=node18).js.diff +conformance/nodeModulesAllowJsImportAssignment(module=node20).js.diff +conformance/nodeModulesAllowJsImportAssignment(module=nodenext).js.diff + ## JS declaration emit now preserves side-effect imports, declaration order changes inconsequentially conformance/nodeModulesAllowJsExportAssignment(module=node16).js.diff conformance/nodeModulesAllowJsExportAssignment(module=node18).js.diff diff --git a/testdata/submoduleTriaged.txt b/testdata/submoduleTriaged.txt index 32c7cdc8290..ce7a7249ba7 100644 --- a/testdata/submoduleTriaged.txt +++ b/testdata/submoduleTriaged.txt @@ -39,13 +39,6 @@ compiler/multiLinePropertyAccessAndArrowFunctionIndent1.errors.txt.diff compiler/parseErrorIncorrectReturnToken.errors.txt.diff conformance/parserStatementIsNotAMemberVariableDeclaration1(alwaysstrict=true).errors.txt.diff -# JS declaration emit adds `export import` modifier for require-style import assignments -## https://github.com/microsoft/typescript-go/issues/3542 -conformance/nodeModulesAllowJsImportAssignment(module=node16).js.diff -conformance/nodeModulesAllowJsImportAssignment(module=node18).js.diff -conformance/nodeModulesAllowJsImportAssignment(module=node20).js.diff -conformance/nodeModulesAllowJsImportAssignment(module=nodenext).js.diff - # CJS class expression exports now emit anonymous constructor types instead of named class declarations ## https://github.com/microsoft/typescript-go/issues/3544 conformance/commonJSImportExportedClassExpression.js.diff From 0751b192b3141fd2030156faba5d74d4990263d8 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Jun 2026 17:24:40 -0700 Subject: [PATCH 21/49] Return diagnostics API positions as UTF-16 offsets (#4298) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/api/proto.go | 16 ++++++++++++---- internal/api/proto_test.go | 23 +++++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/internal/api/proto.go b/internal/api/proto.go index 8951fb6481b..65051f202a4 100644 --- a/internal/api/proto.go +++ b/internal/api/proto.go @@ -1000,9 +1000,17 @@ type DiagnosticResponse struct { // NewDiagnosticResponse converts an ast.Diagnostic to a DiagnosticResponse. func NewDiagnosticResponse(d *ast.Diagnostic) *DiagnosticResponse { + pos := d.Pos() + end := d.End() + file := d.File() + if file != nil { + positionMap := file.GetPositionMap() + pos = positionMap.UTF8ToUTF16(pos) + end = positionMap.UTF8ToUTF16(end) + } resp := &DiagnosticResponse{ - Pos: d.Pos(), - End: d.End(), + Pos: pos, + End: end, Code: d.Code(), Category: d.Category(), Text: d.Localize(locale.Default), @@ -1010,8 +1018,8 @@ func NewDiagnosticResponse(d *ast.Diagnostic) *DiagnosticResponse { ReportsDeprecated: d.ReportsDeprecated(), } - if d.File() != nil { - resp.FileName = d.File().FileName() + if file != nil { + resp.FileName = file.FileName() } if chain := d.MessageChain(); len(chain) > 0 { diff --git a/internal/api/proto_test.go b/internal/api/proto_test.go index 6ed8625e12a..6e17411e0c0 100644 --- a/internal/api/proto_test.go +++ b/internal/api/proto_test.go @@ -1,10 +1,15 @@ package api_test import ( + "strings" "testing" "github.com/microsoft/typescript-go/internal/api" + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/diagnostics" "github.com/microsoft/typescript-go/internal/json" + "github.com/microsoft/typescript-go/internal/parser" "gotest.tools/v3/assert" ) @@ -58,3 +63,21 @@ func TestDocumentIdentifierUnmarshalJSON(t *testing.T) { }) } } + +func TestNewDiagnosticResponseUsesUTF16Offsets(t *testing.T) { + t.Parallel() + + text := "const 💩 = 1;" + file := parser.ParseSourceFile(ast.SourceFileParseOptions{FileName: "/unicode.ts"}, text, core.ScriptKindTS) + pos := strings.Index(text, "=") + assert.Assert(t, pos > 0) + end := pos + len("=") + + diag := ast.NewDiagnostic(file, core.NewTextRange(pos, end), diagnostics.Expression_expected) + resp := api.NewDiagnosticResponse(diag) + + assert.Equal(t, resp.Pos, 9) + assert.Equal(t, resp.End, 10) + assert.Equal(t, resp.Pos, file.GetPositionMap().UTF8ToUTF16(pos)) + assert.Equal(t, resp.End, file.GetPositionMap().UTF8ToUTF16(end)) +} From c78d39e7075b4fc641b12b1f35d905c54cdc13ef Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 12 Jun 2026 19:12:36 -0700 Subject: [PATCH 22/49] Add missing diagnostic context setting logic for newer js declaration emit features (#4306) --- .../transformers/declarations/diagnostics.go | 18 +++++++ .../transformers/declarations/transform.go | 48 ++++++++++++------- internal/transformers/declarations/util.go | 3 +- ...rtyPrivateModuleDeclarationEmit.errors.txt | 12 +++++ ...opertyPrivateModuleDeclarationEmit.symbols | 25 ++++++++++ ...PropertyPrivateModuleDeclarationEmit.types | 26 ++++++++++ ...inePropertyPrivateModuleDeclarationEmit.ts | 15 ++++++ 7 files changed, 130 insertions(+), 17 deletions(-) create mode 100644 testdata/baselines/reference/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.errors.txt create mode 100644 testdata/baselines/reference/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.symbols create mode 100644 testdata/baselines/reference/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.types create mode 100644 testdata/tests/cases/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.ts diff --git a/internal/transformers/declarations/diagnostics.go b/internal/transformers/declarations/diagnostics.go index 7f33fc2ed6a..09939d199b2 100644 --- a/internal/transformers/declarations/diagnostics.go +++ b/internal/transformers/declarations/diagnostics.go @@ -197,6 +197,24 @@ func createGetSymbolAccessibilityDiagnosticForNode(node *ast.Node) GetSymbolAcce typeName: typeName, } } + } else if ast.IsCallExpression(node) { + // JS object.defineProperty call + // unique node selection behavior, inline closure + return func(symbolAccessibilityResult printer.SymbolAccessibilityResult) *SymbolAccessibilityDiagnostic { + diagnosticMessage := selectDiagnosticBasedOnModuleName( + symbolAccessibilityResult, + diagnostics.Exported_variable_0_has_or_is_using_name_1_from_external_module_2_but_cannot_be_named, + diagnostics.Exported_variable_0_has_or_is_using_name_1_from_private_module_2, + diagnostics.Exported_variable_0_has_or_is_using_private_name_1, + ) + errorNode := node.Arguments()[1] + typeName := node.Arguments()[1] + return &SymbolAccessibilityDiagnostic{ + errorNode: errorNode, + diagnosticMessage: diagnosticMessage, + typeName: typeName, + } + } } else { panic("Attempted to set a declaration diagnostic context for unhandled node kind: " + node.Kind.String()) } diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go index e3a78ea690a..83feba1e184 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -488,6 +488,28 @@ func (tx *DeclarationTransformer) getTypeReferences() (result []*ast.FileReferen return result } +func (tx *DeclarationTransformer) setupDiagnosticContext(input *ast.Node) (bool, func()) { + canProdiceDiagnostic := canProduceDiagnostics(input) + oldWithinObjectLiteralType := tx.suppressNewDiagnosticContexts + shouldEnterSuppressNewDiagnosticsContextContext := (input.Kind == ast.KindTypeLiteral || input.Kind == ast.KindMappedType) && !(input.Parent.Kind == ast.KindTypeAliasDeclaration || input.Parent.Kind == ast.KindJSTypeAliasDeclaration) + + oldDiag := tx.state.getSymbolAccessibilityDiagnostic + if canProdiceDiagnostic && !tx.suppressNewDiagnosticContexts { + tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input) + } + oldName := tx.state.errorNameNode + + if shouldEnterSuppressNewDiagnosticsContextContext { + tx.suppressNewDiagnosticContexts = true + } + + return canProdiceDiagnostic, func() { + tx.state.getSymbolAccessibilityDiagnostic = oldDiag + tx.state.errorNameNode = oldName + tx.suppressNewDiagnosticContexts = oldWithinObjectLiteralType + } +} + func (tx *DeclarationTransformer) visitDeclarationSubtree(input *ast.Node) *ast.Node { if tx.shouldStripInternal(input) { return nil @@ -534,19 +556,8 @@ func (tx *DeclarationTransformer) visitDeclarationSubtree(input *ast.Node) *ast. tx.enclosingDeclaration = input } - canProdiceDiagnostic := canProduceDiagnostics(input) - oldWithinObjectLiteralType := tx.suppressNewDiagnosticContexts - shouldEnterSuppressNewDiagnosticsContextContext := (input.Kind == ast.KindTypeLiteral || input.Kind == ast.KindMappedType) && !(input.Parent.Kind == ast.KindTypeAliasDeclaration || input.Parent.Kind == ast.KindJSTypeAliasDeclaration) - - oldDiag := tx.state.getSymbolAccessibilityDiagnostic - if canProdiceDiagnostic && !tx.suppressNewDiagnosticContexts { - tx.state.getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(input) - } - oldName := tx.state.errorNameNode - - if shouldEnterSuppressNewDiagnosticsContextContext { - tx.suppressNewDiagnosticContexts = true - } + canProdiceDiagnostic, cleanupDiagnosticContext := tx.setupDiagnosticContext(input) + defer cleanupDiagnosticContext() var result *ast.Node @@ -626,9 +637,6 @@ func (tx *DeclarationTransformer) visitDeclarationSubtree(input *ast.Node) *ast. } tx.enclosingDeclaration = previousEnclosingDeclaration - tx.state.getSymbolAccessibilityDiagnostic = oldDiag - tx.state.errorNameNode = oldName - tx.suppressNewDiagnosticContexts = oldWithinObjectLiteralType return result } @@ -2278,6 +2286,8 @@ func (tx *DeclarationTransformer) getNameExpressionPreferringIdentifier(nameExpr func (tx *DeclarationTransformer) visitExpressionStatement(node *ast.Node) *ast.Node { if expression := node.Expression(); expression != nil { + _, cleanupDiagnosticContext := tx.setupDiagnosticContext(expression) + defer cleanupDiagnosticContext() switch ast.GetAssignmentDeclarationKind(expression) { case ast.JSDeclarationKindModuleExports: if ast.IsSourceFile(node.Parent) && node.Parent.AsSourceFile().CommonJSModuleIndicator != nil { @@ -2388,6 +2398,9 @@ func (tx *DeclarationTransformer) transformExpandoAssignment(node *ast.BinaryExp tx.enclosingDeclaration = oldEnclosing }() + _, cleanupDiagnosticContext := tx.setupDiagnosticContext(node.AsNode()) + defer cleanupDiagnosticContext() + statements := []*ast.Statement{ tx.Factory().NewVariableStatement( varModifiers, @@ -2447,6 +2460,9 @@ func (tx *DeclarationTransformer) transformExpandoHost(name *ast.Node, declarati modifierFlags ^= ast.ModifierFlagsExport } + _, cleanupDiagnosticContext := tx.setupDiagnosticContext(declaration) + defer cleanupDiagnosticContext() + modifiers := tx.Factory().NewModifierList(ast.CreateModifiersFromModifierFlags(modifierFlags, tx.Factory().NewModifier)) replacement := make([]*ast.Node, 0) diff --git a/internal/transformers/declarations/util.go b/internal/transformers/declarations/util.go index bdcc26e524b..6e40cca0faa 100644 --- a/internal/transformers/declarations/util.go +++ b/internal/transformers/declarations/util.go @@ -44,7 +44,8 @@ func canProduceDiagnostics(node *ast.Node) bool { ast.IsIndexSignatureDeclaration(node) || ast.IsPropertyAccessExpression(node) || ast.IsElementAccessExpression(node) || - ast.IsBinaryExpression(node) // || // !!! TODO: JSDoc support + ast.IsBinaryExpression(node) || + ast.IsCallExpression(node) // || // !!! TODO: JSDoc support /* ast.IsJSDocTypeAlias(node); */ } diff --git a/testdata/baselines/reference/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.errors.txt b/testdata/baselines/reference/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.errors.txt new file mode 100644 index 00000000000..de523f455b4 --- /dev/null +++ b/testdata/baselines/reference/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.errors.txt @@ -0,0 +1,12 @@ +/index.cjs(1,32): error TS4023: Exported variable '"api"' has or is using name 'Private' from external module "/node_modules/@types/pkg/index" but cannot be named. + + +==== /index.cjs (1 errors) ==== + Object.defineProperty(exports, "api", { value: require("pkg") }); + ~~~~~ +!!! error TS4023: Exported variable '"api"' has or is using name 'Private' from external module "/node_modules/@types/pkg/index" but cannot be named. +==== /node_modules/@types/pkg/index.d.ts (0 errors) ==== + interface Private {} + declare const obj: { fn(x: Private): void }; + export = obj; + \ No newline at end of file diff --git a/testdata/baselines/reference/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.symbols b/testdata/baselines/reference/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.symbols new file mode 100644 index 00000000000..16ea66c2abd --- /dev/null +++ b/testdata/baselines/reference/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.symbols @@ -0,0 +1,25 @@ +//// [tests/cases/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.ts] //// + +=== /index.cjs === +Object.defineProperty(exports, "api", { value: require("pkg") }); +>Object.defineProperty : Symbol(ObjectConstructor.defineProperty, Decl(lib.es5.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>defineProperty : Symbol(ObjectConstructor.defineProperty, Decl(lib.es5.d.ts, --, --)) +>exports : Symbol(exports, Decl(index.cjs, 0, 0)) +>"api" : Symbol("api", Decl(index.cjs, 0, 0)) +>value : Symbol(value, Decl(index.cjs, 0, 39)) +>require : Symbol(require) + +=== /node_modules/@types/pkg/index.d.ts === +interface Private {} +>Private : Symbol(Private, Decl(index.d.ts, 0, 0)) + +declare const obj: { fn(x: Private): void }; +>obj : Symbol(obj, Decl(index.d.ts, 1, 13)) +>fn : Symbol(fn, Decl(index.d.ts, 1, 20)) +>x : Symbol(x, Decl(index.d.ts, 1, 24)) +>Private : Symbol(Private, Decl(index.d.ts, 0, 0)) + +export = obj; +>obj : Symbol(obj, Decl(index.d.ts, 1, 13)) + diff --git a/testdata/baselines/reference/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.types b/testdata/baselines/reference/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.types new file mode 100644 index 00000000000..2c5e1abf944 --- /dev/null +++ b/testdata/baselines/reference/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.types @@ -0,0 +1,26 @@ +//// [tests/cases/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.ts] //// + +=== /index.cjs === +Object.defineProperty(exports, "api", { value: require("pkg") }); +>Object.defineProperty(exports, "api", { value: require("pkg") }) : typeof import("./index.cjs") +>Object.defineProperty : (o: T, p: PropertyKey, attributes: PropertyDescriptor & ThisType) => T +>Object : ObjectConstructor +>defineProperty : (o: T, p: PropertyKey, attributes: PropertyDescriptor & ThisType) => T +>exports : typeof import("./index.cjs") +>"api" : "api" +>{ value: require("pkg") } : { value: { fn(x: Private): void; }; } +>value : { fn(x: Private): void; } +>require("pkg") : { fn(x: Private): void; } +>require : any +>"pkg" : "pkg" + +=== /node_modules/@types/pkg/index.d.ts === +interface Private {} +declare const obj: { fn(x: Private): void }; +>obj : { fn(x: Private): void; } +>fn : (x: Private) => void +>x : Private + +export = obj; +>obj : { fn(x: Private): void; } + diff --git a/testdata/tests/cases/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.ts b/testdata/tests/cases/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.ts new file mode 100644 index 00000000000..c71d01c8101 --- /dev/null +++ b/testdata/tests/cases/compiler/cjsObjectDefinePropertyPrivateModuleDeclarationEmit.ts @@ -0,0 +1,15 @@ +// @declaration: true +// @emitDeclarationOnly: true +// @allowJs: true +// @checkJs: true +// @module: commonjs +// @target: es6 +// @outDir: ./out + +// @filename: /node_modules/@types/pkg/index.d.ts +interface Private {} +declare const obj: { fn(x: Private): void }; +export = obj; + +// @filename: /index.cjs +Object.defineProperty(exports, "api", { value: require("pkg") }); \ No newline at end of file From 551b02d6c10e78610fc5ca7c23b77c9d531ee59b Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Sat, 13 Jun 2026 15:51:06 -0700 Subject: [PATCH 23/49] Better fix bug with this types and instantiateSymbol (#4307) --- internal/checker/checker.go | 28 ++++++++++------------------ internal/checker/mapper.go | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 32b7abe1995..e92f029d5dd 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -14289,12 +14289,7 @@ func (c *Checker) recordMergedSymbol(target *ast.Symbol, source *ast.Symbol) { } func (c *Checker) getSymbolIfSameReference(s1 *ast.Symbol, s2 *ast.Symbol) *ast.Symbol { - // An instantiated symbol (e.g. a parameter of a signature instantiated to substitute `this`) - // refers to the same declaration as its instantiation target. Whether a given context yields the - // original symbol or an instantiated copy can depend on incidental checker cache state (see - // instantiateSymbol, whose reuse shortcut is gated on the type already being resolved), so unwrap - // instantiations here to keep "same reference" answers deterministic. - if c.getMergedSymbol(c.resolveSymbol(c.getMergedSymbol(c.getTargetSymbol(s1)))) == c.getMergedSymbol(c.resolveSymbol(c.getMergedSymbol(c.getTargetSymbol(s2)))) { + if c.getMergedSymbol(c.resolveSymbol(c.getMergedSymbol(s1))) == c.getMergedSymbol(c.resolveSymbol(c.getMergedSymbol(s2))) { return s1 } return nil @@ -19003,7 +18998,7 @@ func (c *Checker) resolveObjectTypeMembers(t *Type, source *Type, typeParameters } else { instantiated = true mapper = newTypeMapper(typeParameters, typeArguments) - members = c.instantiateSymbolTable(resolved.declaredMembers, mapper, len(typeParameters) == 1 /*mappingThisOnly*/) + members = c.instantiateSymbolTable(resolved.declaredMembers, mapper) callSignatures = c.instantiateSignatures(resolved.declaredCallSignatures, mapper) constructSignatures = c.instantiateSignatures(resolved.declaredConstructSignatures, mapper) indexInfos = c.instantiateIndexInfos(resolved.declaredIndexInfos, mapper) @@ -20610,8 +20605,6 @@ func (c *Checker) resolveAnonymousTypeMembers(t *Type) { } } -// The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true, -// we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation. func (c *Checker) createInstantiatedSymbolTable(symbols []*ast.Symbol, m *TypeMapper) ast.SymbolTable { if len(symbols) == 0 { return nil @@ -20623,20 +20616,14 @@ func (c *Checker) createInstantiatedSymbolTable(symbols []*ast.Symbol, m *TypeMa return result } -// The mappingThisOnly flag indicates that the only type parameter being mapped is "this". When the flag is true, -// we check symbols to see if we can quickly conclude they are free of "this" references, thus needing no instantiation. -func (c *Checker) instantiateSymbolTable(symbols ast.SymbolTable, m *TypeMapper, mappingThisOnly bool) ast.SymbolTable { +func (c *Checker) instantiateSymbolTable(symbols ast.SymbolTable, m *TypeMapper) ast.SymbolTable { if len(symbols) == 0 { return nil } result := make(ast.SymbolTable, len(symbols)) for id, symbol := range symbols { if c.isNamedMember(symbol, id) { - if mappingThisOnly && isThisless(symbol) { - result[id] = symbol - } else { - result[id] = c.instantiateSymbol(symbol, m) - } + result[id] = c.instantiateSymbol(symbol, m) } } return result @@ -20647,6 +20634,9 @@ func (c *Checker) instantiateSymbol(symbol *ast.Symbol, m *TypeMapper) *ast.Symb return nil } links := c.valueSymbolLinks.Get(symbol) + if m != nil && m.MapsThisOnly() && isThisless(symbol) { + return symbol + } // If the type of the symbol is already resolved, and if that type could not possibly // be affected by instantiation, simply return the symbol itself. if links.resolvedType != nil && !c.couldContainTypeVariables(links.resolvedType) { @@ -20679,7 +20669,7 @@ func (c *Checker) instantiateSymbol(symbol *ast.Symbol, m *TypeMapper) *ast.Symb return result } -// Returns true if the class or interface member given by the symbol is free of "this" references. The +// Returns true if the parameter or class/interface member given by the symbol is free of "this" references. The // function may return false for symbols that are actually free of "this" references because it is not // feasible to perform a complete analysis in all cases. In particular, property members with types // inferred from their initializers and function members with inferred return types are conservatively @@ -20689,6 +20679,8 @@ func isThisless(symbol *ast.Symbol) bool { declaration := symbol.Declarations[0] if declaration != nil { switch declaration.Kind { + case ast.KindParameter: + return isThislessVariableLikeDeclaration(declaration) case ast.KindPropertyDeclaration, ast.KindPropertySignature: return isThislessVariableLikeDeclaration(declaration) case ast.KindMethodDeclaration, ast.KindMethodSignature, ast.KindConstructor, ast.KindGetAccessor, ast.KindSetAccessor: diff --git a/internal/checker/mapper.go b/internal/checker/mapper.go index bc9bd9b00fd..a3a2bf4cb3f 100644 --- a/internal/checker/mapper.go +++ b/internal/checker/mapper.go @@ -25,12 +25,14 @@ type TypeMapper struct { func (m *TypeMapper) Map(t *Type) *Type { return m.data.Map(t) } func (m *TypeMapper) Kind() TypeMapperKind { return m.data.Kind() } +func (m *TypeMapper) MapsThisOnly() bool { return m.data.MapsThisOnly() } // TypeMapperData type TypeMapperData interface { Map(t *Type) *Type Kind() TypeMapperKind + MapsThisOnly() bool } // Factory functions @@ -88,6 +90,7 @@ type TypeMapperBase struct { func (m *TypeMapperBase) Map(t *Type) *Type { return t } func (m *TypeMapperBase) Kind() TypeMapperKind { return TypeMapperKindUnknown } +func (m *TypeMapperBase) MapsThisOnly() bool { return false } // SimpleTypeMapper @@ -116,6 +119,10 @@ func (m *SimpleTypeMapper) Kind() TypeMapperKind { return TypeMapperKindSimple } +func (m *SimpleTypeMapper) MapsThisOnly() bool { + return isThisTypeParameter(m.source) +} + // ArrayTypeMapper type ArrayTypeMapper struct { @@ -145,6 +152,10 @@ func (m *ArrayTypeMapper) Kind() TypeMapperKind { return TypeMapperKindArray } +func (m *ArrayTypeMapper) MapsThisOnly() bool { + return len(m.sources) == 1 && isThisTypeParameter(m.sources[0]) +} + // ArrayToSingleTypeMapper type ArrayToSingleTypeMapper struct { @@ -168,6 +179,10 @@ func (m *ArrayToSingleTypeMapper) Map(t *Type) *Type { return t } +func (m *ArrayToSingleTypeMapper) MapsThisOnly() bool { + return len(m.sources) == 1 && isThisTypeParameter(m.sources[0]) +} + // DeferredTypeMapper type DeferredTypeMapper struct { @@ -193,6 +208,10 @@ func (m *DeferredTypeMapper) Map(t *Type) *Type { return t } +func (m *DeferredTypeMapper) MapsThisOnly() bool { + return len(m.sources) == 1 && isThisTypeParameter(m.sources[0]) +} + // FunctionTypeMapper type FunctionTypeMapper struct { From a7ae3a14e91081c194b63afd0cc8745a0645138a Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 15 Jun 2026 09:21:29 -0700 Subject: [PATCH 24/49] Pin JS casing to generated Unicode data (#4321) --- .../_scripts/generate-unicode-data.mts | 56 +- internal/stringutil/js_case.go | 9 +- internal/stringutil/js_case_generated.go | 3043 ++++++++++++++++- internal/stringutil/js_case_test.go | 2 + 4 files changed, 2980 insertions(+), 130 deletions(-) diff --git a/internal/stringutil/_scripts/generate-unicode-data.mts b/internal/stringutil/_scripts/generate-unicode-data.mts index 11ba28f7a7a..bd726d68026 100644 --- a/internal/stringutil/_scripts/generate-unicode-data.mts +++ b/internal/stringutil/_scripts/generate-unicode-data.mts @@ -34,6 +34,7 @@ type SpecialCasingEntry = { codePoint: number; lower: number[]; upper: number[]; + conditionalLower: number[]; condition: string; }; @@ -47,6 +48,11 @@ async function loadMapping(property: string): Promise> { return module.default as Map; } +async function loadSimpleMapping(property: string): Promise> { + const module = await import(`${PACKAGE}/${property}/code-points.js`); + return module.default as Map; +} + // Group a sorted, de-duplicated run of code points into ranges sharing a // constant stride. The stride of each range is taken from the gap to the next // code point, so it never includes a code point that is not in the set; this @@ -120,7 +126,7 @@ ${r32} `; } -async function buildSpecialCasing(): Promise { +async function buildSpecialCasing(simpleLowercase: Map, simpleUppercase: Map): Promise { // The unconditional, locale-insensitive multi-rune mappings. Each map keys a // code point to its full lower/upper expansion (identity when unchanged). const lowerMappings = await loadMapping("Special_Casing/Lowercase"); @@ -131,23 +137,32 @@ async function buildSpecialCasing(): Promise { const entries: SpecialCasingEntry[] = []; - const codePoints = new Set([...lowerMappings.keys(), ...upperMappings.keys()]); + const codePoints = new Set([...simpleLowercase.keys(), ...simpleUppercase.keys(), ...lowerMappings.keys(), ...upperMappings.keys()]); for (const codePoint of codePoints) { entries.push({ codePoint, - lower: lowerMappings.get(codePoint) ?? [codePoint], - upper: upperMappings.get(codePoint) ?? [codePoint], + lower: lowerMappings.get(codePoint) ?? [simpleLowercase.get(codePoint) ?? codePoint], + upper: upperMappings.get(codePoint) ?? [simpleUppercase.get(codePoint) ?? codePoint], + conditionalLower: [codePoint], condition: "specialCasingConditionNone", }); } for (const [codePoint, lower] of finalSigmaMappings) { - entries.push({ - codePoint, - lower, - upper: upperMappings.get(codePoint) ?? [codePoint], - condition: "specialCasingConditionFinalSigma", - }); + const entry = entries.find(entry => entry.codePoint === codePoint); + if (entry === undefined) { + entries.push({ + codePoint, + lower: [simpleLowercase.get(codePoint) ?? codePoint], + upper: upperMappings.get(codePoint) ?? [simpleUppercase.get(codePoint) ?? codePoint], + conditionalLower: lower, + condition: "specialCasingConditionFinalSigma", + }); + } + else { + entry.conditionalLower = lower; + entry.condition = "specialCasingConditionFinalSigma"; + } } entries.sort((a, b) => a.codePoint - b.codePoint); @@ -155,15 +170,19 @@ async function buildSpecialCasing(): Promise { } function renderCaseFile(entries: SpecialCasingEntry[], casedTable: RangeTable, caseIgnorableTable: RangeTable): string { - const mappings = entries.map(entry => `\t${goRuneLiteral(entry.codePoint)}: {lower: ${goStringLiteral(entry.lower)}, upper: ${goStringLiteral(entry.upper)}, condition: ${entry.condition}},`).join("\n"); + const mappings = entries.map(entry => { + const conditionalLower = entry.condition === "specialCasingConditionFinalSigma" ? `, conditionalLower: ${goStringLiteral(entry.conditionalLower)}` : ""; + return `\t${goRuneLiteral(entry.codePoint)}: {lower: ${goStringLiteral(entry.lower)}, upper: ${goStringLiteral(entry.upper)}${conditionalLower}, condition: ${entry.condition}},`; + }).join("\n"); return `// Code generated by generate-unicode-data.mts. DO NOT EDIT. // Derived from the ${PACKAGE} package (Unicode ${UNICODE_VERSION}). // Includes only the locale-insensitive multi-rune mappings needed for ECMAScript // default casing, plus the Final_Sigma context mapping. String.prototype.toLowerCase // applies Final_Sigma, but Go's unicode package does not, so the caser applies it -// from this data when in context. Go's unicode package handles the simple one-rune -// mappings, so those are omitted here. +// from this data when in context. Simple one-rune mappings are included here too +// so casing stays pinned to this Unicode version, rather than the Go toolchain's +// unicode tables. package stringutil @@ -177,9 +196,10 @@ const ( ) type specialCasingMapping struct { -\tlower string -\tupper string -\tcondition specialCasingCondition +\tlower string +\tupper string +\tconditionalLower string +\tcondition specialCasingCondition } var specialCasingMappings = map[rune]specialCasingMapping{ @@ -209,7 +229,9 @@ ${renderRangeTable("unicodeESNextIdentifierPart", partTable)} } async function main() { - const entries = await buildSpecialCasing(); + const simpleLowercase = await loadSimpleMapping("Simple_Case_Mapping/Lowercase"); + const simpleUppercase = await loadSimpleMapping("Simple_Case_Mapping/Uppercase"); + const entries = await buildSpecialCasing(simpleLowercase, simpleUppercase); const casedTable = toRangeTable(await loadCodePoints("Binary_Property/Cased")); const caseIgnorableTable = toRangeTable(await loadCodePoints("Binary_Property/Case_Ignorable")); fs.writeFileSync(CASE_OUTPUT_PATH, renderCaseFile(entries, casedTable, caseIgnorableTable)); diff --git a/internal/stringutil/js_case.go b/internal/stringutil/js_case.go index c778124941b..5ba0e5aabbb 100644 --- a/internal/stringutil/js_case.go +++ b/internal/stringutil/js_case.go @@ -26,13 +26,13 @@ func ToLowerJS(str string) string { // bytes because WriteRune would re-encode the surrogate as U+FFFD. builder.WriteString(EncodeJSStringRune(r)) } else if mapping, ok := specialCasingMappings[r]; ok { - if mapping.condition == specialCasingConditionFinalSigma && !isFinalSigmaContext(casedBefore, str, i) { - builder.WriteRune(unicode.ToLower(r)) + if mapping.condition == specialCasingConditionFinalSigma && isFinalSigmaContext(casedBefore, str, i) { + builder.WriteString(mapping.conditionalLower) } else { builder.WriteString(mapping.lower) } } else { - builder.WriteRune(unicode.ToLower(r)) + builder.WriteRune(r) } if !isUnicodeCaseIgnorable(r) { casedBefore = isSigmaCased(r) @@ -58,10 +58,11 @@ func ToUpperJS(str string) string { } else if mapping, ok := specialCasingMappings[r]; ok { builder.WriteString(mapping.upper) } else { - builder.WriteRune(unicode.ToUpper(r)) + builder.WriteRune(r) } i += size } + return builder.String() } diff --git a/internal/stringutil/js_case_generated.go b/internal/stringutil/js_case_generated.go index 12df5a2e4ad..ff224433c75 100644 --- a/internal/stringutil/js_case_generated.go +++ b/internal/stringutil/js_case_generated.go @@ -3,8 +3,9 @@ // Includes only the locale-insensitive multi-rune mappings needed for ECMAScript // default casing, plus the Final_Sigma context mapping. String.prototype.toLowerCase // applies Final_Sigma, but Go's unicode package does not, so the caser applies it -// from this data when in context. Go's unicode package handles the simple one-rune -// mappings, so those are omitted here. +// from this data when in context. Simple one-rune mappings are included here too +// so casing stays pinned to this Unicode version, rather than the Go toolchain's +// unicode tables. package stringutil @@ -18,116 +19,2940 @@ const ( ) type specialCasingMapping struct { - lower string - upper string - condition specialCasingCondition + lower string + upper string + conditionalLower string + condition specialCasingCondition } var specialCasingMappings = map[rune]specialCasingMapping{ - 0xDF: {lower: "\u00DF", upper: "\u0053\u0053", condition: specialCasingConditionNone}, - 0x130: {lower: "\u0069\u0307", upper: "\u0130", condition: specialCasingConditionNone}, - 0x149: {lower: "\u0149", upper: "\u02BC\u004E", condition: specialCasingConditionNone}, - 0x1F0: {lower: "\u01F0", upper: "\u004A\u030C", condition: specialCasingConditionNone}, - 0x390: {lower: "\u0390", upper: "\u0399\u0308\u0301", condition: specialCasingConditionNone}, - 0x3A3: {lower: "\u03C2", upper: "\u03A3", condition: specialCasingConditionFinalSigma}, - 0x3B0: {lower: "\u03B0", upper: "\u03A5\u0308\u0301", condition: specialCasingConditionNone}, - 0x587: {lower: "\u0587", upper: "\u0535\u0552", condition: specialCasingConditionNone}, - 0x1E96: {lower: "\u1E96", upper: "\u0048\u0331", condition: specialCasingConditionNone}, - 0x1E97: {lower: "\u1E97", upper: "\u0054\u0308", condition: specialCasingConditionNone}, - 0x1E98: {lower: "\u1E98", upper: "\u0057\u030A", condition: specialCasingConditionNone}, - 0x1E99: {lower: "\u1E99", upper: "\u0059\u030A", condition: specialCasingConditionNone}, - 0x1E9A: {lower: "\u1E9A", upper: "\u0041\u02BE", condition: specialCasingConditionNone}, - 0x1F50: {lower: "\u1F50", upper: "\u03A5\u0313", condition: specialCasingConditionNone}, - 0x1F52: {lower: "\u1F52", upper: "\u03A5\u0313\u0300", condition: specialCasingConditionNone}, - 0x1F54: {lower: "\u1F54", upper: "\u03A5\u0313\u0301", condition: specialCasingConditionNone}, - 0x1F56: {lower: "\u1F56", upper: "\u03A5\u0313\u0342", condition: specialCasingConditionNone}, - 0x1F80: {lower: "\u1F80", upper: "\u1F08\u0399", condition: specialCasingConditionNone}, - 0x1F81: {lower: "\u1F81", upper: "\u1F09\u0399", condition: specialCasingConditionNone}, - 0x1F82: {lower: "\u1F82", upper: "\u1F0A\u0399", condition: specialCasingConditionNone}, - 0x1F83: {lower: "\u1F83", upper: "\u1F0B\u0399", condition: specialCasingConditionNone}, - 0x1F84: {lower: "\u1F84", upper: "\u1F0C\u0399", condition: specialCasingConditionNone}, - 0x1F85: {lower: "\u1F85", upper: "\u1F0D\u0399", condition: specialCasingConditionNone}, - 0x1F86: {lower: "\u1F86", upper: "\u1F0E\u0399", condition: specialCasingConditionNone}, - 0x1F87: {lower: "\u1F87", upper: "\u1F0F\u0399", condition: specialCasingConditionNone}, - 0x1F88: {lower: "\u1F80", upper: "\u1F08\u0399", condition: specialCasingConditionNone}, - 0x1F89: {lower: "\u1F81", upper: "\u1F09\u0399", condition: specialCasingConditionNone}, - 0x1F8A: {lower: "\u1F82", upper: "\u1F0A\u0399", condition: specialCasingConditionNone}, - 0x1F8B: {lower: "\u1F83", upper: "\u1F0B\u0399", condition: specialCasingConditionNone}, - 0x1F8C: {lower: "\u1F84", upper: "\u1F0C\u0399", condition: specialCasingConditionNone}, - 0x1F8D: {lower: "\u1F85", upper: "\u1F0D\u0399", condition: specialCasingConditionNone}, - 0x1F8E: {lower: "\u1F86", upper: "\u1F0E\u0399", condition: specialCasingConditionNone}, - 0x1F8F: {lower: "\u1F87", upper: "\u1F0F\u0399", condition: specialCasingConditionNone}, - 0x1F90: {lower: "\u1F90", upper: "\u1F28\u0399", condition: specialCasingConditionNone}, - 0x1F91: {lower: "\u1F91", upper: "\u1F29\u0399", condition: specialCasingConditionNone}, - 0x1F92: {lower: "\u1F92", upper: "\u1F2A\u0399", condition: specialCasingConditionNone}, - 0x1F93: {lower: "\u1F93", upper: "\u1F2B\u0399", condition: specialCasingConditionNone}, - 0x1F94: {lower: "\u1F94", upper: "\u1F2C\u0399", condition: specialCasingConditionNone}, - 0x1F95: {lower: "\u1F95", upper: "\u1F2D\u0399", condition: specialCasingConditionNone}, - 0x1F96: {lower: "\u1F96", upper: "\u1F2E\u0399", condition: specialCasingConditionNone}, - 0x1F97: {lower: "\u1F97", upper: "\u1F2F\u0399", condition: specialCasingConditionNone}, - 0x1F98: {lower: "\u1F90", upper: "\u1F28\u0399", condition: specialCasingConditionNone}, - 0x1F99: {lower: "\u1F91", upper: "\u1F29\u0399", condition: specialCasingConditionNone}, - 0x1F9A: {lower: "\u1F92", upper: "\u1F2A\u0399", condition: specialCasingConditionNone}, - 0x1F9B: {lower: "\u1F93", upper: "\u1F2B\u0399", condition: specialCasingConditionNone}, - 0x1F9C: {lower: "\u1F94", upper: "\u1F2C\u0399", condition: specialCasingConditionNone}, - 0x1F9D: {lower: "\u1F95", upper: "\u1F2D\u0399", condition: specialCasingConditionNone}, - 0x1F9E: {lower: "\u1F96", upper: "\u1F2E\u0399", condition: specialCasingConditionNone}, - 0x1F9F: {lower: "\u1F97", upper: "\u1F2F\u0399", condition: specialCasingConditionNone}, - 0x1FA0: {lower: "\u1FA0", upper: "\u1F68\u0399", condition: specialCasingConditionNone}, - 0x1FA1: {lower: "\u1FA1", upper: "\u1F69\u0399", condition: specialCasingConditionNone}, - 0x1FA2: {lower: "\u1FA2", upper: "\u1F6A\u0399", condition: specialCasingConditionNone}, - 0x1FA3: {lower: "\u1FA3", upper: "\u1F6B\u0399", condition: specialCasingConditionNone}, - 0x1FA4: {lower: "\u1FA4", upper: "\u1F6C\u0399", condition: specialCasingConditionNone}, - 0x1FA5: {lower: "\u1FA5", upper: "\u1F6D\u0399", condition: specialCasingConditionNone}, - 0x1FA6: {lower: "\u1FA6", upper: "\u1F6E\u0399", condition: specialCasingConditionNone}, - 0x1FA7: {lower: "\u1FA7", upper: "\u1F6F\u0399", condition: specialCasingConditionNone}, - 0x1FA8: {lower: "\u1FA0", upper: "\u1F68\u0399", condition: specialCasingConditionNone}, - 0x1FA9: {lower: "\u1FA1", upper: "\u1F69\u0399", condition: specialCasingConditionNone}, - 0x1FAA: {lower: "\u1FA2", upper: "\u1F6A\u0399", condition: specialCasingConditionNone}, - 0x1FAB: {lower: "\u1FA3", upper: "\u1F6B\u0399", condition: specialCasingConditionNone}, - 0x1FAC: {lower: "\u1FA4", upper: "\u1F6C\u0399", condition: specialCasingConditionNone}, - 0x1FAD: {lower: "\u1FA5", upper: "\u1F6D\u0399", condition: specialCasingConditionNone}, - 0x1FAE: {lower: "\u1FA6", upper: "\u1F6E\u0399", condition: specialCasingConditionNone}, - 0x1FAF: {lower: "\u1FA7", upper: "\u1F6F\u0399", condition: specialCasingConditionNone}, - 0x1FB2: {lower: "\u1FB2", upper: "\u1FBA\u0399", condition: specialCasingConditionNone}, - 0x1FB3: {lower: "\u1FB3", upper: "\u0391\u0399", condition: specialCasingConditionNone}, - 0x1FB4: {lower: "\u1FB4", upper: "\u0386\u0399", condition: specialCasingConditionNone}, - 0x1FB6: {lower: "\u1FB6", upper: "\u0391\u0342", condition: specialCasingConditionNone}, - 0x1FB7: {lower: "\u1FB7", upper: "\u0391\u0342\u0399", condition: specialCasingConditionNone}, - 0x1FBC: {lower: "\u1FB3", upper: "\u0391\u0399", condition: specialCasingConditionNone}, - 0x1FC2: {lower: "\u1FC2", upper: "\u1FCA\u0399", condition: specialCasingConditionNone}, - 0x1FC3: {lower: "\u1FC3", upper: "\u0397\u0399", condition: specialCasingConditionNone}, - 0x1FC4: {lower: "\u1FC4", upper: "\u0389\u0399", condition: specialCasingConditionNone}, - 0x1FC6: {lower: "\u1FC6", upper: "\u0397\u0342", condition: specialCasingConditionNone}, - 0x1FC7: {lower: "\u1FC7", upper: "\u0397\u0342\u0399", condition: specialCasingConditionNone}, - 0x1FCC: {lower: "\u1FC3", upper: "\u0397\u0399", condition: specialCasingConditionNone}, - 0x1FD2: {lower: "\u1FD2", upper: "\u0399\u0308\u0300", condition: specialCasingConditionNone}, - 0x1FD3: {lower: "\u1FD3", upper: "\u0399\u0308\u0301", condition: specialCasingConditionNone}, - 0x1FD6: {lower: "\u1FD6", upper: "\u0399\u0342", condition: specialCasingConditionNone}, - 0x1FD7: {lower: "\u1FD7", upper: "\u0399\u0308\u0342", condition: specialCasingConditionNone}, - 0x1FE2: {lower: "\u1FE2", upper: "\u03A5\u0308\u0300", condition: specialCasingConditionNone}, - 0x1FE3: {lower: "\u1FE3", upper: "\u03A5\u0308\u0301", condition: specialCasingConditionNone}, - 0x1FE4: {lower: "\u1FE4", upper: "\u03A1\u0313", condition: specialCasingConditionNone}, - 0x1FE6: {lower: "\u1FE6", upper: "\u03A5\u0342", condition: specialCasingConditionNone}, - 0x1FE7: {lower: "\u1FE7", upper: "\u03A5\u0308\u0342", condition: specialCasingConditionNone}, - 0x1FF2: {lower: "\u1FF2", upper: "\u1FFA\u0399", condition: specialCasingConditionNone}, - 0x1FF3: {lower: "\u1FF3", upper: "\u03A9\u0399", condition: specialCasingConditionNone}, - 0x1FF4: {lower: "\u1FF4", upper: "\u038F\u0399", condition: specialCasingConditionNone}, - 0x1FF6: {lower: "\u1FF6", upper: "\u03A9\u0342", condition: specialCasingConditionNone}, - 0x1FF7: {lower: "\u1FF7", upper: "\u03A9\u0342\u0399", condition: specialCasingConditionNone}, - 0x1FFC: {lower: "\u1FF3", upper: "\u03A9\u0399", condition: specialCasingConditionNone}, - 0xFB00: {lower: "\uFB00", upper: "\u0046\u0046", condition: specialCasingConditionNone}, - 0xFB01: {lower: "\uFB01", upper: "\u0046\u0049", condition: specialCasingConditionNone}, - 0xFB02: {lower: "\uFB02", upper: "\u0046\u004C", condition: specialCasingConditionNone}, - 0xFB03: {lower: "\uFB03", upper: "\u0046\u0046\u0049", condition: specialCasingConditionNone}, - 0xFB04: {lower: "\uFB04", upper: "\u0046\u0046\u004C", condition: specialCasingConditionNone}, - 0xFB05: {lower: "\uFB05", upper: "\u0053\u0054", condition: specialCasingConditionNone}, - 0xFB06: {lower: "\uFB06", upper: "\u0053\u0054", condition: specialCasingConditionNone}, - 0xFB13: {lower: "\uFB13", upper: "\u0544\u0546", condition: specialCasingConditionNone}, - 0xFB14: {lower: "\uFB14", upper: "\u0544\u0535", condition: specialCasingConditionNone}, - 0xFB15: {lower: "\uFB15", upper: "\u0544\u053B", condition: specialCasingConditionNone}, - 0xFB16: {lower: "\uFB16", upper: "\u054E\u0546", condition: specialCasingConditionNone}, - 0xFB17: {lower: "\uFB17", upper: "\u0544\u053D", condition: specialCasingConditionNone}, + 0x41: {lower: "\u0061", upper: "\u0041", condition: specialCasingConditionNone}, + 0x42: {lower: "\u0062", upper: "\u0042", condition: specialCasingConditionNone}, + 0x43: {lower: "\u0063", upper: "\u0043", condition: specialCasingConditionNone}, + 0x44: {lower: "\u0064", upper: "\u0044", condition: specialCasingConditionNone}, + 0x45: {lower: "\u0065", upper: "\u0045", condition: specialCasingConditionNone}, + 0x46: {lower: "\u0066", upper: "\u0046", condition: specialCasingConditionNone}, + 0x47: {lower: "\u0067", upper: "\u0047", condition: specialCasingConditionNone}, + 0x48: {lower: "\u0068", upper: "\u0048", condition: specialCasingConditionNone}, + 0x49: {lower: "\u0069", upper: "\u0049", condition: specialCasingConditionNone}, + 0x4A: {lower: "\u006A", upper: "\u004A", condition: specialCasingConditionNone}, + 0x4B: {lower: "\u006B", upper: "\u004B", condition: specialCasingConditionNone}, + 0x4C: {lower: "\u006C", upper: "\u004C", condition: specialCasingConditionNone}, + 0x4D: {lower: "\u006D", upper: "\u004D", condition: specialCasingConditionNone}, + 0x4E: {lower: "\u006E", upper: "\u004E", condition: specialCasingConditionNone}, + 0x4F: {lower: "\u006F", upper: "\u004F", condition: specialCasingConditionNone}, + 0x50: {lower: "\u0070", upper: "\u0050", condition: specialCasingConditionNone}, + 0x51: {lower: "\u0071", upper: "\u0051", condition: specialCasingConditionNone}, + 0x52: {lower: "\u0072", upper: "\u0052", condition: specialCasingConditionNone}, + 0x53: {lower: "\u0073", upper: "\u0053", condition: specialCasingConditionNone}, + 0x54: {lower: "\u0074", upper: "\u0054", condition: specialCasingConditionNone}, + 0x55: {lower: "\u0075", upper: "\u0055", condition: specialCasingConditionNone}, + 0x56: {lower: "\u0076", upper: "\u0056", condition: specialCasingConditionNone}, + 0x57: {lower: "\u0077", upper: "\u0057", condition: specialCasingConditionNone}, + 0x58: {lower: "\u0078", upper: "\u0058", condition: specialCasingConditionNone}, + 0x59: {lower: "\u0079", upper: "\u0059", condition: specialCasingConditionNone}, + 0x5A: {lower: "\u007A", upper: "\u005A", condition: specialCasingConditionNone}, + 0x61: {lower: "\u0061", upper: "\u0041", condition: specialCasingConditionNone}, + 0x62: {lower: "\u0062", upper: "\u0042", condition: specialCasingConditionNone}, + 0x63: {lower: "\u0063", upper: "\u0043", condition: specialCasingConditionNone}, + 0x64: {lower: "\u0064", upper: "\u0044", condition: specialCasingConditionNone}, + 0x65: {lower: "\u0065", upper: "\u0045", condition: specialCasingConditionNone}, + 0x66: {lower: "\u0066", upper: "\u0046", condition: specialCasingConditionNone}, + 0x67: {lower: "\u0067", upper: "\u0047", condition: specialCasingConditionNone}, + 0x68: {lower: "\u0068", upper: "\u0048", condition: specialCasingConditionNone}, + 0x69: {lower: "\u0069", upper: "\u0049", condition: specialCasingConditionNone}, + 0x6A: {lower: "\u006A", upper: "\u004A", condition: specialCasingConditionNone}, + 0x6B: {lower: "\u006B", upper: "\u004B", condition: specialCasingConditionNone}, + 0x6C: {lower: "\u006C", upper: "\u004C", condition: specialCasingConditionNone}, + 0x6D: {lower: "\u006D", upper: "\u004D", condition: specialCasingConditionNone}, + 0x6E: {lower: "\u006E", upper: "\u004E", condition: specialCasingConditionNone}, + 0x6F: {lower: "\u006F", upper: "\u004F", condition: specialCasingConditionNone}, + 0x70: {lower: "\u0070", upper: "\u0050", condition: specialCasingConditionNone}, + 0x71: {lower: "\u0071", upper: "\u0051", condition: specialCasingConditionNone}, + 0x72: {lower: "\u0072", upper: "\u0052", condition: specialCasingConditionNone}, + 0x73: {lower: "\u0073", upper: "\u0053", condition: specialCasingConditionNone}, + 0x74: {lower: "\u0074", upper: "\u0054", condition: specialCasingConditionNone}, + 0x75: {lower: "\u0075", upper: "\u0055", condition: specialCasingConditionNone}, + 0x76: {lower: "\u0076", upper: "\u0056", condition: specialCasingConditionNone}, + 0x77: {lower: "\u0077", upper: "\u0057", condition: specialCasingConditionNone}, + 0x78: {lower: "\u0078", upper: "\u0058", condition: specialCasingConditionNone}, + 0x79: {lower: "\u0079", upper: "\u0059", condition: specialCasingConditionNone}, + 0x7A: {lower: "\u007A", upper: "\u005A", condition: specialCasingConditionNone}, + 0xB5: {lower: "\u00B5", upper: "\u039C", condition: specialCasingConditionNone}, + 0xC0: {lower: "\u00E0", upper: "\u00C0", condition: specialCasingConditionNone}, + 0xC1: {lower: "\u00E1", upper: "\u00C1", condition: specialCasingConditionNone}, + 0xC2: {lower: "\u00E2", upper: "\u00C2", condition: specialCasingConditionNone}, + 0xC3: {lower: "\u00E3", upper: "\u00C3", condition: specialCasingConditionNone}, + 0xC4: {lower: "\u00E4", upper: "\u00C4", condition: specialCasingConditionNone}, + 0xC5: {lower: "\u00E5", upper: "\u00C5", condition: specialCasingConditionNone}, + 0xC6: {lower: "\u00E6", upper: "\u00C6", condition: specialCasingConditionNone}, + 0xC7: {lower: "\u00E7", upper: "\u00C7", condition: specialCasingConditionNone}, + 0xC8: {lower: "\u00E8", upper: "\u00C8", condition: specialCasingConditionNone}, + 0xC9: {lower: "\u00E9", upper: "\u00C9", condition: specialCasingConditionNone}, + 0xCA: {lower: "\u00EA", upper: "\u00CA", condition: specialCasingConditionNone}, + 0xCB: {lower: "\u00EB", upper: "\u00CB", condition: specialCasingConditionNone}, + 0xCC: {lower: "\u00EC", upper: "\u00CC", condition: specialCasingConditionNone}, + 0xCD: {lower: "\u00ED", upper: "\u00CD", condition: specialCasingConditionNone}, + 0xCE: {lower: "\u00EE", upper: "\u00CE", condition: specialCasingConditionNone}, + 0xCF: {lower: "\u00EF", upper: "\u00CF", condition: specialCasingConditionNone}, + 0xD0: {lower: "\u00F0", upper: "\u00D0", condition: specialCasingConditionNone}, + 0xD1: {lower: "\u00F1", upper: "\u00D1", condition: specialCasingConditionNone}, + 0xD2: {lower: "\u00F2", upper: "\u00D2", condition: specialCasingConditionNone}, + 0xD3: {lower: "\u00F3", upper: "\u00D3", condition: specialCasingConditionNone}, + 0xD4: {lower: "\u00F4", upper: "\u00D4", condition: specialCasingConditionNone}, + 0xD5: {lower: "\u00F5", upper: "\u00D5", condition: specialCasingConditionNone}, + 0xD6: {lower: "\u00F6", upper: "\u00D6", condition: specialCasingConditionNone}, + 0xD8: {lower: "\u00F8", upper: "\u00D8", condition: specialCasingConditionNone}, + 0xD9: {lower: "\u00F9", upper: "\u00D9", condition: specialCasingConditionNone}, + 0xDA: {lower: "\u00FA", upper: "\u00DA", condition: specialCasingConditionNone}, + 0xDB: {lower: "\u00FB", upper: "\u00DB", condition: specialCasingConditionNone}, + 0xDC: {lower: "\u00FC", upper: "\u00DC", condition: specialCasingConditionNone}, + 0xDD: {lower: "\u00FD", upper: "\u00DD", condition: specialCasingConditionNone}, + 0xDE: {lower: "\u00FE", upper: "\u00DE", condition: specialCasingConditionNone}, + 0xDF: {lower: "\u00DF", upper: "\u0053\u0053", condition: specialCasingConditionNone}, + 0xE0: {lower: "\u00E0", upper: "\u00C0", condition: specialCasingConditionNone}, + 0xE1: {lower: "\u00E1", upper: "\u00C1", condition: specialCasingConditionNone}, + 0xE2: {lower: "\u00E2", upper: "\u00C2", condition: specialCasingConditionNone}, + 0xE3: {lower: "\u00E3", upper: "\u00C3", condition: specialCasingConditionNone}, + 0xE4: {lower: "\u00E4", upper: "\u00C4", condition: specialCasingConditionNone}, + 0xE5: {lower: "\u00E5", upper: "\u00C5", condition: specialCasingConditionNone}, + 0xE6: {lower: "\u00E6", upper: "\u00C6", condition: specialCasingConditionNone}, + 0xE7: {lower: "\u00E7", upper: "\u00C7", condition: specialCasingConditionNone}, + 0xE8: {lower: "\u00E8", upper: "\u00C8", condition: specialCasingConditionNone}, + 0xE9: {lower: "\u00E9", upper: "\u00C9", condition: specialCasingConditionNone}, + 0xEA: {lower: "\u00EA", upper: "\u00CA", condition: specialCasingConditionNone}, + 0xEB: {lower: "\u00EB", upper: "\u00CB", condition: specialCasingConditionNone}, + 0xEC: {lower: "\u00EC", upper: "\u00CC", condition: specialCasingConditionNone}, + 0xED: {lower: "\u00ED", upper: "\u00CD", condition: specialCasingConditionNone}, + 0xEE: {lower: "\u00EE", upper: "\u00CE", condition: specialCasingConditionNone}, + 0xEF: {lower: "\u00EF", upper: "\u00CF", condition: specialCasingConditionNone}, + 0xF0: {lower: "\u00F0", upper: "\u00D0", condition: specialCasingConditionNone}, + 0xF1: {lower: "\u00F1", upper: "\u00D1", condition: specialCasingConditionNone}, + 0xF2: {lower: "\u00F2", upper: "\u00D2", condition: specialCasingConditionNone}, + 0xF3: {lower: "\u00F3", upper: "\u00D3", condition: specialCasingConditionNone}, + 0xF4: {lower: "\u00F4", upper: "\u00D4", condition: specialCasingConditionNone}, + 0xF5: {lower: "\u00F5", upper: "\u00D5", condition: specialCasingConditionNone}, + 0xF6: {lower: "\u00F6", upper: "\u00D6", condition: specialCasingConditionNone}, + 0xF8: {lower: "\u00F8", upper: "\u00D8", condition: specialCasingConditionNone}, + 0xF9: {lower: "\u00F9", upper: "\u00D9", condition: specialCasingConditionNone}, + 0xFA: {lower: "\u00FA", upper: "\u00DA", condition: specialCasingConditionNone}, + 0xFB: {lower: "\u00FB", upper: "\u00DB", condition: specialCasingConditionNone}, + 0xFC: {lower: "\u00FC", upper: "\u00DC", condition: specialCasingConditionNone}, + 0xFD: {lower: "\u00FD", upper: "\u00DD", condition: specialCasingConditionNone}, + 0xFE: {lower: "\u00FE", upper: "\u00DE", condition: specialCasingConditionNone}, + 0xFF: {lower: "\u00FF", upper: "\u0178", condition: specialCasingConditionNone}, + 0x100: {lower: "\u0101", upper: "\u0100", condition: specialCasingConditionNone}, + 0x101: {lower: "\u0101", upper: "\u0100", condition: specialCasingConditionNone}, + 0x102: {lower: "\u0103", upper: "\u0102", condition: specialCasingConditionNone}, + 0x103: {lower: "\u0103", upper: "\u0102", condition: specialCasingConditionNone}, + 0x104: {lower: "\u0105", upper: "\u0104", condition: specialCasingConditionNone}, + 0x105: {lower: "\u0105", upper: "\u0104", condition: specialCasingConditionNone}, + 0x106: {lower: "\u0107", upper: "\u0106", condition: specialCasingConditionNone}, + 0x107: {lower: "\u0107", upper: "\u0106", condition: specialCasingConditionNone}, + 0x108: {lower: "\u0109", upper: "\u0108", condition: specialCasingConditionNone}, + 0x109: {lower: "\u0109", upper: "\u0108", condition: specialCasingConditionNone}, + 0x10A: {lower: "\u010B", upper: "\u010A", condition: specialCasingConditionNone}, + 0x10B: {lower: "\u010B", upper: "\u010A", condition: specialCasingConditionNone}, + 0x10C: {lower: "\u010D", upper: "\u010C", condition: specialCasingConditionNone}, + 0x10D: {lower: "\u010D", upper: "\u010C", condition: specialCasingConditionNone}, + 0x10E: {lower: "\u010F", upper: "\u010E", condition: specialCasingConditionNone}, + 0x10F: {lower: "\u010F", upper: "\u010E", condition: specialCasingConditionNone}, + 0x110: {lower: "\u0111", upper: "\u0110", condition: specialCasingConditionNone}, + 0x111: {lower: "\u0111", upper: "\u0110", condition: specialCasingConditionNone}, + 0x112: {lower: "\u0113", upper: "\u0112", condition: specialCasingConditionNone}, + 0x113: {lower: "\u0113", upper: "\u0112", condition: specialCasingConditionNone}, + 0x114: {lower: "\u0115", upper: "\u0114", condition: specialCasingConditionNone}, + 0x115: {lower: "\u0115", upper: "\u0114", condition: specialCasingConditionNone}, + 0x116: {lower: "\u0117", upper: "\u0116", condition: specialCasingConditionNone}, + 0x117: {lower: "\u0117", upper: "\u0116", condition: specialCasingConditionNone}, + 0x118: {lower: "\u0119", upper: "\u0118", condition: specialCasingConditionNone}, + 0x119: {lower: "\u0119", upper: "\u0118", condition: specialCasingConditionNone}, + 0x11A: {lower: "\u011B", upper: "\u011A", condition: specialCasingConditionNone}, + 0x11B: {lower: "\u011B", upper: "\u011A", condition: specialCasingConditionNone}, + 0x11C: {lower: "\u011D", upper: "\u011C", condition: specialCasingConditionNone}, + 0x11D: {lower: "\u011D", upper: "\u011C", condition: specialCasingConditionNone}, + 0x11E: {lower: "\u011F", upper: "\u011E", condition: specialCasingConditionNone}, + 0x11F: {lower: "\u011F", upper: "\u011E", condition: specialCasingConditionNone}, + 0x120: {lower: "\u0121", upper: "\u0120", condition: specialCasingConditionNone}, + 0x121: {lower: "\u0121", upper: "\u0120", condition: specialCasingConditionNone}, + 0x122: {lower: "\u0123", upper: "\u0122", condition: specialCasingConditionNone}, + 0x123: {lower: "\u0123", upper: "\u0122", condition: specialCasingConditionNone}, + 0x124: {lower: "\u0125", upper: "\u0124", condition: specialCasingConditionNone}, + 0x125: {lower: "\u0125", upper: "\u0124", condition: specialCasingConditionNone}, + 0x126: {lower: "\u0127", upper: "\u0126", condition: specialCasingConditionNone}, + 0x127: {lower: "\u0127", upper: "\u0126", condition: specialCasingConditionNone}, + 0x128: {lower: "\u0129", upper: "\u0128", condition: specialCasingConditionNone}, + 0x129: {lower: "\u0129", upper: "\u0128", condition: specialCasingConditionNone}, + 0x12A: {lower: "\u012B", upper: "\u012A", condition: specialCasingConditionNone}, + 0x12B: {lower: "\u012B", upper: "\u012A", condition: specialCasingConditionNone}, + 0x12C: {lower: "\u012D", upper: "\u012C", condition: specialCasingConditionNone}, + 0x12D: {lower: "\u012D", upper: "\u012C", condition: specialCasingConditionNone}, + 0x12E: {lower: "\u012F", upper: "\u012E", condition: specialCasingConditionNone}, + 0x12F: {lower: "\u012F", upper: "\u012E", condition: specialCasingConditionNone}, + 0x130: {lower: "\u0069\u0307", upper: "\u0130", condition: specialCasingConditionNone}, + 0x131: {lower: "\u0131", upper: "\u0049", condition: specialCasingConditionNone}, + 0x132: {lower: "\u0133", upper: "\u0132", condition: specialCasingConditionNone}, + 0x133: {lower: "\u0133", upper: "\u0132", condition: specialCasingConditionNone}, + 0x134: {lower: "\u0135", upper: "\u0134", condition: specialCasingConditionNone}, + 0x135: {lower: "\u0135", upper: "\u0134", condition: specialCasingConditionNone}, + 0x136: {lower: "\u0137", upper: "\u0136", condition: specialCasingConditionNone}, + 0x137: {lower: "\u0137", upper: "\u0136", condition: specialCasingConditionNone}, + 0x139: {lower: "\u013A", upper: "\u0139", condition: specialCasingConditionNone}, + 0x13A: {lower: "\u013A", upper: "\u0139", condition: specialCasingConditionNone}, + 0x13B: {lower: "\u013C", upper: "\u013B", condition: specialCasingConditionNone}, + 0x13C: {lower: "\u013C", upper: "\u013B", condition: specialCasingConditionNone}, + 0x13D: {lower: "\u013E", upper: "\u013D", condition: specialCasingConditionNone}, + 0x13E: {lower: "\u013E", upper: "\u013D", condition: specialCasingConditionNone}, + 0x13F: {lower: "\u0140", upper: "\u013F", condition: specialCasingConditionNone}, + 0x140: {lower: "\u0140", upper: "\u013F", condition: specialCasingConditionNone}, + 0x141: {lower: "\u0142", upper: "\u0141", condition: specialCasingConditionNone}, + 0x142: {lower: "\u0142", upper: "\u0141", condition: specialCasingConditionNone}, + 0x143: {lower: "\u0144", upper: "\u0143", condition: specialCasingConditionNone}, + 0x144: {lower: "\u0144", upper: "\u0143", condition: specialCasingConditionNone}, + 0x145: {lower: "\u0146", upper: "\u0145", condition: specialCasingConditionNone}, + 0x146: {lower: "\u0146", upper: "\u0145", condition: specialCasingConditionNone}, + 0x147: {lower: "\u0148", upper: "\u0147", condition: specialCasingConditionNone}, + 0x148: {lower: "\u0148", upper: "\u0147", condition: specialCasingConditionNone}, + 0x149: {lower: "\u0149", upper: "\u02BC\u004E", condition: specialCasingConditionNone}, + 0x14A: {lower: "\u014B", upper: "\u014A", condition: specialCasingConditionNone}, + 0x14B: {lower: "\u014B", upper: "\u014A", condition: specialCasingConditionNone}, + 0x14C: {lower: "\u014D", upper: "\u014C", condition: specialCasingConditionNone}, + 0x14D: {lower: "\u014D", upper: "\u014C", condition: specialCasingConditionNone}, + 0x14E: {lower: "\u014F", upper: "\u014E", condition: specialCasingConditionNone}, + 0x14F: {lower: "\u014F", upper: "\u014E", condition: specialCasingConditionNone}, + 0x150: {lower: "\u0151", upper: "\u0150", condition: specialCasingConditionNone}, + 0x151: {lower: "\u0151", upper: "\u0150", condition: specialCasingConditionNone}, + 0x152: {lower: "\u0153", upper: "\u0152", condition: specialCasingConditionNone}, + 0x153: {lower: "\u0153", upper: "\u0152", condition: specialCasingConditionNone}, + 0x154: {lower: "\u0155", upper: "\u0154", condition: specialCasingConditionNone}, + 0x155: {lower: "\u0155", upper: "\u0154", condition: specialCasingConditionNone}, + 0x156: {lower: "\u0157", upper: "\u0156", condition: specialCasingConditionNone}, + 0x157: {lower: "\u0157", upper: "\u0156", condition: specialCasingConditionNone}, + 0x158: {lower: "\u0159", upper: "\u0158", condition: specialCasingConditionNone}, + 0x159: {lower: "\u0159", upper: "\u0158", condition: specialCasingConditionNone}, + 0x15A: {lower: "\u015B", upper: "\u015A", condition: specialCasingConditionNone}, + 0x15B: {lower: "\u015B", upper: "\u015A", condition: specialCasingConditionNone}, + 0x15C: {lower: "\u015D", upper: "\u015C", condition: specialCasingConditionNone}, + 0x15D: {lower: "\u015D", upper: "\u015C", condition: specialCasingConditionNone}, + 0x15E: {lower: "\u015F", upper: "\u015E", condition: specialCasingConditionNone}, + 0x15F: {lower: "\u015F", upper: "\u015E", condition: specialCasingConditionNone}, + 0x160: {lower: "\u0161", upper: "\u0160", condition: specialCasingConditionNone}, + 0x161: {lower: "\u0161", upper: "\u0160", condition: specialCasingConditionNone}, + 0x162: {lower: "\u0163", upper: "\u0162", condition: specialCasingConditionNone}, + 0x163: {lower: "\u0163", upper: "\u0162", condition: specialCasingConditionNone}, + 0x164: {lower: "\u0165", upper: "\u0164", condition: specialCasingConditionNone}, + 0x165: {lower: "\u0165", upper: "\u0164", condition: specialCasingConditionNone}, + 0x166: {lower: "\u0167", upper: "\u0166", condition: specialCasingConditionNone}, + 0x167: {lower: "\u0167", upper: "\u0166", condition: specialCasingConditionNone}, + 0x168: {lower: "\u0169", upper: "\u0168", condition: specialCasingConditionNone}, + 0x169: {lower: "\u0169", upper: "\u0168", condition: specialCasingConditionNone}, + 0x16A: {lower: "\u016B", upper: "\u016A", condition: specialCasingConditionNone}, + 0x16B: {lower: "\u016B", upper: "\u016A", condition: specialCasingConditionNone}, + 0x16C: {lower: "\u016D", upper: "\u016C", condition: specialCasingConditionNone}, + 0x16D: {lower: "\u016D", upper: "\u016C", condition: specialCasingConditionNone}, + 0x16E: {lower: "\u016F", upper: "\u016E", condition: specialCasingConditionNone}, + 0x16F: {lower: "\u016F", upper: "\u016E", condition: specialCasingConditionNone}, + 0x170: {lower: "\u0171", upper: "\u0170", condition: specialCasingConditionNone}, + 0x171: {lower: "\u0171", upper: "\u0170", condition: specialCasingConditionNone}, + 0x172: {lower: "\u0173", upper: "\u0172", condition: specialCasingConditionNone}, + 0x173: {lower: "\u0173", upper: "\u0172", condition: specialCasingConditionNone}, + 0x174: {lower: "\u0175", upper: "\u0174", condition: specialCasingConditionNone}, + 0x175: {lower: "\u0175", upper: "\u0174", condition: specialCasingConditionNone}, + 0x176: {lower: "\u0177", upper: "\u0176", condition: specialCasingConditionNone}, + 0x177: {lower: "\u0177", upper: "\u0176", condition: specialCasingConditionNone}, + 0x178: {lower: "\u00FF", upper: "\u0178", condition: specialCasingConditionNone}, + 0x179: {lower: "\u017A", upper: "\u0179", condition: specialCasingConditionNone}, + 0x17A: {lower: "\u017A", upper: "\u0179", condition: specialCasingConditionNone}, + 0x17B: {lower: "\u017C", upper: "\u017B", condition: specialCasingConditionNone}, + 0x17C: {lower: "\u017C", upper: "\u017B", condition: specialCasingConditionNone}, + 0x17D: {lower: "\u017E", upper: "\u017D", condition: specialCasingConditionNone}, + 0x17E: {lower: "\u017E", upper: "\u017D", condition: specialCasingConditionNone}, + 0x17F: {lower: "\u017F", upper: "\u0053", condition: specialCasingConditionNone}, + 0x180: {lower: "\u0180", upper: "\u0243", condition: specialCasingConditionNone}, + 0x181: {lower: "\u0253", upper: "\u0181", condition: specialCasingConditionNone}, + 0x182: {lower: "\u0183", upper: "\u0182", condition: specialCasingConditionNone}, + 0x183: {lower: "\u0183", upper: "\u0182", condition: specialCasingConditionNone}, + 0x184: {lower: "\u0185", upper: "\u0184", condition: specialCasingConditionNone}, + 0x185: {lower: "\u0185", upper: "\u0184", condition: specialCasingConditionNone}, + 0x186: {lower: "\u0254", upper: "\u0186", condition: specialCasingConditionNone}, + 0x187: {lower: "\u0188", upper: "\u0187", condition: specialCasingConditionNone}, + 0x188: {lower: "\u0188", upper: "\u0187", condition: specialCasingConditionNone}, + 0x189: {lower: "\u0256", upper: "\u0189", condition: specialCasingConditionNone}, + 0x18A: {lower: "\u0257", upper: "\u018A", condition: specialCasingConditionNone}, + 0x18B: {lower: "\u018C", upper: "\u018B", condition: specialCasingConditionNone}, + 0x18C: {lower: "\u018C", upper: "\u018B", condition: specialCasingConditionNone}, + 0x18E: {lower: "\u01DD", upper: "\u018E", condition: specialCasingConditionNone}, + 0x18F: {lower: "\u0259", upper: "\u018F", condition: specialCasingConditionNone}, + 0x190: {lower: "\u025B", upper: "\u0190", condition: specialCasingConditionNone}, + 0x191: {lower: "\u0192", upper: "\u0191", condition: specialCasingConditionNone}, + 0x192: {lower: "\u0192", upper: "\u0191", condition: specialCasingConditionNone}, + 0x193: {lower: "\u0260", upper: "\u0193", condition: specialCasingConditionNone}, + 0x194: {lower: "\u0263", upper: "\u0194", condition: specialCasingConditionNone}, + 0x195: {lower: "\u0195", upper: "\u01F6", condition: specialCasingConditionNone}, + 0x196: {lower: "\u0269", upper: "\u0196", condition: specialCasingConditionNone}, + 0x197: {lower: "\u0268", upper: "\u0197", condition: specialCasingConditionNone}, + 0x198: {lower: "\u0199", upper: "\u0198", condition: specialCasingConditionNone}, + 0x199: {lower: "\u0199", upper: "\u0198", condition: specialCasingConditionNone}, + 0x19A: {lower: "\u019A", upper: "\u023D", condition: specialCasingConditionNone}, + 0x19C: {lower: "\u026F", upper: "\u019C", condition: specialCasingConditionNone}, + 0x19D: {lower: "\u0272", upper: "\u019D", condition: specialCasingConditionNone}, + 0x19E: {lower: "\u019E", upper: "\u0220", condition: specialCasingConditionNone}, + 0x19F: {lower: "\u0275", upper: "\u019F", condition: specialCasingConditionNone}, + 0x1A0: {lower: "\u01A1", upper: "\u01A0", condition: specialCasingConditionNone}, + 0x1A1: {lower: "\u01A1", upper: "\u01A0", condition: specialCasingConditionNone}, + 0x1A2: {lower: "\u01A3", upper: "\u01A2", condition: specialCasingConditionNone}, + 0x1A3: {lower: "\u01A3", upper: "\u01A2", condition: specialCasingConditionNone}, + 0x1A4: {lower: "\u01A5", upper: "\u01A4", condition: specialCasingConditionNone}, + 0x1A5: {lower: "\u01A5", upper: "\u01A4", condition: specialCasingConditionNone}, + 0x1A6: {lower: "\u0280", upper: "\u01A6", condition: specialCasingConditionNone}, + 0x1A7: {lower: "\u01A8", upper: "\u01A7", condition: specialCasingConditionNone}, + 0x1A8: {lower: "\u01A8", upper: "\u01A7", condition: specialCasingConditionNone}, + 0x1A9: {lower: "\u0283", upper: "\u01A9", condition: specialCasingConditionNone}, + 0x1AC: {lower: "\u01AD", upper: "\u01AC", condition: specialCasingConditionNone}, + 0x1AD: {lower: "\u01AD", upper: "\u01AC", condition: specialCasingConditionNone}, + 0x1AE: {lower: "\u0288", upper: "\u01AE", condition: specialCasingConditionNone}, + 0x1AF: {lower: "\u01B0", upper: "\u01AF", condition: specialCasingConditionNone}, + 0x1B0: {lower: "\u01B0", upper: "\u01AF", condition: specialCasingConditionNone}, + 0x1B1: {lower: "\u028A", upper: "\u01B1", condition: specialCasingConditionNone}, + 0x1B2: {lower: "\u028B", upper: "\u01B2", condition: specialCasingConditionNone}, + 0x1B3: {lower: "\u01B4", upper: "\u01B3", condition: specialCasingConditionNone}, + 0x1B4: {lower: "\u01B4", upper: "\u01B3", condition: specialCasingConditionNone}, + 0x1B5: {lower: "\u01B6", upper: "\u01B5", condition: specialCasingConditionNone}, + 0x1B6: {lower: "\u01B6", upper: "\u01B5", condition: specialCasingConditionNone}, + 0x1B7: {lower: "\u0292", upper: "\u01B7", condition: specialCasingConditionNone}, + 0x1B8: {lower: "\u01B9", upper: "\u01B8", condition: specialCasingConditionNone}, + 0x1B9: {lower: "\u01B9", upper: "\u01B8", condition: specialCasingConditionNone}, + 0x1BC: {lower: "\u01BD", upper: "\u01BC", condition: specialCasingConditionNone}, + 0x1BD: {lower: "\u01BD", upper: "\u01BC", condition: specialCasingConditionNone}, + 0x1BF: {lower: "\u01BF", upper: "\u01F7", condition: specialCasingConditionNone}, + 0x1C4: {lower: "\u01C6", upper: "\u01C4", condition: specialCasingConditionNone}, + 0x1C5: {lower: "\u01C6", upper: "\u01C4", condition: specialCasingConditionNone}, + 0x1C6: {lower: "\u01C6", upper: "\u01C4", condition: specialCasingConditionNone}, + 0x1C7: {lower: "\u01C9", upper: "\u01C7", condition: specialCasingConditionNone}, + 0x1C8: {lower: "\u01C9", upper: "\u01C7", condition: specialCasingConditionNone}, + 0x1C9: {lower: "\u01C9", upper: "\u01C7", condition: specialCasingConditionNone}, + 0x1CA: {lower: "\u01CC", upper: "\u01CA", condition: specialCasingConditionNone}, + 0x1CB: {lower: "\u01CC", upper: "\u01CA", condition: specialCasingConditionNone}, + 0x1CC: {lower: "\u01CC", upper: "\u01CA", condition: specialCasingConditionNone}, + 0x1CD: {lower: "\u01CE", upper: "\u01CD", condition: specialCasingConditionNone}, + 0x1CE: {lower: "\u01CE", upper: "\u01CD", condition: specialCasingConditionNone}, + 0x1CF: {lower: "\u01D0", upper: "\u01CF", condition: specialCasingConditionNone}, + 0x1D0: {lower: "\u01D0", upper: "\u01CF", condition: specialCasingConditionNone}, + 0x1D1: {lower: "\u01D2", upper: "\u01D1", condition: specialCasingConditionNone}, + 0x1D2: {lower: "\u01D2", upper: "\u01D1", condition: specialCasingConditionNone}, + 0x1D3: {lower: "\u01D4", upper: "\u01D3", condition: specialCasingConditionNone}, + 0x1D4: {lower: "\u01D4", upper: "\u01D3", condition: specialCasingConditionNone}, + 0x1D5: {lower: "\u01D6", upper: "\u01D5", condition: specialCasingConditionNone}, + 0x1D6: {lower: "\u01D6", upper: "\u01D5", condition: specialCasingConditionNone}, + 0x1D7: {lower: "\u01D8", upper: "\u01D7", condition: specialCasingConditionNone}, + 0x1D8: {lower: "\u01D8", upper: "\u01D7", condition: specialCasingConditionNone}, + 0x1D9: {lower: "\u01DA", upper: "\u01D9", condition: specialCasingConditionNone}, + 0x1DA: {lower: "\u01DA", upper: "\u01D9", condition: specialCasingConditionNone}, + 0x1DB: {lower: "\u01DC", upper: "\u01DB", condition: specialCasingConditionNone}, + 0x1DC: {lower: "\u01DC", upper: "\u01DB", condition: specialCasingConditionNone}, + 0x1DD: {lower: "\u01DD", upper: "\u018E", condition: specialCasingConditionNone}, + 0x1DE: {lower: "\u01DF", upper: "\u01DE", condition: specialCasingConditionNone}, + 0x1DF: {lower: "\u01DF", upper: "\u01DE", condition: specialCasingConditionNone}, + 0x1E0: {lower: "\u01E1", upper: "\u01E0", condition: specialCasingConditionNone}, + 0x1E1: {lower: "\u01E1", upper: "\u01E0", condition: specialCasingConditionNone}, + 0x1E2: {lower: "\u01E3", upper: "\u01E2", condition: specialCasingConditionNone}, + 0x1E3: {lower: "\u01E3", upper: "\u01E2", condition: specialCasingConditionNone}, + 0x1E4: {lower: "\u01E5", upper: "\u01E4", condition: specialCasingConditionNone}, + 0x1E5: {lower: "\u01E5", upper: "\u01E4", condition: specialCasingConditionNone}, + 0x1E6: {lower: "\u01E7", upper: "\u01E6", condition: specialCasingConditionNone}, + 0x1E7: {lower: "\u01E7", upper: "\u01E6", condition: specialCasingConditionNone}, + 0x1E8: {lower: "\u01E9", upper: "\u01E8", condition: specialCasingConditionNone}, + 0x1E9: {lower: "\u01E9", upper: "\u01E8", condition: specialCasingConditionNone}, + 0x1EA: {lower: "\u01EB", upper: "\u01EA", condition: specialCasingConditionNone}, + 0x1EB: {lower: "\u01EB", upper: "\u01EA", condition: specialCasingConditionNone}, + 0x1EC: {lower: "\u01ED", upper: "\u01EC", condition: specialCasingConditionNone}, + 0x1ED: {lower: "\u01ED", upper: "\u01EC", condition: specialCasingConditionNone}, + 0x1EE: {lower: "\u01EF", upper: "\u01EE", condition: specialCasingConditionNone}, + 0x1EF: {lower: "\u01EF", upper: "\u01EE", condition: specialCasingConditionNone}, + 0x1F0: {lower: "\u01F0", upper: "\u004A\u030C", condition: specialCasingConditionNone}, + 0x1F1: {lower: "\u01F3", upper: "\u01F1", condition: specialCasingConditionNone}, + 0x1F2: {lower: "\u01F3", upper: "\u01F1", condition: specialCasingConditionNone}, + 0x1F3: {lower: "\u01F3", upper: "\u01F1", condition: specialCasingConditionNone}, + 0x1F4: {lower: "\u01F5", upper: "\u01F4", condition: specialCasingConditionNone}, + 0x1F5: {lower: "\u01F5", upper: "\u01F4", condition: specialCasingConditionNone}, + 0x1F6: {lower: "\u0195", upper: "\u01F6", condition: specialCasingConditionNone}, + 0x1F7: {lower: "\u01BF", upper: "\u01F7", condition: specialCasingConditionNone}, + 0x1F8: {lower: "\u01F9", upper: "\u01F8", condition: specialCasingConditionNone}, + 0x1F9: {lower: "\u01F9", upper: "\u01F8", condition: specialCasingConditionNone}, + 0x1FA: {lower: "\u01FB", upper: "\u01FA", condition: specialCasingConditionNone}, + 0x1FB: {lower: "\u01FB", upper: "\u01FA", condition: specialCasingConditionNone}, + 0x1FC: {lower: "\u01FD", upper: "\u01FC", condition: specialCasingConditionNone}, + 0x1FD: {lower: "\u01FD", upper: "\u01FC", condition: specialCasingConditionNone}, + 0x1FE: {lower: "\u01FF", upper: "\u01FE", condition: specialCasingConditionNone}, + 0x1FF: {lower: "\u01FF", upper: "\u01FE", condition: specialCasingConditionNone}, + 0x200: {lower: "\u0201", upper: "\u0200", condition: specialCasingConditionNone}, + 0x201: {lower: "\u0201", upper: "\u0200", condition: specialCasingConditionNone}, + 0x202: {lower: "\u0203", upper: "\u0202", condition: specialCasingConditionNone}, + 0x203: {lower: "\u0203", upper: "\u0202", condition: specialCasingConditionNone}, + 0x204: {lower: "\u0205", upper: "\u0204", condition: specialCasingConditionNone}, + 0x205: {lower: "\u0205", upper: "\u0204", condition: specialCasingConditionNone}, + 0x206: {lower: "\u0207", upper: "\u0206", condition: specialCasingConditionNone}, + 0x207: {lower: "\u0207", upper: "\u0206", condition: specialCasingConditionNone}, + 0x208: {lower: "\u0209", upper: "\u0208", condition: specialCasingConditionNone}, + 0x209: {lower: "\u0209", upper: "\u0208", condition: specialCasingConditionNone}, + 0x20A: {lower: "\u020B", upper: "\u020A", condition: specialCasingConditionNone}, + 0x20B: {lower: "\u020B", upper: "\u020A", condition: specialCasingConditionNone}, + 0x20C: {lower: "\u020D", upper: "\u020C", condition: specialCasingConditionNone}, + 0x20D: {lower: "\u020D", upper: "\u020C", condition: specialCasingConditionNone}, + 0x20E: {lower: "\u020F", upper: "\u020E", condition: specialCasingConditionNone}, + 0x20F: {lower: "\u020F", upper: "\u020E", condition: specialCasingConditionNone}, + 0x210: {lower: "\u0211", upper: "\u0210", condition: specialCasingConditionNone}, + 0x211: {lower: "\u0211", upper: "\u0210", condition: specialCasingConditionNone}, + 0x212: {lower: "\u0213", upper: "\u0212", condition: specialCasingConditionNone}, + 0x213: {lower: "\u0213", upper: "\u0212", condition: specialCasingConditionNone}, + 0x214: {lower: "\u0215", upper: "\u0214", condition: specialCasingConditionNone}, + 0x215: {lower: "\u0215", upper: "\u0214", condition: specialCasingConditionNone}, + 0x216: {lower: "\u0217", upper: "\u0216", condition: specialCasingConditionNone}, + 0x217: {lower: "\u0217", upper: "\u0216", condition: specialCasingConditionNone}, + 0x218: {lower: "\u0219", upper: "\u0218", condition: specialCasingConditionNone}, + 0x219: {lower: "\u0219", upper: "\u0218", condition: specialCasingConditionNone}, + 0x21A: {lower: "\u021B", upper: "\u021A", condition: specialCasingConditionNone}, + 0x21B: {lower: "\u021B", upper: "\u021A", condition: specialCasingConditionNone}, + 0x21C: {lower: "\u021D", upper: "\u021C", condition: specialCasingConditionNone}, + 0x21D: {lower: "\u021D", upper: "\u021C", condition: specialCasingConditionNone}, + 0x21E: {lower: "\u021F", upper: "\u021E", condition: specialCasingConditionNone}, + 0x21F: {lower: "\u021F", upper: "\u021E", condition: specialCasingConditionNone}, + 0x220: {lower: "\u019E", upper: "\u0220", condition: specialCasingConditionNone}, + 0x222: {lower: "\u0223", upper: "\u0222", condition: specialCasingConditionNone}, + 0x223: {lower: "\u0223", upper: "\u0222", condition: specialCasingConditionNone}, + 0x224: {lower: "\u0225", upper: "\u0224", condition: specialCasingConditionNone}, + 0x225: {lower: "\u0225", upper: "\u0224", condition: specialCasingConditionNone}, + 0x226: {lower: "\u0227", upper: "\u0226", condition: specialCasingConditionNone}, + 0x227: {lower: "\u0227", upper: "\u0226", condition: specialCasingConditionNone}, + 0x228: {lower: "\u0229", upper: "\u0228", condition: specialCasingConditionNone}, + 0x229: {lower: "\u0229", upper: "\u0228", condition: specialCasingConditionNone}, + 0x22A: {lower: "\u022B", upper: "\u022A", condition: specialCasingConditionNone}, + 0x22B: {lower: "\u022B", upper: "\u022A", condition: specialCasingConditionNone}, + 0x22C: {lower: "\u022D", upper: "\u022C", condition: specialCasingConditionNone}, + 0x22D: {lower: "\u022D", upper: "\u022C", condition: specialCasingConditionNone}, + 0x22E: {lower: "\u022F", upper: "\u022E", condition: specialCasingConditionNone}, + 0x22F: {lower: "\u022F", upper: "\u022E", condition: specialCasingConditionNone}, + 0x230: {lower: "\u0231", upper: "\u0230", condition: specialCasingConditionNone}, + 0x231: {lower: "\u0231", upper: "\u0230", condition: specialCasingConditionNone}, + 0x232: {lower: "\u0233", upper: "\u0232", condition: specialCasingConditionNone}, + 0x233: {lower: "\u0233", upper: "\u0232", condition: specialCasingConditionNone}, + 0x23A: {lower: "\u2C65", upper: "\u023A", condition: specialCasingConditionNone}, + 0x23B: {lower: "\u023C", upper: "\u023B", condition: specialCasingConditionNone}, + 0x23C: {lower: "\u023C", upper: "\u023B", condition: specialCasingConditionNone}, + 0x23D: {lower: "\u019A", upper: "\u023D", condition: specialCasingConditionNone}, + 0x23E: {lower: "\u2C66", upper: "\u023E", condition: specialCasingConditionNone}, + 0x23F: {lower: "\u023F", upper: "\u2C7E", condition: specialCasingConditionNone}, + 0x240: {lower: "\u0240", upper: "\u2C7F", condition: specialCasingConditionNone}, + 0x241: {lower: "\u0242", upper: "\u0241", condition: specialCasingConditionNone}, + 0x242: {lower: "\u0242", upper: "\u0241", condition: specialCasingConditionNone}, + 0x243: {lower: "\u0180", upper: "\u0243", condition: specialCasingConditionNone}, + 0x244: {lower: "\u0289", upper: "\u0244", condition: specialCasingConditionNone}, + 0x245: {lower: "\u028C", upper: "\u0245", condition: specialCasingConditionNone}, + 0x246: {lower: "\u0247", upper: "\u0246", condition: specialCasingConditionNone}, + 0x247: {lower: "\u0247", upper: "\u0246", condition: specialCasingConditionNone}, + 0x248: {lower: "\u0249", upper: "\u0248", condition: specialCasingConditionNone}, + 0x249: {lower: "\u0249", upper: "\u0248", condition: specialCasingConditionNone}, + 0x24A: {lower: "\u024B", upper: "\u024A", condition: specialCasingConditionNone}, + 0x24B: {lower: "\u024B", upper: "\u024A", condition: specialCasingConditionNone}, + 0x24C: {lower: "\u024D", upper: "\u024C", condition: specialCasingConditionNone}, + 0x24D: {lower: "\u024D", upper: "\u024C", condition: specialCasingConditionNone}, + 0x24E: {lower: "\u024F", upper: "\u024E", condition: specialCasingConditionNone}, + 0x24F: {lower: "\u024F", upper: "\u024E", condition: specialCasingConditionNone}, + 0x250: {lower: "\u0250", upper: "\u2C6F", condition: specialCasingConditionNone}, + 0x251: {lower: "\u0251", upper: "\u2C6D", condition: specialCasingConditionNone}, + 0x252: {lower: "\u0252", upper: "\u2C70", condition: specialCasingConditionNone}, + 0x253: {lower: "\u0253", upper: "\u0181", condition: specialCasingConditionNone}, + 0x254: {lower: "\u0254", upper: "\u0186", condition: specialCasingConditionNone}, + 0x256: {lower: "\u0256", upper: "\u0189", condition: specialCasingConditionNone}, + 0x257: {lower: "\u0257", upper: "\u018A", condition: specialCasingConditionNone}, + 0x259: {lower: "\u0259", upper: "\u018F", condition: specialCasingConditionNone}, + 0x25B: {lower: "\u025B", upper: "\u0190", condition: specialCasingConditionNone}, + 0x25C: {lower: "\u025C", upper: "\uA7AB", condition: specialCasingConditionNone}, + 0x260: {lower: "\u0260", upper: "\u0193", condition: specialCasingConditionNone}, + 0x261: {lower: "\u0261", upper: "\uA7AC", condition: specialCasingConditionNone}, + 0x263: {lower: "\u0263", upper: "\u0194", condition: specialCasingConditionNone}, + 0x265: {lower: "\u0265", upper: "\uA78D", condition: specialCasingConditionNone}, + 0x266: {lower: "\u0266", upper: "\uA7AA", condition: specialCasingConditionNone}, + 0x268: {lower: "\u0268", upper: "\u0197", condition: specialCasingConditionNone}, + 0x269: {lower: "\u0269", upper: "\u0196", condition: specialCasingConditionNone}, + 0x26A: {lower: "\u026A", upper: "\uA7AE", condition: specialCasingConditionNone}, + 0x26B: {lower: "\u026B", upper: "\u2C62", condition: specialCasingConditionNone}, + 0x26C: {lower: "\u026C", upper: "\uA7AD", condition: specialCasingConditionNone}, + 0x26F: {lower: "\u026F", upper: "\u019C", condition: specialCasingConditionNone}, + 0x271: {lower: "\u0271", upper: "\u2C6E", condition: specialCasingConditionNone}, + 0x272: {lower: "\u0272", upper: "\u019D", condition: specialCasingConditionNone}, + 0x275: {lower: "\u0275", upper: "\u019F", condition: specialCasingConditionNone}, + 0x27D: {lower: "\u027D", upper: "\u2C64", condition: specialCasingConditionNone}, + 0x280: {lower: "\u0280", upper: "\u01A6", condition: specialCasingConditionNone}, + 0x282: {lower: "\u0282", upper: "\uA7C5", condition: specialCasingConditionNone}, + 0x283: {lower: "\u0283", upper: "\u01A9", condition: specialCasingConditionNone}, + 0x287: {lower: "\u0287", upper: "\uA7B1", condition: specialCasingConditionNone}, + 0x288: {lower: "\u0288", upper: "\u01AE", condition: specialCasingConditionNone}, + 0x289: {lower: "\u0289", upper: "\u0244", condition: specialCasingConditionNone}, + 0x28A: {lower: "\u028A", upper: "\u01B1", condition: specialCasingConditionNone}, + 0x28B: {lower: "\u028B", upper: "\u01B2", condition: specialCasingConditionNone}, + 0x28C: {lower: "\u028C", upper: "\u0245", condition: specialCasingConditionNone}, + 0x292: {lower: "\u0292", upper: "\u01B7", condition: specialCasingConditionNone}, + 0x29D: {lower: "\u029D", upper: "\uA7B2", condition: specialCasingConditionNone}, + 0x29E: {lower: "\u029E", upper: "\uA7B0", condition: specialCasingConditionNone}, + 0x345: {lower: "\u0345", upper: "\u0399", condition: specialCasingConditionNone}, + 0x370: {lower: "\u0371", upper: "\u0370", condition: specialCasingConditionNone}, + 0x371: {lower: "\u0371", upper: "\u0370", condition: specialCasingConditionNone}, + 0x372: {lower: "\u0373", upper: "\u0372", condition: specialCasingConditionNone}, + 0x373: {lower: "\u0373", upper: "\u0372", condition: specialCasingConditionNone}, + 0x376: {lower: "\u0377", upper: "\u0376", condition: specialCasingConditionNone}, + 0x377: {lower: "\u0377", upper: "\u0376", condition: specialCasingConditionNone}, + 0x37B: {lower: "\u037B", upper: "\u03FD", condition: specialCasingConditionNone}, + 0x37C: {lower: "\u037C", upper: "\u03FE", condition: specialCasingConditionNone}, + 0x37D: {lower: "\u037D", upper: "\u03FF", condition: specialCasingConditionNone}, + 0x37F: {lower: "\u03F3", upper: "\u037F", condition: specialCasingConditionNone}, + 0x386: {lower: "\u03AC", upper: "\u0386", condition: specialCasingConditionNone}, + 0x388: {lower: "\u03AD", upper: "\u0388", condition: specialCasingConditionNone}, + 0x389: {lower: "\u03AE", upper: "\u0389", condition: specialCasingConditionNone}, + 0x38A: {lower: "\u03AF", upper: "\u038A", condition: specialCasingConditionNone}, + 0x38C: {lower: "\u03CC", upper: "\u038C", condition: specialCasingConditionNone}, + 0x38E: {lower: "\u03CD", upper: "\u038E", condition: specialCasingConditionNone}, + 0x38F: {lower: "\u03CE", upper: "\u038F", condition: specialCasingConditionNone}, + 0x390: {lower: "\u0390", upper: "\u0399\u0308\u0301", condition: specialCasingConditionNone}, + 0x391: {lower: "\u03B1", upper: "\u0391", condition: specialCasingConditionNone}, + 0x392: {lower: "\u03B2", upper: "\u0392", condition: specialCasingConditionNone}, + 0x393: {lower: "\u03B3", upper: "\u0393", condition: specialCasingConditionNone}, + 0x394: {lower: "\u03B4", upper: "\u0394", condition: specialCasingConditionNone}, + 0x395: {lower: "\u03B5", upper: "\u0395", condition: specialCasingConditionNone}, + 0x396: {lower: "\u03B6", upper: "\u0396", condition: specialCasingConditionNone}, + 0x397: {lower: "\u03B7", upper: "\u0397", condition: specialCasingConditionNone}, + 0x398: {lower: "\u03B8", upper: "\u0398", condition: specialCasingConditionNone}, + 0x399: {lower: "\u03B9", upper: "\u0399", condition: specialCasingConditionNone}, + 0x39A: {lower: "\u03BA", upper: "\u039A", condition: specialCasingConditionNone}, + 0x39B: {lower: "\u03BB", upper: "\u039B", condition: specialCasingConditionNone}, + 0x39C: {lower: "\u03BC", upper: "\u039C", condition: specialCasingConditionNone}, + 0x39D: {lower: "\u03BD", upper: "\u039D", condition: specialCasingConditionNone}, + 0x39E: {lower: "\u03BE", upper: "\u039E", condition: specialCasingConditionNone}, + 0x39F: {lower: "\u03BF", upper: "\u039F", condition: specialCasingConditionNone}, + 0x3A0: {lower: "\u03C0", upper: "\u03A0", condition: specialCasingConditionNone}, + 0x3A1: {lower: "\u03C1", upper: "\u03A1", condition: specialCasingConditionNone}, + 0x3A3: {lower: "\u03C3", upper: "\u03A3", conditionalLower: "\u03C2", condition: specialCasingConditionFinalSigma}, + 0x3A4: {lower: "\u03C4", upper: "\u03A4", condition: specialCasingConditionNone}, + 0x3A5: {lower: "\u03C5", upper: "\u03A5", condition: specialCasingConditionNone}, + 0x3A6: {lower: "\u03C6", upper: "\u03A6", condition: specialCasingConditionNone}, + 0x3A7: {lower: "\u03C7", upper: "\u03A7", condition: specialCasingConditionNone}, + 0x3A8: {lower: "\u03C8", upper: "\u03A8", condition: specialCasingConditionNone}, + 0x3A9: {lower: "\u03C9", upper: "\u03A9", condition: specialCasingConditionNone}, + 0x3AA: {lower: "\u03CA", upper: "\u03AA", condition: specialCasingConditionNone}, + 0x3AB: {lower: "\u03CB", upper: "\u03AB", condition: specialCasingConditionNone}, + 0x3AC: {lower: "\u03AC", upper: "\u0386", condition: specialCasingConditionNone}, + 0x3AD: {lower: "\u03AD", upper: "\u0388", condition: specialCasingConditionNone}, + 0x3AE: {lower: "\u03AE", upper: "\u0389", condition: specialCasingConditionNone}, + 0x3AF: {lower: "\u03AF", upper: "\u038A", condition: specialCasingConditionNone}, + 0x3B0: {lower: "\u03B0", upper: "\u03A5\u0308\u0301", condition: specialCasingConditionNone}, + 0x3B1: {lower: "\u03B1", upper: "\u0391", condition: specialCasingConditionNone}, + 0x3B2: {lower: "\u03B2", upper: "\u0392", condition: specialCasingConditionNone}, + 0x3B3: {lower: "\u03B3", upper: "\u0393", condition: specialCasingConditionNone}, + 0x3B4: {lower: "\u03B4", upper: "\u0394", condition: specialCasingConditionNone}, + 0x3B5: {lower: "\u03B5", upper: "\u0395", condition: specialCasingConditionNone}, + 0x3B6: {lower: "\u03B6", upper: "\u0396", condition: specialCasingConditionNone}, + 0x3B7: {lower: "\u03B7", upper: "\u0397", condition: specialCasingConditionNone}, + 0x3B8: {lower: "\u03B8", upper: "\u0398", condition: specialCasingConditionNone}, + 0x3B9: {lower: "\u03B9", upper: "\u0399", condition: specialCasingConditionNone}, + 0x3BA: {lower: "\u03BA", upper: "\u039A", condition: specialCasingConditionNone}, + 0x3BB: {lower: "\u03BB", upper: "\u039B", condition: specialCasingConditionNone}, + 0x3BC: {lower: "\u03BC", upper: "\u039C", condition: specialCasingConditionNone}, + 0x3BD: {lower: "\u03BD", upper: "\u039D", condition: specialCasingConditionNone}, + 0x3BE: {lower: "\u03BE", upper: "\u039E", condition: specialCasingConditionNone}, + 0x3BF: {lower: "\u03BF", upper: "\u039F", condition: specialCasingConditionNone}, + 0x3C0: {lower: "\u03C0", upper: "\u03A0", condition: specialCasingConditionNone}, + 0x3C1: {lower: "\u03C1", upper: "\u03A1", condition: specialCasingConditionNone}, + 0x3C2: {lower: "\u03C2", upper: "\u03A3", condition: specialCasingConditionNone}, + 0x3C3: {lower: "\u03C3", upper: "\u03A3", condition: specialCasingConditionNone}, + 0x3C4: {lower: "\u03C4", upper: "\u03A4", condition: specialCasingConditionNone}, + 0x3C5: {lower: "\u03C5", upper: "\u03A5", condition: specialCasingConditionNone}, + 0x3C6: {lower: "\u03C6", upper: "\u03A6", condition: specialCasingConditionNone}, + 0x3C7: {lower: "\u03C7", upper: "\u03A7", condition: specialCasingConditionNone}, + 0x3C8: {lower: "\u03C8", upper: "\u03A8", condition: specialCasingConditionNone}, + 0x3C9: {lower: "\u03C9", upper: "\u03A9", condition: specialCasingConditionNone}, + 0x3CA: {lower: "\u03CA", upper: "\u03AA", condition: specialCasingConditionNone}, + 0x3CB: {lower: "\u03CB", upper: "\u03AB", condition: specialCasingConditionNone}, + 0x3CC: {lower: "\u03CC", upper: "\u038C", condition: specialCasingConditionNone}, + 0x3CD: {lower: "\u03CD", upper: "\u038E", condition: specialCasingConditionNone}, + 0x3CE: {lower: "\u03CE", upper: "\u038F", condition: specialCasingConditionNone}, + 0x3CF: {lower: "\u03D7", upper: "\u03CF", condition: specialCasingConditionNone}, + 0x3D0: {lower: "\u03D0", upper: "\u0392", condition: specialCasingConditionNone}, + 0x3D1: {lower: "\u03D1", upper: "\u0398", condition: specialCasingConditionNone}, + 0x3D5: {lower: "\u03D5", upper: "\u03A6", condition: specialCasingConditionNone}, + 0x3D6: {lower: "\u03D6", upper: "\u03A0", condition: specialCasingConditionNone}, + 0x3D7: {lower: "\u03D7", upper: "\u03CF", condition: specialCasingConditionNone}, + 0x3D8: {lower: "\u03D9", upper: "\u03D8", condition: specialCasingConditionNone}, + 0x3D9: {lower: "\u03D9", upper: "\u03D8", condition: specialCasingConditionNone}, + 0x3DA: {lower: "\u03DB", upper: "\u03DA", condition: specialCasingConditionNone}, + 0x3DB: {lower: "\u03DB", upper: "\u03DA", condition: specialCasingConditionNone}, + 0x3DC: {lower: "\u03DD", upper: "\u03DC", condition: specialCasingConditionNone}, + 0x3DD: {lower: "\u03DD", upper: "\u03DC", condition: specialCasingConditionNone}, + 0x3DE: {lower: "\u03DF", upper: "\u03DE", condition: specialCasingConditionNone}, + 0x3DF: {lower: "\u03DF", upper: "\u03DE", condition: specialCasingConditionNone}, + 0x3E0: {lower: "\u03E1", upper: "\u03E0", condition: specialCasingConditionNone}, + 0x3E1: {lower: "\u03E1", upper: "\u03E0", condition: specialCasingConditionNone}, + 0x3E2: {lower: "\u03E3", upper: "\u03E2", condition: specialCasingConditionNone}, + 0x3E3: {lower: "\u03E3", upper: "\u03E2", condition: specialCasingConditionNone}, + 0x3E4: {lower: "\u03E5", upper: "\u03E4", condition: specialCasingConditionNone}, + 0x3E5: {lower: "\u03E5", upper: "\u03E4", condition: specialCasingConditionNone}, + 0x3E6: {lower: "\u03E7", upper: "\u03E6", condition: specialCasingConditionNone}, + 0x3E7: {lower: "\u03E7", upper: "\u03E6", condition: specialCasingConditionNone}, + 0x3E8: {lower: "\u03E9", upper: "\u03E8", condition: specialCasingConditionNone}, + 0x3E9: {lower: "\u03E9", upper: "\u03E8", condition: specialCasingConditionNone}, + 0x3EA: {lower: "\u03EB", upper: "\u03EA", condition: specialCasingConditionNone}, + 0x3EB: {lower: "\u03EB", upper: "\u03EA", condition: specialCasingConditionNone}, + 0x3EC: {lower: "\u03ED", upper: "\u03EC", condition: specialCasingConditionNone}, + 0x3ED: {lower: "\u03ED", upper: "\u03EC", condition: specialCasingConditionNone}, + 0x3EE: {lower: "\u03EF", upper: "\u03EE", condition: specialCasingConditionNone}, + 0x3EF: {lower: "\u03EF", upper: "\u03EE", condition: specialCasingConditionNone}, + 0x3F0: {lower: "\u03F0", upper: "\u039A", condition: specialCasingConditionNone}, + 0x3F1: {lower: "\u03F1", upper: "\u03A1", condition: specialCasingConditionNone}, + 0x3F2: {lower: "\u03F2", upper: "\u03F9", condition: specialCasingConditionNone}, + 0x3F3: {lower: "\u03F3", upper: "\u037F", condition: specialCasingConditionNone}, + 0x3F4: {lower: "\u03B8", upper: "\u03F4", condition: specialCasingConditionNone}, + 0x3F5: {lower: "\u03F5", upper: "\u0395", condition: specialCasingConditionNone}, + 0x3F7: {lower: "\u03F8", upper: "\u03F7", condition: specialCasingConditionNone}, + 0x3F8: {lower: "\u03F8", upper: "\u03F7", condition: specialCasingConditionNone}, + 0x3F9: {lower: "\u03F2", upper: "\u03F9", condition: specialCasingConditionNone}, + 0x3FA: {lower: "\u03FB", upper: "\u03FA", condition: specialCasingConditionNone}, + 0x3FB: {lower: "\u03FB", upper: "\u03FA", condition: specialCasingConditionNone}, + 0x3FD: {lower: "\u037B", upper: "\u03FD", condition: specialCasingConditionNone}, + 0x3FE: {lower: "\u037C", upper: "\u03FE", condition: specialCasingConditionNone}, + 0x3FF: {lower: "\u037D", upper: "\u03FF", condition: specialCasingConditionNone}, + 0x400: {lower: "\u0450", upper: "\u0400", condition: specialCasingConditionNone}, + 0x401: {lower: "\u0451", upper: "\u0401", condition: specialCasingConditionNone}, + 0x402: {lower: "\u0452", upper: "\u0402", condition: specialCasingConditionNone}, + 0x403: {lower: "\u0453", upper: "\u0403", condition: specialCasingConditionNone}, + 0x404: {lower: "\u0454", upper: "\u0404", condition: specialCasingConditionNone}, + 0x405: {lower: "\u0455", upper: "\u0405", condition: specialCasingConditionNone}, + 0x406: {lower: "\u0456", upper: "\u0406", condition: specialCasingConditionNone}, + 0x407: {lower: "\u0457", upper: "\u0407", condition: specialCasingConditionNone}, + 0x408: {lower: "\u0458", upper: "\u0408", condition: specialCasingConditionNone}, + 0x409: {lower: "\u0459", upper: "\u0409", condition: specialCasingConditionNone}, + 0x40A: {lower: "\u045A", upper: "\u040A", condition: specialCasingConditionNone}, + 0x40B: {lower: "\u045B", upper: "\u040B", condition: specialCasingConditionNone}, + 0x40C: {lower: "\u045C", upper: "\u040C", condition: specialCasingConditionNone}, + 0x40D: {lower: "\u045D", upper: "\u040D", condition: specialCasingConditionNone}, + 0x40E: {lower: "\u045E", upper: "\u040E", condition: specialCasingConditionNone}, + 0x40F: {lower: "\u045F", upper: "\u040F", condition: specialCasingConditionNone}, + 0x410: {lower: "\u0430", upper: "\u0410", condition: specialCasingConditionNone}, + 0x411: {lower: "\u0431", upper: "\u0411", condition: specialCasingConditionNone}, + 0x412: {lower: "\u0432", upper: "\u0412", condition: specialCasingConditionNone}, + 0x413: {lower: "\u0433", upper: "\u0413", condition: specialCasingConditionNone}, + 0x414: {lower: "\u0434", upper: "\u0414", condition: specialCasingConditionNone}, + 0x415: {lower: "\u0435", upper: "\u0415", condition: specialCasingConditionNone}, + 0x416: {lower: "\u0436", upper: "\u0416", condition: specialCasingConditionNone}, + 0x417: {lower: "\u0437", upper: "\u0417", condition: specialCasingConditionNone}, + 0x418: {lower: "\u0438", upper: "\u0418", condition: specialCasingConditionNone}, + 0x419: {lower: "\u0439", upper: "\u0419", condition: specialCasingConditionNone}, + 0x41A: {lower: "\u043A", upper: "\u041A", condition: specialCasingConditionNone}, + 0x41B: {lower: "\u043B", upper: "\u041B", condition: specialCasingConditionNone}, + 0x41C: {lower: "\u043C", upper: "\u041C", condition: specialCasingConditionNone}, + 0x41D: {lower: "\u043D", upper: "\u041D", condition: specialCasingConditionNone}, + 0x41E: {lower: "\u043E", upper: "\u041E", condition: specialCasingConditionNone}, + 0x41F: {lower: "\u043F", upper: "\u041F", condition: specialCasingConditionNone}, + 0x420: {lower: "\u0440", upper: "\u0420", condition: specialCasingConditionNone}, + 0x421: {lower: "\u0441", upper: "\u0421", condition: specialCasingConditionNone}, + 0x422: {lower: "\u0442", upper: "\u0422", condition: specialCasingConditionNone}, + 0x423: {lower: "\u0443", upper: "\u0423", condition: specialCasingConditionNone}, + 0x424: {lower: "\u0444", upper: "\u0424", condition: specialCasingConditionNone}, + 0x425: {lower: "\u0445", upper: "\u0425", condition: specialCasingConditionNone}, + 0x426: {lower: "\u0446", upper: "\u0426", condition: specialCasingConditionNone}, + 0x427: {lower: "\u0447", upper: "\u0427", condition: specialCasingConditionNone}, + 0x428: {lower: "\u0448", upper: "\u0428", condition: specialCasingConditionNone}, + 0x429: {lower: "\u0449", upper: "\u0429", condition: specialCasingConditionNone}, + 0x42A: {lower: "\u044A", upper: "\u042A", condition: specialCasingConditionNone}, + 0x42B: {lower: "\u044B", upper: "\u042B", condition: specialCasingConditionNone}, + 0x42C: {lower: "\u044C", upper: "\u042C", condition: specialCasingConditionNone}, + 0x42D: {lower: "\u044D", upper: "\u042D", condition: specialCasingConditionNone}, + 0x42E: {lower: "\u044E", upper: "\u042E", condition: specialCasingConditionNone}, + 0x42F: {lower: "\u044F", upper: "\u042F", condition: specialCasingConditionNone}, + 0x430: {lower: "\u0430", upper: "\u0410", condition: specialCasingConditionNone}, + 0x431: {lower: "\u0431", upper: "\u0411", condition: specialCasingConditionNone}, + 0x432: {lower: "\u0432", upper: "\u0412", condition: specialCasingConditionNone}, + 0x433: {lower: "\u0433", upper: "\u0413", condition: specialCasingConditionNone}, + 0x434: {lower: "\u0434", upper: "\u0414", condition: specialCasingConditionNone}, + 0x435: {lower: "\u0435", upper: "\u0415", condition: specialCasingConditionNone}, + 0x436: {lower: "\u0436", upper: "\u0416", condition: specialCasingConditionNone}, + 0x437: {lower: "\u0437", upper: "\u0417", condition: specialCasingConditionNone}, + 0x438: {lower: "\u0438", upper: "\u0418", condition: specialCasingConditionNone}, + 0x439: {lower: "\u0439", upper: "\u0419", condition: specialCasingConditionNone}, + 0x43A: {lower: "\u043A", upper: "\u041A", condition: specialCasingConditionNone}, + 0x43B: {lower: "\u043B", upper: "\u041B", condition: specialCasingConditionNone}, + 0x43C: {lower: "\u043C", upper: "\u041C", condition: specialCasingConditionNone}, + 0x43D: {lower: "\u043D", upper: "\u041D", condition: specialCasingConditionNone}, + 0x43E: {lower: "\u043E", upper: "\u041E", condition: specialCasingConditionNone}, + 0x43F: {lower: "\u043F", upper: "\u041F", condition: specialCasingConditionNone}, + 0x440: {lower: "\u0440", upper: "\u0420", condition: specialCasingConditionNone}, + 0x441: {lower: "\u0441", upper: "\u0421", condition: specialCasingConditionNone}, + 0x442: {lower: "\u0442", upper: "\u0422", condition: specialCasingConditionNone}, + 0x443: {lower: "\u0443", upper: "\u0423", condition: specialCasingConditionNone}, + 0x444: {lower: "\u0444", upper: "\u0424", condition: specialCasingConditionNone}, + 0x445: {lower: "\u0445", upper: "\u0425", condition: specialCasingConditionNone}, + 0x446: {lower: "\u0446", upper: "\u0426", condition: specialCasingConditionNone}, + 0x447: {lower: "\u0447", upper: "\u0427", condition: specialCasingConditionNone}, + 0x448: {lower: "\u0448", upper: "\u0428", condition: specialCasingConditionNone}, + 0x449: {lower: "\u0449", upper: "\u0429", condition: specialCasingConditionNone}, + 0x44A: {lower: "\u044A", upper: "\u042A", condition: specialCasingConditionNone}, + 0x44B: {lower: "\u044B", upper: "\u042B", condition: specialCasingConditionNone}, + 0x44C: {lower: "\u044C", upper: "\u042C", condition: specialCasingConditionNone}, + 0x44D: {lower: "\u044D", upper: "\u042D", condition: specialCasingConditionNone}, + 0x44E: {lower: "\u044E", upper: "\u042E", condition: specialCasingConditionNone}, + 0x44F: {lower: "\u044F", upper: "\u042F", condition: specialCasingConditionNone}, + 0x450: {lower: "\u0450", upper: "\u0400", condition: specialCasingConditionNone}, + 0x451: {lower: "\u0451", upper: "\u0401", condition: specialCasingConditionNone}, + 0x452: {lower: "\u0452", upper: "\u0402", condition: specialCasingConditionNone}, + 0x453: {lower: "\u0453", upper: "\u0403", condition: specialCasingConditionNone}, + 0x454: {lower: "\u0454", upper: "\u0404", condition: specialCasingConditionNone}, + 0x455: {lower: "\u0455", upper: "\u0405", condition: specialCasingConditionNone}, + 0x456: {lower: "\u0456", upper: "\u0406", condition: specialCasingConditionNone}, + 0x457: {lower: "\u0457", upper: "\u0407", condition: specialCasingConditionNone}, + 0x458: {lower: "\u0458", upper: "\u0408", condition: specialCasingConditionNone}, + 0x459: {lower: "\u0459", upper: "\u0409", condition: specialCasingConditionNone}, + 0x45A: {lower: "\u045A", upper: "\u040A", condition: specialCasingConditionNone}, + 0x45B: {lower: "\u045B", upper: "\u040B", condition: specialCasingConditionNone}, + 0x45C: {lower: "\u045C", upper: "\u040C", condition: specialCasingConditionNone}, + 0x45D: {lower: "\u045D", upper: "\u040D", condition: specialCasingConditionNone}, + 0x45E: {lower: "\u045E", upper: "\u040E", condition: specialCasingConditionNone}, + 0x45F: {lower: "\u045F", upper: "\u040F", condition: specialCasingConditionNone}, + 0x460: {lower: "\u0461", upper: "\u0460", condition: specialCasingConditionNone}, + 0x461: {lower: "\u0461", upper: "\u0460", condition: specialCasingConditionNone}, + 0x462: {lower: "\u0463", upper: "\u0462", condition: specialCasingConditionNone}, + 0x463: {lower: "\u0463", upper: "\u0462", condition: specialCasingConditionNone}, + 0x464: {lower: "\u0465", upper: "\u0464", condition: specialCasingConditionNone}, + 0x465: {lower: "\u0465", upper: "\u0464", condition: specialCasingConditionNone}, + 0x466: {lower: "\u0467", upper: "\u0466", condition: specialCasingConditionNone}, + 0x467: {lower: "\u0467", upper: "\u0466", condition: specialCasingConditionNone}, + 0x468: {lower: "\u0469", upper: "\u0468", condition: specialCasingConditionNone}, + 0x469: {lower: "\u0469", upper: "\u0468", condition: specialCasingConditionNone}, + 0x46A: {lower: "\u046B", upper: "\u046A", condition: specialCasingConditionNone}, + 0x46B: {lower: "\u046B", upper: "\u046A", condition: specialCasingConditionNone}, + 0x46C: {lower: "\u046D", upper: "\u046C", condition: specialCasingConditionNone}, + 0x46D: {lower: "\u046D", upper: "\u046C", condition: specialCasingConditionNone}, + 0x46E: {lower: "\u046F", upper: "\u046E", condition: specialCasingConditionNone}, + 0x46F: {lower: "\u046F", upper: "\u046E", condition: specialCasingConditionNone}, + 0x470: {lower: "\u0471", upper: "\u0470", condition: specialCasingConditionNone}, + 0x471: {lower: "\u0471", upper: "\u0470", condition: specialCasingConditionNone}, + 0x472: {lower: "\u0473", upper: "\u0472", condition: specialCasingConditionNone}, + 0x473: {lower: "\u0473", upper: "\u0472", condition: specialCasingConditionNone}, + 0x474: {lower: "\u0475", upper: "\u0474", condition: specialCasingConditionNone}, + 0x475: {lower: "\u0475", upper: "\u0474", condition: specialCasingConditionNone}, + 0x476: {lower: "\u0477", upper: "\u0476", condition: specialCasingConditionNone}, + 0x477: {lower: "\u0477", upper: "\u0476", condition: specialCasingConditionNone}, + 0x478: {lower: "\u0479", upper: "\u0478", condition: specialCasingConditionNone}, + 0x479: {lower: "\u0479", upper: "\u0478", condition: specialCasingConditionNone}, + 0x47A: {lower: "\u047B", upper: "\u047A", condition: specialCasingConditionNone}, + 0x47B: {lower: "\u047B", upper: "\u047A", condition: specialCasingConditionNone}, + 0x47C: {lower: "\u047D", upper: "\u047C", condition: specialCasingConditionNone}, + 0x47D: {lower: "\u047D", upper: "\u047C", condition: specialCasingConditionNone}, + 0x47E: {lower: "\u047F", upper: "\u047E", condition: specialCasingConditionNone}, + 0x47F: {lower: "\u047F", upper: "\u047E", condition: specialCasingConditionNone}, + 0x480: {lower: "\u0481", upper: "\u0480", condition: specialCasingConditionNone}, + 0x481: {lower: "\u0481", upper: "\u0480", condition: specialCasingConditionNone}, + 0x48A: {lower: "\u048B", upper: "\u048A", condition: specialCasingConditionNone}, + 0x48B: {lower: "\u048B", upper: "\u048A", condition: specialCasingConditionNone}, + 0x48C: {lower: "\u048D", upper: "\u048C", condition: specialCasingConditionNone}, + 0x48D: {lower: "\u048D", upper: "\u048C", condition: specialCasingConditionNone}, + 0x48E: {lower: "\u048F", upper: "\u048E", condition: specialCasingConditionNone}, + 0x48F: {lower: "\u048F", upper: "\u048E", condition: specialCasingConditionNone}, + 0x490: {lower: "\u0491", upper: "\u0490", condition: specialCasingConditionNone}, + 0x491: {lower: "\u0491", upper: "\u0490", condition: specialCasingConditionNone}, + 0x492: {lower: "\u0493", upper: "\u0492", condition: specialCasingConditionNone}, + 0x493: {lower: "\u0493", upper: "\u0492", condition: specialCasingConditionNone}, + 0x494: {lower: "\u0495", upper: "\u0494", condition: specialCasingConditionNone}, + 0x495: {lower: "\u0495", upper: "\u0494", condition: specialCasingConditionNone}, + 0x496: {lower: "\u0497", upper: "\u0496", condition: specialCasingConditionNone}, + 0x497: {lower: "\u0497", upper: "\u0496", condition: specialCasingConditionNone}, + 0x498: {lower: "\u0499", upper: "\u0498", condition: specialCasingConditionNone}, + 0x499: {lower: "\u0499", upper: "\u0498", condition: specialCasingConditionNone}, + 0x49A: {lower: "\u049B", upper: "\u049A", condition: specialCasingConditionNone}, + 0x49B: {lower: "\u049B", upper: "\u049A", condition: specialCasingConditionNone}, + 0x49C: {lower: "\u049D", upper: "\u049C", condition: specialCasingConditionNone}, + 0x49D: {lower: "\u049D", upper: "\u049C", condition: specialCasingConditionNone}, + 0x49E: {lower: "\u049F", upper: "\u049E", condition: specialCasingConditionNone}, + 0x49F: {lower: "\u049F", upper: "\u049E", condition: specialCasingConditionNone}, + 0x4A0: {lower: "\u04A1", upper: "\u04A0", condition: specialCasingConditionNone}, + 0x4A1: {lower: "\u04A1", upper: "\u04A0", condition: specialCasingConditionNone}, + 0x4A2: {lower: "\u04A3", upper: "\u04A2", condition: specialCasingConditionNone}, + 0x4A3: {lower: "\u04A3", upper: "\u04A2", condition: specialCasingConditionNone}, + 0x4A4: {lower: "\u04A5", upper: "\u04A4", condition: specialCasingConditionNone}, + 0x4A5: {lower: "\u04A5", upper: "\u04A4", condition: specialCasingConditionNone}, + 0x4A6: {lower: "\u04A7", upper: "\u04A6", condition: specialCasingConditionNone}, + 0x4A7: {lower: "\u04A7", upper: "\u04A6", condition: specialCasingConditionNone}, + 0x4A8: {lower: "\u04A9", upper: "\u04A8", condition: specialCasingConditionNone}, + 0x4A9: {lower: "\u04A9", upper: "\u04A8", condition: specialCasingConditionNone}, + 0x4AA: {lower: "\u04AB", upper: "\u04AA", condition: specialCasingConditionNone}, + 0x4AB: {lower: "\u04AB", upper: "\u04AA", condition: specialCasingConditionNone}, + 0x4AC: {lower: "\u04AD", upper: "\u04AC", condition: specialCasingConditionNone}, + 0x4AD: {lower: "\u04AD", upper: "\u04AC", condition: specialCasingConditionNone}, + 0x4AE: {lower: "\u04AF", upper: "\u04AE", condition: specialCasingConditionNone}, + 0x4AF: {lower: "\u04AF", upper: "\u04AE", condition: specialCasingConditionNone}, + 0x4B0: {lower: "\u04B1", upper: "\u04B0", condition: specialCasingConditionNone}, + 0x4B1: {lower: "\u04B1", upper: "\u04B0", condition: specialCasingConditionNone}, + 0x4B2: {lower: "\u04B3", upper: "\u04B2", condition: specialCasingConditionNone}, + 0x4B3: {lower: "\u04B3", upper: "\u04B2", condition: specialCasingConditionNone}, + 0x4B4: {lower: "\u04B5", upper: "\u04B4", condition: specialCasingConditionNone}, + 0x4B5: {lower: "\u04B5", upper: "\u04B4", condition: specialCasingConditionNone}, + 0x4B6: {lower: "\u04B7", upper: "\u04B6", condition: specialCasingConditionNone}, + 0x4B7: {lower: "\u04B7", upper: "\u04B6", condition: specialCasingConditionNone}, + 0x4B8: {lower: "\u04B9", upper: "\u04B8", condition: specialCasingConditionNone}, + 0x4B9: {lower: "\u04B9", upper: "\u04B8", condition: specialCasingConditionNone}, + 0x4BA: {lower: "\u04BB", upper: "\u04BA", condition: specialCasingConditionNone}, + 0x4BB: {lower: "\u04BB", upper: "\u04BA", condition: specialCasingConditionNone}, + 0x4BC: {lower: "\u04BD", upper: "\u04BC", condition: specialCasingConditionNone}, + 0x4BD: {lower: "\u04BD", upper: "\u04BC", condition: specialCasingConditionNone}, + 0x4BE: {lower: "\u04BF", upper: "\u04BE", condition: specialCasingConditionNone}, + 0x4BF: {lower: "\u04BF", upper: "\u04BE", condition: specialCasingConditionNone}, + 0x4C0: {lower: "\u04CF", upper: "\u04C0", condition: specialCasingConditionNone}, + 0x4C1: {lower: "\u04C2", upper: "\u04C1", condition: specialCasingConditionNone}, + 0x4C2: {lower: "\u04C2", upper: "\u04C1", condition: specialCasingConditionNone}, + 0x4C3: {lower: "\u04C4", upper: "\u04C3", condition: specialCasingConditionNone}, + 0x4C4: {lower: "\u04C4", upper: "\u04C3", condition: specialCasingConditionNone}, + 0x4C5: {lower: "\u04C6", upper: "\u04C5", condition: specialCasingConditionNone}, + 0x4C6: {lower: "\u04C6", upper: "\u04C5", condition: specialCasingConditionNone}, + 0x4C7: {lower: "\u04C8", upper: "\u04C7", condition: specialCasingConditionNone}, + 0x4C8: {lower: "\u04C8", upper: "\u04C7", condition: specialCasingConditionNone}, + 0x4C9: {lower: "\u04CA", upper: "\u04C9", condition: specialCasingConditionNone}, + 0x4CA: {lower: "\u04CA", upper: "\u04C9", condition: specialCasingConditionNone}, + 0x4CB: {lower: "\u04CC", upper: "\u04CB", condition: specialCasingConditionNone}, + 0x4CC: {lower: "\u04CC", upper: "\u04CB", condition: specialCasingConditionNone}, + 0x4CD: {lower: "\u04CE", upper: "\u04CD", condition: specialCasingConditionNone}, + 0x4CE: {lower: "\u04CE", upper: "\u04CD", condition: specialCasingConditionNone}, + 0x4CF: {lower: "\u04CF", upper: "\u04C0", condition: specialCasingConditionNone}, + 0x4D0: {lower: "\u04D1", upper: "\u04D0", condition: specialCasingConditionNone}, + 0x4D1: {lower: "\u04D1", upper: "\u04D0", condition: specialCasingConditionNone}, + 0x4D2: {lower: "\u04D3", upper: "\u04D2", condition: specialCasingConditionNone}, + 0x4D3: {lower: "\u04D3", upper: "\u04D2", condition: specialCasingConditionNone}, + 0x4D4: {lower: "\u04D5", upper: "\u04D4", condition: specialCasingConditionNone}, + 0x4D5: {lower: "\u04D5", upper: "\u04D4", condition: specialCasingConditionNone}, + 0x4D6: {lower: "\u04D7", upper: "\u04D6", condition: specialCasingConditionNone}, + 0x4D7: {lower: "\u04D7", upper: "\u04D6", condition: specialCasingConditionNone}, + 0x4D8: {lower: "\u04D9", upper: "\u04D8", condition: specialCasingConditionNone}, + 0x4D9: {lower: "\u04D9", upper: "\u04D8", condition: specialCasingConditionNone}, + 0x4DA: {lower: "\u04DB", upper: "\u04DA", condition: specialCasingConditionNone}, + 0x4DB: {lower: "\u04DB", upper: "\u04DA", condition: specialCasingConditionNone}, + 0x4DC: {lower: "\u04DD", upper: "\u04DC", condition: specialCasingConditionNone}, + 0x4DD: {lower: "\u04DD", upper: "\u04DC", condition: specialCasingConditionNone}, + 0x4DE: {lower: "\u04DF", upper: "\u04DE", condition: specialCasingConditionNone}, + 0x4DF: {lower: "\u04DF", upper: "\u04DE", condition: specialCasingConditionNone}, + 0x4E0: {lower: "\u04E1", upper: "\u04E0", condition: specialCasingConditionNone}, + 0x4E1: {lower: "\u04E1", upper: "\u04E0", condition: specialCasingConditionNone}, + 0x4E2: {lower: "\u04E3", upper: "\u04E2", condition: specialCasingConditionNone}, + 0x4E3: {lower: "\u04E3", upper: "\u04E2", condition: specialCasingConditionNone}, + 0x4E4: {lower: "\u04E5", upper: "\u04E4", condition: specialCasingConditionNone}, + 0x4E5: {lower: "\u04E5", upper: "\u04E4", condition: specialCasingConditionNone}, + 0x4E6: {lower: "\u04E7", upper: "\u04E6", condition: specialCasingConditionNone}, + 0x4E7: {lower: "\u04E7", upper: "\u04E6", condition: specialCasingConditionNone}, + 0x4E8: {lower: "\u04E9", upper: "\u04E8", condition: specialCasingConditionNone}, + 0x4E9: {lower: "\u04E9", upper: "\u04E8", condition: specialCasingConditionNone}, + 0x4EA: {lower: "\u04EB", upper: "\u04EA", condition: specialCasingConditionNone}, + 0x4EB: {lower: "\u04EB", upper: "\u04EA", condition: specialCasingConditionNone}, + 0x4EC: {lower: "\u04ED", upper: "\u04EC", condition: specialCasingConditionNone}, + 0x4ED: {lower: "\u04ED", upper: "\u04EC", condition: specialCasingConditionNone}, + 0x4EE: {lower: "\u04EF", upper: "\u04EE", condition: specialCasingConditionNone}, + 0x4EF: {lower: "\u04EF", upper: "\u04EE", condition: specialCasingConditionNone}, + 0x4F0: {lower: "\u04F1", upper: "\u04F0", condition: specialCasingConditionNone}, + 0x4F1: {lower: "\u04F1", upper: "\u04F0", condition: specialCasingConditionNone}, + 0x4F2: {lower: "\u04F3", upper: "\u04F2", condition: specialCasingConditionNone}, + 0x4F3: {lower: "\u04F3", upper: "\u04F2", condition: specialCasingConditionNone}, + 0x4F4: {lower: "\u04F5", upper: "\u04F4", condition: specialCasingConditionNone}, + 0x4F5: {lower: "\u04F5", upper: "\u04F4", condition: specialCasingConditionNone}, + 0x4F6: {lower: "\u04F7", upper: "\u04F6", condition: specialCasingConditionNone}, + 0x4F7: {lower: "\u04F7", upper: "\u04F6", condition: specialCasingConditionNone}, + 0x4F8: {lower: "\u04F9", upper: "\u04F8", condition: specialCasingConditionNone}, + 0x4F9: {lower: "\u04F9", upper: "\u04F8", condition: specialCasingConditionNone}, + 0x4FA: {lower: "\u04FB", upper: "\u04FA", condition: specialCasingConditionNone}, + 0x4FB: {lower: "\u04FB", upper: "\u04FA", condition: specialCasingConditionNone}, + 0x4FC: {lower: "\u04FD", upper: "\u04FC", condition: specialCasingConditionNone}, + 0x4FD: {lower: "\u04FD", upper: "\u04FC", condition: specialCasingConditionNone}, + 0x4FE: {lower: "\u04FF", upper: "\u04FE", condition: specialCasingConditionNone}, + 0x4FF: {lower: "\u04FF", upper: "\u04FE", condition: specialCasingConditionNone}, + 0x500: {lower: "\u0501", upper: "\u0500", condition: specialCasingConditionNone}, + 0x501: {lower: "\u0501", upper: "\u0500", condition: specialCasingConditionNone}, + 0x502: {lower: "\u0503", upper: "\u0502", condition: specialCasingConditionNone}, + 0x503: {lower: "\u0503", upper: "\u0502", condition: specialCasingConditionNone}, + 0x504: {lower: "\u0505", upper: "\u0504", condition: specialCasingConditionNone}, + 0x505: {lower: "\u0505", upper: "\u0504", condition: specialCasingConditionNone}, + 0x506: {lower: "\u0507", upper: "\u0506", condition: specialCasingConditionNone}, + 0x507: {lower: "\u0507", upper: "\u0506", condition: specialCasingConditionNone}, + 0x508: {lower: "\u0509", upper: "\u0508", condition: specialCasingConditionNone}, + 0x509: {lower: "\u0509", upper: "\u0508", condition: specialCasingConditionNone}, + 0x50A: {lower: "\u050B", upper: "\u050A", condition: specialCasingConditionNone}, + 0x50B: {lower: "\u050B", upper: "\u050A", condition: specialCasingConditionNone}, + 0x50C: {lower: "\u050D", upper: "\u050C", condition: specialCasingConditionNone}, + 0x50D: {lower: "\u050D", upper: "\u050C", condition: specialCasingConditionNone}, + 0x50E: {lower: "\u050F", upper: "\u050E", condition: specialCasingConditionNone}, + 0x50F: {lower: "\u050F", upper: "\u050E", condition: specialCasingConditionNone}, + 0x510: {lower: "\u0511", upper: "\u0510", condition: specialCasingConditionNone}, + 0x511: {lower: "\u0511", upper: "\u0510", condition: specialCasingConditionNone}, + 0x512: {lower: "\u0513", upper: "\u0512", condition: specialCasingConditionNone}, + 0x513: {lower: "\u0513", upper: "\u0512", condition: specialCasingConditionNone}, + 0x514: {lower: "\u0515", upper: "\u0514", condition: specialCasingConditionNone}, + 0x515: {lower: "\u0515", upper: "\u0514", condition: specialCasingConditionNone}, + 0x516: {lower: "\u0517", upper: "\u0516", condition: specialCasingConditionNone}, + 0x517: {lower: "\u0517", upper: "\u0516", condition: specialCasingConditionNone}, + 0x518: {lower: "\u0519", upper: "\u0518", condition: specialCasingConditionNone}, + 0x519: {lower: "\u0519", upper: "\u0518", condition: specialCasingConditionNone}, + 0x51A: {lower: "\u051B", upper: "\u051A", condition: specialCasingConditionNone}, + 0x51B: {lower: "\u051B", upper: "\u051A", condition: specialCasingConditionNone}, + 0x51C: {lower: "\u051D", upper: "\u051C", condition: specialCasingConditionNone}, + 0x51D: {lower: "\u051D", upper: "\u051C", condition: specialCasingConditionNone}, + 0x51E: {lower: "\u051F", upper: "\u051E", condition: specialCasingConditionNone}, + 0x51F: {lower: "\u051F", upper: "\u051E", condition: specialCasingConditionNone}, + 0x520: {lower: "\u0521", upper: "\u0520", condition: specialCasingConditionNone}, + 0x521: {lower: "\u0521", upper: "\u0520", condition: specialCasingConditionNone}, + 0x522: {lower: "\u0523", upper: "\u0522", condition: specialCasingConditionNone}, + 0x523: {lower: "\u0523", upper: "\u0522", condition: specialCasingConditionNone}, + 0x524: {lower: "\u0525", upper: "\u0524", condition: specialCasingConditionNone}, + 0x525: {lower: "\u0525", upper: "\u0524", condition: specialCasingConditionNone}, + 0x526: {lower: "\u0527", upper: "\u0526", condition: specialCasingConditionNone}, + 0x527: {lower: "\u0527", upper: "\u0526", condition: specialCasingConditionNone}, + 0x528: {lower: "\u0529", upper: "\u0528", condition: specialCasingConditionNone}, + 0x529: {lower: "\u0529", upper: "\u0528", condition: specialCasingConditionNone}, + 0x52A: {lower: "\u052B", upper: "\u052A", condition: specialCasingConditionNone}, + 0x52B: {lower: "\u052B", upper: "\u052A", condition: specialCasingConditionNone}, + 0x52C: {lower: "\u052D", upper: "\u052C", condition: specialCasingConditionNone}, + 0x52D: {lower: "\u052D", upper: "\u052C", condition: specialCasingConditionNone}, + 0x52E: {lower: "\u052F", upper: "\u052E", condition: specialCasingConditionNone}, + 0x52F: {lower: "\u052F", upper: "\u052E", condition: specialCasingConditionNone}, + 0x531: {lower: "\u0561", upper: "\u0531", condition: specialCasingConditionNone}, + 0x532: {lower: "\u0562", upper: "\u0532", condition: specialCasingConditionNone}, + 0x533: {lower: "\u0563", upper: "\u0533", condition: specialCasingConditionNone}, + 0x534: {lower: "\u0564", upper: "\u0534", condition: specialCasingConditionNone}, + 0x535: {lower: "\u0565", upper: "\u0535", condition: specialCasingConditionNone}, + 0x536: {lower: "\u0566", upper: "\u0536", condition: specialCasingConditionNone}, + 0x537: {lower: "\u0567", upper: "\u0537", condition: specialCasingConditionNone}, + 0x538: {lower: "\u0568", upper: "\u0538", condition: specialCasingConditionNone}, + 0x539: {lower: "\u0569", upper: "\u0539", condition: specialCasingConditionNone}, + 0x53A: {lower: "\u056A", upper: "\u053A", condition: specialCasingConditionNone}, + 0x53B: {lower: "\u056B", upper: "\u053B", condition: specialCasingConditionNone}, + 0x53C: {lower: "\u056C", upper: "\u053C", condition: specialCasingConditionNone}, + 0x53D: {lower: "\u056D", upper: "\u053D", condition: specialCasingConditionNone}, + 0x53E: {lower: "\u056E", upper: "\u053E", condition: specialCasingConditionNone}, + 0x53F: {lower: "\u056F", upper: "\u053F", condition: specialCasingConditionNone}, + 0x540: {lower: "\u0570", upper: "\u0540", condition: specialCasingConditionNone}, + 0x541: {lower: "\u0571", upper: "\u0541", condition: specialCasingConditionNone}, + 0x542: {lower: "\u0572", upper: "\u0542", condition: specialCasingConditionNone}, + 0x543: {lower: "\u0573", upper: "\u0543", condition: specialCasingConditionNone}, + 0x544: {lower: "\u0574", upper: "\u0544", condition: specialCasingConditionNone}, + 0x545: {lower: "\u0575", upper: "\u0545", condition: specialCasingConditionNone}, + 0x546: {lower: "\u0576", upper: "\u0546", condition: specialCasingConditionNone}, + 0x547: {lower: "\u0577", upper: "\u0547", condition: specialCasingConditionNone}, + 0x548: {lower: "\u0578", upper: "\u0548", condition: specialCasingConditionNone}, + 0x549: {lower: "\u0579", upper: "\u0549", condition: specialCasingConditionNone}, + 0x54A: {lower: "\u057A", upper: "\u054A", condition: specialCasingConditionNone}, + 0x54B: {lower: "\u057B", upper: "\u054B", condition: specialCasingConditionNone}, + 0x54C: {lower: "\u057C", upper: "\u054C", condition: specialCasingConditionNone}, + 0x54D: {lower: "\u057D", upper: "\u054D", condition: specialCasingConditionNone}, + 0x54E: {lower: "\u057E", upper: "\u054E", condition: specialCasingConditionNone}, + 0x54F: {lower: "\u057F", upper: "\u054F", condition: specialCasingConditionNone}, + 0x550: {lower: "\u0580", upper: "\u0550", condition: specialCasingConditionNone}, + 0x551: {lower: "\u0581", upper: "\u0551", condition: specialCasingConditionNone}, + 0x552: {lower: "\u0582", upper: "\u0552", condition: specialCasingConditionNone}, + 0x553: {lower: "\u0583", upper: "\u0553", condition: specialCasingConditionNone}, + 0x554: {lower: "\u0584", upper: "\u0554", condition: specialCasingConditionNone}, + 0x555: {lower: "\u0585", upper: "\u0555", condition: specialCasingConditionNone}, + 0x556: {lower: "\u0586", upper: "\u0556", condition: specialCasingConditionNone}, + 0x561: {lower: "\u0561", upper: "\u0531", condition: specialCasingConditionNone}, + 0x562: {lower: "\u0562", upper: "\u0532", condition: specialCasingConditionNone}, + 0x563: {lower: "\u0563", upper: "\u0533", condition: specialCasingConditionNone}, + 0x564: {lower: "\u0564", upper: "\u0534", condition: specialCasingConditionNone}, + 0x565: {lower: "\u0565", upper: "\u0535", condition: specialCasingConditionNone}, + 0x566: {lower: "\u0566", upper: "\u0536", condition: specialCasingConditionNone}, + 0x567: {lower: "\u0567", upper: "\u0537", condition: specialCasingConditionNone}, + 0x568: {lower: "\u0568", upper: "\u0538", condition: specialCasingConditionNone}, + 0x569: {lower: "\u0569", upper: "\u0539", condition: specialCasingConditionNone}, + 0x56A: {lower: "\u056A", upper: "\u053A", condition: specialCasingConditionNone}, + 0x56B: {lower: "\u056B", upper: "\u053B", condition: specialCasingConditionNone}, + 0x56C: {lower: "\u056C", upper: "\u053C", condition: specialCasingConditionNone}, + 0x56D: {lower: "\u056D", upper: "\u053D", condition: specialCasingConditionNone}, + 0x56E: {lower: "\u056E", upper: "\u053E", condition: specialCasingConditionNone}, + 0x56F: {lower: "\u056F", upper: "\u053F", condition: specialCasingConditionNone}, + 0x570: {lower: "\u0570", upper: "\u0540", condition: specialCasingConditionNone}, + 0x571: {lower: "\u0571", upper: "\u0541", condition: specialCasingConditionNone}, + 0x572: {lower: "\u0572", upper: "\u0542", condition: specialCasingConditionNone}, + 0x573: {lower: "\u0573", upper: "\u0543", condition: specialCasingConditionNone}, + 0x574: {lower: "\u0574", upper: "\u0544", condition: specialCasingConditionNone}, + 0x575: {lower: "\u0575", upper: "\u0545", condition: specialCasingConditionNone}, + 0x576: {lower: "\u0576", upper: "\u0546", condition: specialCasingConditionNone}, + 0x577: {lower: "\u0577", upper: "\u0547", condition: specialCasingConditionNone}, + 0x578: {lower: "\u0578", upper: "\u0548", condition: specialCasingConditionNone}, + 0x579: {lower: "\u0579", upper: "\u0549", condition: specialCasingConditionNone}, + 0x57A: {lower: "\u057A", upper: "\u054A", condition: specialCasingConditionNone}, + 0x57B: {lower: "\u057B", upper: "\u054B", condition: specialCasingConditionNone}, + 0x57C: {lower: "\u057C", upper: "\u054C", condition: specialCasingConditionNone}, + 0x57D: {lower: "\u057D", upper: "\u054D", condition: specialCasingConditionNone}, + 0x57E: {lower: "\u057E", upper: "\u054E", condition: specialCasingConditionNone}, + 0x57F: {lower: "\u057F", upper: "\u054F", condition: specialCasingConditionNone}, + 0x580: {lower: "\u0580", upper: "\u0550", condition: specialCasingConditionNone}, + 0x581: {lower: "\u0581", upper: "\u0551", condition: specialCasingConditionNone}, + 0x582: {lower: "\u0582", upper: "\u0552", condition: specialCasingConditionNone}, + 0x583: {lower: "\u0583", upper: "\u0553", condition: specialCasingConditionNone}, + 0x584: {lower: "\u0584", upper: "\u0554", condition: specialCasingConditionNone}, + 0x585: {lower: "\u0585", upper: "\u0555", condition: specialCasingConditionNone}, + 0x586: {lower: "\u0586", upper: "\u0556", condition: specialCasingConditionNone}, + 0x587: {lower: "\u0587", upper: "\u0535\u0552", condition: specialCasingConditionNone}, + 0x10A0: {lower: "\u2D00", upper: "\u10A0", condition: specialCasingConditionNone}, + 0x10A1: {lower: "\u2D01", upper: "\u10A1", condition: specialCasingConditionNone}, + 0x10A2: {lower: "\u2D02", upper: "\u10A2", condition: specialCasingConditionNone}, + 0x10A3: {lower: "\u2D03", upper: "\u10A3", condition: specialCasingConditionNone}, + 0x10A4: {lower: "\u2D04", upper: "\u10A4", condition: specialCasingConditionNone}, + 0x10A5: {lower: "\u2D05", upper: "\u10A5", condition: specialCasingConditionNone}, + 0x10A6: {lower: "\u2D06", upper: "\u10A6", condition: specialCasingConditionNone}, + 0x10A7: {lower: "\u2D07", upper: "\u10A7", condition: specialCasingConditionNone}, + 0x10A8: {lower: "\u2D08", upper: "\u10A8", condition: specialCasingConditionNone}, + 0x10A9: {lower: "\u2D09", upper: "\u10A9", condition: specialCasingConditionNone}, + 0x10AA: {lower: "\u2D0A", upper: "\u10AA", condition: specialCasingConditionNone}, + 0x10AB: {lower: "\u2D0B", upper: "\u10AB", condition: specialCasingConditionNone}, + 0x10AC: {lower: "\u2D0C", upper: "\u10AC", condition: specialCasingConditionNone}, + 0x10AD: {lower: "\u2D0D", upper: "\u10AD", condition: specialCasingConditionNone}, + 0x10AE: {lower: "\u2D0E", upper: "\u10AE", condition: specialCasingConditionNone}, + 0x10AF: {lower: "\u2D0F", upper: "\u10AF", condition: specialCasingConditionNone}, + 0x10B0: {lower: "\u2D10", upper: "\u10B0", condition: specialCasingConditionNone}, + 0x10B1: {lower: "\u2D11", upper: "\u10B1", condition: specialCasingConditionNone}, + 0x10B2: {lower: "\u2D12", upper: "\u10B2", condition: specialCasingConditionNone}, + 0x10B3: {lower: "\u2D13", upper: "\u10B3", condition: specialCasingConditionNone}, + 0x10B4: {lower: "\u2D14", upper: "\u10B4", condition: specialCasingConditionNone}, + 0x10B5: {lower: "\u2D15", upper: "\u10B5", condition: specialCasingConditionNone}, + 0x10B6: {lower: "\u2D16", upper: "\u10B6", condition: specialCasingConditionNone}, + 0x10B7: {lower: "\u2D17", upper: "\u10B7", condition: specialCasingConditionNone}, + 0x10B8: {lower: "\u2D18", upper: "\u10B8", condition: specialCasingConditionNone}, + 0x10B9: {lower: "\u2D19", upper: "\u10B9", condition: specialCasingConditionNone}, + 0x10BA: {lower: "\u2D1A", upper: "\u10BA", condition: specialCasingConditionNone}, + 0x10BB: {lower: "\u2D1B", upper: "\u10BB", condition: specialCasingConditionNone}, + 0x10BC: {lower: "\u2D1C", upper: "\u10BC", condition: specialCasingConditionNone}, + 0x10BD: {lower: "\u2D1D", upper: "\u10BD", condition: specialCasingConditionNone}, + 0x10BE: {lower: "\u2D1E", upper: "\u10BE", condition: specialCasingConditionNone}, + 0x10BF: {lower: "\u2D1F", upper: "\u10BF", condition: specialCasingConditionNone}, + 0x10C0: {lower: "\u2D20", upper: "\u10C0", condition: specialCasingConditionNone}, + 0x10C1: {lower: "\u2D21", upper: "\u10C1", condition: specialCasingConditionNone}, + 0x10C2: {lower: "\u2D22", upper: "\u10C2", condition: specialCasingConditionNone}, + 0x10C3: {lower: "\u2D23", upper: "\u10C3", condition: specialCasingConditionNone}, + 0x10C4: {lower: "\u2D24", upper: "\u10C4", condition: specialCasingConditionNone}, + 0x10C5: {lower: "\u2D25", upper: "\u10C5", condition: specialCasingConditionNone}, + 0x10C7: {lower: "\u2D27", upper: "\u10C7", condition: specialCasingConditionNone}, + 0x10CD: {lower: "\u2D2D", upper: "\u10CD", condition: specialCasingConditionNone}, + 0x10D0: {lower: "\u10D0", upper: "\u1C90", condition: specialCasingConditionNone}, + 0x10D1: {lower: "\u10D1", upper: "\u1C91", condition: specialCasingConditionNone}, + 0x10D2: {lower: "\u10D2", upper: "\u1C92", condition: specialCasingConditionNone}, + 0x10D3: {lower: "\u10D3", upper: "\u1C93", condition: specialCasingConditionNone}, + 0x10D4: {lower: "\u10D4", upper: "\u1C94", condition: specialCasingConditionNone}, + 0x10D5: {lower: "\u10D5", upper: "\u1C95", condition: specialCasingConditionNone}, + 0x10D6: {lower: "\u10D6", upper: "\u1C96", condition: specialCasingConditionNone}, + 0x10D7: {lower: "\u10D7", upper: "\u1C97", condition: specialCasingConditionNone}, + 0x10D8: {lower: "\u10D8", upper: "\u1C98", condition: specialCasingConditionNone}, + 0x10D9: {lower: "\u10D9", upper: "\u1C99", condition: specialCasingConditionNone}, + 0x10DA: {lower: "\u10DA", upper: "\u1C9A", condition: specialCasingConditionNone}, + 0x10DB: {lower: "\u10DB", upper: "\u1C9B", condition: specialCasingConditionNone}, + 0x10DC: {lower: "\u10DC", upper: "\u1C9C", condition: specialCasingConditionNone}, + 0x10DD: {lower: "\u10DD", upper: "\u1C9D", condition: specialCasingConditionNone}, + 0x10DE: {lower: "\u10DE", upper: "\u1C9E", condition: specialCasingConditionNone}, + 0x10DF: {lower: "\u10DF", upper: "\u1C9F", condition: specialCasingConditionNone}, + 0x10E0: {lower: "\u10E0", upper: "\u1CA0", condition: specialCasingConditionNone}, + 0x10E1: {lower: "\u10E1", upper: "\u1CA1", condition: specialCasingConditionNone}, + 0x10E2: {lower: "\u10E2", upper: "\u1CA2", condition: specialCasingConditionNone}, + 0x10E3: {lower: "\u10E3", upper: "\u1CA3", condition: specialCasingConditionNone}, + 0x10E4: {lower: "\u10E4", upper: "\u1CA4", condition: specialCasingConditionNone}, + 0x10E5: {lower: "\u10E5", upper: "\u1CA5", condition: specialCasingConditionNone}, + 0x10E6: {lower: "\u10E6", upper: "\u1CA6", condition: specialCasingConditionNone}, + 0x10E7: {lower: "\u10E7", upper: "\u1CA7", condition: specialCasingConditionNone}, + 0x10E8: {lower: "\u10E8", upper: "\u1CA8", condition: specialCasingConditionNone}, + 0x10E9: {lower: "\u10E9", upper: "\u1CA9", condition: specialCasingConditionNone}, + 0x10EA: {lower: "\u10EA", upper: "\u1CAA", condition: specialCasingConditionNone}, + 0x10EB: {lower: "\u10EB", upper: "\u1CAB", condition: specialCasingConditionNone}, + 0x10EC: {lower: "\u10EC", upper: "\u1CAC", condition: specialCasingConditionNone}, + 0x10ED: {lower: "\u10ED", upper: "\u1CAD", condition: specialCasingConditionNone}, + 0x10EE: {lower: "\u10EE", upper: "\u1CAE", condition: specialCasingConditionNone}, + 0x10EF: {lower: "\u10EF", upper: "\u1CAF", condition: specialCasingConditionNone}, + 0x10F0: {lower: "\u10F0", upper: "\u1CB0", condition: specialCasingConditionNone}, + 0x10F1: {lower: "\u10F1", upper: "\u1CB1", condition: specialCasingConditionNone}, + 0x10F2: {lower: "\u10F2", upper: "\u1CB2", condition: specialCasingConditionNone}, + 0x10F3: {lower: "\u10F3", upper: "\u1CB3", condition: specialCasingConditionNone}, + 0x10F4: {lower: "\u10F4", upper: "\u1CB4", condition: specialCasingConditionNone}, + 0x10F5: {lower: "\u10F5", upper: "\u1CB5", condition: specialCasingConditionNone}, + 0x10F6: {lower: "\u10F6", upper: "\u1CB6", condition: specialCasingConditionNone}, + 0x10F7: {lower: "\u10F7", upper: "\u1CB7", condition: specialCasingConditionNone}, + 0x10F8: {lower: "\u10F8", upper: "\u1CB8", condition: specialCasingConditionNone}, + 0x10F9: {lower: "\u10F9", upper: "\u1CB9", condition: specialCasingConditionNone}, + 0x10FA: {lower: "\u10FA", upper: "\u1CBA", condition: specialCasingConditionNone}, + 0x10FD: {lower: "\u10FD", upper: "\u1CBD", condition: specialCasingConditionNone}, + 0x10FE: {lower: "\u10FE", upper: "\u1CBE", condition: specialCasingConditionNone}, + 0x10FF: {lower: "\u10FF", upper: "\u1CBF", condition: specialCasingConditionNone}, + 0x13A0: {lower: "\uAB70", upper: "\u13A0", condition: specialCasingConditionNone}, + 0x13A1: {lower: "\uAB71", upper: "\u13A1", condition: specialCasingConditionNone}, + 0x13A2: {lower: "\uAB72", upper: "\u13A2", condition: specialCasingConditionNone}, + 0x13A3: {lower: "\uAB73", upper: "\u13A3", condition: specialCasingConditionNone}, + 0x13A4: {lower: "\uAB74", upper: "\u13A4", condition: specialCasingConditionNone}, + 0x13A5: {lower: "\uAB75", upper: "\u13A5", condition: specialCasingConditionNone}, + 0x13A6: {lower: "\uAB76", upper: "\u13A6", condition: specialCasingConditionNone}, + 0x13A7: {lower: "\uAB77", upper: "\u13A7", condition: specialCasingConditionNone}, + 0x13A8: {lower: "\uAB78", upper: "\u13A8", condition: specialCasingConditionNone}, + 0x13A9: {lower: "\uAB79", upper: "\u13A9", condition: specialCasingConditionNone}, + 0x13AA: {lower: "\uAB7A", upper: "\u13AA", condition: specialCasingConditionNone}, + 0x13AB: {lower: "\uAB7B", upper: "\u13AB", condition: specialCasingConditionNone}, + 0x13AC: {lower: "\uAB7C", upper: "\u13AC", condition: specialCasingConditionNone}, + 0x13AD: {lower: "\uAB7D", upper: "\u13AD", condition: specialCasingConditionNone}, + 0x13AE: {lower: "\uAB7E", upper: "\u13AE", condition: specialCasingConditionNone}, + 0x13AF: {lower: "\uAB7F", upper: "\u13AF", condition: specialCasingConditionNone}, + 0x13B0: {lower: "\uAB80", upper: "\u13B0", condition: specialCasingConditionNone}, + 0x13B1: {lower: "\uAB81", upper: "\u13B1", condition: specialCasingConditionNone}, + 0x13B2: {lower: "\uAB82", upper: "\u13B2", condition: specialCasingConditionNone}, + 0x13B3: {lower: "\uAB83", upper: "\u13B3", condition: specialCasingConditionNone}, + 0x13B4: {lower: "\uAB84", upper: "\u13B4", condition: specialCasingConditionNone}, + 0x13B5: {lower: "\uAB85", upper: "\u13B5", condition: specialCasingConditionNone}, + 0x13B6: {lower: "\uAB86", upper: "\u13B6", condition: specialCasingConditionNone}, + 0x13B7: {lower: "\uAB87", upper: "\u13B7", condition: specialCasingConditionNone}, + 0x13B8: {lower: "\uAB88", upper: "\u13B8", condition: specialCasingConditionNone}, + 0x13B9: {lower: "\uAB89", upper: "\u13B9", condition: specialCasingConditionNone}, + 0x13BA: {lower: "\uAB8A", upper: "\u13BA", condition: specialCasingConditionNone}, + 0x13BB: {lower: "\uAB8B", upper: "\u13BB", condition: specialCasingConditionNone}, + 0x13BC: {lower: "\uAB8C", upper: "\u13BC", condition: specialCasingConditionNone}, + 0x13BD: {lower: "\uAB8D", upper: "\u13BD", condition: specialCasingConditionNone}, + 0x13BE: {lower: "\uAB8E", upper: "\u13BE", condition: specialCasingConditionNone}, + 0x13BF: {lower: "\uAB8F", upper: "\u13BF", condition: specialCasingConditionNone}, + 0x13C0: {lower: "\uAB90", upper: "\u13C0", condition: specialCasingConditionNone}, + 0x13C1: {lower: "\uAB91", upper: "\u13C1", condition: specialCasingConditionNone}, + 0x13C2: {lower: "\uAB92", upper: "\u13C2", condition: specialCasingConditionNone}, + 0x13C3: {lower: "\uAB93", upper: "\u13C3", condition: specialCasingConditionNone}, + 0x13C4: {lower: "\uAB94", upper: "\u13C4", condition: specialCasingConditionNone}, + 0x13C5: {lower: "\uAB95", upper: "\u13C5", condition: specialCasingConditionNone}, + 0x13C6: {lower: "\uAB96", upper: "\u13C6", condition: specialCasingConditionNone}, + 0x13C7: {lower: "\uAB97", upper: "\u13C7", condition: specialCasingConditionNone}, + 0x13C8: {lower: "\uAB98", upper: "\u13C8", condition: specialCasingConditionNone}, + 0x13C9: {lower: "\uAB99", upper: "\u13C9", condition: specialCasingConditionNone}, + 0x13CA: {lower: "\uAB9A", upper: "\u13CA", condition: specialCasingConditionNone}, + 0x13CB: {lower: "\uAB9B", upper: "\u13CB", condition: specialCasingConditionNone}, + 0x13CC: {lower: "\uAB9C", upper: "\u13CC", condition: specialCasingConditionNone}, + 0x13CD: {lower: "\uAB9D", upper: "\u13CD", condition: specialCasingConditionNone}, + 0x13CE: {lower: "\uAB9E", upper: "\u13CE", condition: specialCasingConditionNone}, + 0x13CF: {lower: "\uAB9F", upper: "\u13CF", condition: specialCasingConditionNone}, + 0x13D0: {lower: "\uABA0", upper: "\u13D0", condition: specialCasingConditionNone}, + 0x13D1: {lower: "\uABA1", upper: "\u13D1", condition: specialCasingConditionNone}, + 0x13D2: {lower: "\uABA2", upper: "\u13D2", condition: specialCasingConditionNone}, + 0x13D3: {lower: "\uABA3", upper: "\u13D3", condition: specialCasingConditionNone}, + 0x13D4: {lower: "\uABA4", upper: "\u13D4", condition: specialCasingConditionNone}, + 0x13D5: {lower: "\uABA5", upper: "\u13D5", condition: specialCasingConditionNone}, + 0x13D6: {lower: "\uABA6", upper: "\u13D6", condition: specialCasingConditionNone}, + 0x13D7: {lower: "\uABA7", upper: "\u13D7", condition: specialCasingConditionNone}, + 0x13D8: {lower: "\uABA8", upper: "\u13D8", condition: specialCasingConditionNone}, + 0x13D9: {lower: "\uABA9", upper: "\u13D9", condition: specialCasingConditionNone}, + 0x13DA: {lower: "\uABAA", upper: "\u13DA", condition: specialCasingConditionNone}, + 0x13DB: {lower: "\uABAB", upper: "\u13DB", condition: specialCasingConditionNone}, + 0x13DC: {lower: "\uABAC", upper: "\u13DC", condition: specialCasingConditionNone}, + 0x13DD: {lower: "\uABAD", upper: "\u13DD", condition: specialCasingConditionNone}, + 0x13DE: {lower: "\uABAE", upper: "\u13DE", condition: specialCasingConditionNone}, + 0x13DF: {lower: "\uABAF", upper: "\u13DF", condition: specialCasingConditionNone}, + 0x13E0: {lower: "\uABB0", upper: "\u13E0", condition: specialCasingConditionNone}, + 0x13E1: {lower: "\uABB1", upper: "\u13E1", condition: specialCasingConditionNone}, + 0x13E2: {lower: "\uABB2", upper: "\u13E2", condition: specialCasingConditionNone}, + 0x13E3: {lower: "\uABB3", upper: "\u13E3", condition: specialCasingConditionNone}, + 0x13E4: {lower: "\uABB4", upper: "\u13E4", condition: specialCasingConditionNone}, + 0x13E5: {lower: "\uABB5", upper: "\u13E5", condition: specialCasingConditionNone}, + 0x13E6: {lower: "\uABB6", upper: "\u13E6", condition: specialCasingConditionNone}, + 0x13E7: {lower: "\uABB7", upper: "\u13E7", condition: specialCasingConditionNone}, + 0x13E8: {lower: "\uABB8", upper: "\u13E8", condition: specialCasingConditionNone}, + 0x13E9: {lower: "\uABB9", upper: "\u13E9", condition: specialCasingConditionNone}, + 0x13EA: {lower: "\uABBA", upper: "\u13EA", condition: specialCasingConditionNone}, + 0x13EB: {lower: "\uABBB", upper: "\u13EB", condition: specialCasingConditionNone}, + 0x13EC: {lower: "\uABBC", upper: "\u13EC", condition: specialCasingConditionNone}, + 0x13ED: {lower: "\uABBD", upper: "\u13ED", condition: specialCasingConditionNone}, + 0x13EE: {lower: "\uABBE", upper: "\u13EE", condition: specialCasingConditionNone}, + 0x13EF: {lower: "\uABBF", upper: "\u13EF", condition: specialCasingConditionNone}, + 0x13F0: {lower: "\u13F8", upper: "\u13F0", condition: specialCasingConditionNone}, + 0x13F1: {lower: "\u13F9", upper: "\u13F1", condition: specialCasingConditionNone}, + 0x13F2: {lower: "\u13FA", upper: "\u13F2", condition: specialCasingConditionNone}, + 0x13F3: {lower: "\u13FB", upper: "\u13F3", condition: specialCasingConditionNone}, + 0x13F4: {lower: "\u13FC", upper: "\u13F4", condition: specialCasingConditionNone}, + 0x13F5: {lower: "\u13FD", upper: "\u13F5", condition: specialCasingConditionNone}, + 0x13F8: {lower: "\u13F8", upper: "\u13F0", condition: specialCasingConditionNone}, + 0x13F9: {lower: "\u13F9", upper: "\u13F1", condition: specialCasingConditionNone}, + 0x13FA: {lower: "\u13FA", upper: "\u13F2", condition: specialCasingConditionNone}, + 0x13FB: {lower: "\u13FB", upper: "\u13F3", condition: specialCasingConditionNone}, + 0x13FC: {lower: "\u13FC", upper: "\u13F4", condition: specialCasingConditionNone}, + 0x13FD: {lower: "\u13FD", upper: "\u13F5", condition: specialCasingConditionNone}, + 0x1C80: {lower: "\u1C80", upper: "\u0412", condition: specialCasingConditionNone}, + 0x1C81: {lower: "\u1C81", upper: "\u0414", condition: specialCasingConditionNone}, + 0x1C82: {lower: "\u1C82", upper: "\u041E", condition: specialCasingConditionNone}, + 0x1C83: {lower: "\u1C83", upper: "\u0421", condition: specialCasingConditionNone}, + 0x1C84: {lower: "\u1C84", upper: "\u0422", condition: specialCasingConditionNone}, + 0x1C85: {lower: "\u1C85", upper: "\u0422", condition: specialCasingConditionNone}, + 0x1C86: {lower: "\u1C86", upper: "\u042A", condition: specialCasingConditionNone}, + 0x1C87: {lower: "\u1C87", upper: "\u0462", condition: specialCasingConditionNone}, + 0x1C88: {lower: "\u1C88", upper: "\uA64A", condition: specialCasingConditionNone}, + 0x1C90: {lower: "\u10D0", upper: "\u1C90", condition: specialCasingConditionNone}, + 0x1C91: {lower: "\u10D1", upper: "\u1C91", condition: specialCasingConditionNone}, + 0x1C92: {lower: "\u10D2", upper: "\u1C92", condition: specialCasingConditionNone}, + 0x1C93: {lower: "\u10D3", upper: "\u1C93", condition: specialCasingConditionNone}, + 0x1C94: {lower: "\u10D4", upper: "\u1C94", condition: specialCasingConditionNone}, + 0x1C95: {lower: "\u10D5", upper: "\u1C95", condition: specialCasingConditionNone}, + 0x1C96: {lower: "\u10D6", upper: "\u1C96", condition: specialCasingConditionNone}, + 0x1C97: {lower: "\u10D7", upper: "\u1C97", condition: specialCasingConditionNone}, + 0x1C98: {lower: "\u10D8", upper: "\u1C98", condition: specialCasingConditionNone}, + 0x1C99: {lower: "\u10D9", upper: "\u1C99", condition: specialCasingConditionNone}, + 0x1C9A: {lower: "\u10DA", upper: "\u1C9A", condition: specialCasingConditionNone}, + 0x1C9B: {lower: "\u10DB", upper: "\u1C9B", condition: specialCasingConditionNone}, + 0x1C9C: {lower: "\u10DC", upper: "\u1C9C", condition: specialCasingConditionNone}, + 0x1C9D: {lower: "\u10DD", upper: "\u1C9D", condition: specialCasingConditionNone}, + 0x1C9E: {lower: "\u10DE", upper: "\u1C9E", condition: specialCasingConditionNone}, + 0x1C9F: {lower: "\u10DF", upper: "\u1C9F", condition: specialCasingConditionNone}, + 0x1CA0: {lower: "\u10E0", upper: "\u1CA0", condition: specialCasingConditionNone}, + 0x1CA1: {lower: "\u10E1", upper: "\u1CA1", condition: specialCasingConditionNone}, + 0x1CA2: {lower: "\u10E2", upper: "\u1CA2", condition: specialCasingConditionNone}, + 0x1CA3: {lower: "\u10E3", upper: "\u1CA3", condition: specialCasingConditionNone}, + 0x1CA4: {lower: "\u10E4", upper: "\u1CA4", condition: specialCasingConditionNone}, + 0x1CA5: {lower: "\u10E5", upper: "\u1CA5", condition: specialCasingConditionNone}, + 0x1CA6: {lower: "\u10E6", upper: "\u1CA6", condition: specialCasingConditionNone}, + 0x1CA7: {lower: "\u10E7", upper: "\u1CA7", condition: specialCasingConditionNone}, + 0x1CA8: {lower: "\u10E8", upper: "\u1CA8", condition: specialCasingConditionNone}, + 0x1CA9: {lower: "\u10E9", upper: "\u1CA9", condition: specialCasingConditionNone}, + 0x1CAA: {lower: "\u10EA", upper: "\u1CAA", condition: specialCasingConditionNone}, + 0x1CAB: {lower: "\u10EB", upper: "\u1CAB", condition: specialCasingConditionNone}, + 0x1CAC: {lower: "\u10EC", upper: "\u1CAC", condition: specialCasingConditionNone}, + 0x1CAD: {lower: "\u10ED", upper: "\u1CAD", condition: specialCasingConditionNone}, + 0x1CAE: {lower: "\u10EE", upper: "\u1CAE", condition: specialCasingConditionNone}, + 0x1CAF: {lower: "\u10EF", upper: "\u1CAF", condition: specialCasingConditionNone}, + 0x1CB0: {lower: "\u10F0", upper: "\u1CB0", condition: specialCasingConditionNone}, + 0x1CB1: {lower: "\u10F1", upper: "\u1CB1", condition: specialCasingConditionNone}, + 0x1CB2: {lower: "\u10F2", upper: "\u1CB2", condition: specialCasingConditionNone}, + 0x1CB3: {lower: "\u10F3", upper: "\u1CB3", condition: specialCasingConditionNone}, + 0x1CB4: {lower: "\u10F4", upper: "\u1CB4", condition: specialCasingConditionNone}, + 0x1CB5: {lower: "\u10F5", upper: "\u1CB5", condition: specialCasingConditionNone}, + 0x1CB6: {lower: "\u10F6", upper: "\u1CB6", condition: specialCasingConditionNone}, + 0x1CB7: {lower: "\u10F7", upper: "\u1CB7", condition: specialCasingConditionNone}, + 0x1CB8: {lower: "\u10F8", upper: "\u1CB8", condition: specialCasingConditionNone}, + 0x1CB9: {lower: "\u10F9", upper: "\u1CB9", condition: specialCasingConditionNone}, + 0x1CBA: {lower: "\u10FA", upper: "\u1CBA", condition: specialCasingConditionNone}, + 0x1CBD: {lower: "\u10FD", upper: "\u1CBD", condition: specialCasingConditionNone}, + 0x1CBE: {lower: "\u10FE", upper: "\u1CBE", condition: specialCasingConditionNone}, + 0x1CBF: {lower: "\u10FF", upper: "\u1CBF", condition: specialCasingConditionNone}, + 0x1D79: {lower: "\u1D79", upper: "\uA77D", condition: specialCasingConditionNone}, + 0x1D7D: {lower: "\u1D7D", upper: "\u2C63", condition: specialCasingConditionNone}, + 0x1D8E: {lower: "\u1D8E", upper: "\uA7C6", condition: specialCasingConditionNone}, + 0x1E00: {lower: "\u1E01", upper: "\u1E00", condition: specialCasingConditionNone}, + 0x1E01: {lower: "\u1E01", upper: "\u1E00", condition: specialCasingConditionNone}, + 0x1E02: {lower: "\u1E03", upper: "\u1E02", condition: specialCasingConditionNone}, + 0x1E03: {lower: "\u1E03", upper: "\u1E02", condition: specialCasingConditionNone}, + 0x1E04: {lower: "\u1E05", upper: "\u1E04", condition: specialCasingConditionNone}, + 0x1E05: {lower: "\u1E05", upper: "\u1E04", condition: specialCasingConditionNone}, + 0x1E06: {lower: "\u1E07", upper: "\u1E06", condition: specialCasingConditionNone}, + 0x1E07: {lower: "\u1E07", upper: "\u1E06", condition: specialCasingConditionNone}, + 0x1E08: {lower: "\u1E09", upper: "\u1E08", condition: specialCasingConditionNone}, + 0x1E09: {lower: "\u1E09", upper: "\u1E08", condition: specialCasingConditionNone}, + 0x1E0A: {lower: "\u1E0B", upper: "\u1E0A", condition: specialCasingConditionNone}, + 0x1E0B: {lower: "\u1E0B", upper: "\u1E0A", condition: specialCasingConditionNone}, + 0x1E0C: {lower: "\u1E0D", upper: "\u1E0C", condition: specialCasingConditionNone}, + 0x1E0D: {lower: "\u1E0D", upper: "\u1E0C", condition: specialCasingConditionNone}, + 0x1E0E: {lower: "\u1E0F", upper: "\u1E0E", condition: specialCasingConditionNone}, + 0x1E0F: {lower: "\u1E0F", upper: "\u1E0E", condition: specialCasingConditionNone}, + 0x1E10: {lower: "\u1E11", upper: "\u1E10", condition: specialCasingConditionNone}, + 0x1E11: {lower: "\u1E11", upper: "\u1E10", condition: specialCasingConditionNone}, + 0x1E12: {lower: "\u1E13", upper: "\u1E12", condition: specialCasingConditionNone}, + 0x1E13: {lower: "\u1E13", upper: "\u1E12", condition: specialCasingConditionNone}, + 0x1E14: {lower: "\u1E15", upper: "\u1E14", condition: specialCasingConditionNone}, + 0x1E15: {lower: "\u1E15", upper: "\u1E14", condition: specialCasingConditionNone}, + 0x1E16: {lower: "\u1E17", upper: "\u1E16", condition: specialCasingConditionNone}, + 0x1E17: {lower: "\u1E17", upper: "\u1E16", condition: specialCasingConditionNone}, + 0x1E18: {lower: "\u1E19", upper: "\u1E18", condition: specialCasingConditionNone}, + 0x1E19: {lower: "\u1E19", upper: "\u1E18", condition: specialCasingConditionNone}, + 0x1E1A: {lower: "\u1E1B", upper: "\u1E1A", condition: specialCasingConditionNone}, + 0x1E1B: {lower: "\u1E1B", upper: "\u1E1A", condition: specialCasingConditionNone}, + 0x1E1C: {lower: "\u1E1D", upper: "\u1E1C", condition: specialCasingConditionNone}, + 0x1E1D: {lower: "\u1E1D", upper: "\u1E1C", condition: specialCasingConditionNone}, + 0x1E1E: {lower: "\u1E1F", upper: "\u1E1E", condition: specialCasingConditionNone}, + 0x1E1F: {lower: "\u1E1F", upper: "\u1E1E", condition: specialCasingConditionNone}, + 0x1E20: {lower: "\u1E21", upper: "\u1E20", condition: specialCasingConditionNone}, + 0x1E21: {lower: "\u1E21", upper: "\u1E20", condition: specialCasingConditionNone}, + 0x1E22: {lower: "\u1E23", upper: "\u1E22", condition: specialCasingConditionNone}, + 0x1E23: {lower: "\u1E23", upper: "\u1E22", condition: specialCasingConditionNone}, + 0x1E24: {lower: "\u1E25", upper: "\u1E24", condition: specialCasingConditionNone}, + 0x1E25: {lower: "\u1E25", upper: "\u1E24", condition: specialCasingConditionNone}, + 0x1E26: {lower: "\u1E27", upper: "\u1E26", condition: specialCasingConditionNone}, + 0x1E27: {lower: "\u1E27", upper: "\u1E26", condition: specialCasingConditionNone}, + 0x1E28: {lower: "\u1E29", upper: "\u1E28", condition: specialCasingConditionNone}, + 0x1E29: {lower: "\u1E29", upper: "\u1E28", condition: specialCasingConditionNone}, + 0x1E2A: {lower: "\u1E2B", upper: "\u1E2A", condition: specialCasingConditionNone}, + 0x1E2B: {lower: "\u1E2B", upper: "\u1E2A", condition: specialCasingConditionNone}, + 0x1E2C: {lower: "\u1E2D", upper: "\u1E2C", condition: specialCasingConditionNone}, + 0x1E2D: {lower: "\u1E2D", upper: "\u1E2C", condition: specialCasingConditionNone}, + 0x1E2E: {lower: "\u1E2F", upper: "\u1E2E", condition: specialCasingConditionNone}, + 0x1E2F: {lower: "\u1E2F", upper: "\u1E2E", condition: specialCasingConditionNone}, + 0x1E30: {lower: "\u1E31", upper: "\u1E30", condition: specialCasingConditionNone}, + 0x1E31: {lower: "\u1E31", upper: "\u1E30", condition: specialCasingConditionNone}, + 0x1E32: {lower: "\u1E33", upper: "\u1E32", condition: specialCasingConditionNone}, + 0x1E33: {lower: "\u1E33", upper: "\u1E32", condition: specialCasingConditionNone}, + 0x1E34: {lower: "\u1E35", upper: "\u1E34", condition: specialCasingConditionNone}, + 0x1E35: {lower: "\u1E35", upper: "\u1E34", condition: specialCasingConditionNone}, + 0x1E36: {lower: "\u1E37", upper: "\u1E36", condition: specialCasingConditionNone}, + 0x1E37: {lower: "\u1E37", upper: "\u1E36", condition: specialCasingConditionNone}, + 0x1E38: {lower: "\u1E39", upper: "\u1E38", condition: specialCasingConditionNone}, + 0x1E39: {lower: "\u1E39", upper: "\u1E38", condition: specialCasingConditionNone}, + 0x1E3A: {lower: "\u1E3B", upper: "\u1E3A", condition: specialCasingConditionNone}, + 0x1E3B: {lower: "\u1E3B", upper: "\u1E3A", condition: specialCasingConditionNone}, + 0x1E3C: {lower: "\u1E3D", upper: "\u1E3C", condition: specialCasingConditionNone}, + 0x1E3D: {lower: "\u1E3D", upper: "\u1E3C", condition: specialCasingConditionNone}, + 0x1E3E: {lower: "\u1E3F", upper: "\u1E3E", condition: specialCasingConditionNone}, + 0x1E3F: {lower: "\u1E3F", upper: "\u1E3E", condition: specialCasingConditionNone}, + 0x1E40: {lower: "\u1E41", upper: "\u1E40", condition: specialCasingConditionNone}, + 0x1E41: {lower: "\u1E41", upper: "\u1E40", condition: specialCasingConditionNone}, + 0x1E42: {lower: "\u1E43", upper: "\u1E42", condition: specialCasingConditionNone}, + 0x1E43: {lower: "\u1E43", upper: "\u1E42", condition: specialCasingConditionNone}, + 0x1E44: {lower: "\u1E45", upper: "\u1E44", condition: specialCasingConditionNone}, + 0x1E45: {lower: "\u1E45", upper: "\u1E44", condition: specialCasingConditionNone}, + 0x1E46: {lower: "\u1E47", upper: "\u1E46", condition: specialCasingConditionNone}, + 0x1E47: {lower: "\u1E47", upper: "\u1E46", condition: specialCasingConditionNone}, + 0x1E48: {lower: "\u1E49", upper: "\u1E48", condition: specialCasingConditionNone}, + 0x1E49: {lower: "\u1E49", upper: "\u1E48", condition: specialCasingConditionNone}, + 0x1E4A: {lower: "\u1E4B", upper: "\u1E4A", condition: specialCasingConditionNone}, + 0x1E4B: {lower: "\u1E4B", upper: "\u1E4A", condition: specialCasingConditionNone}, + 0x1E4C: {lower: "\u1E4D", upper: "\u1E4C", condition: specialCasingConditionNone}, + 0x1E4D: {lower: "\u1E4D", upper: "\u1E4C", condition: specialCasingConditionNone}, + 0x1E4E: {lower: "\u1E4F", upper: "\u1E4E", condition: specialCasingConditionNone}, + 0x1E4F: {lower: "\u1E4F", upper: "\u1E4E", condition: specialCasingConditionNone}, + 0x1E50: {lower: "\u1E51", upper: "\u1E50", condition: specialCasingConditionNone}, + 0x1E51: {lower: "\u1E51", upper: "\u1E50", condition: specialCasingConditionNone}, + 0x1E52: {lower: "\u1E53", upper: "\u1E52", condition: specialCasingConditionNone}, + 0x1E53: {lower: "\u1E53", upper: "\u1E52", condition: specialCasingConditionNone}, + 0x1E54: {lower: "\u1E55", upper: "\u1E54", condition: specialCasingConditionNone}, + 0x1E55: {lower: "\u1E55", upper: "\u1E54", condition: specialCasingConditionNone}, + 0x1E56: {lower: "\u1E57", upper: "\u1E56", condition: specialCasingConditionNone}, + 0x1E57: {lower: "\u1E57", upper: "\u1E56", condition: specialCasingConditionNone}, + 0x1E58: {lower: "\u1E59", upper: "\u1E58", condition: specialCasingConditionNone}, + 0x1E59: {lower: "\u1E59", upper: "\u1E58", condition: specialCasingConditionNone}, + 0x1E5A: {lower: "\u1E5B", upper: "\u1E5A", condition: specialCasingConditionNone}, + 0x1E5B: {lower: "\u1E5B", upper: "\u1E5A", condition: specialCasingConditionNone}, + 0x1E5C: {lower: "\u1E5D", upper: "\u1E5C", condition: specialCasingConditionNone}, + 0x1E5D: {lower: "\u1E5D", upper: "\u1E5C", condition: specialCasingConditionNone}, + 0x1E5E: {lower: "\u1E5F", upper: "\u1E5E", condition: specialCasingConditionNone}, + 0x1E5F: {lower: "\u1E5F", upper: "\u1E5E", condition: specialCasingConditionNone}, + 0x1E60: {lower: "\u1E61", upper: "\u1E60", condition: specialCasingConditionNone}, + 0x1E61: {lower: "\u1E61", upper: "\u1E60", condition: specialCasingConditionNone}, + 0x1E62: {lower: "\u1E63", upper: "\u1E62", condition: specialCasingConditionNone}, + 0x1E63: {lower: "\u1E63", upper: "\u1E62", condition: specialCasingConditionNone}, + 0x1E64: {lower: "\u1E65", upper: "\u1E64", condition: specialCasingConditionNone}, + 0x1E65: {lower: "\u1E65", upper: "\u1E64", condition: specialCasingConditionNone}, + 0x1E66: {lower: "\u1E67", upper: "\u1E66", condition: specialCasingConditionNone}, + 0x1E67: {lower: "\u1E67", upper: "\u1E66", condition: specialCasingConditionNone}, + 0x1E68: {lower: "\u1E69", upper: "\u1E68", condition: specialCasingConditionNone}, + 0x1E69: {lower: "\u1E69", upper: "\u1E68", condition: specialCasingConditionNone}, + 0x1E6A: {lower: "\u1E6B", upper: "\u1E6A", condition: specialCasingConditionNone}, + 0x1E6B: {lower: "\u1E6B", upper: "\u1E6A", condition: specialCasingConditionNone}, + 0x1E6C: {lower: "\u1E6D", upper: "\u1E6C", condition: specialCasingConditionNone}, + 0x1E6D: {lower: "\u1E6D", upper: "\u1E6C", condition: specialCasingConditionNone}, + 0x1E6E: {lower: "\u1E6F", upper: "\u1E6E", condition: specialCasingConditionNone}, + 0x1E6F: {lower: "\u1E6F", upper: "\u1E6E", condition: specialCasingConditionNone}, + 0x1E70: {lower: "\u1E71", upper: "\u1E70", condition: specialCasingConditionNone}, + 0x1E71: {lower: "\u1E71", upper: "\u1E70", condition: specialCasingConditionNone}, + 0x1E72: {lower: "\u1E73", upper: "\u1E72", condition: specialCasingConditionNone}, + 0x1E73: {lower: "\u1E73", upper: "\u1E72", condition: specialCasingConditionNone}, + 0x1E74: {lower: "\u1E75", upper: "\u1E74", condition: specialCasingConditionNone}, + 0x1E75: {lower: "\u1E75", upper: "\u1E74", condition: specialCasingConditionNone}, + 0x1E76: {lower: "\u1E77", upper: "\u1E76", condition: specialCasingConditionNone}, + 0x1E77: {lower: "\u1E77", upper: "\u1E76", condition: specialCasingConditionNone}, + 0x1E78: {lower: "\u1E79", upper: "\u1E78", condition: specialCasingConditionNone}, + 0x1E79: {lower: "\u1E79", upper: "\u1E78", condition: specialCasingConditionNone}, + 0x1E7A: {lower: "\u1E7B", upper: "\u1E7A", condition: specialCasingConditionNone}, + 0x1E7B: {lower: "\u1E7B", upper: "\u1E7A", condition: specialCasingConditionNone}, + 0x1E7C: {lower: "\u1E7D", upper: "\u1E7C", condition: specialCasingConditionNone}, + 0x1E7D: {lower: "\u1E7D", upper: "\u1E7C", condition: specialCasingConditionNone}, + 0x1E7E: {lower: "\u1E7F", upper: "\u1E7E", condition: specialCasingConditionNone}, + 0x1E7F: {lower: "\u1E7F", upper: "\u1E7E", condition: specialCasingConditionNone}, + 0x1E80: {lower: "\u1E81", upper: "\u1E80", condition: specialCasingConditionNone}, + 0x1E81: {lower: "\u1E81", upper: "\u1E80", condition: specialCasingConditionNone}, + 0x1E82: {lower: "\u1E83", upper: "\u1E82", condition: specialCasingConditionNone}, + 0x1E83: {lower: "\u1E83", upper: "\u1E82", condition: specialCasingConditionNone}, + 0x1E84: {lower: "\u1E85", upper: "\u1E84", condition: specialCasingConditionNone}, + 0x1E85: {lower: "\u1E85", upper: "\u1E84", condition: specialCasingConditionNone}, + 0x1E86: {lower: "\u1E87", upper: "\u1E86", condition: specialCasingConditionNone}, + 0x1E87: {lower: "\u1E87", upper: "\u1E86", condition: specialCasingConditionNone}, + 0x1E88: {lower: "\u1E89", upper: "\u1E88", condition: specialCasingConditionNone}, + 0x1E89: {lower: "\u1E89", upper: "\u1E88", condition: specialCasingConditionNone}, + 0x1E8A: {lower: "\u1E8B", upper: "\u1E8A", condition: specialCasingConditionNone}, + 0x1E8B: {lower: "\u1E8B", upper: "\u1E8A", condition: specialCasingConditionNone}, + 0x1E8C: {lower: "\u1E8D", upper: "\u1E8C", condition: specialCasingConditionNone}, + 0x1E8D: {lower: "\u1E8D", upper: "\u1E8C", condition: specialCasingConditionNone}, + 0x1E8E: {lower: "\u1E8F", upper: "\u1E8E", condition: specialCasingConditionNone}, + 0x1E8F: {lower: "\u1E8F", upper: "\u1E8E", condition: specialCasingConditionNone}, + 0x1E90: {lower: "\u1E91", upper: "\u1E90", condition: specialCasingConditionNone}, + 0x1E91: {lower: "\u1E91", upper: "\u1E90", condition: specialCasingConditionNone}, + 0x1E92: {lower: "\u1E93", upper: "\u1E92", condition: specialCasingConditionNone}, + 0x1E93: {lower: "\u1E93", upper: "\u1E92", condition: specialCasingConditionNone}, + 0x1E94: {lower: "\u1E95", upper: "\u1E94", condition: specialCasingConditionNone}, + 0x1E95: {lower: "\u1E95", upper: "\u1E94", condition: specialCasingConditionNone}, + 0x1E96: {lower: "\u1E96", upper: "\u0048\u0331", condition: specialCasingConditionNone}, + 0x1E97: {lower: "\u1E97", upper: "\u0054\u0308", condition: specialCasingConditionNone}, + 0x1E98: {lower: "\u1E98", upper: "\u0057\u030A", condition: specialCasingConditionNone}, + 0x1E99: {lower: "\u1E99", upper: "\u0059\u030A", condition: specialCasingConditionNone}, + 0x1E9A: {lower: "\u1E9A", upper: "\u0041\u02BE", condition: specialCasingConditionNone}, + 0x1E9B: {lower: "\u1E9B", upper: "\u1E60", condition: specialCasingConditionNone}, + 0x1E9E: {lower: "\u00DF", upper: "\u1E9E", condition: specialCasingConditionNone}, + 0x1EA0: {lower: "\u1EA1", upper: "\u1EA0", condition: specialCasingConditionNone}, + 0x1EA1: {lower: "\u1EA1", upper: "\u1EA0", condition: specialCasingConditionNone}, + 0x1EA2: {lower: "\u1EA3", upper: "\u1EA2", condition: specialCasingConditionNone}, + 0x1EA3: {lower: "\u1EA3", upper: "\u1EA2", condition: specialCasingConditionNone}, + 0x1EA4: {lower: "\u1EA5", upper: "\u1EA4", condition: specialCasingConditionNone}, + 0x1EA5: {lower: "\u1EA5", upper: "\u1EA4", condition: specialCasingConditionNone}, + 0x1EA6: {lower: "\u1EA7", upper: "\u1EA6", condition: specialCasingConditionNone}, + 0x1EA7: {lower: "\u1EA7", upper: "\u1EA6", condition: specialCasingConditionNone}, + 0x1EA8: {lower: "\u1EA9", upper: "\u1EA8", condition: specialCasingConditionNone}, + 0x1EA9: {lower: "\u1EA9", upper: "\u1EA8", condition: specialCasingConditionNone}, + 0x1EAA: {lower: "\u1EAB", upper: "\u1EAA", condition: specialCasingConditionNone}, + 0x1EAB: {lower: "\u1EAB", upper: "\u1EAA", condition: specialCasingConditionNone}, + 0x1EAC: {lower: "\u1EAD", upper: "\u1EAC", condition: specialCasingConditionNone}, + 0x1EAD: {lower: "\u1EAD", upper: "\u1EAC", condition: specialCasingConditionNone}, + 0x1EAE: {lower: "\u1EAF", upper: "\u1EAE", condition: specialCasingConditionNone}, + 0x1EAF: {lower: "\u1EAF", upper: "\u1EAE", condition: specialCasingConditionNone}, + 0x1EB0: {lower: "\u1EB1", upper: "\u1EB0", condition: specialCasingConditionNone}, + 0x1EB1: {lower: "\u1EB1", upper: "\u1EB0", condition: specialCasingConditionNone}, + 0x1EB2: {lower: "\u1EB3", upper: "\u1EB2", condition: specialCasingConditionNone}, + 0x1EB3: {lower: "\u1EB3", upper: "\u1EB2", condition: specialCasingConditionNone}, + 0x1EB4: {lower: "\u1EB5", upper: "\u1EB4", condition: specialCasingConditionNone}, + 0x1EB5: {lower: "\u1EB5", upper: "\u1EB4", condition: specialCasingConditionNone}, + 0x1EB6: {lower: "\u1EB7", upper: "\u1EB6", condition: specialCasingConditionNone}, + 0x1EB7: {lower: "\u1EB7", upper: "\u1EB6", condition: specialCasingConditionNone}, + 0x1EB8: {lower: "\u1EB9", upper: "\u1EB8", condition: specialCasingConditionNone}, + 0x1EB9: {lower: "\u1EB9", upper: "\u1EB8", condition: specialCasingConditionNone}, + 0x1EBA: {lower: "\u1EBB", upper: "\u1EBA", condition: specialCasingConditionNone}, + 0x1EBB: {lower: "\u1EBB", upper: "\u1EBA", condition: specialCasingConditionNone}, + 0x1EBC: {lower: "\u1EBD", upper: "\u1EBC", condition: specialCasingConditionNone}, + 0x1EBD: {lower: "\u1EBD", upper: "\u1EBC", condition: specialCasingConditionNone}, + 0x1EBE: {lower: "\u1EBF", upper: "\u1EBE", condition: specialCasingConditionNone}, + 0x1EBF: {lower: "\u1EBF", upper: "\u1EBE", condition: specialCasingConditionNone}, + 0x1EC0: {lower: "\u1EC1", upper: "\u1EC0", condition: specialCasingConditionNone}, + 0x1EC1: {lower: "\u1EC1", upper: "\u1EC0", condition: specialCasingConditionNone}, + 0x1EC2: {lower: "\u1EC3", upper: "\u1EC2", condition: specialCasingConditionNone}, + 0x1EC3: {lower: "\u1EC3", upper: "\u1EC2", condition: specialCasingConditionNone}, + 0x1EC4: {lower: "\u1EC5", upper: "\u1EC4", condition: specialCasingConditionNone}, + 0x1EC5: {lower: "\u1EC5", upper: "\u1EC4", condition: specialCasingConditionNone}, + 0x1EC6: {lower: "\u1EC7", upper: "\u1EC6", condition: specialCasingConditionNone}, + 0x1EC7: {lower: "\u1EC7", upper: "\u1EC6", condition: specialCasingConditionNone}, + 0x1EC8: {lower: "\u1EC9", upper: "\u1EC8", condition: specialCasingConditionNone}, + 0x1EC9: {lower: "\u1EC9", upper: "\u1EC8", condition: specialCasingConditionNone}, + 0x1ECA: {lower: "\u1ECB", upper: "\u1ECA", condition: specialCasingConditionNone}, + 0x1ECB: {lower: "\u1ECB", upper: "\u1ECA", condition: specialCasingConditionNone}, + 0x1ECC: {lower: "\u1ECD", upper: "\u1ECC", condition: specialCasingConditionNone}, + 0x1ECD: {lower: "\u1ECD", upper: "\u1ECC", condition: specialCasingConditionNone}, + 0x1ECE: {lower: "\u1ECF", upper: "\u1ECE", condition: specialCasingConditionNone}, + 0x1ECF: {lower: "\u1ECF", upper: "\u1ECE", condition: specialCasingConditionNone}, + 0x1ED0: {lower: "\u1ED1", upper: "\u1ED0", condition: specialCasingConditionNone}, + 0x1ED1: {lower: "\u1ED1", upper: "\u1ED0", condition: specialCasingConditionNone}, + 0x1ED2: {lower: "\u1ED3", upper: "\u1ED2", condition: specialCasingConditionNone}, + 0x1ED3: {lower: "\u1ED3", upper: "\u1ED2", condition: specialCasingConditionNone}, + 0x1ED4: {lower: "\u1ED5", upper: "\u1ED4", condition: specialCasingConditionNone}, + 0x1ED5: {lower: "\u1ED5", upper: "\u1ED4", condition: specialCasingConditionNone}, + 0x1ED6: {lower: "\u1ED7", upper: "\u1ED6", condition: specialCasingConditionNone}, + 0x1ED7: {lower: "\u1ED7", upper: "\u1ED6", condition: specialCasingConditionNone}, + 0x1ED8: {lower: "\u1ED9", upper: "\u1ED8", condition: specialCasingConditionNone}, + 0x1ED9: {lower: "\u1ED9", upper: "\u1ED8", condition: specialCasingConditionNone}, + 0x1EDA: {lower: "\u1EDB", upper: "\u1EDA", condition: specialCasingConditionNone}, + 0x1EDB: {lower: "\u1EDB", upper: "\u1EDA", condition: specialCasingConditionNone}, + 0x1EDC: {lower: "\u1EDD", upper: "\u1EDC", condition: specialCasingConditionNone}, + 0x1EDD: {lower: "\u1EDD", upper: "\u1EDC", condition: specialCasingConditionNone}, + 0x1EDE: {lower: "\u1EDF", upper: "\u1EDE", condition: specialCasingConditionNone}, + 0x1EDF: {lower: "\u1EDF", upper: "\u1EDE", condition: specialCasingConditionNone}, + 0x1EE0: {lower: "\u1EE1", upper: "\u1EE0", condition: specialCasingConditionNone}, + 0x1EE1: {lower: "\u1EE1", upper: "\u1EE0", condition: specialCasingConditionNone}, + 0x1EE2: {lower: "\u1EE3", upper: "\u1EE2", condition: specialCasingConditionNone}, + 0x1EE3: {lower: "\u1EE3", upper: "\u1EE2", condition: specialCasingConditionNone}, + 0x1EE4: {lower: "\u1EE5", upper: "\u1EE4", condition: specialCasingConditionNone}, + 0x1EE5: {lower: "\u1EE5", upper: "\u1EE4", condition: specialCasingConditionNone}, + 0x1EE6: {lower: "\u1EE7", upper: "\u1EE6", condition: specialCasingConditionNone}, + 0x1EE7: {lower: "\u1EE7", upper: "\u1EE6", condition: specialCasingConditionNone}, + 0x1EE8: {lower: "\u1EE9", upper: "\u1EE8", condition: specialCasingConditionNone}, + 0x1EE9: {lower: "\u1EE9", upper: "\u1EE8", condition: specialCasingConditionNone}, + 0x1EEA: {lower: "\u1EEB", upper: "\u1EEA", condition: specialCasingConditionNone}, + 0x1EEB: {lower: "\u1EEB", upper: "\u1EEA", condition: specialCasingConditionNone}, + 0x1EEC: {lower: "\u1EED", upper: "\u1EEC", condition: specialCasingConditionNone}, + 0x1EED: {lower: "\u1EED", upper: "\u1EEC", condition: specialCasingConditionNone}, + 0x1EEE: {lower: "\u1EEF", upper: "\u1EEE", condition: specialCasingConditionNone}, + 0x1EEF: {lower: "\u1EEF", upper: "\u1EEE", condition: specialCasingConditionNone}, + 0x1EF0: {lower: "\u1EF1", upper: "\u1EF0", condition: specialCasingConditionNone}, + 0x1EF1: {lower: "\u1EF1", upper: "\u1EF0", condition: specialCasingConditionNone}, + 0x1EF2: {lower: "\u1EF3", upper: "\u1EF2", condition: specialCasingConditionNone}, + 0x1EF3: {lower: "\u1EF3", upper: "\u1EF2", condition: specialCasingConditionNone}, + 0x1EF4: {lower: "\u1EF5", upper: "\u1EF4", condition: specialCasingConditionNone}, + 0x1EF5: {lower: "\u1EF5", upper: "\u1EF4", condition: specialCasingConditionNone}, + 0x1EF6: {lower: "\u1EF7", upper: "\u1EF6", condition: specialCasingConditionNone}, + 0x1EF7: {lower: "\u1EF7", upper: "\u1EF6", condition: specialCasingConditionNone}, + 0x1EF8: {lower: "\u1EF9", upper: "\u1EF8", condition: specialCasingConditionNone}, + 0x1EF9: {lower: "\u1EF9", upper: "\u1EF8", condition: specialCasingConditionNone}, + 0x1EFA: {lower: "\u1EFB", upper: "\u1EFA", condition: specialCasingConditionNone}, + 0x1EFB: {lower: "\u1EFB", upper: "\u1EFA", condition: specialCasingConditionNone}, + 0x1EFC: {lower: "\u1EFD", upper: "\u1EFC", condition: specialCasingConditionNone}, + 0x1EFD: {lower: "\u1EFD", upper: "\u1EFC", condition: specialCasingConditionNone}, + 0x1EFE: {lower: "\u1EFF", upper: "\u1EFE", condition: specialCasingConditionNone}, + 0x1EFF: {lower: "\u1EFF", upper: "\u1EFE", condition: specialCasingConditionNone}, + 0x1F00: {lower: "\u1F00", upper: "\u1F08", condition: specialCasingConditionNone}, + 0x1F01: {lower: "\u1F01", upper: "\u1F09", condition: specialCasingConditionNone}, + 0x1F02: {lower: "\u1F02", upper: "\u1F0A", condition: specialCasingConditionNone}, + 0x1F03: {lower: "\u1F03", upper: "\u1F0B", condition: specialCasingConditionNone}, + 0x1F04: {lower: "\u1F04", upper: "\u1F0C", condition: specialCasingConditionNone}, + 0x1F05: {lower: "\u1F05", upper: "\u1F0D", condition: specialCasingConditionNone}, + 0x1F06: {lower: "\u1F06", upper: "\u1F0E", condition: specialCasingConditionNone}, + 0x1F07: {lower: "\u1F07", upper: "\u1F0F", condition: specialCasingConditionNone}, + 0x1F08: {lower: "\u1F00", upper: "\u1F08", condition: specialCasingConditionNone}, + 0x1F09: {lower: "\u1F01", upper: "\u1F09", condition: specialCasingConditionNone}, + 0x1F0A: {lower: "\u1F02", upper: "\u1F0A", condition: specialCasingConditionNone}, + 0x1F0B: {lower: "\u1F03", upper: "\u1F0B", condition: specialCasingConditionNone}, + 0x1F0C: {lower: "\u1F04", upper: "\u1F0C", condition: specialCasingConditionNone}, + 0x1F0D: {lower: "\u1F05", upper: "\u1F0D", condition: specialCasingConditionNone}, + 0x1F0E: {lower: "\u1F06", upper: "\u1F0E", condition: specialCasingConditionNone}, + 0x1F0F: {lower: "\u1F07", upper: "\u1F0F", condition: specialCasingConditionNone}, + 0x1F10: {lower: "\u1F10", upper: "\u1F18", condition: specialCasingConditionNone}, + 0x1F11: {lower: "\u1F11", upper: "\u1F19", condition: specialCasingConditionNone}, + 0x1F12: {lower: "\u1F12", upper: "\u1F1A", condition: specialCasingConditionNone}, + 0x1F13: {lower: "\u1F13", upper: "\u1F1B", condition: specialCasingConditionNone}, + 0x1F14: {lower: "\u1F14", upper: "\u1F1C", condition: specialCasingConditionNone}, + 0x1F15: {lower: "\u1F15", upper: "\u1F1D", condition: specialCasingConditionNone}, + 0x1F18: {lower: "\u1F10", upper: "\u1F18", condition: specialCasingConditionNone}, + 0x1F19: {lower: "\u1F11", upper: "\u1F19", condition: specialCasingConditionNone}, + 0x1F1A: {lower: "\u1F12", upper: "\u1F1A", condition: specialCasingConditionNone}, + 0x1F1B: {lower: "\u1F13", upper: "\u1F1B", condition: specialCasingConditionNone}, + 0x1F1C: {lower: "\u1F14", upper: "\u1F1C", condition: specialCasingConditionNone}, + 0x1F1D: {lower: "\u1F15", upper: "\u1F1D", condition: specialCasingConditionNone}, + 0x1F20: {lower: "\u1F20", upper: "\u1F28", condition: specialCasingConditionNone}, + 0x1F21: {lower: "\u1F21", upper: "\u1F29", condition: specialCasingConditionNone}, + 0x1F22: {lower: "\u1F22", upper: "\u1F2A", condition: specialCasingConditionNone}, + 0x1F23: {lower: "\u1F23", upper: "\u1F2B", condition: specialCasingConditionNone}, + 0x1F24: {lower: "\u1F24", upper: "\u1F2C", condition: specialCasingConditionNone}, + 0x1F25: {lower: "\u1F25", upper: "\u1F2D", condition: specialCasingConditionNone}, + 0x1F26: {lower: "\u1F26", upper: "\u1F2E", condition: specialCasingConditionNone}, + 0x1F27: {lower: "\u1F27", upper: "\u1F2F", condition: specialCasingConditionNone}, + 0x1F28: {lower: "\u1F20", upper: "\u1F28", condition: specialCasingConditionNone}, + 0x1F29: {lower: "\u1F21", upper: "\u1F29", condition: specialCasingConditionNone}, + 0x1F2A: {lower: "\u1F22", upper: "\u1F2A", condition: specialCasingConditionNone}, + 0x1F2B: {lower: "\u1F23", upper: "\u1F2B", condition: specialCasingConditionNone}, + 0x1F2C: {lower: "\u1F24", upper: "\u1F2C", condition: specialCasingConditionNone}, + 0x1F2D: {lower: "\u1F25", upper: "\u1F2D", condition: specialCasingConditionNone}, + 0x1F2E: {lower: "\u1F26", upper: "\u1F2E", condition: specialCasingConditionNone}, + 0x1F2F: {lower: "\u1F27", upper: "\u1F2F", condition: specialCasingConditionNone}, + 0x1F30: {lower: "\u1F30", upper: "\u1F38", condition: specialCasingConditionNone}, + 0x1F31: {lower: "\u1F31", upper: "\u1F39", condition: specialCasingConditionNone}, + 0x1F32: {lower: "\u1F32", upper: "\u1F3A", condition: specialCasingConditionNone}, + 0x1F33: {lower: "\u1F33", upper: "\u1F3B", condition: specialCasingConditionNone}, + 0x1F34: {lower: "\u1F34", upper: "\u1F3C", condition: specialCasingConditionNone}, + 0x1F35: {lower: "\u1F35", upper: "\u1F3D", condition: specialCasingConditionNone}, + 0x1F36: {lower: "\u1F36", upper: "\u1F3E", condition: specialCasingConditionNone}, + 0x1F37: {lower: "\u1F37", upper: "\u1F3F", condition: specialCasingConditionNone}, + 0x1F38: {lower: "\u1F30", upper: "\u1F38", condition: specialCasingConditionNone}, + 0x1F39: {lower: "\u1F31", upper: "\u1F39", condition: specialCasingConditionNone}, + 0x1F3A: {lower: "\u1F32", upper: "\u1F3A", condition: specialCasingConditionNone}, + 0x1F3B: {lower: "\u1F33", upper: "\u1F3B", condition: specialCasingConditionNone}, + 0x1F3C: {lower: "\u1F34", upper: "\u1F3C", condition: specialCasingConditionNone}, + 0x1F3D: {lower: "\u1F35", upper: "\u1F3D", condition: specialCasingConditionNone}, + 0x1F3E: {lower: "\u1F36", upper: "\u1F3E", condition: specialCasingConditionNone}, + 0x1F3F: {lower: "\u1F37", upper: "\u1F3F", condition: specialCasingConditionNone}, + 0x1F40: {lower: "\u1F40", upper: "\u1F48", condition: specialCasingConditionNone}, + 0x1F41: {lower: "\u1F41", upper: "\u1F49", condition: specialCasingConditionNone}, + 0x1F42: {lower: "\u1F42", upper: "\u1F4A", condition: specialCasingConditionNone}, + 0x1F43: {lower: "\u1F43", upper: "\u1F4B", condition: specialCasingConditionNone}, + 0x1F44: {lower: "\u1F44", upper: "\u1F4C", condition: specialCasingConditionNone}, + 0x1F45: {lower: "\u1F45", upper: "\u1F4D", condition: specialCasingConditionNone}, + 0x1F48: {lower: "\u1F40", upper: "\u1F48", condition: specialCasingConditionNone}, + 0x1F49: {lower: "\u1F41", upper: "\u1F49", condition: specialCasingConditionNone}, + 0x1F4A: {lower: "\u1F42", upper: "\u1F4A", condition: specialCasingConditionNone}, + 0x1F4B: {lower: "\u1F43", upper: "\u1F4B", condition: specialCasingConditionNone}, + 0x1F4C: {lower: "\u1F44", upper: "\u1F4C", condition: specialCasingConditionNone}, + 0x1F4D: {lower: "\u1F45", upper: "\u1F4D", condition: specialCasingConditionNone}, + 0x1F50: {lower: "\u1F50", upper: "\u03A5\u0313", condition: specialCasingConditionNone}, + 0x1F51: {lower: "\u1F51", upper: "\u1F59", condition: specialCasingConditionNone}, + 0x1F52: {lower: "\u1F52", upper: "\u03A5\u0313\u0300", condition: specialCasingConditionNone}, + 0x1F53: {lower: "\u1F53", upper: "\u1F5B", condition: specialCasingConditionNone}, + 0x1F54: {lower: "\u1F54", upper: "\u03A5\u0313\u0301", condition: specialCasingConditionNone}, + 0x1F55: {lower: "\u1F55", upper: "\u1F5D", condition: specialCasingConditionNone}, + 0x1F56: {lower: "\u1F56", upper: "\u03A5\u0313\u0342", condition: specialCasingConditionNone}, + 0x1F57: {lower: "\u1F57", upper: "\u1F5F", condition: specialCasingConditionNone}, + 0x1F59: {lower: "\u1F51", upper: "\u1F59", condition: specialCasingConditionNone}, + 0x1F5B: {lower: "\u1F53", upper: "\u1F5B", condition: specialCasingConditionNone}, + 0x1F5D: {lower: "\u1F55", upper: "\u1F5D", condition: specialCasingConditionNone}, + 0x1F5F: {lower: "\u1F57", upper: "\u1F5F", condition: specialCasingConditionNone}, + 0x1F60: {lower: "\u1F60", upper: "\u1F68", condition: specialCasingConditionNone}, + 0x1F61: {lower: "\u1F61", upper: "\u1F69", condition: specialCasingConditionNone}, + 0x1F62: {lower: "\u1F62", upper: "\u1F6A", condition: specialCasingConditionNone}, + 0x1F63: {lower: "\u1F63", upper: "\u1F6B", condition: specialCasingConditionNone}, + 0x1F64: {lower: "\u1F64", upper: "\u1F6C", condition: specialCasingConditionNone}, + 0x1F65: {lower: "\u1F65", upper: "\u1F6D", condition: specialCasingConditionNone}, + 0x1F66: {lower: "\u1F66", upper: "\u1F6E", condition: specialCasingConditionNone}, + 0x1F67: {lower: "\u1F67", upper: "\u1F6F", condition: specialCasingConditionNone}, + 0x1F68: {lower: "\u1F60", upper: "\u1F68", condition: specialCasingConditionNone}, + 0x1F69: {lower: "\u1F61", upper: "\u1F69", condition: specialCasingConditionNone}, + 0x1F6A: {lower: "\u1F62", upper: "\u1F6A", condition: specialCasingConditionNone}, + 0x1F6B: {lower: "\u1F63", upper: "\u1F6B", condition: specialCasingConditionNone}, + 0x1F6C: {lower: "\u1F64", upper: "\u1F6C", condition: specialCasingConditionNone}, + 0x1F6D: {lower: "\u1F65", upper: "\u1F6D", condition: specialCasingConditionNone}, + 0x1F6E: {lower: "\u1F66", upper: "\u1F6E", condition: specialCasingConditionNone}, + 0x1F6F: {lower: "\u1F67", upper: "\u1F6F", condition: specialCasingConditionNone}, + 0x1F70: {lower: "\u1F70", upper: "\u1FBA", condition: specialCasingConditionNone}, + 0x1F71: {lower: "\u1F71", upper: "\u1FBB", condition: specialCasingConditionNone}, + 0x1F72: {lower: "\u1F72", upper: "\u1FC8", condition: specialCasingConditionNone}, + 0x1F73: {lower: "\u1F73", upper: "\u1FC9", condition: specialCasingConditionNone}, + 0x1F74: {lower: "\u1F74", upper: "\u1FCA", condition: specialCasingConditionNone}, + 0x1F75: {lower: "\u1F75", upper: "\u1FCB", condition: specialCasingConditionNone}, + 0x1F76: {lower: "\u1F76", upper: "\u1FDA", condition: specialCasingConditionNone}, + 0x1F77: {lower: "\u1F77", upper: "\u1FDB", condition: specialCasingConditionNone}, + 0x1F78: {lower: "\u1F78", upper: "\u1FF8", condition: specialCasingConditionNone}, + 0x1F79: {lower: "\u1F79", upper: "\u1FF9", condition: specialCasingConditionNone}, + 0x1F7A: {lower: "\u1F7A", upper: "\u1FEA", condition: specialCasingConditionNone}, + 0x1F7B: {lower: "\u1F7B", upper: "\u1FEB", condition: specialCasingConditionNone}, + 0x1F7C: {lower: "\u1F7C", upper: "\u1FFA", condition: specialCasingConditionNone}, + 0x1F7D: {lower: "\u1F7D", upper: "\u1FFB", condition: specialCasingConditionNone}, + 0x1F80: {lower: "\u1F80", upper: "\u1F08\u0399", condition: specialCasingConditionNone}, + 0x1F81: {lower: "\u1F81", upper: "\u1F09\u0399", condition: specialCasingConditionNone}, + 0x1F82: {lower: "\u1F82", upper: "\u1F0A\u0399", condition: specialCasingConditionNone}, + 0x1F83: {lower: "\u1F83", upper: "\u1F0B\u0399", condition: specialCasingConditionNone}, + 0x1F84: {lower: "\u1F84", upper: "\u1F0C\u0399", condition: specialCasingConditionNone}, + 0x1F85: {lower: "\u1F85", upper: "\u1F0D\u0399", condition: specialCasingConditionNone}, + 0x1F86: {lower: "\u1F86", upper: "\u1F0E\u0399", condition: specialCasingConditionNone}, + 0x1F87: {lower: "\u1F87", upper: "\u1F0F\u0399", condition: specialCasingConditionNone}, + 0x1F88: {lower: "\u1F80", upper: "\u1F08\u0399", condition: specialCasingConditionNone}, + 0x1F89: {lower: "\u1F81", upper: "\u1F09\u0399", condition: specialCasingConditionNone}, + 0x1F8A: {lower: "\u1F82", upper: "\u1F0A\u0399", condition: specialCasingConditionNone}, + 0x1F8B: {lower: "\u1F83", upper: "\u1F0B\u0399", condition: specialCasingConditionNone}, + 0x1F8C: {lower: "\u1F84", upper: "\u1F0C\u0399", condition: specialCasingConditionNone}, + 0x1F8D: {lower: "\u1F85", upper: "\u1F0D\u0399", condition: specialCasingConditionNone}, + 0x1F8E: {lower: "\u1F86", upper: "\u1F0E\u0399", condition: specialCasingConditionNone}, + 0x1F8F: {lower: "\u1F87", upper: "\u1F0F\u0399", condition: specialCasingConditionNone}, + 0x1F90: {lower: "\u1F90", upper: "\u1F28\u0399", condition: specialCasingConditionNone}, + 0x1F91: {lower: "\u1F91", upper: "\u1F29\u0399", condition: specialCasingConditionNone}, + 0x1F92: {lower: "\u1F92", upper: "\u1F2A\u0399", condition: specialCasingConditionNone}, + 0x1F93: {lower: "\u1F93", upper: "\u1F2B\u0399", condition: specialCasingConditionNone}, + 0x1F94: {lower: "\u1F94", upper: "\u1F2C\u0399", condition: specialCasingConditionNone}, + 0x1F95: {lower: "\u1F95", upper: "\u1F2D\u0399", condition: specialCasingConditionNone}, + 0x1F96: {lower: "\u1F96", upper: "\u1F2E\u0399", condition: specialCasingConditionNone}, + 0x1F97: {lower: "\u1F97", upper: "\u1F2F\u0399", condition: specialCasingConditionNone}, + 0x1F98: {lower: "\u1F90", upper: "\u1F28\u0399", condition: specialCasingConditionNone}, + 0x1F99: {lower: "\u1F91", upper: "\u1F29\u0399", condition: specialCasingConditionNone}, + 0x1F9A: {lower: "\u1F92", upper: "\u1F2A\u0399", condition: specialCasingConditionNone}, + 0x1F9B: {lower: "\u1F93", upper: "\u1F2B\u0399", condition: specialCasingConditionNone}, + 0x1F9C: {lower: "\u1F94", upper: "\u1F2C\u0399", condition: specialCasingConditionNone}, + 0x1F9D: {lower: "\u1F95", upper: "\u1F2D\u0399", condition: specialCasingConditionNone}, + 0x1F9E: {lower: "\u1F96", upper: "\u1F2E\u0399", condition: specialCasingConditionNone}, + 0x1F9F: {lower: "\u1F97", upper: "\u1F2F\u0399", condition: specialCasingConditionNone}, + 0x1FA0: {lower: "\u1FA0", upper: "\u1F68\u0399", condition: specialCasingConditionNone}, + 0x1FA1: {lower: "\u1FA1", upper: "\u1F69\u0399", condition: specialCasingConditionNone}, + 0x1FA2: {lower: "\u1FA2", upper: "\u1F6A\u0399", condition: specialCasingConditionNone}, + 0x1FA3: {lower: "\u1FA3", upper: "\u1F6B\u0399", condition: specialCasingConditionNone}, + 0x1FA4: {lower: "\u1FA4", upper: "\u1F6C\u0399", condition: specialCasingConditionNone}, + 0x1FA5: {lower: "\u1FA5", upper: "\u1F6D\u0399", condition: specialCasingConditionNone}, + 0x1FA6: {lower: "\u1FA6", upper: "\u1F6E\u0399", condition: specialCasingConditionNone}, + 0x1FA7: {lower: "\u1FA7", upper: "\u1F6F\u0399", condition: specialCasingConditionNone}, + 0x1FA8: {lower: "\u1FA0", upper: "\u1F68\u0399", condition: specialCasingConditionNone}, + 0x1FA9: {lower: "\u1FA1", upper: "\u1F69\u0399", condition: specialCasingConditionNone}, + 0x1FAA: {lower: "\u1FA2", upper: "\u1F6A\u0399", condition: specialCasingConditionNone}, + 0x1FAB: {lower: "\u1FA3", upper: "\u1F6B\u0399", condition: specialCasingConditionNone}, + 0x1FAC: {lower: "\u1FA4", upper: "\u1F6C\u0399", condition: specialCasingConditionNone}, + 0x1FAD: {lower: "\u1FA5", upper: "\u1F6D\u0399", condition: specialCasingConditionNone}, + 0x1FAE: {lower: "\u1FA6", upper: "\u1F6E\u0399", condition: specialCasingConditionNone}, + 0x1FAF: {lower: "\u1FA7", upper: "\u1F6F\u0399", condition: specialCasingConditionNone}, + 0x1FB0: {lower: "\u1FB0", upper: "\u1FB8", condition: specialCasingConditionNone}, + 0x1FB1: {lower: "\u1FB1", upper: "\u1FB9", condition: specialCasingConditionNone}, + 0x1FB2: {lower: "\u1FB2", upper: "\u1FBA\u0399", condition: specialCasingConditionNone}, + 0x1FB3: {lower: "\u1FB3", upper: "\u0391\u0399", condition: specialCasingConditionNone}, + 0x1FB4: {lower: "\u1FB4", upper: "\u0386\u0399", condition: specialCasingConditionNone}, + 0x1FB6: {lower: "\u1FB6", upper: "\u0391\u0342", condition: specialCasingConditionNone}, + 0x1FB7: {lower: "\u1FB7", upper: "\u0391\u0342\u0399", condition: specialCasingConditionNone}, + 0x1FB8: {lower: "\u1FB0", upper: "\u1FB8", condition: specialCasingConditionNone}, + 0x1FB9: {lower: "\u1FB1", upper: "\u1FB9", condition: specialCasingConditionNone}, + 0x1FBA: {lower: "\u1F70", upper: "\u1FBA", condition: specialCasingConditionNone}, + 0x1FBB: {lower: "\u1F71", upper: "\u1FBB", condition: specialCasingConditionNone}, + 0x1FBC: {lower: "\u1FB3", upper: "\u0391\u0399", condition: specialCasingConditionNone}, + 0x1FBE: {lower: "\u1FBE", upper: "\u0399", condition: specialCasingConditionNone}, + 0x1FC2: {lower: "\u1FC2", upper: "\u1FCA\u0399", condition: specialCasingConditionNone}, + 0x1FC3: {lower: "\u1FC3", upper: "\u0397\u0399", condition: specialCasingConditionNone}, + 0x1FC4: {lower: "\u1FC4", upper: "\u0389\u0399", condition: specialCasingConditionNone}, + 0x1FC6: {lower: "\u1FC6", upper: "\u0397\u0342", condition: specialCasingConditionNone}, + 0x1FC7: {lower: "\u1FC7", upper: "\u0397\u0342\u0399", condition: specialCasingConditionNone}, + 0x1FC8: {lower: "\u1F72", upper: "\u1FC8", condition: specialCasingConditionNone}, + 0x1FC9: {lower: "\u1F73", upper: "\u1FC9", condition: specialCasingConditionNone}, + 0x1FCA: {lower: "\u1F74", upper: "\u1FCA", condition: specialCasingConditionNone}, + 0x1FCB: {lower: "\u1F75", upper: "\u1FCB", condition: specialCasingConditionNone}, + 0x1FCC: {lower: "\u1FC3", upper: "\u0397\u0399", condition: specialCasingConditionNone}, + 0x1FD0: {lower: "\u1FD0", upper: "\u1FD8", condition: specialCasingConditionNone}, + 0x1FD1: {lower: "\u1FD1", upper: "\u1FD9", condition: specialCasingConditionNone}, + 0x1FD2: {lower: "\u1FD2", upper: "\u0399\u0308\u0300", condition: specialCasingConditionNone}, + 0x1FD3: {lower: "\u1FD3", upper: "\u0399\u0308\u0301", condition: specialCasingConditionNone}, + 0x1FD6: {lower: "\u1FD6", upper: "\u0399\u0342", condition: specialCasingConditionNone}, + 0x1FD7: {lower: "\u1FD7", upper: "\u0399\u0308\u0342", condition: specialCasingConditionNone}, + 0x1FD8: {lower: "\u1FD0", upper: "\u1FD8", condition: specialCasingConditionNone}, + 0x1FD9: {lower: "\u1FD1", upper: "\u1FD9", condition: specialCasingConditionNone}, + 0x1FDA: {lower: "\u1F76", upper: "\u1FDA", condition: specialCasingConditionNone}, + 0x1FDB: {lower: "\u1F77", upper: "\u1FDB", condition: specialCasingConditionNone}, + 0x1FE0: {lower: "\u1FE0", upper: "\u1FE8", condition: specialCasingConditionNone}, + 0x1FE1: {lower: "\u1FE1", upper: "\u1FE9", condition: specialCasingConditionNone}, + 0x1FE2: {lower: "\u1FE2", upper: "\u03A5\u0308\u0300", condition: specialCasingConditionNone}, + 0x1FE3: {lower: "\u1FE3", upper: "\u03A5\u0308\u0301", condition: specialCasingConditionNone}, + 0x1FE4: {lower: "\u1FE4", upper: "\u03A1\u0313", condition: specialCasingConditionNone}, + 0x1FE5: {lower: "\u1FE5", upper: "\u1FEC", condition: specialCasingConditionNone}, + 0x1FE6: {lower: "\u1FE6", upper: "\u03A5\u0342", condition: specialCasingConditionNone}, + 0x1FE7: {lower: "\u1FE7", upper: "\u03A5\u0308\u0342", condition: specialCasingConditionNone}, + 0x1FE8: {lower: "\u1FE0", upper: "\u1FE8", condition: specialCasingConditionNone}, + 0x1FE9: {lower: "\u1FE1", upper: "\u1FE9", condition: specialCasingConditionNone}, + 0x1FEA: {lower: "\u1F7A", upper: "\u1FEA", condition: specialCasingConditionNone}, + 0x1FEB: {lower: "\u1F7B", upper: "\u1FEB", condition: specialCasingConditionNone}, + 0x1FEC: {lower: "\u1FE5", upper: "\u1FEC", condition: specialCasingConditionNone}, + 0x1FF2: {lower: "\u1FF2", upper: "\u1FFA\u0399", condition: specialCasingConditionNone}, + 0x1FF3: {lower: "\u1FF3", upper: "\u03A9\u0399", condition: specialCasingConditionNone}, + 0x1FF4: {lower: "\u1FF4", upper: "\u038F\u0399", condition: specialCasingConditionNone}, + 0x1FF6: {lower: "\u1FF6", upper: "\u03A9\u0342", condition: specialCasingConditionNone}, + 0x1FF7: {lower: "\u1FF7", upper: "\u03A9\u0342\u0399", condition: specialCasingConditionNone}, + 0x1FF8: {lower: "\u1F78", upper: "\u1FF8", condition: specialCasingConditionNone}, + 0x1FF9: {lower: "\u1F79", upper: "\u1FF9", condition: specialCasingConditionNone}, + 0x1FFA: {lower: "\u1F7C", upper: "\u1FFA", condition: specialCasingConditionNone}, + 0x1FFB: {lower: "\u1F7D", upper: "\u1FFB", condition: specialCasingConditionNone}, + 0x1FFC: {lower: "\u1FF3", upper: "\u03A9\u0399", condition: specialCasingConditionNone}, + 0x2126: {lower: "\u03C9", upper: "\u2126", condition: specialCasingConditionNone}, + 0x212A: {lower: "\u006B", upper: "\u212A", condition: specialCasingConditionNone}, + 0x212B: {lower: "\u00E5", upper: "\u212B", condition: specialCasingConditionNone}, + 0x2132: {lower: "\u214E", upper: "\u2132", condition: specialCasingConditionNone}, + 0x214E: {lower: "\u214E", upper: "\u2132", condition: specialCasingConditionNone}, + 0x2160: {lower: "\u2170", upper: "\u2160", condition: specialCasingConditionNone}, + 0x2161: {lower: "\u2171", upper: "\u2161", condition: specialCasingConditionNone}, + 0x2162: {lower: "\u2172", upper: "\u2162", condition: specialCasingConditionNone}, + 0x2163: {lower: "\u2173", upper: "\u2163", condition: specialCasingConditionNone}, + 0x2164: {lower: "\u2174", upper: "\u2164", condition: specialCasingConditionNone}, + 0x2165: {lower: "\u2175", upper: "\u2165", condition: specialCasingConditionNone}, + 0x2166: {lower: "\u2176", upper: "\u2166", condition: specialCasingConditionNone}, + 0x2167: {lower: "\u2177", upper: "\u2167", condition: specialCasingConditionNone}, + 0x2168: {lower: "\u2178", upper: "\u2168", condition: specialCasingConditionNone}, + 0x2169: {lower: "\u2179", upper: "\u2169", condition: specialCasingConditionNone}, + 0x216A: {lower: "\u217A", upper: "\u216A", condition: specialCasingConditionNone}, + 0x216B: {lower: "\u217B", upper: "\u216B", condition: specialCasingConditionNone}, + 0x216C: {lower: "\u217C", upper: "\u216C", condition: specialCasingConditionNone}, + 0x216D: {lower: "\u217D", upper: "\u216D", condition: specialCasingConditionNone}, + 0x216E: {lower: "\u217E", upper: "\u216E", condition: specialCasingConditionNone}, + 0x216F: {lower: "\u217F", upper: "\u216F", condition: specialCasingConditionNone}, + 0x2170: {lower: "\u2170", upper: "\u2160", condition: specialCasingConditionNone}, + 0x2171: {lower: "\u2171", upper: "\u2161", condition: specialCasingConditionNone}, + 0x2172: {lower: "\u2172", upper: "\u2162", condition: specialCasingConditionNone}, + 0x2173: {lower: "\u2173", upper: "\u2163", condition: specialCasingConditionNone}, + 0x2174: {lower: "\u2174", upper: "\u2164", condition: specialCasingConditionNone}, + 0x2175: {lower: "\u2175", upper: "\u2165", condition: specialCasingConditionNone}, + 0x2176: {lower: "\u2176", upper: "\u2166", condition: specialCasingConditionNone}, + 0x2177: {lower: "\u2177", upper: "\u2167", condition: specialCasingConditionNone}, + 0x2178: {lower: "\u2178", upper: "\u2168", condition: specialCasingConditionNone}, + 0x2179: {lower: "\u2179", upper: "\u2169", condition: specialCasingConditionNone}, + 0x217A: {lower: "\u217A", upper: "\u216A", condition: specialCasingConditionNone}, + 0x217B: {lower: "\u217B", upper: "\u216B", condition: specialCasingConditionNone}, + 0x217C: {lower: "\u217C", upper: "\u216C", condition: specialCasingConditionNone}, + 0x217D: {lower: "\u217D", upper: "\u216D", condition: specialCasingConditionNone}, + 0x217E: {lower: "\u217E", upper: "\u216E", condition: specialCasingConditionNone}, + 0x217F: {lower: "\u217F", upper: "\u216F", condition: specialCasingConditionNone}, + 0x2183: {lower: "\u2184", upper: "\u2183", condition: specialCasingConditionNone}, + 0x2184: {lower: "\u2184", upper: "\u2183", condition: specialCasingConditionNone}, + 0x24B6: {lower: "\u24D0", upper: "\u24B6", condition: specialCasingConditionNone}, + 0x24B7: {lower: "\u24D1", upper: "\u24B7", condition: specialCasingConditionNone}, + 0x24B8: {lower: "\u24D2", upper: "\u24B8", condition: specialCasingConditionNone}, + 0x24B9: {lower: "\u24D3", upper: "\u24B9", condition: specialCasingConditionNone}, + 0x24BA: {lower: "\u24D4", upper: "\u24BA", condition: specialCasingConditionNone}, + 0x24BB: {lower: "\u24D5", upper: "\u24BB", condition: specialCasingConditionNone}, + 0x24BC: {lower: "\u24D6", upper: "\u24BC", condition: specialCasingConditionNone}, + 0x24BD: {lower: "\u24D7", upper: "\u24BD", condition: specialCasingConditionNone}, + 0x24BE: {lower: "\u24D8", upper: "\u24BE", condition: specialCasingConditionNone}, + 0x24BF: {lower: "\u24D9", upper: "\u24BF", condition: specialCasingConditionNone}, + 0x24C0: {lower: "\u24DA", upper: "\u24C0", condition: specialCasingConditionNone}, + 0x24C1: {lower: "\u24DB", upper: "\u24C1", condition: specialCasingConditionNone}, + 0x24C2: {lower: "\u24DC", upper: "\u24C2", condition: specialCasingConditionNone}, + 0x24C3: {lower: "\u24DD", upper: "\u24C3", condition: specialCasingConditionNone}, + 0x24C4: {lower: "\u24DE", upper: "\u24C4", condition: specialCasingConditionNone}, + 0x24C5: {lower: "\u24DF", upper: "\u24C5", condition: specialCasingConditionNone}, + 0x24C6: {lower: "\u24E0", upper: "\u24C6", condition: specialCasingConditionNone}, + 0x24C7: {lower: "\u24E1", upper: "\u24C7", condition: specialCasingConditionNone}, + 0x24C8: {lower: "\u24E2", upper: "\u24C8", condition: specialCasingConditionNone}, + 0x24C9: {lower: "\u24E3", upper: "\u24C9", condition: specialCasingConditionNone}, + 0x24CA: {lower: "\u24E4", upper: "\u24CA", condition: specialCasingConditionNone}, + 0x24CB: {lower: "\u24E5", upper: "\u24CB", condition: specialCasingConditionNone}, + 0x24CC: {lower: "\u24E6", upper: "\u24CC", condition: specialCasingConditionNone}, + 0x24CD: {lower: "\u24E7", upper: "\u24CD", condition: specialCasingConditionNone}, + 0x24CE: {lower: "\u24E8", upper: "\u24CE", condition: specialCasingConditionNone}, + 0x24CF: {lower: "\u24E9", upper: "\u24CF", condition: specialCasingConditionNone}, + 0x24D0: {lower: "\u24D0", upper: "\u24B6", condition: specialCasingConditionNone}, + 0x24D1: {lower: "\u24D1", upper: "\u24B7", condition: specialCasingConditionNone}, + 0x24D2: {lower: "\u24D2", upper: "\u24B8", condition: specialCasingConditionNone}, + 0x24D3: {lower: "\u24D3", upper: "\u24B9", condition: specialCasingConditionNone}, + 0x24D4: {lower: "\u24D4", upper: "\u24BA", condition: specialCasingConditionNone}, + 0x24D5: {lower: "\u24D5", upper: "\u24BB", condition: specialCasingConditionNone}, + 0x24D6: {lower: "\u24D6", upper: "\u24BC", condition: specialCasingConditionNone}, + 0x24D7: {lower: "\u24D7", upper: "\u24BD", condition: specialCasingConditionNone}, + 0x24D8: {lower: "\u24D8", upper: "\u24BE", condition: specialCasingConditionNone}, + 0x24D9: {lower: "\u24D9", upper: "\u24BF", condition: specialCasingConditionNone}, + 0x24DA: {lower: "\u24DA", upper: "\u24C0", condition: specialCasingConditionNone}, + 0x24DB: {lower: "\u24DB", upper: "\u24C1", condition: specialCasingConditionNone}, + 0x24DC: {lower: "\u24DC", upper: "\u24C2", condition: specialCasingConditionNone}, + 0x24DD: {lower: "\u24DD", upper: "\u24C3", condition: specialCasingConditionNone}, + 0x24DE: {lower: "\u24DE", upper: "\u24C4", condition: specialCasingConditionNone}, + 0x24DF: {lower: "\u24DF", upper: "\u24C5", condition: specialCasingConditionNone}, + 0x24E0: {lower: "\u24E0", upper: "\u24C6", condition: specialCasingConditionNone}, + 0x24E1: {lower: "\u24E1", upper: "\u24C7", condition: specialCasingConditionNone}, + 0x24E2: {lower: "\u24E2", upper: "\u24C8", condition: specialCasingConditionNone}, + 0x24E3: {lower: "\u24E3", upper: "\u24C9", condition: specialCasingConditionNone}, + 0x24E4: {lower: "\u24E4", upper: "\u24CA", condition: specialCasingConditionNone}, + 0x24E5: {lower: "\u24E5", upper: "\u24CB", condition: specialCasingConditionNone}, + 0x24E6: {lower: "\u24E6", upper: "\u24CC", condition: specialCasingConditionNone}, + 0x24E7: {lower: "\u24E7", upper: "\u24CD", condition: specialCasingConditionNone}, + 0x24E8: {lower: "\u24E8", upper: "\u24CE", condition: specialCasingConditionNone}, + 0x24E9: {lower: "\u24E9", upper: "\u24CF", condition: specialCasingConditionNone}, + 0x2C00: {lower: "\u2C30", upper: "\u2C00", condition: specialCasingConditionNone}, + 0x2C01: {lower: "\u2C31", upper: "\u2C01", condition: specialCasingConditionNone}, + 0x2C02: {lower: "\u2C32", upper: "\u2C02", condition: specialCasingConditionNone}, + 0x2C03: {lower: "\u2C33", upper: "\u2C03", condition: specialCasingConditionNone}, + 0x2C04: {lower: "\u2C34", upper: "\u2C04", condition: specialCasingConditionNone}, + 0x2C05: {lower: "\u2C35", upper: "\u2C05", condition: specialCasingConditionNone}, + 0x2C06: {lower: "\u2C36", upper: "\u2C06", condition: specialCasingConditionNone}, + 0x2C07: {lower: "\u2C37", upper: "\u2C07", condition: specialCasingConditionNone}, + 0x2C08: {lower: "\u2C38", upper: "\u2C08", condition: specialCasingConditionNone}, + 0x2C09: {lower: "\u2C39", upper: "\u2C09", condition: specialCasingConditionNone}, + 0x2C0A: {lower: "\u2C3A", upper: "\u2C0A", condition: specialCasingConditionNone}, + 0x2C0B: {lower: "\u2C3B", upper: "\u2C0B", condition: specialCasingConditionNone}, + 0x2C0C: {lower: "\u2C3C", upper: "\u2C0C", condition: specialCasingConditionNone}, + 0x2C0D: {lower: "\u2C3D", upper: "\u2C0D", condition: specialCasingConditionNone}, + 0x2C0E: {lower: "\u2C3E", upper: "\u2C0E", condition: specialCasingConditionNone}, + 0x2C0F: {lower: "\u2C3F", upper: "\u2C0F", condition: specialCasingConditionNone}, + 0x2C10: {lower: "\u2C40", upper: "\u2C10", condition: specialCasingConditionNone}, + 0x2C11: {lower: "\u2C41", upper: "\u2C11", condition: specialCasingConditionNone}, + 0x2C12: {lower: "\u2C42", upper: "\u2C12", condition: specialCasingConditionNone}, + 0x2C13: {lower: "\u2C43", upper: "\u2C13", condition: specialCasingConditionNone}, + 0x2C14: {lower: "\u2C44", upper: "\u2C14", condition: specialCasingConditionNone}, + 0x2C15: {lower: "\u2C45", upper: "\u2C15", condition: specialCasingConditionNone}, + 0x2C16: {lower: "\u2C46", upper: "\u2C16", condition: specialCasingConditionNone}, + 0x2C17: {lower: "\u2C47", upper: "\u2C17", condition: specialCasingConditionNone}, + 0x2C18: {lower: "\u2C48", upper: "\u2C18", condition: specialCasingConditionNone}, + 0x2C19: {lower: "\u2C49", upper: "\u2C19", condition: specialCasingConditionNone}, + 0x2C1A: {lower: "\u2C4A", upper: "\u2C1A", condition: specialCasingConditionNone}, + 0x2C1B: {lower: "\u2C4B", upper: "\u2C1B", condition: specialCasingConditionNone}, + 0x2C1C: {lower: "\u2C4C", upper: "\u2C1C", condition: specialCasingConditionNone}, + 0x2C1D: {lower: "\u2C4D", upper: "\u2C1D", condition: specialCasingConditionNone}, + 0x2C1E: {lower: "\u2C4E", upper: "\u2C1E", condition: specialCasingConditionNone}, + 0x2C1F: {lower: "\u2C4F", upper: "\u2C1F", condition: specialCasingConditionNone}, + 0x2C20: {lower: "\u2C50", upper: "\u2C20", condition: specialCasingConditionNone}, + 0x2C21: {lower: "\u2C51", upper: "\u2C21", condition: specialCasingConditionNone}, + 0x2C22: {lower: "\u2C52", upper: "\u2C22", condition: specialCasingConditionNone}, + 0x2C23: {lower: "\u2C53", upper: "\u2C23", condition: specialCasingConditionNone}, + 0x2C24: {lower: "\u2C54", upper: "\u2C24", condition: specialCasingConditionNone}, + 0x2C25: {lower: "\u2C55", upper: "\u2C25", condition: specialCasingConditionNone}, + 0x2C26: {lower: "\u2C56", upper: "\u2C26", condition: specialCasingConditionNone}, + 0x2C27: {lower: "\u2C57", upper: "\u2C27", condition: specialCasingConditionNone}, + 0x2C28: {lower: "\u2C58", upper: "\u2C28", condition: specialCasingConditionNone}, + 0x2C29: {lower: "\u2C59", upper: "\u2C29", condition: specialCasingConditionNone}, + 0x2C2A: {lower: "\u2C5A", upper: "\u2C2A", condition: specialCasingConditionNone}, + 0x2C2B: {lower: "\u2C5B", upper: "\u2C2B", condition: specialCasingConditionNone}, + 0x2C2C: {lower: "\u2C5C", upper: "\u2C2C", condition: specialCasingConditionNone}, + 0x2C2D: {lower: "\u2C5D", upper: "\u2C2D", condition: specialCasingConditionNone}, + 0x2C2E: {lower: "\u2C5E", upper: "\u2C2E", condition: specialCasingConditionNone}, + 0x2C2F: {lower: "\u2C5F", upper: "\u2C2F", condition: specialCasingConditionNone}, + 0x2C30: {lower: "\u2C30", upper: "\u2C00", condition: specialCasingConditionNone}, + 0x2C31: {lower: "\u2C31", upper: "\u2C01", condition: specialCasingConditionNone}, + 0x2C32: {lower: "\u2C32", upper: "\u2C02", condition: specialCasingConditionNone}, + 0x2C33: {lower: "\u2C33", upper: "\u2C03", condition: specialCasingConditionNone}, + 0x2C34: {lower: "\u2C34", upper: "\u2C04", condition: specialCasingConditionNone}, + 0x2C35: {lower: "\u2C35", upper: "\u2C05", condition: specialCasingConditionNone}, + 0x2C36: {lower: "\u2C36", upper: "\u2C06", condition: specialCasingConditionNone}, + 0x2C37: {lower: "\u2C37", upper: "\u2C07", condition: specialCasingConditionNone}, + 0x2C38: {lower: "\u2C38", upper: "\u2C08", condition: specialCasingConditionNone}, + 0x2C39: {lower: "\u2C39", upper: "\u2C09", condition: specialCasingConditionNone}, + 0x2C3A: {lower: "\u2C3A", upper: "\u2C0A", condition: specialCasingConditionNone}, + 0x2C3B: {lower: "\u2C3B", upper: "\u2C0B", condition: specialCasingConditionNone}, + 0x2C3C: {lower: "\u2C3C", upper: "\u2C0C", condition: specialCasingConditionNone}, + 0x2C3D: {lower: "\u2C3D", upper: "\u2C0D", condition: specialCasingConditionNone}, + 0x2C3E: {lower: "\u2C3E", upper: "\u2C0E", condition: specialCasingConditionNone}, + 0x2C3F: {lower: "\u2C3F", upper: "\u2C0F", condition: specialCasingConditionNone}, + 0x2C40: {lower: "\u2C40", upper: "\u2C10", condition: specialCasingConditionNone}, + 0x2C41: {lower: "\u2C41", upper: "\u2C11", condition: specialCasingConditionNone}, + 0x2C42: {lower: "\u2C42", upper: "\u2C12", condition: specialCasingConditionNone}, + 0x2C43: {lower: "\u2C43", upper: "\u2C13", condition: specialCasingConditionNone}, + 0x2C44: {lower: "\u2C44", upper: "\u2C14", condition: specialCasingConditionNone}, + 0x2C45: {lower: "\u2C45", upper: "\u2C15", condition: specialCasingConditionNone}, + 0x2C46: {lower: "\u2C46", upper: "\u2C16", condition: specialCasingConditionNone}, + 0x2C47: {lower: "\u2C47", upper: "\u2C17", condition: specialCasingConditionNone}, + 0x2C48: {lower: "\u2C48", upper: "\u2C18", condition: specialCasingConditionNone}, + 0x2C49: {lower: "\u2C49", upper: "\u2C19", condition: specialCasingConditionNone}, + 0x2C4A: {lower: "\u2C4A", upper: "\u2C1A", condition: specialCasingConditionNone}, + 0x2C4B: {lower: "\u2C4B", upper: "\u2C1B", condition: specialCasingConditionNone}, + 0x2C4C: {lower: "\u2C4C", upper: "\u2C1C", condition: specialCasingConditionNone}, + 0x2C4D: {lower: "\u2C4D", upper: "\u2C1D", condition: specialCasingConditionNone}, + 0x2C4E: {lower: "\u2C4E", upper: "\u2C1E", condition: specialCasingConditionNone}, + 0x2C4F: {lower: "\u2C4F", upper: "\u2C1F", condition: specialCasingConditionNone}, + 0x2C50: {lower: "\u2C50", upper: "\u2C20", condition: specialCasingConditionNone}, + 0x2C51: {lower: "\u2C51", upper: "\u2C21", condition: specialCasingConditionNone}, + 0x2C52: {lower: "\u2C52", upper: "\u2C22", condition: specialCasingConditionNone}, + 0x2C53: {lower: "\u2C53", upper: "\u2C23", condition: specialCasingConditionNone}, + 0x2C54: {lower: "\u2C54", upper: "\u2C24", condition: specialCasingConditionNone}, + 0x2C55: {lower: "\u2C55", upper: "\u2C25", condition: specialCasingConditionNone}, + 0x2C56: {lower: "\u2C56", upper: "\u2C26", condition: specialCasingConditionNone}, + 0x2C57: {lower: "\u2C57", upper: "\u2C27", condition: specialCasingConditionNone}, + 0x2C58: {lower: "\u2C58", upper: "\u2C28", condition: specialCasingConditionNone}, + 0x2C59: {lower: "\u2C59", upper: "\u2C29", condition: specialCasingConditionNone}, + 0x2C5A: {lower: "\u2C5A", upper: "\u2C2A", condition: specialCasingConditionNone}, + 0x2C5B: {lower: "\u2C5B", upper: "\u2C2B", condition: specialCasingConditionNone}, + 0x2C5C: {lower: "\u2C5C", upper: "\u2C2C", condition: specialCasingConditionNone}, + 0x2C5D: {lower: "\u2C5D", upper: "\u2C2D", condition: specialCasingConditionNone}, + 0x2C5E: {lower: "\u2C5E", upper: "\u2C2E", condition: specialCasingConditionNone}, + 0x2C5F: {lower: "\u2C5F", upper: "\u2C2F", condition: specialCasingConditionNone}, + 0x2C60: {lower: "\u2C61", upper: "\u2C60", condition: specialCasingConditionNone}, + 0x2C61: {lower: "\u2C61", upper: "\u2C60", condition: specialCasingConditionNone}, + 0x2C62: {lower: "\u026B", upper: "\u2C62", condition: specialCasingConditionNone}, + 0x2C63: {lower: "\u1D7D", upper: "\u2C63", condition: specialCasingConditionNone}, + 0x2C64: {lower: "\u027D", upper: "\u2C64", condition: specialCasingConditionNone}, + 0x2C65: {lower: "\u2C65", upper: "\u023A", condition: specialCasingConditionNone}, + 0x2C66: {lower: "\u2C66", upper: "\u023E", condition: specialCasingConditionNone}, + 0x2C67: {lower: "\u2C68", upper: "\u2C67", condition: specialCasingConditionNone}, + 0x2C68: {lower: "\u2C68", upper: "\u2C67", condition: specialCasingConditionNone}, + 0x2C69: {lower: "\u2C6A", upper: "\u2C69", condition: specialCasingConditionNone}, + 0x2C6A: {lower: "\u2C6A", upper: "\u2C69", condition: specialCasingConditionNone}, + 0x2C6B: {lower: "\u2C6C", upper: "\u2C6B", condition: specialCasingConditionNone}, + 0x2C6C: {lower: "\u2C6C", upper: "\u2C6B", condition: specialCasingConditionNone}, + 0x2C6D: {lower: "\u0251", upper: "\u2C6D", condition: specialCasingConditionNone}, + 0x2C6E: {lower: "\u0271", upper: "\u2C6E", condition: specialCasingConditionNone}, + 0x2C6F: {lower: "\u0250", upper: "\u2C6F", condition: specialCasingConditionNone}, + 0x2C70: {lower: "\u0252", upper: "\u2C70", condition: specialCasingConditionNone}, + 0x2C72: {lower: "\u2C73", upper: "\u2C72", condition: specialCasingConditionNone}, + 0x2C73: {lower: "\u2C73", upper: "\u2C72", condition: specialCasingConditionNone}, + 0x2C75: {lower: "\u2C76", upper: "\u2C75", condition: specialCasingConditionNone}, + 0x2C76: {lower: "\u2C76", upper: "\u2C75", condition: specialCasingConditionNone}, + 0x2C7E: {lower: "\u023F", upper: "\u2C7E", condition: specialCasingConditionNone}, + 0x2C7F: {lower: "\u0240", upper: "\u2C7F", condition: specialCasingConditionNone}, + 0x2C80: {lower: "\u2C81", upper: "\u2C80", condition: specialCasingConditionNone}, + 0x2C81: {lower: "\u2C81", upper: "\u2C80", condition: specialCasingConditionNone}, + 0x2C82: {lower: "\u2C83", upper: "\u2C82", condition: specialCasingConditionNone}, + 0x2C83: {lower: "\u2C83", upper: "\u2C82", condition: specialCasingConditionNone}, + 0x2C84: {lower: "\u2C85", upper: "\u2C84", condition: specialCasingConditionNone}, + 0x2C85: {lower: "\u2C85", upper: "\u2C84", condition: specialCasingConditionNone}, + 0x2C86: {lower: "\u2C87", upper: "\u2C86", condition: specialCasingConditionNone}, + 0x2C87: {lower: "\u2C87", upper: "\u2C86", condition: specialCasingConditionNone}, + 0x2C88: {lower: "\u2C89", upper: "\u2C88", condition: specialCasingConditionNone}, + 0x2C89: {lower: "\u2C89", upper: "\u2C88", condition: specialCasingConditionNone}, + 0x2C8A: {lower: "\u2C8B", upper: "\u2C8A", condition: specialCasingConditionNone}, + 0x2C8B: {lower: "\u2C8B", upper: "\u2C8A", condition: specialCasingConditionNone}, + 0x2C8C: {lower: "\u2C8D", upper: "\u2C8C", condition: specialCasingConditionNone}, + 0x2C8D: {lower: "\u2C8D", upper: "\u2C8C", condition: specialCasingConditionNone}, + 0x2C8E: {lower: "\u2C8F", upper: "\u2C8E", condition: specialCasingConditionNone}, + 0x2C8F: {lower: "\u2C8F", upper: "\u2C8E", condition: specialCasingConditionNone}, + 0x2C90: {lower: "\u2C91", upper: "\u2C90", condition: specialCasingConditionNone}, + 0x2C91: {lower: "\u2C91", upper: "\u2C90", condition: specialCasingConditionNone}, + 0x2C92: {lower: "\u2C93", upper: "\u2C92", condition: specialCasingConditionNone}, + 0x2C93: {lower: "\u2C93", upper: "\u2C92", condition: specialCasingConditionNone}, + 0x2C94: {lower: "\u2C95", upper: "\u2C94", condition: specialCasingConditionNone}, + 0x2C95: {lower: "\u2C95", upper: "\u2C94", condition: specialCasingConditionNone}, + 0x2C96: {lower: "\u2C97", upper: "\u2C96", condition: specialCasingConditionNone}, + 0x2C97: {lower: "\u2C97", upper: "\u2C96", condition: specialCasingConditionNone}, + 0x2C98: {lower: "\u2C99", upper: "\u2C98", condition: specialCasingConditionNone}, + 0x2C99: {lower: "\u2C99", upper: "\u2C98", condition: specialCasingConditionNone}, + 0x2C9A: {lower: "\u2C9B", upper: "\u2C9A", condition: specialCasingConditionNone}, + 0x2C9B: {lower: "\u2C9B", upper: "\u2C9A", condition: specialCasingConditionNone}, + 0x2C9C: {lower: "\u2C9D", upper: "\u2C9C", condition: specialCasingConditionNone}, + 0x2C9D: {lower: "\u2C9D", upper: "\u2C9C", condition: specialCasingConditionNone}, + 0x2C9E: {lower: "\u2C9F", upper: "\u2C9E", condition: specialCasingConditionNone}, + 0x2C9F: {lower: "\u2C9F", upper: "\u2C9E", condition: specialCasingConditionNone}, + 0x2CA0: {lower: "\u2CA1", upper: "\u2CA0", condition: specialCasingConditionNone}, + 0x2CA1: {lower: "\u2CA1", upper: "\u2CA0", condition: specialCasingConditionNone}, + 0x2CA2: {lower: "\u2CA3", upper: "\u2CA2", condition: specialCasingConditionNone}, + 0x2CA3: {lower: "\u2CA3", upper: "\u2CA2", condition: specialCasingConditionNone}, + 0x2CA4: {lower: "\u2CA5", upper: "\u2CA4", condition: specialCasingConditionNone}, + 0x2CA5: {lower: "\u2CA5", upper: "\u2CA4", condition: specialCasingConditionNone}, + 0x2CA6: {lower: "\u2CA7", upper: "\u2CA6", condition: specialCasingConditionNone}, + 0x2CA7: {lower: "\u2CA7", upper: "\u2CA6", condition: specialCasingConditionNone}, + 0x2CA8: {lower: "\u2CA9", upper: "\u2CA8", condition: specialCasingConditionNone}, + 0x2CA9: {lower: "\u2CA9", upper: "\u2CA8", condition: specialCasingConditionNone}, + 0x2CAA: {lower: "\u2CAB", upper: "\u2CAA", condition: specialCasingConditionNone}, + 0x2CAB: {lower: "\u2CAB", upper: "\u2CAA", condition: specialCasingConditionNone}, + 0x2CAC: {lower: "\u2CAD", upper: "\u2CAC", condition: specialCasingConditionNone}, + 0x2CAD: {lower: "\u2CAD", upper: "\u2CAC", condition: specialCasingConditionNone}, + 0x2CAE: {lower: "\u2CAF", upper: "\u2CAE", condition: specialCasingConditionNone}, + 0x2CAF: {lower: "\u2CAF", upper: "\u2CAE", condition: specialCasingConditionNone}, + 0x2CB0: {lower: "\u2CB1", upper: "\u2CB0", condition: specialCasingConditionNone}, + 0x2CB1: {lower: "\u2CB1", upper: "\u2CB0", condition: specialCasingConditionNone}, + 0x2CB2: {lower: "\u2CB3", upper: "\u2CB2", condition: specialCasingConditionNone}, + 0x2CB3: {lower: "\u2CB3", upper: "\u2CB2", condition: specialCasingConditionNone}, + 0x2CB4: {lower: "\u2CB5", upper: "\u2CB4", condition: specialCasingConditionNone}, + 0x2CB5: {lower: "\u2CB5", upper: "\u2CB4", condition: specialCasingConditionNone}, + 0x2CB6: {lower: "\u2CB7", upper: "\u2CB6", condition: specialCasingConditionNone}, + 0x2CB7: {lower: "\u2CB7", upper: "\u2CB6", condition: specialCasingConditionNone}, + 0x2CB8: {lower: "\u2CB9", upper: "\u2CB8", condition: specialCasingConditionNone}, + 0x2CB9: {lower: "\u2CB9", upper: "\u2CB8", condition: specialCasingConditionNone}, + 0x2CBA: {lower: "\u2CBB", upper: "\u2CBA", condition: specialCasingConditionNone}, + 0x2CBB: {lower: "\u2CBB", upper: "\u2CBA", condition: specialCasingConditionNone}, + 0x2CBC: {lower: "\u2CBD", upper: "\u2CBC", condition: specialCasingConditionNone}, + 0x2CBD: {lower: "\u2CBD", upper: "\u2CBC", condition: specialCasingConditionNone}, + 0x2CBE: {lower: "\u2CBF", upper: "\u2CBE", condition: specialCasingConditionNone}, + 0x2CBF: {lower: "\u2CBF", upper: "\u2CBE", condition: specialCasingConditionNone}, + 0x2CC0: {lower: "\u2CC1", upper: "\u2CC0", condition: specialCasingConditionNone}, + 0x2CC1: {lower: "\u2CC1", upper: "\u2CC0", condition: specialCasingConditionNone}, + 0x2CC2: {lower: "\u2CC3", upper: "\u2CC2", condition: specialCasingConditionNone}, + 0x2CC3: {lower: "\u2CC3", upper: "\u2CC2", condition: specialCasingConditionNone}, + 0x2CC4: {lower: "\u2CC5", upper: "\u2CC4", condition: specialCasingConditionNone}, + 0x2CC5: {lower: "\u2CC5", upper: "\u2CC4", condition: specialCasingConditionNone}, + 0x2CC6: {lower: "\u2CC7", upper: "\u2CC6", condition: specialCasingConditionNone}, + 0x2CC7: {lower: "\u2CC7", upper: "\u2CC6", condition: specialCasingConditionNone}, + 0x2CC8: {lower: "\u2CC9", upper: "\u2CC8", condition: specialCasingConditionNone}, + 0x2CC9: {lower: "\u2CC9", upper: "\u2CC8", condition: specialCasingConditionNone}, + 0x2CCA: {lower: "\u2CCB", upper: "\u2CCA", condition: specialCasingConditionNone}, + 0x2CCB: {lower: "\u2CCB", upper: "\u2CCA", condition: specialCasingConditionNone}, + 0x2CCC: {lower: "\u2CCD", upper: "\u2CCC", condition: specialCasingConditionNone}, + 0x2CCD: {lower: "\u2CCD", upper: "\u2CCC", condition: specialCasingConditionNone}, + 0x2CCE: {lower: "\u2CCF", upper: "\u2CCE", condition: specialCasingConditionNone}, + 0x2CCF: {lower: "\u2CCF", upper: "\u2CCE", condition: specialCasingConditionNone}, + 0x2CD0: {lower: "\u2CD1", upper: "\u2CD0", condition: specialCasingConditionNone}, + 0x2CD1: {lower: "\u2CD1", upper: "\u2CD0", condition: specialCasingConditionNone}, + 0x2CD2: {lower: "\u2CD3", upper: "\u2CD2", condition: specialCasingConditionNone}, + 0x2CD3: {lower: "\u2CD3", upper: "\u2CD2", condition: specialCasingConditionNone}, + 0x2CD4: {lower: "\u2CD5", upper: "\u2CD4", condition: specialCasingConditionNone}, + 0x2CD5: {lower: "\u2CD5", upper: "\u2CD4", condition: specialCasingConditionNone}, + 0x2CD6: {lower: "\u2CD7", upper: "\u2CD6", condition: specialCasingConditionNone}, + 0x2CD7: {lower: "\u2CD7", upper: "\u2CD6", condition: specialCasingConditionNone}, + 0x2CD8: {lower: "\u2CD9", upper: "\u2CD8", condition: specialCasingConditionNone}, + 0x2CD9: {lower: "\u2CD9", upper: "\u2CD8", condition: specialCasingConditionNone}, + 0x2CDA: {lower: "\u2CDB", upper: "\u2CDA", condition: specialCasingConditionNone}, + 0x2CDB: {lower: "\u2CDB", upper: "\u2CDA", condition: specialCasingConditionNone}, + 0x2CDC: {lower: "\u2CDD", upper: "\u2CDC", condition: specialCasingConditionNone}, + 0x2CDD: {lower: "\u2CDD", upper: "\u2CDC", condition: specialCasingConditionNone}, + 0x2CDE: {lower: "\u2CDF", upper: "\u2CDE", condition: specialCasingConditionNone}, + 0x2CDF: {lower: "\u2CDF", upper: "\u2CDE", condition: specialCasingConditionNone}, + 0x2CE0: {lower: "\u2CE1", upper: "\u2CE0", condition: specialCasingConditionNone}, + 0x2CE1: {lower: "\u2CE1", upper: "\u2CE0", condition: specialCasingConditionNone}, + 0x2CE2: {lower: "\u2CE3", upper: "\u2CE2", condition: specialCasingConditionNone}, + 0x2CE3: {lower: "\u2CE3", upper: "\u2CE2", condition: specialCasingConditionNone}, + 0x2CEB: {lower: "\u2CEC", upper: "\u2CEB", condition: specialCasingConditionNone}, + 0x2CEC: {lower: "\u2CEC", upper: "\u2CEB", condition: specialCasingConditionNone}, + 0x2CED: {lower: "\u2CEE", upper: "\u2CED", condition: specialCasingConditionNone}, + 0x2CEE: {lower: "\u2CEE", upper: "\u2CED", condition: specialCasingConditionNone}, + 0x2CF2: {lower: "\u2CF3", upper: "\u2CF2", condition: specialCasingConditionNone}, + 0x2CF3: {lower: "\u2CF3", upper: "\u2CF2", condition: specialCasingConditionNone}, + 0x2D00: {lower: "\u2D00", upper: "\u10A0", condition: specialCasingConditionNone}, + 0x2D01: {lower: "\u2D01", upper: "\u10A1", condition: specialCasingConditionNone}, + 0x2D02: {lower: "\u2D02", upper: "\u10A2", condition: specialCasingConditionNone}, + 0x2D03: {lower: "\u2D03", upper: "\u10A3", condition: specialCasingConditionNone}, + 0x2D04: {lower: "\u2D04", upper: "\u10A4", condition: specialCasingConditionNone}, + 0x2D05: {lower: "\u2D05", upper: "\u10A5", condition: specialCasingConditionNone}, + 0x2D06: {lower: "\u2D06", upper: "\u10A6", condition: specialCasingConditionNone}, + 0x2D07: {lower: "\u2D07", upper: "\u10A7", condition: specialCasingConditionNone}, + 0x2D08: {lower: "\u2D08", upper: "\u10A8", condition: specialCasingConditionNone}, + 0x2D09: {lower: "\u2D09", upper: "\u10A9", condition: specialCasingConditionNone}, + 0x2D0A: {lower: "\u2D0A", upper: "\u10AA", condition: specialCasingConditionNone}, + 0x2D0B: {lower: "\u2D0B", upper: "\u10AB", condition: specialCasingConditionNone}, + 0x2D0C: {lower: "\u2D0C", upper: "\u10AC", condition: specialCasingConditionNone}, + 0x2D0D: {lower: "\u2D0D", upper: "\u10AD", condition: specialCasingConditionNone}, + 0x2D0E: {lower: "\u2D0E", upper: "\u10AE", condition: specialCasingConditionNone}, + 0x2D0F: {lower: "\u2D0F", upper: "\u10AF", condition: specialCasingConditionNone}, + 0x2D10: {lower: "\u2D10", upper: "\u10B0", condition: specialCasingConditionNone}, + 0x2D11: {lower: "\u2D11", upper: "\u10B1", condition: specialCasingConditionNone}, + 0x2D12: {lower: "\u2D12", upper: "\u10B2", condition: specialCasingConditionNone}, + 0x2D13: {lower: "\u2D13", upper: "\u10B3", condition: specialCasingConditionNone}, + 0x2D14: {lower: "\u2D14", upper: "\u10B4", condition: specialCasingConditionNone}, + 0x2D15: {lower: "\u2D15", upper: "\u10B5", condition: specialCasingConditionNone}, + 0x2D16: {lower: "\u2D16", upper: "\u10B6", condition: specialCasingConditionNone}, + 0x2D17: {lower: "\u2D17", upper: "\u10B7", condition: specialCasingConditionNone}, + 0x2D18: {lower: "\u2D18", upper: "\u10B8", condition: specialCasingConditionNone}, + 0x2D19: {lower: "\u2D19", upper: "\u10B9", condition: specialCasingConditionNone}, + 0x2D1A: {lower: "\u2D1A", upper: "\u10BA", condition: specialCasingConditionNone}, + 0x2D1B: {lower: "\u2D1B", upper: "\u10BB", condition: specialCasingConditionNone}, + 0x2D1C: {lower: "\u2D1C", upper: "\u10BC", condition: specialCasingConditionNone}, + 0x2D1D: {lower: "\u2D1D", upper: "\u10BD", condition: specialCasingConditionNone}, + 0x2D1E: {lower: "\u2D1E", upper: "\u10BE", condition: specialCasingConditionNone}, + 0x2D1F: {lower: "\u2D1F", upper: "\u10BF", condition: specialCasingConditionNone}, + 0x2D20: {lower: "\u2D20", upper: "\u10C0", condition: specialCasingConditionNone}, + 0x2D21: {lower: "\u2D21", upper: "\u10C1", condition: specialCasingConditionNone}, + 0x2D22: {lower: "\u2D22", upper: "\u10C2", condition: specialCasingConditionNone}, + 0x2D23: {lower: "\u2D23", upper: "\u10C3", condition: specialCasingConditionNone}, + 0x2D24: {lower: "\u2D24", upper: "\u10C4", condition: specialCasingConditionNone}, + 0x2D25: {lower: "\u2D25", upper: "\u10C5", condition: specialCasingConditionNone}, + 0x2D27: {lower: "\u2D27", upper: "\u10C7", condition: specialCasingConditionNone}, + 0x2D2D: {lower: "\u2D2D", upper: "\u10CD", condition: specialCasingConditionNone}, + 0xA640: {lower: "\uA641", upper: "\uA640", condition: specialCasingConditionNone}, + 0xA641: {lower: "\uA641", upper: "\uA640", condition: specialCasingConditionNone}, + 0xA642: {lower: "\uA643", upper: "\uA642", condition: specialCasingConditionNone}, + 0xA643: {lower: "\uA643", upper: "\uA642", condition: specialCasingConditionNone}, + 0xA644: {lower: "\uA645", upper: "\uA644", condition: specialCasingConditionNone}, + 0xA645: {lower: "\uA645", upper: "\uA644", condition: specialCasingConditionNone}, + 0xA646: {lower: "\uA647", upper: "\uA646", condition: specialCasingConditionNone}, + 0xA647: {lower: "\uA647", upper: "\uA646", condition: specialCasingConditionNone}, + 0xA648: {lower: "\uA649", upper: "\uA648", condition: specialCasingConditionNone}, + 0xA649: {lower: "\uA649", upper: "\uA648", condition: specialCasingConditionNone}, + 0xA64A: {lower: "\uA64B", upper: "\uA64A", condition: specialCasingConditionNone}, + 0xA64B: {lower: "\uA64B", upper: "\uA64A", condition: specialCasingConditionNone}, + 0xA64C: {lower: "\uA64D", upper: "\uA64C", condition: specialCasingConditionNone}, + 0xA64D: {lower: "\uA64D", upper: "\uA64C", condition: specialCasingConditionNone}, + 0xA64E: {lower: "\uA64F", upper: "\uA64E", condition: specialCasingConditionNone}, + 0xA64F: {lower: "\uA64F", upper: "\uA64E", condition: specialCasingConditionNone}, + 0xA650: {lower: "\uA651", upper: "\uA650", condition: specialCasingConditionNone}, + 0xA651: {lower: "\uA651", upper: "\uA650", condition: specialCasingConditionNone}, + 0xA652: {lower: "\uA653", upper: "\uA652", condition: specialCasingConditionNone}, + 0xA653: {lower: "\uA653", upper: "\uA652", condition: specialCasingConditionNone}, + 0xA654: {lower: "\uA655", upper: "\uA654", condition: specialCasingConditionNone}, + 0xA655: {lower: "\uA655", upper: "\uA654", condition: specialCasingConditionNone}, + 0xA656: {lower: "\uA657", upper: "\uA656", condition: specialCasingConditionNone}, + 0xA657: {lower: "\uA657", upper: "\uA656", condition: specialCasingConditionNone}, + 0xA658: {lower: "\uA659", upper: "\uA658", condition: specialCasingConditionNone}, + 0xA659: {lower: "\uA659", upper: "\uA658", condition: specialCasingConditionNone}, + 0xA65A: {lower: "\uA65B", upper: "\uA65A", condition: specialCasingConditionNone}, + 0xA65B: {lower: "\uA65B", upper: "\uA65A", condition: specialCasingConditionNone}, + 0xA65C: {lower: "\uA65D", upper: "\uA65C", condition: specialCasingConditionNone}, + 0xA65D: {lower: "\uA65D", upper: "\uA65C", condition: specialCasingConditionNone}, + 0xA65E: {lower: "\uA65F", upper: "\uA65E", condition: specialCasingConditionNone}, + 0xA65F: {lower: "\uA65F", upper: "\uA65E", condition: specialCasingConditionNone}, + 0xA660: {lower: "\uA661", upper: "\uA660", condition: specialCasingConditionNone}, + 0xA661: {lower: "\uA661", upper: "\uA660", condition: specialCasingConditionNone}, + 0xA662: {lower: "\uA663", upper: "\uA662", condition: specialCasingConditionNone}, + 0xA663: {lower: "\uA663", upper: "\uA662", condition: specialCasingConditionNone}, + 0xA664: {lower: "\uA665", upper: "\uA664", condition: specialCasingConditionNone}, + 0xA665: {lower: "\uA665", upper: "\uA664", condition: specialCasingConditionNone}, + 0xA666: {lower: "\uA667", upper: "\uA666", condition: specialCasingConditionNone}, + 0xA667: {lower: "\uA667", upper: "\uA666", condition: specialCasingConditionNone}, + 0xA668: {lower: "\uA669", upper: "\uA668", condition: specialCasingConditionNone}, + 0xA669: {lower: "\uA669", upper: "\uA668", condition: specialCasingConditionNone}, + 0xA66A: {lower: "\uA66B", upper: "\uA66A", condition: specialCasingConditionNone}, + 0xA66B: {lower: "\uA66B", upper: "\uA66A", condition: specialCasingConditionNone}, + 0xA66C: {lower: "\uA66D", upper: "\uA66C", condition: specialCasingConditionNone}, + 0xA66D: {lower: "\uA66D", upper: "\uA66C", condition: specialCasingConditionNone}, + 0xA680: {lower: "\uA681", upper: "\uA680", condition: specialCasingConditionNone}, + 0xA681: {lower: "\uA681", upper: "\uA680", condition: specialCasingConditionNone}, + 0xA682: {lower: "\uA683", upper: "\uA682", condition: specialCasingConditionNone}, + 0xA683: {lower: "\uA683", upper: "\uA682", condition: specialCasingConditionNone}, + 0xA684: {lower: "\uA685", upper: "\uA684", condition: specialCasingConditionNone}, + 0xA685: {lower: "\uA685", upper: "\uA684", condition: specialCasingConditionNone}, + 0xA686: {lower: "\uA687", upper: "\uA686", condition: specialCasingConditionNone}, + 0xA687: {lower: "\uA687", upper: "\uA686", condition: specialCasingConditionNone}, + 0xA688: {lower: "\uA689", upper: "\uA688", condition: specialCasingConditionNone}, + 0xA689: {lower: "\uA689", upper: "\uA688", condition: specialCasingConditionNone}, + 0xA68A: {lower: "\uA68B", upper: "\uA68A", condition: specialCasingConditionNone}, + 0xA68B: {lower: "\uA68B", upper: "\uA68A", condition: specialCasingConditionNone}, + 0xA68C: {lower: "\uA68D", upper: "\uA68C", condition: specialCasingConditionNone}, + 0xA68D: {lower: "\uA68D", upper: "\uA68C", condition: specialCasingConditionNone}, + 0xA68E: {lower: "\uA68F", upper: "\uA68E", condition: specialCasingConditionNone}, + 0xA68F: {lower: "\uA68F", upper: "\uA68E", condition: specialCasingConditionNone}, + 0xA690: {lower: "\uA691", upper: "\uA690", condition: specialCasingConditionNone}, + 0xA691: {lower: "\uA691", upper: "\uA690", condition: specialCasingConditionNone}, + 0xA692: {lower: "\uA693", upper: "\uA692", condition: specialCasingConditionNone}, + 0xA693: {lower: "\uA693", upper: "\uA692", condition: specialCasingConditionNone}, + 0xA694: {lower: "\uA695", upper: "\uA694", condition: specialCasingConditionNone}, + 0xA695: {lower: "\uA695", upper: "\uA694", condition: specialCasingConditionNone}, + 0xA696: {lower: "\uA697", upper: "\uA696", condition: specialCasingConditionNone}, + 0xA697: {lower: "\uA697", upper: "\uA696", condition: specialCasingConditionNone}, + 0xA698: {lower: "\uA699", upper: "\uA698", condition: specialCasingConditionNone}, + 0xA699: {lower: "\uA699", upper: "\uA698", condition: specialCasingConditionNone}, + 0xA69A: {lower: "\uA69B", upper: "\uA69A", condition: specialCasingConditionNone}, + 0xA69B: {lower: "\uA69B", upper: "\uA69A", condition: specialCasingConditionNone}, + 0xA722: {lower: "\uA723", upper: "\uA722", condition: specialCasingConditionNone}, + 0xA723: {lower: "\uA723", upper: "\uA722", condition: specialCasingConditionNone}, + 0xA724: {lower: "\uA725", upper: "\uA724", condition: specialCasingConditionNone}, + 0xA725: {lower: "\uA725", upper: "\uA724", condition: specialCasingConditionNone}, + 0xA726: {lower: "\uA727", upper: "\uA726", condition: specialCasingConditionNone}, + 0xA727: {lower: "\uA727", upper: "\uA726", condition: specialCasingConditionNone}, + 0xA728: {lower: "\uA729", upper: "\uA728", condition: specialCasingConditionNone}, + 0xA729: {lower: "\uA729", upper: "\uA728", condition: specialCasingConditionNone}, + 0xA72A: {lower: "\uA72B", upper: "\uA72A", condition: specialCasingConditionNone}, + 0xA72B: {lower: "\uA72B", upper: "\uA72A", condition: specialCasingConditionNone}, + 0xA72C: {lower: "\uA72D", upper: "\uA72C", condition: specialCasingConditionNone}, + 0xA72D: {lower: "\uA72D", upper: "\uA72C", condition: specialCasingConditionNone}, + 0xA72E: {lower: "\uA72F", upper: "\uA72E", condition: specialCasingConditionNone}, + 0xA72F: {lower: "\uA72F", upper: "\uA72E", condition: specialCasingConditionNone}, + 0xA732: {lower: "\uA733", upper: "\uA732", condition: specialCasingConditionNone}, + 0xA733: {lower: "\uA733", upper: "\uA732", condition: specialCasingConditionNone}, + 0xA734: {lower: "\uA735", upper: "\uA734", condition: specialCasingConditionNone}, + 0xA735: {lower: "\uA735", upper: "\uA734", condition: specialCasingConditionNone}, + 0xA736: {lower: "\uA737", upper: "\uA736", condition: specialCasingConditionNone}, + 0xA737: {lower: "\uA737", upper: "\uA736", condition: specialCasingConditionNone}, + 0xA738: {lower: "\uA739", upper: "\uA738", condition: specialCasingConditionNone}, + 0xA739: {lower: "\uA739", upper: "\uA738", condition: specialCasingConditionNone}, + 0xA73A: {lower: "\uA73B", upper: "\uA73A", condition: specialCasingConditionNone}, + 0xA73B: {lower: "\uA73B", upper: "\uA73A", condition: specialCasingConditionNone}, + 0xA73C: {lower: "\uA73D", upper: "\uA73C", condition: specialCasingConditionNone}, + 0xA73D: {lower: "\uA73D", upper: "\uA73C", condition: specialCasingConditionNone}, + 0xA73E: {lower: "\uA73F", upper: "\uA73E", condition: specialCasingConditionNone}, + 0xA73F: {lower: "\uA73F", upper: "\uA73E", condition: specialCasingConditionNone}, + 0xA740: {lower: "\uA741", upper: "\uA740", condition: specialCasingConditionNone}, + 0xA741: {lower: "\uA741", upper: "\uA740", condition: specialCasingConditionNone}, + 0xA742: {lower: "\uA743", upper: "\uA742", condition: specialCasingConditionNone}, + 0xA743: {lower: "\uA743", upper: "\uA742", condition: specialCasingConditionNone}, + 0xA744: {lower: "\uA745", upper: "\uA744", condition: specialCasingConditionNone}, + 0xA745: {lower: "\uA745", upper: "\uA744", condition: specialCasingConditionNone}, + 0xA746: {lower: "\uA747", upper: "\uA746", condition: specialCasingConditionNone}, + 0xA747: {lower: "\uA747", upper: "\uA746", condition: specialCasingConditionNone}, + 0xA748: {lower: "\uA749", upper: "\uA748", condition: specialCasingConditionNone}, + 0xA749: {lower: "\uA749", upper: "\uA748", condition: specialCasingConditionNone}, + 0xA74A: {lower: "\uA74B", upper: "\uA74A", condition: specialCasingConditionNone}, + 0xA74B: {lower: "\uA74B", upper: "\uA74A", condition: specialCasingConditionNone}, + 0xA74C: {lower: "\uA74D", upper: "\uA74C", condition: specialCasingConditionNone}, + 0xA74D: {lower: "\uA74D", upper: "\uA74C", condition: specialCasingConditionNone}, + 0xA74E: {lower: "\uA74F", upper: "\uA74E", condition: specialCasingConditionNone}, + 0xA74F: {lower: "\uA74F", upper: "\uA74E", condition: specialCasingConditionNone}, + 0xA750: {lower: "\uA751", upper: "\uA750", condition: specialCasingConditionNone}, + 0xA751: {lower: "\uA751", upper: "\uA750", condition: specialCasingConditionNone}, + 0xA752: {lower: "\uA753", upper: "\uA752", condition: specialCasingConditionNone}, + 0xA753: {lower: "\uA753", upper: "\uA752", condition: specialCasingConditionNone}, + 0xA754: {lower: "\uA755", upper: "\uA754", condition: specialCasingConditionNone}, + 0xA755: {lower: "\uA755", upper: "\uA754", condition: specialCasingConditionNone}, + 0xA756: {lower: "\uA757", upper: "\uA756", condition: specialCasingConditionNone}, + 0xA757: {lower: "\uA757", upper: "\uA756", condition: specialCasingConditionNone}, + 0xA758: {lower: "\uA759", upper: "\uA758", condition: specialCasingConditionNone}, + 0xA759: {lower: "\uA759", upper: "\uA758", condition: specialCasingConditionNone}, + 0xA75A: {lower: "\uA75B", upper: "\uA75A", condition: specialCasingConditionNone}, + 0xA75B: {lower: "\uA75B", upper: "\uA75A", condition: specialCasingConditionNone}, + 0xA75C: {lower: "\uA75D", upper: "\uA75C", condition: specialCasingConditionNone}, + 0xA75D: {lower: "\uA75D", upper: "\uA75C", condition: specialCasingConditionNone}, + 0xA75E: {lower: "\uA75F", upper: "\uA75E", condition: specialCasingConditionNone}, + 0xA75F: {lower: "\uA75F", upper: "\uA75E", condition: specialCasingConditionNone}, + 0xA760: {lower: "\uA761", upper: "\uA760", condition: specialCasingConditionNone}, + 0xA761: {lower: "\uA761", upper: "\uA760", condition: specialCasingConditionNone}, + 0xA762: {lower: "\uA763", upper: "\uA762", condition: specialCasingConditionNone}, + 0xA763: {lower: "\uA763", upper: "\uA762", condition: specialCasingConditionNone}, + 0xA764: {lower: "\uA765", upper: "\uA764", condition: specialCasingConditionNone}, + 0xA765: {lower: "\uA765", upper: "\uA764", condition: specialCasingConditionNone}, + 0xA766: {lower: "\uA767", upper: "\uA766", condition: specialCasingConditionNone}, + 0xA767: {lower: "\uA767", upper: "\uA766", condition: specialCasingConditionNone}, + 0xA768: {lower: "\uA769", upper: "\uA768", condition: specialCasingConditionNone}, + 0xA769: {lower: "\uA769", upper: "\uA768", condition: specialCasingConditionNone}, + 0xA76A: {lower: "\uA76B", upper: "\uA76A", condition: specialCasingConditionNone}, + 0xA76B: {lower: "\uA76B", upper: "\uA76A", condition: specialCasingConditionNone}, + 0xA76C: {lower: "\uA76D", upper: "\uA76C", condition: specialCasingConditionNone}, + 0xA76D: {lower: "\uA76D", upper: "\uA76C", condition: specialCasingConditionNone}, + 0xA76E: {lower: "\uA76F", upper: "\uA76E", condition: specialCasingConditionNone}, + 0xA76F: {lower: "\uA76F", upper: "\uA76E", condition: specialCasingConditionNone}, + 0xA779: {lower: "\uA77A", upper: "\uA779", condition: specialCasingConditionNone}, + 0xA77A: {lower: "\uA77A", upper: "\uA779", condition: specialCasingConditionNone}, + 0xA77B: {lower: "\uA77C", upper: "\uA77B", condition: specialCasingConditionNone}, + 0xA77C: {lower: "\uA77C", upper: "\uA77B", condition: specialCasingConditionNone}, + 0xA77D: {lower: "\u1D79", upper: "\uA77D", condition: specialCasingConditionNone}, + 0xA77E: {lower: "\uA77F", upper: "\uA77E", condition: specialCasingConditionNone}, + 0xA77F: {lower: "\uA77F", upper: "\uA77E", condition: specialCasingConditionNone}, + 0xA780: {lower: "\uA781", upper: "\uA780", condition: specialCasingConditionNone}, + 0xA781: {lower: "\uA781", upper: "\uA780", condition: specialCasingConditionNone}, + 0xA782: {lower: "\uA783", upper: "\uA782", condition: specialCasingConditionNone}, + 0xA783: {lower: "\uA783", upper: "\uA782", condition: specialCasingConditionNone}, + 0xA784: {lower: "\uA785", upper: "\uA784", condition: specialCasingConditionNone}, + 0xA785: {lower: "\uA785", upper: "\uA784", condition: specialCasingConditionNone}, + 0xA786: {lower: "\uA787", upper: "\uA786", condition: specialCasingConditionNone}, + 0xA787: {lower: "\uA787", upper: "\uA786", condition: specialCasingConditionNone}, + 0xA78B: {lower: "\uA78C", upper: "\uA78B", condition: specialCasingConditionNone}, + 0xA78C: {lower: "\uA78C", upper: "\uA78B", condition: specialCasingConditionNone}, + 0xA78D: {lower: "\u0265", upper: "\uA78D", condition: specialCasingConditionNone}, + 0xA790: {lower: "\uA791", upper: "\uA790", condition: specialCasingConditionNone}, + 0xA791: {lower: "\uA791", upper: "\uA790", condition: specialCasingConditionNone}, + 0xA792: {lower: "\uA793", upper: "\uA792", condition: specialCasingConditionNone}, + 0xA793: {lower: "\uA793", upper: "\uA792", condition: specialCasingConditionNone}, + 0xA794: {lower: "\uA794", upper: "\uA7C4", condition: specialCasingConditionNone}, + 0xA796: {lower: "\uA797", upper: "\uA796", condition: specialCasingConditionNone}, + 0xA797: {lower: "\uA797", upper: "\uA796", condition: specialCasingConditionNone}, + 0xA798: {lower: "\uA799", upper: "\uA798", condition: specialCasingConditionNone}, + 0xA799: {lower: "\uA799", upper: "\uA798", condition: specialCasingConditionNone}, + 0xA79A: {lower: "\uA79B", upper: "\uA79A", condition: specialCasingConditionNone}, + 0xA79B: {lower: "\uA79B", upper: "\uA79A", condition: specialCasingConditionNone}, + 0xA79C: {lower: "\uA79D", upper: "\uA79C", condition: specialCasingConditionNone}, + 0xA79D: {lower: "\uA79D", upper: "\uA79C", condition: specialCasingConditionNone}, + 0xA79E: {lower: "\uA79F", upper: "\uA79E", condition: specialCasingConditionNone}, + 0xA79F: {lower: "\uA79F", upper: "\uA79E", condition: specialCasingConditionNone}, + 0xA7A0: {lower: "\uA7A1", upper: "\uA7A0", condition: specialCasingConditionNone}, + 0xA7A1: {lower: "\uA7A1", upper: "\uA7A0", condition: specialCasingConditionNone}, + 0xA7A2: {lower: "\uA7A3", upper: "\uA7A2", condition: specialCasingConditionNone}, + 0xA7A3: {lower: "\uA7A3", upper: "\uA7A2", condition: specialCasingConditionNone}, + 0xA7A4: {lower: "\uA7A5", upper: "\uA7A4", condition: specialCasingConditionNone}, + 0xA7A5: {lower: "\uA7A5", upper: "\uA7A4", condition: specialCasingConditionNone}, + 0xA7A6: {lower: "\uA7A7", upper: "\uA7A6", condition: specialCasingConditionNone}, + 0xA7A7: {lower: "\uA7A7", upper: "\uA7A6", condition: specialCasingConditionNone}, + 0xA7A8: {lower: "\uA7A9", upper: "\uA7A8", condition: specialCasingConditionNone}, + 0xA7A9: {lower: "\uA7A9", upper: "\uA7A8", condition: specialCasingConditionNone}, + 0xA7AA: {lower: "\u0266", upper: "\uA7AA", condition: specialCasingConditionNone}, + 0xA7AB: {lower: "\u025C", upper: "\uA7AB", condition: specialCasingConditionNone}, + 0xA7AC: {lower: "\u0261", upper: "\uA7AC", condition: specialCasingConditionNone}, + 0xA7AD: {lower: "\u026C", upper: "\uA7AD", condition: specialCasingConditionNone}, + 0xA7AE: {lower: "\u026A", upper: "\uA7AE", condition: specialCasingConditionNone}, + 0xA7B0: {lower: "\u029E", upper: "\uA7B0", condition: specialCasingConditionNone}, + 0xA7B1: {lower: "\u0287", upper: "\uA7B1", condition: specialCasingConditionNone}, + 0xA7B2: {lower: "\u029D", upper: "\uA7B2", condition: specialCasingConditionNone}, + 0xA7B3: {lower: "\uAB53", upper: "\uA7B3", condition: specialCasingConditionNone}, + 0xA7B4: {lower: "\uA7B5", upper: "\uA7B4", condition: specialCasingConditionNone}, + 0xA7B5: {lower: "\uA7B5", upper: "\uA7B4", condition: specialCasingConditionNone}, + 0xA7B6: {lower: "\uA7B7", upper: "\uA7B6", condition: specialCasingConditionNone}, + 0xA7B7: {lower: "\uA7B7", upper: "\uA7B6", condition: specialCasingConditionNone}, + 0xA7B8: {lower: "\uA7B9", upper: "\uA7B8", condition: specialCasingConditionNone}, + 0xA7B9: {lower: "\uA7B9", upper: "\uA7B8", condition: specialCasingConditionNone}, + 0xA7BA: {lower: "\uA7BB", upper: "\uA7BA", condition: specialCasingConditionNone}, + 0xA7BB: {lower: "\uA7BB", upper: "\uA7BA", condition: specialCasingConditionNone}, + 0xA7BC: {lower: "\uA7BD", upper: "\uA7BC", condition: specialCasingConditionNone}, + 0xA7BD: {lower: "\uA7BD", upper: "\uA7BC", condition: specialCasingConditionNone}, + 0xA7BE: {lower: "\uA7BF", upper: "\uA7BE", condition: specialCasingConditionNone}, + 0xA7BF: {lower: "\uA7BF", upper: "\uA7BE", condition: specialCasingConditionNone}, + 0xA7C0: {lower: "\uA7C1", upper: "\uA7C0", condition: specialCasingConditionNone}, + 0xA7C1: {lower: "\uA7C1", upper: "\uA7C0", condition: specialCasingConditionNone}, + 0xA7C2: {lower: "\uA7C3", upper: "\uA7C2", condition: specialCasingConditionNone}, + 0xA7C3: {lower: "\uA7C3", upper: "\uA7C2", condition: specialCasingConditionNone}, + 0xA7C4: {lower: "\uA794", upper: "\uA7C4", condition: specialCasingConditionNone}, + 0xA7C5: {lower: "\u0282", upper: "\uA7C5", condition: specialCasingConditionNone}, + 0xA7C6: {lower: "\u1D8E", upper: "\uA7C6", condition: specialCasingConditionNone}, + 0xA7C7: {lower: "\uA7C8", upper: "\uA7C7", condition: specialCasingConditionNone}, + 0xA7C8: {lower: "\uA7C8", upper: "\uA7C7", condition: specialCasingConditionNone}, + 0xA7C9: {lower: "\uA7CA", upper: "\uA7C9", condition: specialCasingConditionNone}, + 0xA7CA: {lower: "\uA7CA", upper: "\uA7C9", condition: specialCasingConditionNone}, + 0xA7D0: {lower: "\uA7D1", upper: "\uA7D0", condition: specialCasingConditionNone}, + 0xA7D1: {lower: "\uA7D1", upper: "\uA7D0", condition: specialCasingConditionNone}, + 0xA7D6: {lower: "\uA7D7", upper: "\uA7D6", condition: specialCasingConditionNone}, + 0xA7D7: {lower: "\uA7D7", upper: "\uA7D6", condition: specialCasingConditionNone}, + 0xA7D8: {lower: "\uA7D9", upper: "\uA7D8", condition: specialCasingConditionNone}, + 0xA7D9: {lower: "\uA7D9", upper: "\uA7D8", condition: specialCasingConditionNone}, + 0xA7F5: {lower: "\uA7F6", upper: "\uA7F5", condition: specialCasingConditionNone}, + 0xA7F6: {lower: "\uA7F6", upper: "\uA7F5", condition: specialCasingConditionNone}, + 0xAB53: {lower: "\uAB53", upper: "\uA7B3", condition: specialCasingConditionNone}, + 0xAB70: {lower: "\uAB70", upper: "\u13A0", condition: specialCasingConditionNone}, + 0xAB71: {lower: "\uAB71", upper: "\u13A1", condition: specialCasingConditionNone}, + 0xAB72: {lower: "\uAB72", upper: "\u13A2", condition: specialCasingConditionNone}, + 0xAB73: {lower: "\uAB73", upper: "\u13A3", condition: specialCasingConditionNone}, + 0xAB74: {lower: "\uAB74", upper: "\u13A4", condition: specialCasingConditionNone}, + 0xAB75: {lower: "\uAB75", upper: "\u13A5", condition: specialCasingConditionNone}, + 0xAB76: {lower: "\uAB76", upper: "\u13A6", condition: specialCasingConditionNone}, + 0xAB77: {lower: "\uAB77", upper: "\u13A7", condition: specialCasingConditionNone}, + 0xAB78: {lower: "\uAB78", upper: "\u13A8", condition: specialCasingConditionNone}, + 0xAB79: {lower: "\uAB79", upper: "\u13A9", condition: specialCasingConditionNone}, + 0xAB7A: {lower: "\uAB7A", upper: "\u13AA", condition: specialCasingConditionNone}, + 0xAB7B: {lower: "\uAB7B", upper: "\u13AB", condition: specialCasingConditionNone}, + 0xAB7C: {lower: "\uAB7C", upper: "\u13AC", condition: specialCasingConditionNone}, + 0xAB7D: {lower: "\uAB7D", upper: "\u13AD", condition: specialCasingConditionNone}, + 0xAB7E: {lower: "\uAB7E", upper: "\u13AE", condition: specialCasingConditionNone}, + 0xAB7F: {lower: "\uAB7F", upper: "\u13AF", condition: specialCasingConditionNone}, + 0xAB80: {lower: "\uAB80", upper: "\u13B0", condition: specialCasingConditionNone}, + 0xAB81: {lower: "\uAB81", upper: "\u13B1", condition: specialCasingConditionNone}, + 0xAB82: {lower: "\uAB82", upper: "\u13B2", condition: specialCasingConditionNone}, + 0xAB83: {lower: "\uAB83", upper: "\u13B3", condition: specialCasingConditionNone}, + 0xAB84: {lower: "\uAB84", upper: "\u13B4", condition: specialCasingConditionNone}, + 0xAB85: {lower: "\uAB85", upper: "\u13B5", condition: specialCasingConditionNone}, + 0xAB86: {lower: "\uAB86", upper: "\u13B6", condition: specialCasingConditionNone}, + 0xAB87: {lower: "\uAB87", upper: "\u13B7", condition: specialCasingConditionNone}, + 0xAB88: {lower: "\uAB88", upper: "\u13B8", condition: specialCasingConditionNone}, + 0xAB89: {lower: "\uAB89", upper: "\u13B9", condition: specialCasingConditionNone}, + 0xAB8A: {lower: "\uAB8A", upper: "\u13BA", condition: specialCasingConditionNone}, + 0xAB8B: {lower: "\uAB8B", upper: "\u13BB", condition: specialCasingConditionNone}, + 0xAB8C: {lower: "\uAB8C", upper: "\u13BC", condition: specialCasingConditionNone}, + 0xAB8D: {lower: "\uAB8D", upper: "\u13BD", condition: specialCasingConditionNone}, + 0xAB8E: {lower: "\uAB8E", upper: "\u13BE", condition: specialCasingConditionNone}, + 0xAB8F: {lower: "\uAB8F", upper: "\u13BF", condition: specialCasingConditionNone}, + 0xAB90: {lower: "\uAB90", upper: "\u13C0", condition: specialCasingConditionNone}, + 0xAB91: {lower: "\uAB91", upper: "\u13C1", condition: specialCasingConditionNone}, + 0xAB92: {lower: "\uAB92", upper: "\u13C2", condition: specialCasingConditionNone}, + 0xAB93: {lower: "\uAB93", upper: "\u13C3", condition: specialCasingConditionNone}, + 0xAB94: {lower: "\uAB94", upper: "\u13C4", condition: specialCasingConditionNone}, + 0xAB95: {lower: "\uAB95", upper: "\u13C5", condition: specialCasingConditionNone}, + 0xAB96: {lower: "\uAB96", upper: "\u13C6", condition: specialCasingConditionNone}, + 0xAB97: {lower: "\uAB97", upper: "\u13C7", condition: specialCasingConditionNone}, + 0xAB98: {lower: "\uAB98", upper: "\u13C8", condition: specialCasingConditionNone}, + 0xAB99: {lower: "\uAB99", upper: "\u13C9", condition: specialCasingConditionNone}, + 0xAB9A: {lower: "\uAB9A", upper: "\u13CA", condition: specialCasingConditionNone}, + 0xAB9B: {lower: "\uAB9B", upper: "\u13CB", condition: specialCasingConditionNone}, + 0xAB9C: {lower: "\uAB9C", upper: "\u13CC", condition: specialCasingConditionNone}, + 0xAB9D: {lower: "\uAB9D", upper: "\u13CD", condition: specialCasingConditionNone}, + 0xAB9E: {lower: "\uAB9E", upper: "\u13CE", condition: specialCasingConditionNone}, + 0xAB9F: {lower: "\uAB9F", upper: "\u13CF", condition: specialCasingConditionNone}, + 0xABA0: {lower: "\uABA0", upper: "\u13D0", condition: specialCasingConditionNone}, + 0xABA1: {lower: "\uABA1", upper: "\u13D1", condition: specialCasingConditionNone}, + 0xABA2: {lower: "\uABA2", upper: "\u13D2", condition: specialCasingConditionNone}, + 0xABA3: {lower: "\uABA3", upper: "\u13D3", condition: specialCasingConditionNone}, + 0xABA4: {lower: "\uABA4", upper: "\u13D4", condition: specialCasingConditionNone}, + 0xABA5: {lower: "\uABA5", upper: "\u13D5", condition: specialCasingConditionNone}, + 0xABA6: {lower: "\uABA6", upper: "\u13D6", condition: specialCasingConditionNone}, + 0xABA7: {lower: "\uABA7", upper: "\u13D7", condition: specialCasingConditionNone}, + 0xABA8: {lower: "\uABA8", upper: "\u13D8", condition: specialCasingConditionNone}, + 0xABA9: {lower: "\uABA9", upper: "\u13D9", condition: specialCasingConditionNone}, + 0xABAA: {lower: "\uABAA", upper: "\u13DA", condition: specialCasingConditionNone}, + 0xABAB: {lower: "\uABAB", upper: "\u13DB", condition: specialCasingConditionNone}, + 0xABAC: {lower: "\uABAC", upper: "\u13DC", condition: specialCasingConditionNone}, + 0xABAD: {lower: "\uABAD", upper: "\u13DD", condition: specialCasingConditionNone}, + 0xABAE: {lower: "\uABAE", upper: "\u13DE", condition: specialCasingConditionNone}, + 0xABAF: {lower: "\uABAF", upper: "\u13DF", condition: specialCasingConditionNone}, + 0xABB0: {lower: "\uABB0", upper: "\u13E0", condition: specialCasingConditionNone}, + 0xABB1: {lower: "\uABB1", upper: "\u13E1", condition: specialCasingConditionNone}, + 0xABB2: {lower: "\uABB2", upper: "\u13E2", condition: specialCasingConditionNone}, + 0xABB3: {lower: "\uABB3", upper: "\u13E3", condition: specialCasingConditionNone}, + 0xABB4: {lower: "\uABB4", upper: "\u13E4", condition: specialCasingConditionNone}, + 0xABB5: {lower: "\uABB5", upper: "\u13E5", condition: specialCasingConditionNone}, + 0xABB6: {lower: "\uABB6", upper: "\u13E6", condition: specialCasingConditionNone}, + 0xABB7: {lower: "\uABB7", upper: "\u13E7", condition: specialCasingConditionNone}, + 0xABB8: {lower: "\uABB8", upper: "\u13E8", condition: specialCasingConditionNone}, + 0xABB9: {lower: "\uABB9", upper: "\u13E9", condition: specialCasingConditionNone}, + 0xABBA: {lower: "\uABBA", upper: "\u13EA", condition: specialCasingConditionNone}, + 0xABBB: {lower: "\uABBB", upper: "\u13EB", condition: specialCasingConditionNone}, + 0xABBC: {lower: "\uABBC", upper: "\u13EC", condition: specialCasingConditionNone}, + 0xABBD: {lower: "\uABBD", upper: "\u13ED", condition: specialCasingConditionNone}, + 0xABBE: {lower: "\uABBE", upper: "\u13EE", condition: specialCasingConditionNone}, + 0xABBF: {lower: "\uABBF", upper: "\u13EF", condition: specialCasingConditionNone}, + 0xFB00: {lower: "\uFB00", upper: "\u0046\u0046", condition: specialCasingConditionNone}, + 0xFB01: {lower: "\uFB01", upper: "\u0046\u0049", condition: specialCasingConditionNone}, + 0xFB02: {lower: "\uFB02", upper: "\u0046\u004C", condition: specialCasingConditionNone}, + 0xFB03: {lower: "\uFB03", upper: "\u0046\u0046\u0049", condition: specialCasingConditionNone}, + 0xFB04: {lower: "\uFB04", upper: "\u0046\u0046\u004C", condition: specialCasingConditionNone}, + 0xFB05: {lower: "\uFB05", upper: "\u0053\u0054", condition: specialCasingConditionNone}, + 0xFB06: {lower: "\uFB06", upper: "\u0053\u0054", condition: specialCasingConditionNone}, + 0xFB13: {lower: "\uFB13", upper: "\u0544\u0546", condition: specialCasingConditionNone}, + 0xFB14: {lower: "\uFB14", upper: "\u0544\u0535", condition: specialCasingConditionNone}, + 0xFB15: {lower: "\uFB15", upper: "\u0544\u053B", condition: specialCasingConditionNone}, + 0xFB16: {lower: "\uFB16", upper: "\u054E\u0546", condition: specialCasingConditionNone}, + 0xFB17: {lower: "\uFB17", upper: "\u0544\u053D", condition: specialCasingConditionNone}, + 0xFF21: {lower: "\uFF41", upper: "\uFF21", condition: specialCasingConditionNone}, + 0xFF22: {lower: "\uFF42", upper: "\uFF22", condition: specialCasingConditionNone}, + 0xFF23: {lower: "\uFF43", upper: "\uFF23", condition: specialCasingConditionNone}, + 0xFF24: {lower: "\uFF44", upper: "\uFF24", condition: specialCasingConditionNone}, + 0xFF25: {lower: "\uFF45", upper: "\uFF25", condition: specialCasingConditionNone}, + 0xFF26: {lower: "\uFF46", upper: "\uFF26", condition: specialCasingConditionNone}, + 0xFF27: {lower: "\uFF47", upper: "\uFF27", condition: specialCasingConditionNone}, + 0xFF28: {lower: "\uFF48", upper: "\uFF28", condition: specialCasingConditionNone}, + 0xFF29: {lower: "\uFF49", upper: "\uFF29", condition: specialCasingConditionNone}, + 0xFF2A: {lower: "\uFF4A", upper: "\uFF2A", condition: specialCasingConditionNone}, + 0xFF2B: {lower: "\uFF4B", upper: "\uFF2B", condition: specialCasingConditionNone}, + 0xFF2C: {lower: "\uFF4C", upper: "\uFF2C", condition: specialCasingConditionNone}, + 0xFF2D: {lower: "\uFF4D", upper: "\uFF2D", condition: specialCasingConditionNone}, + 0xFF2E: {lower: "\uFF4E", upper: "\uFF2E", condition: specialCasingConditionNone}, + 0xFF2F: {lower: "\uFF4F", upper: "\uFF2F", condition: specialCasingConditionNone}, + 0xFF30: {lower: "\uFF50", upper: "\uFF30", condition: specialCasingConditionNone}, + 0xFF31: {lower: "\uFF51", upper: "\uFF31", condition: specialCasingConditionNone}, + 0xFF32: {lower: "\uFF52", upper: "\uFF32", condition: specialCasingConditionNone}, + 0xFF33: {lower: "\uFF53", upper: "\uFF33", condition: specialCasingConditionNone}, + 0xFF34: {lower: "\uFF54", upper: "\uFF34", condition: specialCasingConditionNone}, + 0xFF35: {lower: "\uFF55", upper: "\uFF35", condition: specialCasingConditionNone}, + 0xFF36: {lower: "\uFF56", upper: "\uFF36", condition: specialCasingConditionNone}, + 0xFF37: {lower: "\uFF57", upper: "\uFF37", condition: specialCasingConditionNone}, + 0xFF38: {lower: "\uFF58", upper: "\uFF38", condition: specialCasingConditionNone}, + 0xFF39: {lower: "\uFF59", upper: "\uFF39", condition: specialCasingConditionNone}, + 0xFF3A: {lower: "\uFF5A", upper: "\uFF3A", condition: specialCasingConditionNone}, + 0xFF41: {lower: "\uFF41", upper: "\uFF21", condition: specialCasingConditionNone}, + 0xFF42: {lower: "\uFF42", upper: "\uFF22", condition: specialCasingConditionNone}, + 0xFF43: {lower: "\uFF43", upper: "\uFF23", condition: specialCasingConditionNone}, + 0xFF44: {lower: "\uFF44", upper: "\uFF24", condition: specialCasingConditionNone}, + 0xFF45: {lower: "\uFF45", upper: "\uFF25", condition: specialCasingConditionNone}, + 0xFF46: {lower: "\uFF46", upper: "\uFF26", condition: specialCasingConditionNone}, + 0xFF47: {lower: "\uFF47", upper: "\uFF27", condition: specialCasingConditionNone}, + 0xFF48: {lower: "\uFF48", upper: "\uFF28", condition: specialCasingConditionNone}, + 0xFF49: {lower: "\uFF49", upper: "\uFF29", condition: specialCasingConditionNone}, + 0xFF4A: {lower: "\uFF4A", upper: "\uFF2A", condition: specialCasingConditionNone}, + 0xFF4B: {lower: "\uFF4B", upper: "\uFF2B", condition: specialCasingConditionNone}, + 0xFF4C: {lower: "\uFF4C", upper: "\uFF2C", condition: specialCasingConditionNone}, + 0xFF4D: {lower: "\uFF4D", upper: "\uFF2D", condition: specialCasingConditionNone}, + 0xFF4E: {lower: "\uFF4E", upper: "\uFF2E", condition: specialCasingConditionNone}, + 0xFF4F: {lower: "\uFF4F", upper: "\uFF2F", condition: specialCasingConditionNone}, + 0xFF50: {lower: "\uFF50", upper: "\uFF30", condition: specialCasingConditionNone}, + 0xFF51: {lower: "\uFF51", upper: "\uFF31", condition: specialCasingConditionNone}, + 0xFF52: {lower: "\uFF52", upper: "\uFF32", condition: specialCasingConditionNone}, + 0xFF53: {lower: "\uFF53", upper: "\uFF33", condition: specialCasingConditionNone}, + 0xFF54: {lower: "\uFF54", upper: "\uFF34", condition: specialCasingConditionNone}, + 0xFF55: {lower: "\uFF55", upper: "\uFF35", condition: specialCasingConditionNone}, + 0xFF56: {lower: "\uFF56", upper: "\uFF36", condition: specialCasingConditionNone}, + 0xFF57: {lower: "\uFF57", upper: "\uFF37", condition: specialCasingConditionNone}, + 0xFF58: {lower: "\uFF58", upper: "\uFF38", condition: specialCasingConditionNone}, + 0xFF59: {lower: "\uFF59", upper: "\uFF39", condition: specialCasingConditionNone}, + 0xFF5A: {lower: "\uFF5A", upper: "\uFF3A", condition: specialCasingConditionNone}, + 0x10400: {lower: "\U00010428", upper: "\U00010400", condition: specialCasingConditionNone}, + 0x10401: {lower: "\U00010429", upper: "\U00010401", condition: specialCasingConditionNone}, + 0x10402: {lower: "\U0001042A", upper: "\U00010402", condition: specialCasingConditionNone}, + 0x10403: {lower: "\U0001042B", upper: "\U00010403", condition: specialCasingConditionNone}, + 0x10404: {lower: "\U0001042C", upper: "\U00010404", condition: specialCasingConditionNone}, + 0x10405: {lower: "\U0001042D", upper: "\U00010405", condition: specialCasingConditionNone}, + 0x10406: {lower: "\U0001042E", upper: "\U00010406", condition: specialCasingConditionNone}, + 0x10407: {lower: "\U0001042F", upper: "\U00010407", condition: specialCasingConditionNone}, + 0x10408: {lower: "\U00010430", upper: "\U00010408", condition: specialCasingConditionNone}, + 0x10409: {lower: "\U00010431", upper: "\U00010409", condition: specialCasingConditionNone}, + 0x1040A: {lower: "\U00010432", upper: "\U0001040A", condition: specialCasingConditionNone}, + 0x1040B: {lower: "\U00010433", upper: "\U0001040B", condition: specialCasingConditionNone}, + 0x1040C: {lower: "\U00010434", upper: "\U0001040C", condition: specialCasingConditionNone}, + 0x1040D: {lower: "\U00010435", upper: "\U0001040D", condition: specialCasingConditionNone}, + 0x1040E: {lower: "\U00010436", upper: "\U0001040E", condition: specialCasingConditionNone}, + 0x1040F: {lower: "\U00010437", upper: "\U0001040F", condition: specialCasingConditionNone}, + 0x10410: {lower: "\U00010438", upper: "\U00010410", condition: specialCasingConditionNone}, + 0x10411: {lower: "\U00010439", upper: "\U00010411", condition: specialCasingConditionNone}, + 0x10412: {lower: "\U0001043A", upper: "\U00010412", condition: specialCasingConditionNone}, + 0x10413: {lower: "\U0001043B", upper: "\U00010413", condition: specialCasingConditionNone}, + 0x10414: {lower: "\U0001043C", upper: "\U00010414", condition: specialCasingConditionNone}, + 0x10415: {lower: "\U0001043D", upper: "\U00010415", condition: specialCasingConditionNone}, + 0x10416: {lower: "\U0001043E", upper: "\U00010416", condition: specialCasingConditionNone}, + 0x10417: {lower: "\U0001043F", upper: "\U00010417", condition: specialCasingConditionNone}, + 0x10418: {lower: "\U00010440", upper: "\U00010418", condition: specialCasingConditionNone}, + 0x10419: {lower: "\U00010441", upper: "\U00010419", condition: specialCasingConditionNone}, + 0x1041A: {lower: "\U00010442", upper: "\U0001041A", condition: specialCasingConditionNone}, + 0x1041B: {lower: "\U00010443", upper: "\U0001041B", condition: specialCasingConditionNone}, + 0x1041C: {lower: "\U00010444", upper: "\U0001041C", condition: specialCasingConditionNone}, + 0x1041D: {lower: "\U00010445", upper: "\U0001041D", condition: specialCasingConditionNone}, + 0x1041E: {lower: "\U00010446", upper: "\U0001041E", condition: specialCasingConditionNone}, + 0x1041F: {lower: "\U00010447", upper: "\U0001041F", condition: specialCasingConditionNone}, + 0x10420: {lower: "\U00010448", upper: "\U00010420", condition: specialCasingConditionNone}, + 0x10421: {lower: "\U00010449", upper: "\U00010421", condition: specialCasingConditionNone}, + 0x10422: {lower: "\U0001044A", upper: "\U00010422", condition: specialCasingConditionNone}, + 0x10423: {lower: "\U0001044B", upper: "\U00010423", condition: specialCasingConditionNone}, + 0x10424: {lower: "\U0001044C", upper: "\U00010424", condition: specialCasingConditionNone}, + 0x10425: {lower: "\U0001044D", upper: "\U00010425", condition: specialCasingConditionNone}, + 0x10426: {lower: "\U0001044E", upper: "\U00010426", condition: specialCasingConditionNone}, + 0x10427: {lower: "\U0001044F", upper: "\U00010427", condition: specialCasingConditionNone}, + 0x10428: {lower: "\U00010428", upper: "\U00010400", condition: specialCasingConditionNone}, + 0x10429: {lower: "\U00010429", upper: "\U00010401", condition: specialCasingConditionNone}, + 0x1042A: {lower: "\U0001042A", upper: "\U00010402", condition: specialCasingConditionNone}, + 0x1042B: {lower: "\U0001042B", upper: "\U00010403", condition: specialCasingConditionNone}, + 0x1042C: {lower: "\U0001042C", upper: "\U00010404", condition: specialCasingConditionNone}, + 0x1042D: {lower: "\U0001042D", upper: "\U00010405", condition: specialCasingConditionNone}, + 0x1042E: {lower: "\U0001042E", upper: "\U00010406", condition: specialCasingConditionNone}, + 0x1042F: {lower: "\U0001042F", upper: "\U00010407", condition: specialCasingConditionNone}, + 0x10430: {lower: "\U00010430", upper: "\U00010408", condition: specialCasingConditionNone}, + 0x10431: {lower: "\U00010431", upper: "\U00010409", condition: specialCasingConditionNone}, + 0x10432: {lower: "\U00010432", upper: "\U0001040A", condition: specialCasingConditionNone}, + 0x10433: {lower: "\U00010433", upper: "\U0001040B", condition: specialCasingConditionNone}, + 0x10434: {lower: "\U00010434", upper: "\U0001040C", condition: specialCasingConditionNone}, + 0x10435: {lower: "\U00010435", upper: "\U0001040D", condition: specialCasingConditionNone}, + 0x10436: {lower: "\U00010436", upper: "\U0001040E", condition: specialCasingConditionNone}, + 0x10437: {lower: "\U00010437", upper: "\U0001040F", condition: specialCasingConditionNone}, + 0x10438: {lower: "\U00010438", upper: "\U00010410", condition: specialCasingConditionNone}, + 0x10439: {lower: "\U00010439", upper: "\U00010411", condition: specialCasingConditionNone}, + 0x1043A: {lower: "\U0001043A", upper: "\U00010412", condition: specialCasingConditionNone}, + 0x1043B: {lower: "\U0001043B", upper: "\U00010413", condition: specialCasingConditionNone}, + 0x1043C: {lower: "\U0001043C", upper: "\U00010414", condition: specialCasingConditionNone}, + 0x1043D: {lower: "\U0001043D", upper: "\U00010415", condition: specialCasingConditionNone}, + 0x1043E: {lower: "\U0001043E", upper: "\U00010416", condition: specialCasingConditionNone}, + 0x1043F: {lower: "\U0001043F", upper: "\U00010417", condition: specialCasingConditionNone}, + 0x10440: {lower: "\U00010440", upper: "\U00010418", condition: specialCasingConditionNone}, + 0x10441: {lower: "\U00010441", upper: "\U00010419", condition: specialCasingConditionNone}, + 0x10442: {lower: "\U00010442", upper: "\U0001041A", condition: specialCasingConditionNone}, + 0x10443: {lower: "\U00010443", upper: "\U0001041B", condition: specialCasingConditionNone}, + 0x10444: {lower: "\U00010444", upper: "\U0001041C", condition: specialCasingConditionNone}, + 0x10445: {lower: "\U00010445", upper: "\U0001041D", condition: specialCasingConditionNone}, + 0x10446: {lower: "\U00010446", upper: "\U0001041E", condition: specialCasingConditionNone}, + 0x10447: {lower: "\U00010447", upper: "\U0001041F", condition: specialCasingConditionNone}, + 0x10448: {lower: "\U00010448", upper: "\U00010420", condition: specialCasingConditionNone}, + 0x10449: {lower: "\U00010449", upper: "\U00010421", condition: specialCasingConditionNone}, + 0x1044A: {lower: "\U0001044A", upper: "\U00010422", condition: specialCasingConditionNone}, + 0x1044B: {lower: "\U0001044B", upper: "\U00010423", condition: specialCasingConditionNone}, + 0x1044C: {lower: "\U0001044C", upper: "\U00010424", condition: specialCasingConditionNone}, + 0x1044D: {lower: "\U0001044D", upper: "\U00010425", condition: specialCasingConditionNone}, + 0x1044E: {lower: "\U0001044E", upper: "\U00010426", condition: specialCasingConditionNone}, + 0x1044F: {lower: "\U0001044F", upper: "\U00010427", condition: specialCasingConditionNone}, + 0x104B0: {lower: "\U000104D8", upper: "\U000104B0", condition: specialCasingConditionNone}, + 0x104B1: {lower: "\U000104D9", upper: "\U000104B1", condition: specialCasingConditionNone}, + 0x104B2: {lower: "\U000104DA", upper: "\U000104B2", condition: specialCasingConditionNone}, + 0x104B3: {lower: "\U000104DB", upper: "\U000104B3", condition: specialCasingConditionNone}, + 0x104B4: {lower: "\U000104DC", upper: "\U000104B4", condition: specialCasingConditionNone}, + 0x104B5: {lower: "\U000104DD", upper: "\U000104B5", condition: specialCasingConditionNone}, + 0x104B6: {lower: "\U000104DE", upper: "\U000104B6", condition: specialCasingConditionNone}, + 0x104B7: {lower: "\U000104DF", upper: "\U000104B7", condition: specialCasingConditionNone}, + 0x104B8: {lower: "\U000104E0", upper: "\U000104B8", condition: specialCasingConditionNone}, + 0x104B9: {lower: "\U000104E1", upper: "\U000104B9", condition: specialCasingConditionNone}, + 0x104BA: {lower: "\U000104E2", upper: "\U000104BA", condition: specialCasingConditionNone}, + 0x104BB: {lower: "\U000104E3", upper: "\U000104BB", condition: specialCasingConditionNone}, + 0x104BC: {lower: "\U000104E4", upper: "\U000104BC", condition: specialCasingConditionNone}, + 0x104BD: {lower: "\U000104E5", upper: "\U000104BD", condition: specialCasingConditionNone}, + 0x104BE: {lower: "\U000104E6", upper: "\U000104BE", condition: specialCasingConditionNone}, + 0x104BF: {lower: "\U000104E7", upper: "\U000104BF", condition: specialCasingConditionNone}, + 0x104C0: {lower: "\U000104E8", upper: "\U000104C0", condition: specialCasingConditionNone}, + 0x104C1: {lower: "\U000104E9", upper: "\U000104C1", condition: specialCasingConditionNone}, + 0x104C2: {lower: "\U000104EA", upper: "\U000104C2", condition: specialCasingConditionNone}, + 0x104C3: {lower: "\U000104EB", upper: "\U000104C3", condition: specialCasingConditionNone}, + 0x104C4: {lower: "\U000104EC", upper: "\U000104C4", condition: specialCasingConditionNone}, + 0x104C5: {lower: "\U000104ED", upper: "\U000104C5", condition: specialCasingConditionNone}, + 0x104C6: {lower: "\U000104EE", upper: "\U000104C6", condition: specialCasingConditionNone}, + 0x104C7: {lower: "\U000104EF", upper: "\U000104C7", condition: specialCasingConditionNone}, + 0x104C8: {lower: "\U000104F0", upper: "\U000104C8", condition: specialCasingConditionNone}, + 0x104C9: {lower: "\U000104F1", upper: "\U000104C9", condition: specialCasingConditionNone}, + 0x104CA: {lower: "\U000104F2", upper: "\U000104CA", condition: specialCasingConditionNone}, + 0x104CB: {lower: "\U000104F3", upper: "\U000104CB", condition: specialCasingConditionNone}, + 0x104CC: {lower: "\U000104F4", upper: "\U000104CC", condition: specialCasingConditionNone}, + 0x104CD: {lower: "\U000104F5", upper: "\U000104CD", condition: specialCasingConditionNone}, + 0x104CE: {lower: "\U000104F6", upper: "\U000104CE", condition: specialCasingConditionNone}, + 0x104CF: {lower: "\U000104F7", upper: "\U000104CF", condition: specialCasingConditionNone}, + 0x104D0: {lower: "\U000104F8", upper: "\U000104D0", condition: specialCasingConditionNone}, + 0x104D1: {lower: "\U000104F9", upper: "\U000104D1", condition: specialCasingConditionNone}, + 0x104D2: {lower: "\U000104FA", upper: "\U000104D2", condition: specialCasingConditionNone}, + 0x104D3: {lower: "\U000104FB", upper: "\U000104D3", condition: specialCasingConditionNone}, + 0x104D8: {lower: "\U000104D8", upper: "\U000104B0", condition: specialCasingConditionNone}, + 0x104D9: {lower: "\U000104D9", upper: "\U000104B1", condition: specialCasingConditionNone}, + 0x104DA: {lower: "\U000104DA", upper: "\U000104B2", condition: specialCasingConditionNone}, + 0x104DB: {lower: "\U000104DB", upper: "\U000104B3", condition: specialCasingConditionNone}, + 0x104DC: {lower: "\U000104DC", upper: "\U000104B4", condition: specialCasingConditionNone}, + 0x104DD: {lower: "\U000104DD", upper: "\U000104B5", condition: specialCasingConditionNone}, + 0x104DE: {lower: "\U000104DE", upper: "\U000104B6", condition: specialCasingConditionNone}, + 0x104DF: {lower: "\U000104DF", upper: "\U000104B7", condition: specialCasingConditionNone}, + 0x104E0: {lower: "\U000104E0", upper: "\U000104B8", condition: specialCasingConditionNone}, + 0x104E1: {lower: "\U000104E1", upper: "\U000104B9", condition: specialCasingConditionNone}, + 0x104E2: {lower: "\U000104E2", upper: "\U000104BA", condition: specialCasingConditionNone}, + 0x104E3: {lower: "\U000104E3", upper: "\U000104BB", condition: specialCasingConditionNone}, + 0x104E4: {lower: "\U000104E4", upper: "\U000104BC", condition: specialCasingConditionNone}, + 0x104E5: {lower: "\U000104E5", upper: "\U000104BD", condition: specialCasingConditionNone}, + 0x104E6: {lower: "\U000104E6", upper: "\U000104BE", condition: specialCasingConditionNone}, + 0x104E7: {lower: "\U000104E7", upper: "\U000104BF", condition: specialCasingConditionNone}, + 0x104E8: {lower: "\U000104E8", upper: "\U000104C0", condition: specialCasingConditionNone}, + 0x104E9: {lower: "\U000104E9", upper: "\U000104C1", condition: specialCasingConditionNone}, + 0x104EA: {lower: "\U000104EA", upper: "\U000104C2", condition: specialCasingConditionNone}, + 0x104EB: {lower: "\U000104EB", upper: "\U000104C3", condition: specialCasingConditionNone}, + 0x104EC: {lower: "\U000104EC", upper: "\U000104C4", condition: specialCasingConditionNone}, + 0x104ED: {lower: "\U000104ED", upper: "\U000104C5", condition: specialCasingConditionNone}, + 0x104EE: {lower: "\U000104EE", upper: "\U000104C6", condition: specialCasingConditionNone}, + 0x104EF: {lower: "\U000104EF", upper: "\U000104C7", condition: specialCasingConditionNone}, + 0x104F0: {lower: "\U000104F0", upper: "\U000104C8", condition: specialCasingConditionNone}, + 0x104F1: {lower: "\U000104F1", upper: "\U000104C9", condition: specialCasingConditionNone}, + 0x104F2: {lower: "\U000104F2", upper: "\U000104CA", condition: specialCasingConditionNone}, + 0x104F3: {lower: "\U000104F3", upper: "\U000104CB", condition: specialCasingConditionNone}, + 0x104F4: {lower: "\U000104F4", upper: "\U000104CC", condition: specialCasingConditionNone}, + 0x104F5: {lower: "\U000104F5", upper: "\U000104CD", condition: specialCasingConditionNone}, + 0x104F6: {lower: "\U000104F6", upper: "\U000104CE", condition: specialCasingConditionNone}, + 0x104F7: {lower: "\U000104F7", upper: "\U000104CF", condition: specialCasingConditionNone}, + 0x104F8: {lower: "\U000104F8", upper: "\U000104D0", condition: specialCasingConditionNone}, + 0x104F9: {lower: "\U000104F9", upper: "\U000104D1", condition: specialCasingConditionNone}, + 0x104FA: {lower: "\U000104FA", upper: "\U000104D2", condition: specialCasingConditionNone}, + 0x104FB: {lower: "\U000104FB", upper: "\U000104D3", condition: specialCasingConditionNone}, + 0x10570: {lower: "\U00010597", upper: "\U00010570", condition: specialCasingConditionNone}, + 0x10571: {lower: "\U00010598", upper: "\U00010571", condition: specialCasingConditionNone}, + 0x10572: {lower: "\U00010599", upper: "\U00010572", condition: specialCasingConditionNone}, + 0x10573: {lower: "\U0001059A", upper: "\U00010573", condition: specialCasingConditionNone}, + 0x10574: {lower: "\U0001059B", upper: "\U00010574", condition: specialCasingConditionNone}, + 0x10575: {lower: "\U0001059C", upper: "\U00010575", condition: specialCasingConditionNone}, + 0x10576: {lower: "\U0001059D", upper: "\U00010576", condition: specialCasingConditionNone}, + 0x10577: {lower: "\U0001059E", upper: "\U00010577", condition: specialCasingConditionNone}, + 0x10578: {lower: "\U0001059F", upper: "\U00010578", condition: specialCasingConditionNone}, + 0x10579: {lower: "\U000105A0", upper: "\U00010579", condition: specialCasingConditionNone}, + 0x1057A: {lower: "\U000105A1", upper: "\U0001057A", condition: specialCasingConditionNone}, + 0x1057C: {lower: "\U000105A3", upper: "\U0001057C", condition: specialCasingConditionNone}, + 0x1057D: {lower: "\U000105A4", upper: "\U0001057D", condition: specialCasingConditionNone}, + 0x1057E: {lower: "\U000105A5", upper: "\U0001057E", condition: specialCasingConditionNone}, + 0x1057F: {lower: "\U000105A6", upper: "\U0001057F", condition: specialCasingConditionNone}, + 0x10580: {lower: "\U000105A7", upper: "\U00010580", condition: specialCasingConditionNone}, + 0x10581: {lower: "\U000105A8", upper: "\U00010581", condition: specialCasingConditionNone}, + 0x10582: {lower: "\U000105A9", upper: "\U00010582", condition: specialCasingConditionNone}, + 0x10583: {lower: "\U000105AA", upper: "\U00010583", condition: specialCasingConditionNone}, + 0x10584: {lower: "\U000105AB", upper: "\U00010584", condition: specialCasingConditionNone}, + 0x10585: {lower: "\U000105AC", upper: "\U00010585", condition: specialCasingConditionNone}, + 0x10586: {lower: "\U000105AD", upper: "\U00010586", condition: specialCasingConditionNone}, + 0x10587: {lower: "\U000105AE", upper: "\U00010587", condition: specialCasingConditionNone}, + 0x10588: {lower: "\U000105AF", upper: "\U00010588", condition: specialCasingConditionNone}, + 0x10589: {lower: "\U000105B0", upper: "\U00010589", condition: specialCasingConditionNone}, + 0x1058A: {lower: "\U000105B1", upper: "\U0001058A", condition: specialCasingConditionNone}, + 0x1058C: {lower: "\U000105B3", upper: "\U0001058C", condition: specialCasingConditionNone}, + 0x1058D: {lower: "\U000105B4", upper: "\U0001058D", condition: specialCasingConditionNone}, + 0x1058E: {lower: "\U000105B5", upper: "\U0001058E", condition: specialCasingConditionNone}, + 0x1058F: {lower: "\U000105B6", upper: "\U0001058F", condition: specialCasingConditionNone}, + 0x10590: {lower: "\U000105B7", upper: "\U00010590", condition: specialCasingConditionNone}, + 0x10591: {lower: "\U000105B8", upper: "\U00010591", condition: specialCasingConditionNone}, + 0x10592: {lower: "\U000105B9", upper: "\U00010592", condition: specialCasingConditionNone}, + 0x10594: {lower: "\U000105BB", upper: "\U00010594", condition: specialCasingConditionNone}, + 0x10595: {lower: "\U000105BC", upper: "\U00010595", condition: specialCasingConditionNone}, + 0x10597: {lower: "\U00010597", upper: "\U00010570", condition: specialCasingConditionNone}, + 0x10598: {lower: "\U00010598", upper: "\U00010571", condition: specialCasingConditionNone}, + 0x10599: {lower: "\U00010599", upper: "\U00010572", condition: specialCasingConditionNone}, + 0x1059A: {lower: "\U0001059A", upper: "\U00010573", condition: specialCasingConditionNone}, + 0x1059B: {lower: "\U0001059B", upper: "\U00010574", condition: specialCasingConditionNone}, + 0x1059C: {lower: "\U0001059C", upper: "\U00010575", condition: specialCasingConditionNone}, + 0x1059D: {lower: "\U0001059D", upper: "\U00010576", condition: specialCasingConditionNone}, + 0x1059E: {lower: "\U0001059E", upper: "\U00010577", condition: specialCasingConditionNone}, + 0x1059F: {lower: "\U0001059F", upper: "\U00010578", condition: specialCasingConditionNone}, + 0x105A0: {lower: "\U000105A0", upper: "\U00010579", condition: specialCasingConditionNone}, + 0x105A1: {lower: "\U000105A1", upper: "\U0001057A", condition: specialCasingConditionNone}, + 0x105A3: {lower: "\U000105A3", upper: "\U0001057C", condition: specialCasingConditionNone}, + 0x105A4: {lower: "\U000105A4", upper: "\U0001057D", condition: specialCasingConditionNone}, + 0x105A5: {lower: "\U000105A5", upper: "\U0001057E", condition: specialCasingConditionNone}, + 0x105A6: {lower: "\U000105A6", upper: "\U0001057F", condition: specialCasingConditionNone}, + 0x105A7: {lower: "\U000105A7", upper: "\U00010580", condition: specialCasingConditionNone}, + 0x105A8: {lower: "\U000105A8", upper: "\U00010581", condition: specialCasingConditionNone}, + 0x105A9: {lower: "\U000105A9", upper: "\U00010582", condition: specialCasingConditionNone}, + 0x105AA: {lower: "\U000105AA", upper: "\U00010583", condition: specialCasingConditionNone}, + 0x105AB: {lower: "\U000105AB", upper: "\U00010584", condition: specialCasingConditionNone}, + 0x105AC: {lower: "\U000105AC", upper: "\U00010585", condition: specialCasingConditionNone}, + 0x105AD: {lower: "\U000105AD", upper: "\U00010586", condition: specialCasingConditionNone}, + 0x105AE: {lower: "\U000105AE", upper: "\U00010587", condition: specialCasingConditionNone}, + 0x105AF: {lower: "\U000105AF", upper: "\U00010588", condition: specialCasingConditionNone}, + 0x105B0: {lower: "\U000105B0", upper: "\U00010589", condition: specialCasingConditionNone}, + 0x105B1: {lower: "\U000105B1", upper: "\U0001058A", condition: specialCasingConditionNone}, + 0x105B3: {lower: "\U000105B3", upper: "\U0001058C", condition: specialCasingConditionNone}, + 0x105B4: {lower: "\U000105B4", upper: "\U0001058D", condition: specialCasingConditionNone}, + 0x105B5: {lower: "\U000105B5", upper: "\U0001058E", condition: specialCasingConditionNone}, + 0x105B6: {lower: "\U000105B6", upper: "\U0001058F", condition: specialCasingConditionNone}, + 0x105B7: {lower: "\U000105B7", upper: "\U00010590", condition: specialCasingConditionNone}, + 0x105B8: {lower: "\U000105B8", upper: "\U00010591", condition: specialCasingConditionNone}, + 0x105B9: {lower: "\U000105B9", upper: "\U00010592", condition: specialCasingConditionNone}, + 0x105BB: {lower: "\U000105BB", upper: "\U00010594", condition: specialCasingConditionNone}, + 0x105BC: {lower: "\U000105BC", upper: "\U00010595", condition: specialCasingConditionNone}, + 0x10C80: {lower: "\U00010CC0", upper: "\U00010C80", condition: specialCasingConditionNone}, + 0x10C81: {lower: "\U00010CC1", upper: "\U00010C81", condition: specialCasingConditionNone}, + 0x10C82: {lower: "\U00010CC2", upper: "\U00010C82", condition: specialCasingConditionNone}, + 0x10C83: {lower: "\U00010CC3", upper: "\U00010C83", condition: specialCasingConditionNone}, + 0x10C84: {lower: "\U00010CC4", upper: "\U00010C84", condition: specialCasingConditionNone}, + 0x10C85: {lower: "\U00010CC5", upper: "\U00010C85", condition: specialCasingConditionNone}, + 0x10C86: {lower: "\U00010CC6", upper: "\U00010C86", condition: specialCasingConditionNone}, + 0x10C87: {lower: "\U00010CC7", upper: "\U00010C87", condition: specialCasingConditionNone}, + 0x10C88: {lower: "\U00010CC8", upper: "\U00010C88", condition: specialCasingConditionNone}, + 0x10C89: {lower: "\U00010CC9", upper: "\U00010C89", condition: specialCasingConditionNone}, + 0x10C8A: {lower: "\U00010CCA", upper: "\U00010C8A", condition: specialCasingConditionNone}, + 0x10C8B: {lower: "\U00010CCB", upper: "\U00010C8B", condition: specialCasingConditionNone}, + 0x10C8C: {lower: "\U00010CCC", upper: "\U00010C8C", condition: specialCasingConditionNone}, + 0x10C8D: {lower: "\U00010CCD", upper: "\U00010C8D", condition: specialCasingConditionNone}, + 0x10C8E: {lower: "\U00010CCE", upper: "\U00010C8E", condition: specialCasingConditionNone}, + 0x10C8F: {lower: "\U00010CCF", upper: "\U00010C8F", condition: specialCasingConditionNone}, + 0x10C90: {lower: "\U00010CD0", upper: "\U00010C90", condition: specialCasingConditionNone}, + 0x10C91: {lower: "\U00010CD1", upper: "\U00010C91", condition: specialCasingConditionNone}, + 0x10C92: {lower: "\U00010CD2", upper: "\U00010C92", condition: specialCasingConditionNone}, + 0x10C93: {lower: "\U00010CD3", upper: "\U00010C93", condition: specialCasingConditionNone}, + 0x10C94: {lower: "\U00010CD4", upper: "\U00010C94", condition: specialCasingConditionNone}, + 0x10C95: {lower: "\U00010CD5", upper: "\U00010C95", condition: specialCasingConditionNone}, + 0x10C96: {lower: "\U00010CD6", upper: "\U00010C96", condition: specialCasingConditionNone}, + 0x10C97: {lower: "\U00010CD7", upper: "\U00010C97", condition: specialCasingConditionNone}, + 0x10C98: {lower: "\U00010CD8", upper: "\U00010C98", condition: specialCasingConditionNone}, + 0x10C99: {lower: "\U00010CD9", upper: "\U00010C99", condition: specialCasingConditionNone}, + 0x10C9A: {lower: "\U00010CDA", upper: "\U00010C9A", condition: specialCasingConditionNone}, + 0x10C9B: {lower: "\U00010CDB", upper: "\U00010C9B", condition: specialCasingConditionNone}, + 0x10C9C: {lower: "\U00010CDC", upper: "\U00010C9C", condition: specialCasingConditionNone}, + 0x10C9D: {lower: "\U00010CDD", upper: "\U00010C9D", condition: specialCasingConditionNone}, + 0x10C9E: {lower: "\U00010CDE", upper: "\U00010C9E", condition: specialCasingConditionNone}, + 0x10C9F: {lower: "\U00010CDF", upper: "\U00010C9F", condition: specialCasingConditionNone}, + 0x10CA0: {lower: "\U00010CE0", upper: "\U00010CA0", condition: specialCasingConditionNone}, + 0x10CA1: {lower: "\U00010CE1", upper: "\U00010CA1", condition: specialCasingConditionNone}, + 0x10CA2: {lower: "\U00010CE2", upper: "\U00010CA2", condition: specialCasingConditionNone}, + 0x10CA3: {lower: "\U00010CE3", upper: "\U00010CA3", condition: specialCasingConditionNone}, + 0x10CA4: {lower: "\U00010CE4", upper: "\U00010CA4", condition: specialCasingConditionNone}, + 0x10CA5: {lower: "\U00010CE5", upper: "\U00010CA5", condition: specialCasingConditionNone}, + 0x10CA6: {lower: "\U00010CE6", upper: "\U00010CA6", condition: specialCasingConditionNone}, + 0x10CA7: {lower: "\U00010CE7", upper: "\U00010CA7", condition: specialCasingConditionNone}, + 0x10CA8: {lower: "\U00010CE8", upper: "\U00010CA8", condition: specialCasingConditionNone}, + 0x10CA9: {lower: "\U00010CE9", upper: "\U00010CA9", condition: specialCasingConditionNone}, + 0x10CAA: {lower: "\U00010CEA", upper: "\U00010CAA", condition: specialCasingConditionNone}, + 0x10CAB: {lower: "\U00010CEB", upper: "\U00010CAB", condition: specialCasingConditionNone}, + 0x10CAC: {lower: "\U00010CEC", upper: "\U00010CAC", condition: specialCasingConditionNone}, + 0x10CAD: {lower: "\U00010CED", upper: "\U00010CAD", condition: specialCasingConditionNone}, + 0x10CAE: {lower: "\U00010CEE", upper: "\U00010CAE", condition: specialCasingConditionNone}, + 0x10CAF: {lower: "\U00010CEF", upper: "\U00010CAF", condition: specialCasingConditionNone}, + 0x10CB0: {lower: "\U00010CF0", upper: "\U00010CB0", condition: specialCasingConditionNone}, + 0x10CB1: {lower: "\U00010CF1", upper: "\U00010CB1", condition: specialCasingConditionNone}, + 0x10CB2: {lower: "\U00010CF2", upper: "\U00010CB2", condition: specialCasingConditionNone}, + 0x10CC0: {lower: "\U00010CC0", upper: "\U00010C80", condition: specialCasingConditionNone}, + 0x10CC1: {lower: "\U00010CC1", upper: "\U00010C81", condition: specialCasingConditionNone}, + 0x10CC2: {lower: "\U00010CC2", upper: "\U00010C82", condition: specialCasingConditionNone}, + 0x10CC3: {lower: "\U00010CC3", upper: "\U00010C83", condition: specialCasingConditionNone}, + 0x10CC4: {lower: "\U00010CC4", upper: "\U00010C84", condition: specialCasingConditionNone}, + 0x10CC5: {lower: "\U00010CC5", upper: "\U00010C85", condition: specialCasingConditionNone}, + 0x10CC6: {lower: "\U00010CC6", upper: "\U00010C86", condition: specialCasingConditionNone}, + 0x10CC7: {lower: "\U00010CC7", upper: "\U00010C87", condition: specialCasingConditionNone}, + 0x10CC8: {lower: "\U00010CC8", upper: "\U00010C88", condition: specialCasingConditionNone}, + 0x10CC9: {lower: "\U00010CC9", upper: "\U00010C89", condition: specialCasingConditionNone}, + 0x10CCA: {lower: "\U00010CCA", upper: "\U00010C8A", condition: specialCasingConditionNone}, + 0x10CCB: {lower: "\U00010CCB", upper: "\U00010C8B", condition: specialCasingConditionNone}, + 0x10CCC: {lower: "\U00010CCC", upper: "\U00010C8C", condition: specialCasingConditionNone}, + 0x10CCD: {lower: "\U00010CCD", upper: "\U00010C8D", condition: specialCasingConditionNone}, + 0x10CCE: {lower: "\U00010CCE", upper: "\U00010C8E", condition: specialCasingConditionNone}, + 0x10CCF: {lower: "\U00010CCF", upper: "\U00010C8F", condition: specialCasingConditionNone}, + 0x10CD0: {lower: "\U00010CD0", upper: "\U00010C90", condition: specialCasingConditionNone}, + 0x10CD1: {lower: "\U00010CD1", upper: "\U00010C91", condition: specialCasingConditionNone}, + 0x10CD2: {lower: "\U00010CD2", upper: "\U00010C92", condition: specialCasingConditionNone}, + 0x10CD3: {lower: "\U00010CD3", upper: "\U00010C93", condition: specialCasingConditionNone}, + 0x10CD4: {lower: "\U00010CD4", upper: "\U00010C94", condition: specialCasingConditionNone}, + 0x10CD5: {lower: "\U00010CD5", upper: "\U00010C95", condition: specialCasingConditionNone}, + 0x10CD6: {lower: "\U00010CD6", upper: "\U00010C96", condition: specialCasingConditionNone}, + 0x10CD7: {lower: "\U00010CD7", upper: "\U00010C97", condition: specialCasingConditionNone}, + 0x10CD8: {lower: "\U00010CD8", upper: "\U00010C98", condition: specialCasingConditionNone}, + 0x10CD9: {lower: "\U00010CD9", upper: "\U00010C99", condition: specialCasingConditionNone}, + 0x10CDA: {lower: "\U00010CDA", upper: "\U00010C9A", condition: specialCasingConditionNone}, + 0x10CDB: {lower: "\U00010CDB", upper: "\U00010C9B", condition: specialCasingConditionNone}, + 0x10CDC: {lower: "\U00010CDC", upper: "\U00010C9C", condition: specialCasingConditionNone}, + 0x10CDD: {lower: "\U00010CDD", upper: "\U00010C9D", condition: specialCasingConditionNone}, + 0x10CDE: {lower: "\U00010CDE", upper: "\U00010C9E", condition: specialCasingConditionNone}, + 0x10CDF: {lower: "\U00010CDF", upper: "\U00010C9F", condition: specialCasingConditionNone}, + 0x10CE0: {lower: "\U00010CE0", upper: "\U00010CA0", condition: specialCasingConditionNone}, + 0x10CE1: {lower: "\U00010CE1", upper: "\U00010CA1", condition: specialCasingConditionNone}, + 0x10CE2: {lower: "\U00010CE2", upper: "\U00010CA2", condition: specialCasingConditionNone}, + 0x10CE3: {lower: "\U00010CE3", upper: "\U00010CA3", condition: specialCasingConditionNone}, + 0x10CE4: {lower: "\U00010CE4", upper: "\U00010CA4", condition: specialCasingConditionNone}, + 0x10CE5: {lower: "\U00010CE5", upper: "\U00010CA5", condition: specialCasingConditionNone}, + 0x10CE6: {lower: "\U00010CE6", upper: "\U00010CA6", condition: specialCasingConditionNone}, + 0x10CE7: {lower: "\U00010CE7", upper: "\U00010CA7", condition: specialCasingConditionNone}, + 0x10CE8: {lower: "\U00010CE8", upper: "\U00010CA8", condition: specialCasingConditionNone}, + 0x10CE9: {lower: "\U00010CE9", upper: "\U00010CA9", condition: specialCasingConditionNone}, + 0x10CEA: {lower: "\U00010CEA", upper: "\U00010CAA", condition: specialCasingConditionNone}, + 0x10CEB: {lower: "\U00010CEB", upper: "\U00010CAB", condition: specialCasingConditionNone}, + 0x10CEC: {lower: "\U00010CEC", upper: "\U00010CAC", condition: specialCasingConditionNone}, + 0x10CED: {lower: "\U00010CED", upper: "\U00010CAD", condition: specialCasingConditionNone}, + 0x10CEE: {lower: "\U00010CEE", upper: "\U00010CAE", condition: specialCasingConditionNone}, + 0x10CEF: {lower: "\U00010CEF", upper: "\U00010CAF", condition: specialCasingConditionNone}, + 0x10CF0: {lower: "\U00010CF0", upper: "\U00010CB0", condition: specialCasingConditionNone}, + 0x10CF1: {lower: "\U00010CF1", upper: "\U00010CB1", condition: specialCasingConditionNone}, + 0x10CF2: {lower: "\U00010CF2", upper: "\U00010CB2", condition: specialCasingConditionNone}, + 0x118A0: {lower: "\U000118C0", upper: "\U000118A0", condition: specialCasingConditionNone}, + 0x118A1: {lower: "\U000118C1", upper: "\U000118A1", condition: specialCasingConditionNone}, + 0x118A2: {lower: "\U000118C2", upper: "\U000118A2", condition: specialCasingConditionNone}, + 0x118A3: {lower: "\U000118C3", upper: "\U000118A3", condition: specialCasingConditionNone}, + 0x118A4: {lower: "\U000118C4", upper: "\U000118A4", condition: specialCasingConditionNone}, + 0x118A5: {lower: "\U000118C5", upper: "\U000118A5", condition: specialCasingConditionNone}, + 0x118A6: {lower: "\U000118C6", upper: "\U000118A6", condition: specialCasingConditionNone}, + 0x118A7: {lower: "\U000118C7", upper: "\U000118A7", condition: specialCasingConditionNone}, + 0x118A8: {lower: "\U000118C8", upper: "\U000118A8", condition: specialCasingConditionNone}, + 0x118A9: {lower: "\U000118C9", upper: "\U000118A9", condition: specialCasingConditionNone}, + 0x118AA: {lower: "\U000118CA", upper: "\U000118AA", condition: specialCasingConditionNone}, + 0x118AB: {lower: "\U000118CB", upper: "\U000118AB", condition: specialCasingConditionNone}, + 0x118AC: {lower: "\U000118CC", upper: "\U000118AC", condition: specialCasingConditionNone}, + 0x118AD: {lower: "\U000118CD", upper: "\U000118AD", condition: specialCasingConditionNone}, + 0x118AE: {lower: "\U000118CE", upper: "\U000118AE", condition: specialCasingConditionNone}, + 0x118AF: {lower: "\U000118CF", upper: "\U000118AF", condition: specialCasingConditionNone}, + 0x118B0: {lower: "\U000118D0", upper: "\U000118B0", condition: specialCasingConditionNone}, + 0x118B1: {lower: "\U000118D1", upper: "\U000118B1", condition: specialCasingConditionNone}, + 0x118B2: {lower: "\U000118D2", upper: "\U000118B2", condition: specialCasingConditionNone}, + 0x118B3: {lower: "\U000118D3", upper: "\U000118B3", condition: specialCasingConditionNone}, + 0x118B4: {lower: "\U000118D4", upper: "\U000118B4", condition: specialCasingConditionNone}, + 0x118B5: {lower: "\U000118D5", upper: "\U000118B5", condition: specialCasingConditionNone}, + 0x118B6: {lower: "\U000118D6", upper: "\U000118B6", condition: specialCasingConditionNone}, + 0x118B7: {lower: "\U000118D7", upper: "\U000118B7", condition: specialCasingConditionNone}, + 0x118B8: {lower: "\U000118D8", upper: "\U000118B8", condition: specialCasingConditionNone}, + 0x118B9: {lower: "\U000118D9", upper: "\U000118B9", condition: specialCasingConditionNone}, + 0x118BA: {lower: "\U000118DA", upper: "\U000118BA", condition: specialCasingConditionNone}, + 0x118BB: {lower: "\U000118DB", upper: "\U000118BB", condition: specialCasingConditionNone}, + 0x118BC: {lower: "\U000118DC", upper: "\U000118BC", condition: specialCasingConditionNone}, + 0x118BD: {lower: "\U000118DD", upper: "\U000118BD", condition: specialCasingConditionNone}, + 0x118BE: {lower: "\U000118DE", upper: "\U000118BE", condition: specialCasingConditionNone}, + 0x118BF: {lower: "\U000118DF", upper: "\U000118BF", condition: specialCasingConditionNone}, + 0x118C0: {lower: "\U000118C0", upper: "\U000118A0", condition: specialCasingConditionNone}, + 0x118C1: {lower: "\U000118C1", upper: "\U000118A1", condition: specialCasingConditionNone}, + 0x118C2: {lower: "\U000118C2", upper: "\U000118A2", condition: specialCasingConditionNone}, + 0x118C3: {lower: "\U000118C3", upper: "\U000118A3", condition: specialCasingConditionNone}, + 0x118C4: {lower: "\U000118C4", upper: "\U000118A4", condition: specialCasingConditionNone}, + 0x118C5: {lower: "\U000118C5", upper: "\U000118A5", condition: specialCasingConditionNone}, + 0x118C6: {lower: "\U000118C6", upper: "\U000118A6", condition: specialCasingConditionNone}, + 0x118C7: {lower: "\U000118C7", upper: "\U000118A7", condition: specialCasingConditionNone}, + 0x118C8: {lower: "\U000118C8", upper: "\U000118A8", condition: specialCasingConditionNone}, + 0x118C9: {lower: "\U000118C9", upper: "\U000118A9", condition: specialCasingConditionNone}, + 0x118CA: {lower: "\U000118CA", upper: "\U000118AA", condition: specialCasingConditionNone}, + 0x118CB: {lower: "\U000118CB", upper: "\U000118AB", condition: specialCasingConditionNone}, + 0x118CC: {lower: "\U000118CC", upper: "\U000118AC", condition: specialCasingConditionNone}, + 0x118CD: {lower: "\U000118CD", upper: "\U000118AD", condition: specialCasingConditionNone}, + 0x118CE: {lower: "\U000118CE", upper: "\U000118AE", condition: specialCasingConditionNone}, + 0x118CF: {lower: "\U000118CF", upper: "\U000118AF", condition: specialCasingConditionNone}, + 0x118D0: {lower: "\U000118D0", upper: "\U000118B0", condition: specialCasingConditionNone}, + 0x118D1: {lower: "\U000118D1", upper: "\U000118B1", condition: specialCasingConditionNone}, + 0x118D2: {lower: "\U000118D2", upper: "\U000118B2", condition: specialCasingConditionNone}, + 0x118D3: {lower: "\U000118D3", upper: "\U000118B3", condition: specialCasingConditionNone}, + 0x118D4: {lower: "\U000118D4", upper: "\U000118B4", condition: specialCasingConditionNone}, + 0x118D5: {lower: "\U000118D5", upper: "\U000118B5", condition: specialCasingConditionNone}, + 0x118D6: {lower: "\U000118D6", upper: "\U000118B6", condition: specialCasingConditionNone}, + 0x118D7: {lower: "\U000118D7", upper: "\U000118B7", condition: specialCasingConditionNone}, + 0x118D8: {lower: "\U000118D8", upper: "\U000118B8", condition: specialCasingConditionNone}, + 0x118D9: {lower: "\U000118D9", upper: "\U000118B9", condition: specialCasingConditionNone}, + 0x118DA: {lower: "\U000118DA", upper: "\U000118BA", condition: specialCasingConditionNone}, + 0x118DB: {lower: "\U000118DB", upper: "\U000118BB", condition: specialCasingConditionNone}, + 0x118DC: {lower: "\U000118DC", upper: "\U000118BC", condition: specialCasingConditionNone}, + 0x118DD: {lower: "\U000118DD", upper: "\U000118BD", condition: specialCasingConditionNone}, + 0x118DE: {lower: "\U000118DE", upper: "\U000118BE", condition: specialCasingConditionNone}, + 0x118DF: {lower: "\U000118DF", upper: "\U000118BF", condition: specialCasingConditionNone}, + 0x16E40: {lower: "\U00016E60", upper: "\U00016E40", condition: specialCasingConditionNone}, + 0x16E41: {lower: "\U00016E61", upper: "\U00016E41", condition: specialCasingConditionNone}, + 0x16E42: {lower: "\U00016E62", upper: "\U00016E42", condition: specialCasingConditionNone}, + 0x16E43: {lower: "\U00016E63", upper: "\U00016E43", condition: specialCasingConditionNone}, + 0x16E44: {lower: "\U00016E64", upper: "\U00016E44", condition: specialCasingConditionNone}, + 0x16E45: {lower: "\U00016E65", upper: "\U00016E45", condition: specialCasingConditionNone}, + 0x16E46: {lower: "\U00016E66", upper: "\U00016E46", condition: specialCasingConditionNone}, + 0x16E47: {lower: "\U00016E67", upper: "\U00016E47", condition: specialCasingConditionNone}, + 0x16E48: {lower: "\U00016E68", upper: "\U00016E48", condition: specialCasingConditionNone}, + 0x16E49: {lower: "\U00016E69", upper: "\U00016E49", condition: specialCasingConditionNone}, + 0x16E4A: {lower: "\U00016E6A", upper: "\U00016E4A", condition: specialCasingConditionNone}, + 0x16E4B: {lower: "\U00016E6B", upper: "\U00016E4B", condition: specialCasingConditionNone}, + 0x16E4C: {lower: "\U00016E6C", upper: "\U00016E4C", condition: specialCasingConditionNone}, + 0x16E4D: {lower: "\U00016E6D", upper: "\U00016E4D", condition: specialCasingConditionNone}, + 0x16E4E: {lower: "\U00016E6E", upper: "\U00016E4E", condition: specialCasingConditionNone}, + 0x16E4F: {lower: "\U00016E6F", upper: "\U00016E4F", condition: specialCasingConditionNone}, + 0x16E50: {lower: "\U00016E70", upper: "\U00016E50", condition: specialCasingConditionNone}, + 0x16E51: {lower: "\U00016E71", upper: "\U00016E51", condition: specialCasingConditionNone}, + 0x16E52: {lower: "\U00016E72", upper: "\U00016E52", condition: specialCasingConditionNone}, + 0x16E53: {lower: "\U00016E73", upper: "\U00016E53", condition: specialCasingConditionNone}, + 0x16E54: {lower: "\U00016E74", upper: "\U00016E54", condition: specialCasingConditionNone}, + 0x16E55: {lower: "\U00016E75", upper: "\U00016E55", condition: specialCasingConditionNone}, + 0x16E56: {lower: "\U00016E76", upper: "\U00016E56", condition: specialCasingConditionNone}, + 0x16E57: {lower: "\U00016E77", upper: "\U00016E57", condition: specialCasingConditionNone}, + 0x16E58: {lower: "\U00016E78", upper: "\U00016E58", condition: specialCasingConditionNone}, + 0x16E59: {lower: "\U00016E79", upper: "\U00016E59", condition: specialCasingConditionNone}, + 0x16E5A: {lower: "\U00016E7A", upper: "\U00016E5A", condition: specialCasingConditionNone}, + 0x16E5B: {lower: "\U00016E7B", upper: "\U00016E5B", condition: specialCasingConditionNone}, + 0x16E5C: {lower: "\U00016E7C", upper: "\U00016E5C", condition: specialCasingConditionNone}, + 0x16E5D: {lower: "\U00016E7D", upper: "\U00016E5D", condition: specialCasingConditionNone}, + 0x16E5E: {lower: "\U00016E7E", upper: "\U00016E5E", condition: specialCasingConditionNone}, + 0x16E5F: {lower: "\U00016E7F", upper: "\U00016E5F", condition: specialCasingConditionNone}, + 0x16E60: {lower: "\U00016E60", upper: "\U00016E40", condition: specialCasingConditionNone}, + 0x16E61: {lower: "\U00016E61", upper: "\U00016E41", condition: specialCasingConditionNone}, + 0x16E62: {lower: "\U00016E62", upper: "\U00016E42", condition: specialCasingConditionNone}, + 0x16E63: {lower: "\U00016E63", upper: "\U00016E43", condition: specialCasingConditionNone}, + 0x16E64: {lower: "\U00016E64", upper: "\U00016E44", condition: specialCasingConditionNone}, + 0x16E65: {lower: "\U00016E65", upper: "\U00016E45", condition: specialCasingConditionNone}, + 0x16E66: {lower: "\U00016E66", upper: "\U00016E46", condition: specialCasingConditionNone}, + 0x16E67: {lower: "\U00016E67", upper: "\U00016E47", condition: specialCasingConditionNone}, + 0x16E68: {lower: "\U00016E68", upper: "\U00016E48", condition: specialCasingConditionNone}, + 0x16E69: {lower: "\U00016E69", upper: "\U00016E49", condition: specialCasingConditionNone}, + 0x16E6A: {lower: "\U00016E6A", upper: "\U00016E4A", condition: specialCasingConditionNone}, + 0x16E6B: {lower: "\U00016E6B", upper: "\U00016E4B", condition: specialCasingConditionNone}, + 0x16E6C: {lower: "\U00016E6C", upper: "\U00016E4C", condition: specialCasingConditionNone}, + 0x16E6D: {lower: "\U00016E6D", upper: "\U00016E4D", condition: specialCasingConditionNone}, + 0x16E6E: {lower: "\U00016E6E", upper: "\U00016E4E", condition: specialCasingConditionNone}, + 0x16E6F: {lower: "\U00016E6F", upper: "\U00016E4F", condition: specialCasingConditionNone}, + 0x16E70: {lower: "\U00016E70", upper: "\U00016E50", condition: specialCasingConditionNone}, + 0x16E71: {lower: "\U00016E71", upper: "\U00016E51", condition: specialCasingConditionNone}, + 0x16E72: {lower: "\U00016E72", upper: "\U00016E52", condition: specialCasingConditionNone}, + 0x16E73: {lower: "\U00016E73", upper: "\U00016E53", condition: specialCasingConditionNone}, + 0x16E74: {lower: "\U00016E74", upper: "\U00016E54", condition: specialCasingConditionNone}, + 0x16E75: {lower: "\U00016E75", upper: "\U00016E55", condition: specialCasingConditionNone}, + 0x16E76: {lower: "\U00016E76", upper: "\U00016E56", condition: specialCasingConditionNone}, + 0x16E77: {lower: "\U00016E77", upper: "\U00016E57", condition: specialCasingConditionNone}, + 0x16E78: {lower: "\U00016E78", upper: "\U00016E58", condition: specialCasingConditionNone}, + 0x16E79: {lower: "\U00016E79", upper: "\U00016E59", condition: specialCasingConditionNone}, + 0x16E7A: {lower: "\U00016E7A", upper: "\U00016E5A", condition: specialCasingConditionNone}, + 0x16E7B: {lower: "\U00016E7B", upper: "\U00016E5B", condition: specialCasingConditionNone}, + 0x16E7C: {lower: "\U00016E7C", upper: "\U00016E5C", condition: specialCasingConditionNone}, + 0x16E7D: {lower: "\U00016E7D", upper: "\U00016E5D", condition: specialCasingConditionNone}, + 0x16E7E: {lower: "\U00016E7E", upper: "\U00016E5E", condition: specialCasingConditionNone}, + 0x16E7F: {lower: "\U00016E7F", upper: "\U00016E5F", condition: specialCasingConditionNone}, + 0x1E900: {lower: "\U0001E922", upper: "\U0001E900", condition: specialCasingConditionNone}, + 0x1E901: {lower: "\U0001E923", upper: "\U0001E901", condition: specialCasingConditionNone}, + 0x1E902: {lower: "\U0001E924", upper: "\U0001E902", condition: specialCasingConditionNone}, + 0x1E903: {lower: "\U0001E925", upper: "\U0001E903", condition: specialCasingConditionNone}, + 0x1E904: {lower: "\U0001E926", upper: "\U0001E904", condition: specialCasingConditionNone}, + 0x1E905: {lower: "\U0001E927", upper: "\U0001E905", condition: specialCasingConditionNone}, + 0x1E906: {lower: "\U0001E928", upper: "\U0001E906", condition: specialCasingConditionNone}, + 0x1E907: {lower: "\U0001E929", upper: "\U0001E907", condition: specialCasingConditionNone}, + 0x1E908: {lower: "\U0001E92A", upper: "\U0001E908", condition: specialCasingConditionNone}, + 0x1E909: {lower: "\U0001E92B", upper: "\U0001E909", condition: specialCasingConditionNone}, + 0x1E90A: {lower: "\U0001E92C", upper: "\U0001E90A", condition: specialCasingConditionNone}, + 0x1E90B: {lower: "\U0001E92D", upper: "\U0001E90B", condition: specialCasingConditionNone}, + 0x1E90C: {lower: "\U0001E92E", upper: "\U0001E90C", condition: specialCasingConditionNone}, + 0x1E90D: {lower: "\U0001E92F", upper: "\U0001E90D", condition: specialCasingConditionNone}, + 0x1E90E: {lower: "\U0001E930", upper: "\U0001E90E", condition: specialCasingConditionNone}, + 0x1E90F: {lower: "\U0001E931", upper: "\U0001E90F", condition: specialCasingConditionNone}, + 0x1E910: {lower: "\U0001E932", upper: "\U0001E910", condition: specialCasingConditionNone}, + 0x1E911: {lower: "\U0001E933", upper: "\U0001E911", condition: specialCasingConditionNone}, + 0x1E912: {lower: "\U0001E934", upper: "\U0001E912", condition: specialCasingConditionNone}, + 0x1E913: {lower: "\U0001E935", upper: "\U0001E913", condition: specialCasingConditionNone}, + 0x1E914: {lower: "\U0001E936", upper: "\U0001E914", condition: specialCasingConditionNone}, + 0x1E915: {lower: "\U0001E937", upper: "\U0001E915", condition: specialCasingConditionNone}, + 0x1E916: {lower: "\U0001E938", upper: "\U0001E916", condition: specialCasingConditionNone}, + 0x1E917: {lower: "\U0001E939", upper: "\U0001E917", condition: specialCasingConditionNone}, + 0x1E918: {lower: "\U0001E93A", upper: "\U0001E918", condition: specialCasingConditionNone}, + 0x1E919: {lower: "\U0001E93B", upper: "\U0001E919", condition: specialCasingConditionNone}, + 0x1E91A: {lower: "\U0001E93C", upper: "\U0001E91A", condition: specialCasingConditionNone}, + 0x1E91B: {lower: "\U0001E93D", upper: "\U0001E91B", condition: specialCasingConditionNone}, + 0x1E91C: {lower: "\U0001E93E", upper: "\U0001E91C", condition: specialCasingConditionNone}, + 0x1E91D: {lower: "\U0001E93F", upper: "\U0001E91D", condition: specialCasingConditionNone}, + 0x1E91E: {lower: "\U0001E940", upper: "\U0001E91E", condition: specialCasingConditionNone}, + 0x1E91F: {lower: "\U0001E941", upper: "\U0001E91F", condition: specialCasingConditionNone}, + 0x1E920: {lower: "\U0001E942", upper: "\U0001E920", condition: specialCasingConditionNone}, + 0x1E921: {lower: "\U0001E943", upper: "\U0001E921", condition: specialCasingConditionNone}, + 0x1E922: {lower: "\U0001E922", upper: "\U0001E900", condition: specialCasingConditionNone}, + 0x1E923: {lower: "\U0001E923", upper: "\U0001E901", condition: specialCasingConditionNone}, + 0x1E924: {lower: "\U0001E924", upper: "\U0001E902", condition: specialCasingConditionNone}, + 0x1E925: {lower: "\U0001E925", upper: "\U0001E903", condition: specialCasingConditionNone}, + 0x1E926: {lower: "\U0001E926", upper: "\U0001E904", condition: specialCasingConditionNone}, + 0x1E927: {lower: "\U0001E927", upper: "\U0001E905", condition: specialCasingConditionNone}, + 0x1E928: {lower: "\U0001E928", upper: "\U0001E906", condition: specialCasingConditionNone}, + 0x1E929: {lower: "\U0001E929", upper: "\U0001E907", condition: specialCasingConditionNone}, + 0x1E92A: {lower: "\U0001E92A", upper: "\U0001E908", condition: specialCasingConditionNone}, + 0x1E92B: {lower: "\U0001E92B", upper: "\U0001E909", condition: specialCasingConditionNone}, + 0x1E92C: {lower: "\U0001E92C", upper: "\U0001E90A", condition: specialCasingConditionNone}, + 0x1E92D: {lower: "\U0001E92D", upper: "\U0001E90B", condition: specialCasingConditionNone}, + 0x1E92E: {lower: "\U0001E92E", upper: "\U0001E90C", condition: specialCasingConditionNone}, + 0x1E92F: {lower: "\U0001E92F", upper: "\U0001E90D", condition: specialCasingConditionNone}, + 0x1E930: {lower: "\U0001E930", upper: "\U0001E90E", condition: specialCasingConditionNone}, + 0x1E931: {lower: "\U0001E931", upper: "\U0001E90F", condition: specialCasingConditionNone}, + 0x1E932: {lower: "\U0001E932", upper: "\U0001E910", condition: specialCasingConditionNone}, + 0x1E933: {lower: "\U0001E933", upper: "\U0001E911", condition: specialCasingConditionNone}, + 0x1E934: {lower: "\U0001E934", upper: "\U0001E912", condition: specialCasingConditionNone}, + 0x1E935: {lower: "\U0001E935", upper: "\U0001E913", condition: specialCasingConditionNone}, + 0x1E936: {lower: "\U0001E936", upper: "\U0001E914", condition: specialCasingConditionNone}, + 0x1E937: {lower: "\U0001E937", upper: "\U0001E915", condition: specialCasingConditionNone}, + 0x1E938: {lower: "\U0001E938", upper: "\U0001E916", condition: specialCasingConditionNone}, + 0x1E939: {lower: "\U0001E939", upper: "\U0001E917", condition: specialCasingConditionNone}, + 0x1E93A: {lower: "\U0001E93A", upper: "\U0001E918", condition: specialCasingConditionNone}, + 0x1E93B: {lower: "\U0001E93B", upper: "\U0001E919", condition: specialCasingConditionNone}, + 0x1E93C: {lower: "\U0001E93C", upper: "\U0001E91A", condition: specialCasingConditionNone}, + 0x1E93D: {lower: "\U0001E93D", upper: "\U0001E91B", condition: specialCasingConditionNone}, + 0x1E93E: {lower: "\U0001E93E", upper: "\U0001E91C", condition: specialCasingConditionNone}, + 0x1E93F: {lower: "\U0001E93F", upper: "\U0001E91D", condition: specialCasingConditionNone}, + 0x1E940: {lower: "\U0001E940", upper: "\U0001E91E", condition: specialCasingConditionNone}, + 0x1E941: {lower: "\U0001E941", upper: "\U0001E91F", condition: specialCasingConditionNone}, + 0x1E942: {lower: "\U0001E942", upper: "\U0001E920", condition: specialCasingConditionNone}, + 0x1E943: {lower: "\U0001E943", upper: "\U0001E921", condition: specialCasingConditionNone}, } var unicodeCasedRanges = &unicode.RangeTable{ diff --git a/internal/stringutil/js_case_test.go b/internal/stringutil/js_case_test.go index a5ed3d2ef4d..e5c5402390f 100644 --- a/internal/stringutil/js_case_test.go +++ b/internal/stringutil/js_case_test.go @@ -15,7 +15,9 @@ func TestJSCasing(t *testing.T) { {name: "lowercase dotted i", got: ToLowerJS("İSPANYOL"), want: "i̇spanyol"}, {name: "lowercase lone sigma", got: ToLowerJS("Σ"), want: "σ"}, {name: "lowercase final sigma", got: ToLowerJS("ΟΣ"), want: "ος"}, + {name: "lowercase non-sigma greek", got: ToLowerJS("Ω"), want: "ω"}, {name: "uppercase sharp s", got: ToUpperJS("ßfoo"), want: "SSFOO"}, + {name: "uppercase non-ascii simple mapping", got: ToUpperJS("ω"), want: "Ω"}, {name: "uppercase ligature", got: ToUpperJS("fioo"), want: "FIOO"}, {name: "capitalize-style uppercase", got: ToUpperJS("ß") + "foo", want: "SSfoo"}, {name: "uncapitalize-style lowercase", got: ToLowerJS("İ") + "foo", want: "i̇foo"}, From f315ef97b58466259379254b179572671d586d03 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 15 Jun 2026 09:32:41 -0700 Subject: [PATCH 25/49] Ensure node_modules buckets are removed from auto-import registry once they no longer exist (#4303) --- internal/ls/autoimport/registry.go | 31 ++---- internal/ls/autoimport/registry_test.go | 121 ++++++++++++++++++++++++ internal/ls/autoimport/util.go | 10 +- internal/project/snapshotfs.go | 13 ++- internal/project/snapshotfs_test.go | 97 +++++++++++++++++++ 5 files changed, 243 insertions(+), 29 deletions(-) diff --git a/internal/ls/autoimport/registry.go b/internal/ls/autoimport/registry.go index e3480292230..6c6468095c6 100644 --- a/internal/ls/autoimport/registry.go +++ b/internal/ls/autoimport/registry.go @@ -633,11 +633,6 @@ func (b *registryBuilder) updateBucketAndDirectoryExistence(change RegistryChang }) } - if packageJsonChanged { - // package.json changes affecting node_modules are handled by comparing dependencies in updateIndexes - return - } - if hasNodeModules { if _, ok := b.nodeModules.Get(dirPath); !ok { b.nodeModules.Add(dirPath, newRegistryBucket()) @@ -648,12 +643,15 @@ func (b *registryBuilder) updateBucketAndDirectoryExistence(change RegistryChang } var addedNodeModulesDirs, removedNodeModulesDirs []tspath.Path + packageJsonChanged := func(dirName string) bool { + uri := lsconv.FileNameToDocumentURI(tspath.CombinePaths(dirName, "package.json")) + return change.Changed.Has(uri) || change.Deleted.Has(uri) || change.Created.Has(uri) + } core.DiffMapsFunc( b.base.directories, neededDirectories, func(dir *directory, dirName string) bool { - packageJsonUri := lsconv.FileNameToDocumentURI(tspath.CombinePaths(dirName, "package.json")) - return !change.Changed.Has(packageJsonUri) && !change.Deleted.Has(packageJsonUri) && !change.Created.Has(packageJsonUri) + return !packageJsonChanged(dirName) && dir.hasNodeModules == b.host.FS().DirectoryExists(tspath.CombinePaths(dirName, "node_modules")) }, func(dirPath tspath.Path, dirName string) { // Need and don't have @@ -679,13 +677,13 @@ func (b *registryBuilder) updateBucketAndDirectoryExistence(change RegistryChang } }, func(dirPath tspath.Path, dir *directory, dirName string) { - // package.json may have changed - updateDirectory(dirPath, dirName, true) + updateDirectory(dirPath, dirName, packageJsonChanged(dirName)) if logger != nil { logger.Logf("Changed directory: %s", dirPath) } }, ) + if logger != nil { for _, dirPath := range addedNodeModulesDirs { logger.Logf("Added node_modules bucket: %s", dirPath) @@ -797,7 +795,6 @@ func (b *registryBuilder) updateIndexes(ctx context.Context, change RegistryChan packageNames *collections.Set[string] directoryPackageNames *collections.Set[string] discovered []*discoveredPackage - discoverErr error } projectPath, _ := b.host.GetDefaultProject(change.RequestedFile) @@ -890,12 +887,7 @@ func (b *registryBuilder) updateIndexes(ctx context.Context, change RegistryChan if task.isUpdate { task.packageNames = task.dirtyPackages } else { - var err error - task.directoryPackageNames, err = getPackageNamesInNodeModules(tspath.CombinePaths(task.dirName, "node_modules"), b.host.FS()) - if err != nil { - task.discoverErr = err - return - } + task.directoryPackageNames = getPackageNamesInNodeModules(tspath.CombinePaths(task.dirName, "node_modules"), b.host.FS()) task.packageNames = core.Coalesce(task.dependencyNames, task.directoryPackageNames) } task.discovered = b.discoverBucketPackages(task.packageNames, task.dirName, task.dirPath) @@ -918,9 +910,6 @@ func (b *registryBuilder) updateIndexes(ctx context.Context, change RegistryChan // filter to only those whose main extraction failed, then deduplicate by typesRealpath. var typesFallbackCandidates []*discoveredPackage for _, task := range nodeModulesTasks { - if task.discoverErr != nil { - continue - } for _, pkg := range task.discovered { if pkg.realpath != "" { if !seen[pkg.realpath] { @@ -1007,10 +996,6 @@ func (b *registryBuilder) updateIndexes(ctx context.Context, change RegistryChan for _, task := range nodeModulesTasks { br := &bucketBuildResult{entry: task.entry} allResults = append(allResults, br) - if task.discoverErr != nil { - br.err = task.discoverErr - continue - } wg.Go(func() { if task.isUpdate { b.updateNodeModulesBucket( diff --git a/internal/ls/autoimport/registry_test.go b/internal/ls/autoimport/registry_test.go index c1dc601f9bb..b9596f0f9cd 100644 --- a/internal/ls/autoimport/registry_test.go +++ b/internal/ls/autoimport/registry_test.go @@ -228,6 +228,127 @@ export const bar = 2;`, assert.Equal(t, len(stats.ProjectBuckets), 1) }) + t.Run("deleting node_modules leaves the registry prepared for importing", func(t *testing.T) { + t.Parallel() + fixture := autoimporttestutil.SetupLifecycleSession(t, lifecycleProjectRoot, 1) + session := fixture.Session() + sessionUtils := fixture.Utils() + project := fixture.SingleProject() + mainFile := project.File(0) + ctx := context.Background() + + preferences := lsutil.NewDefaultUserPreferences() + preferences.IncludeCompletionsForModuleExports = core.TSTrue + preferences.IncludeCompletionsForImportStatements = core.TSTrue + + // Build auto-imports once so both buckets are clean and prepared. + session.DidOpenFile(ctx, mainFile.URI(), 1, mainFile.Content(), lsproto.LanguageKindTypeScript) + _, err := session.GetCurrentLanguageServiceWithAutoImports(ctx, mainFile.URI()) + assert.NilError(t, err) + + snapshot := session.Snapshot() + defaultProject := snapshot.GetDefaultProject(mainFile.URI()) + assert.Assert(t, defaultProject != nil) + projectPath := defaultProject.ConfigFilePath() + assert.Assert(t, snapshot.AutoImportRegistry().IsPreparedForImportingFile(mainFile.FileName(), projectPath, preferences)) + assert.Equal(t, len(autoImportStats(t, session).NodeModulesBuckets), 1) + + // Simulate the user deleting node_modules: remove the directory from disk + // and notify the session of the deletion, which marks the node_modules + // bucket dirty. + nodeModulesDir := tspath.CombinePaths(project.Root(), "node_modules") + assert.NilError(t, sessionUtils.FS().Remove(nodeModulesDir)) + session.DidChangeWatchedFiles(ctx, []*lsproto.FileEvent{ + {Type: lsproto.FileChangeTypeDeleted, Uri: lsconv.FileNameToDocumentURI(nodeModulesDir)}, + }) + + // Re-preparing auto-imports must succeed and leave the registry prepared. + // Before the fix, the node_modules bucket's rebuild bailed out with + // vfs.ErrNotExist, leaving the stale dirty bucket in place so the registry + // reported "needs rebuild" forever (which crashed the request handler). + _, err = session.GetCurrentLanguageServiceWithAutoImports(ctx, mainFile.URI()) + assert.NilError(t, err) + + snapshot = session.Snapshot() + assert.Assert(t, snapshot.AutoImportRegistry().IsPreparedForImportingFile(mainFile.FileName(), projectPath, preferences), + "registry should be prepared after node_modules is deleted") + // The node_modules bucket should be removed entirely, not left behind as an + // empty bucket. + assert.Equal(t, len(autoImportStats(t, session).NodeModulesBuckets), 0) + }) + + t.Run("deleting node_modules alongside a package.json change removes the bucket", func(t *testing.T) { + t.Parallel() + fixture := autoimporttestutil.SetupLifecycleSession(t, lifecycleProjectRoot, 1) + session := fixture.Session() + sessionUtils := fixture.Utils() + project := fixture.SingleProject() + mainFile := project.File(0) + packageJSON := project.PackageJSONFile() + ctx := context.Background() + + preferences := lsutil.NewDefaultUserPreferences() + preferences.IncludeCompletionsForModuleExports = core.TSTrue + preferences.IncludeCompletionsForImportStatements = core.TSTrue + + session.DidOpenFile(ctx, mainFile.URI(), 1, mainFile.Content(), lsproto.LanguageKindTypeScript) + _, err := session.GetCurrentLanguageServiceWithAutoImports(ctx, mainFile.URI()) + assert.NilError(t, err) + + snapshot := session.Snapshot() + defaultProject := snapshot.GetDefaultProject(mainFile.URI()) + assert.Assert(t, defaultProject != nil) + projectPath := defaultProject.ConfigFilePath() + assert.Equal(t, len(autoImportStats(t, session).NodeModulesBuckets), 1) + + // In a single changeset, edit package.json AND delete node_modules. The + // package.json change must not prevent the now-missing node_modules bucket + // from being removed. + assert.NilError(t, sessionUtils.FS().WriteFile(packageJSON.FileName(), `{"name": "app", "dependencies": {}}`)) + nodeModulesDir := tspath.CombinePaths(project.Root(), "node_modules") + assert.NilError(t, sessionUtils.FS().Remove(nodeModulesDir)) + session.DidChangeWatchedFiles(ctx, []*lsproto.FileEvent{ + {Type: lsproto.FileChangeTypeChanged, Uri: packageJSON.URI()}, + {Type: lsproto.FileChangeTypeDeleted, Uri: lsconv.FileNameToDocumentURI(nodeModulesDir)}, + }) + + _, err = session.GetCurrentLanguageServiceWithAutoImports(ctx, mainFile.URI()) + assert.NilError(t, err) + + snapshot = session.Snapshot() + assert.Assert(t, snapshot.AutoImportRegistry().IsPreparedForImportingFile(mainFile.FileName(), projectPath, preferences)) + assert.Equal(t, len(autoImportStats(t, session).NodeModulesBuckets), 0) + }) + + t.Run("deleting a package directory inside node_modules invalidates the bucket", func(t *testing.T) { + t.Parallel() + fixture := autoimporttestutil.SetupLifecycleSession(t, lifecycleProjectRoot, 1) + session := fixture.Session() + sessionUtils := fixture.Utils() + project := fixture.SingleProject() + mainFile := project.File(0) + nodePackage := project.NodeModules()[0] + ctx := context.Background() + + session.DidOpenFile(ctx, mainFile.URI(), 1, mainFile.Content(), lsproto.LanguageKindTypeScript) + _, err := session.GetCurrentLanguageServiceWithAutoImports(ctx, mainFile.URI()) + assert.NilError(t, err) + assert.Assert(t, singleBucket(t, autoImportStats(t, session).NodeModulesBuckets).ExportCount > 0) + + // Delete just the package directory, leaving node_modules itself in place. The + // package's files are read transiently by the registry, so they are never tracked + // in diskFiles/diskDirectories; only the directory deletion event is reported, and + // it must survive snapshotfs filtering to invalidate the bucket. + assert.NilError(t, sessionUtils.FS().Remove(nodePackage.Directory)) + session.DidChangeWatchedFiles(ctx, []*lsproto.FileEvent{ + {Type: lsproto.FileChangeTypeDeleted, Uri: lsconv.FileNameToDocumentURI(nodePackage.Directory)}, + }) + + _, err = session.GetCurrentLanguageServiceWithAutoImports(ctx, mainFile.URI()) + assert.NilError(t, err) + assert.Equal(t, singleBucket(t, autoImportStats(t, session).NodeModulesBuckets).ExportCount, 0) + }) + t.Run("node_modules bucket dependency selection changes with open files", func(t *testing.T) { t.Parallel() monorepoRoot := "/home/src/monorepo" diff --git a/internal/ls/autoimport/util.go b/internal/ls/autoimport/util.go index d8300a3c064..b915890ad2b 100644 --- a/internal/ls/autoimport/util.go +++ b/internal/ls/autoimport/util.go @@ -85,14 +85,14 @@ func wordIndices(s string) []int { return indices } -func getPackageNamesInNodeModules(nodeModulesDir string, fs vfs.FS) (*collections.Set[string], error) { +func getPackageNamesInNodeModules(nodeModulesDir string, fs vfs.FS) *collections.Set[string] { packageNames := &collections.Set[string]{} if tspath.GetBaseFileName(nodeModulesDir) != "node_modules" { panic("nodeModulesDir is not a node_modules directory") } - if !fs.DirectoryExists(nodeModulesDir) { - return nil, vfs.ErrNotExist - } + // A missing node_modules directory yields no entries (GetAccessibleEntries returns + // empty), so there's no need to check existence first: a deleted node_modules is + // handled upstream in updateBucketAndDirectoryExistence, which drops the bucket. entries := fs.GetAccessibleEntries(nodeModulesDir) for _, baseName := range entries.Directories { if baseName[0] == '.' { @@ -112,7 +112,7 @@ func getPackageNamesInNodeModules(nodeModulesDir string, fs vfs.FS) (*collection } packageNames.Add(baseName) } - return packageNames, nil + return packageNames } func getDefaultLikeExportNameFromDeclaration(symbol *ast.Symbol) string { diff --git a/internal/project/snapshotfs.go b/internal/project/snapshotfs.go index 4963d8a6f07..b8629b15d29 100644 --- a/internal/project/snapshotfs.go +++ b/internal/project/snapshotfs.go @@ -554,7 +554,10 @@ func (s *snapshotFSBuilder) expandAndFilterWatchEvents(change FileChangeSummary) path := s.toPath(uri.FileName()) if _, ok := s.diskDirectories.Get(path); ok { s.collectFilesRecursive(path, &filteredDeleted) - } else if s.isRelevantFileName(uri) { + } else if s.isRelevantFileName(uri) || isNodeModulesPath(path) { + // node_modules deletions must always be preserved for auto-import registry change handlers. + // They won't be in diskDirectories since the registry doesn't use the snapshotFSBuilder for + // its file system, since we don't want to retain files read there. filteredDeleted.Add(uri) } } @@ -578,6 +581,14 @@ func (s *snapshotFSBuilder) expandAndFilterWatchEvents(change FileChangeSummary) return change } +// isNodeModulesPath reports whether path is a node_modules directory itself or +// lives inside one. Used to preserve node_modules watch deletions, whose package +// files are read transiently and therefore never tracked in diskDirectories. +func isNodeModulesPath(path tspath.Path) bool { + s := string(path) + return strings.HasSuffix(s, "/node_modules") || strings.Contains(s, "/node_modules/") +} + // collectFilesRecursive recursively collects all cached file URIs under the // given directory path using the diskDirectories and diskFiles maps. func (s *snapshotFSBuilder) collectFilesRecursive(dirPath tspath.Path, files *collections.Set[lsproto.DocumentUri]) { diff --git a/internal/project/snapshotfs_test.go b/internal/project/snapshotfs_test.go index 7d48814d8d0..4597b191e28 100644 --- a/internal/project/snapshotfs_test.go +++ b/internal/project/snapshotfs_test.go @@ -7,6 +7,7 @@ import ( "github.com/microsoft/typescript-go/internal/lsp/lsproto" "github.com/microsoft/typescript-go/internal/project/dirty" "github.com/microsoft/typescript-go/internal/tspath" + "github.com/microsoft/typescript-go/internal/vfs" "github.com/microsoft/typescript-go/internal/vfs/vfstest" "gotest.tools/v3/assert" ) @@ -1383,3 +1384,99 @@ func TestRealpathAliasLifecycle(t *testing.T) { "snapshot1 must not contain alias added in snapshot2") }) } + +func TestExpandAndFilterWatchEvents(t *testing.T) { + t.Parallel() + + toPath := func(fileName string) tspath.Path { + return tspath.Path(fileName) + } + + newBuilder := func(testFS vfs.FS) *snapshotFSBuilder { + return newSnapshotFSBuilder( + testFS, + make(map[tspath.Path]*Overlay), + make(map[tspath.Path]*Overlay), + make(map[tspath.Path]*diskFile), + make(map[tspath.Path]dirty.CloneableMap[tspath.Path, string]), + nil, + lsproto.PositionEncodingKindUTF16, + toPath, + ) + } + + t.Run("preserves node_modules directory deletion even when untracked", func(t *testing.T) { + t.Parallel() + // node_modules package files are read transiently and never tracked in + // diskDirectories, so a node_modules directory deletion can neither be + // expanded nor matched by extension. It must still be preserved. + builder := newBuilder(vfstest.FromMap(map[string]string{ + "/project/index.ts": "export const x = 1;", + }, false)) + + change := FileChangeSummary{} + change.Deleted.Add("file:///project/node_modules") + + expanded := builder.expandAndFilterWatchEvents(change) + assert.Assert(t, expanded.Deleted.Has("file:///project/node_modules"), + "bare node_modules directory deletion should be preserved") + }) + + t.Run("preserves deletion of a package directory inside node_modules", func(t *testing.T) { + t.Parallel() + builder := newBuilder(vfstest.FromMap(map[string]string{ + "/project/index.ts": "export const x = 1;", + }, false)) + + change := FileChangeSummary{} + change.Deleted.Add("file:///project/node_modules/@scope/pkg") + + expanded := builder.expandAndFilterWatchEvents(change) + assert.Assert(t, expanded.Deleted.Has("file:///project/node_modules/@scope/pkg"), + "package directory deletion inside node_modules should be preserved") + }) + + t.Run("drops irrelevant untracked deletion outside node_modules", func(t *testing.T) { + t.Parallel() + builder := newBuilder(vfstest.FromMap(map[string]string{ + "/project/index.ts": "export const x = 1;", + }, false)) + + change := FileChangeSummary{} + change.Deleted.Add("file:///project/build") + + expanded := builder.expandAndFilterWatchEvents(change) + assert.Equal(t, expanded.Deleted.Len(), 0, + "untracked non-node_modules directory deletion should be dropped") + }) + + t.Run("expands tracked directory deletion into file deletions", func(t *testing.T) { + t.Parallel() + existingDiskFiles := map[tspath.Path]*diskFile{ + tspath.Path("/src/foo.ts"): newDiskFile("/src/foo.ts", "const foo = 1;"), + } + existingDirs := map[tspath.Path]dirty.CloneableMap[tspath.Path, string]{ + tspath.Path("/"): {tspath.Path("/src"): "src"}, + tspath.Path("/src"): {tspath.Path("/src/foo.ts"): "foo.ts"}, + } + builder := newSnapshotFSBuilder( + vfstest.FromMap(map[string]string{"/src/foo.ts": "const foo = 1;"}, false), + make(map[tspath.Path]*Overlay), + make(map[tspath.Path]*Overlay), + existingDiskFiles, + existingDirs, + nil, + lsproto.PositionEncodingKindUTF16, + toPath, + ) + + change := FileChangeSummary{} + change.Deleted.Add("file:///src") + + expanded := builder.expandAndFilterWatchEvents(change) + assert.Assert(t, expanded.Deleted.Has("file:///src/foo.ts"), + "tracked directory deletion should expand to contained file deletions") + assert.Assert(t, !expanded.Deleted.Has("file:///src"), + "the directory URI itself should be replaced by its files") + }) +} From b8e44908979c85b2c5a31eff6aa5bac38f4f2f4b Mon Sep 17 00:00:00 2001 From: John Favret <64748847+johnfav03@users.noreply.github.com> Date: Mon, 15 Jun 2026 12:19:58 -0500 Subject: [PATCH 26/49] Fixed CJS class expression exports emit anonymous constructor types (#3850) --- internal/checker/checker.go | 1 + internal/checker/emitresolver.go | 18 + internal/checker/symbolaccessibility.go | 36 ++ internal/printer/emitresolver.go | 1 + internal/transformers/declarations/tracker.go | 12 + .../transformers/declarations/transform.go | 315 ++++++++++++++++-- .../cjsExportClassExpressionNameConflict.js | 40 +++ ...sExportClassExpressionNameConflict.symbols | 25 ++ ...cjsExportClassExpressionNameConflict.types | 33 ++ ...sExportNamedClassExpressionNoAssignment.js | 31 ++ ...rtNamedClassExpressionNoAssignment.symbols | 17 + ...portNamedClassExpressionNoAssignment.types | 21 ++ .../commonJSImportExportedClassExpression.js | 24 +- ...tAssignedClassExpression(target=es2015).js | 28 +- ...signedClassExpression(target=es2015).types | 2 +- ...dClassExpression(target=es2015).types.diff | 11 + ...ClassExpressionAnonymous(target=es2015).js | 26 +- ...pressionAnonymousWithSub(target=es2015).js | 19 +- ...ClassExpressionShadowing(target=es2015).js | 16 +- ...ssExpressionShadowing(target=es2015).types | 2 +- ...ressionShadowing(target=es2015).types.diff | 11 +- ...ExportAssignedVisibility(target=es2015).js | 36 +- ...ortAssignedVisibility(target=es2015).types | 2 +- ...signedVisibility(target=es2015).types.diff | 11 + .../conformance/jsdocImportType2.types | 2 +- .../conformance/jsdocImportType2.types.diff | 11 + .../conformance/moduleExportAlias4.symbols | 2 +- .../moduleExportAlias4.symbols.diff | 3 +- .../conformance/moduleExportAlias4.types | 2 +- .../conformance/moduleExportAlias4.types.diff | 8 +- ...monJSImportExportedClassExpression.js.diff | 11 + ...gnedClassExpression(target=es2015).js.diff | 13 + ...ExpressionAnonymous(target=es2015).js.diff | 17 + ...ionAnonymousWithSub(target=es2015).js.diff | 27 +- ...ExpressionShadowing(target=es2015).js.diff | 24 +- ...tAssignedVisibility(target=es2015).js.diff | 17 + ...monJSImportExportedClassExpression.js.diff | 32 -- ...gnedClassExpression(target=es2015).js.diff | 34 -- ...ExpressionAnonymous(target=es2015).js.diff | 34 -- ...tAssignedVisibility(target=es2015).js.diff | 50 --- testdata/submoduleAccepted.txt | 9 + testdata/submoduleTriaged.txt | 9 - .../cjsExportClassExpressionNameConflict.ts | 9 + ...sExportNamedClassExpressionNoAssignment.ts | 11 + 44 files changed, 730 insertions(+), 333 deletions(-) create mode 100644 testdata/baselines/reference/compiler/cjsExportClassExpressionNameConflict.js create mode 100644 testdata/baselines/reference/compiler/cjsExportClassExpressionNameConflict.symbols create mode 100644 testdata/baselines/reference/compiler/cjsExportClassExpressionNameConflict.types create mode 100644 testdata/baselines/reference/compiler/cjsExportNamedClassExpressionNoAssignment.js create mode 100644 testdata/baselines/reference/compiler/cjsExportNamedClassExpressionNoAssignment.symbols create mode 100644 testdata/baselines/reference/compiler/cjsExportNamedClassExpressionNoAssignment.types create mode 100644 testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).types.diff create mode 100644 testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).types.diff create mode 100644 testdata/baselines/reference/submodule/conformance/jsdocImportType2.types.diff create mode 100644 testdata/baselines/reference/submoduleAccepted/conformance/commonJSImportExportedClassExpression.js.diff create mode 100644 testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).js.diff create mode 100644 testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js.diff rename testdata/baselines/reference/{submoduleTriaged => submoduleAccepted}/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js.diff (59%) rename testdata/baselines/reference/{submoduleTriaged => submoduleAccepted}/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js.diff (62%) create mode 100644 testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js.diff delete mode 100644 testdata/baselines/reference/submoduleTriaged/conformance/commonJSImportExportedClassExpression.js.diff delete mode 100644 testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).js.diff delete mode 100644 testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js.diff delete mode 100644 testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js.diff create mode 100644 testdata/tests/cases/compiler/cjsExportClassExpressionNameConflict.ts create mode 100644 testdata/tests/cases/compiler/cjsExportNamedClassExpressionNoAssignment.ts diff --git a/internal/checker/checker.go b/internal/checker/checker.go index e92f029d5dd..95c650ff352 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -648,6 +648,7 @@ type Checker struct { moduleSymbols map[*ast.Node]*ast.Symbol globalThisSymbol *ast.Symbol symbolTableAliasCache map[symbolTableID][]*ast.Symbol + classExpressionNameTables map[ast.NodeId]ast.SymbolTable resolveName func(location *ast.Node, name string, meaning ast.SymbolFlags, nameNotFoundMessage *diagnostics.Message, isUse bool, excludeGlobals bool) *ast.Symbol resolveNameForSymbolSuggestion func(location *ast.Node, name string, meaning ast.SymbolFlags, nameNotFoundMessage *diagnostics.Message, isUse bool, excludeGlobals bool) *ast.Symbol tupleTypes map[CacheHashKey]*Type diff --git a/internal/checker/emitresolver.go b/internal/checker/emitresolver.go index f99b11061f2..572ac70dc2c 100644 --- a/internal/checker/emitresolver.go +++ b/internal/checker/emitresolver.go @@ -896,6 +896,24 @@ func (r *EmitResolver) GetReferencedValueDeclarations(node *ast.IdentifierNode) return r.getReferenceResolver().GetReferencedValueDeclarations(node) } +// IsNameResolvedToDeclaration checks whether `name` resolved at `location` +// resolves to a symbol that includes `declaration` among its declarations. +// When `declaration` is nil, checks whether the name resolves to any symbol. +// This is used by the declaration emitter to check for naming conflicts at file scope. +func (r *EmitResolver) IsNameResolvedToDeclaration(location *ast.Node, name string, declaration *ast.Node) bool { + r.checkerMu.Lock() + defer r.checkerMu.Unlock() + + symbol := r.checker.resolveName(location, name, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace, nil /*nameNotFoundMessage*/, false /*isUse*/, false /*excludeGlobals*/) + if symbol == nil { + return false + } + if declaration == nil { + return true + } + return slices.Contains(symbol.Declarations, declaration) +} + func (r *EmitResolver) GetElementAccessExpressionName(expression *ast.ElementAccessExpression) string { if !ast.IsParseTreeNode(expression.AsNode()) { return "" diff --git a/internal/checker/symbolaccessibility.go b/internal/checker/symbolaccessibility.go index 5505ae840d7..d17ef471908 100644 --- a/internal/checker/symbolaccessibility.go +++ b/internal/checker/symbolaccessibility.go @@ -785,12 +785,48 @@ func (c *Checker) someSymbolTableInScope( if table != nil && callback(table, symbolTableIDFromMembers(sym), false, false, location) { return true } + // Class expression names (e.g., `B` in `class B {}`) are not stored in any + // scope table — the binder uses bindAnonymousDeclaration. Expose the name + // binding here so getAccessibleSymbolChain can resolve self-references. + // This mirrors the special casing of class expression names in + // (*NameResolver).Resolve; if class names are ever bound differently + // (e.g., via class-local type aliases), both sites should be updated. + if ast.IsClassExpression(location) && location.AsClassExpression().Name() != nil { + nameTable := c.getClassExpressionNameTable(location) + if nameTable != nil && callback(nameTable, symbolTableIDFromLocals(location.AsNode()), false, true, location) { + return true + } + } } } return callback(c.globals, symbolTableIDFromGlobals(), false, true, nil) } +// getClassExpressionNameTable returns a cached symbol table containing the class +// expression's name binding. Class expression names are bound via +// bindAnonymousDeclaration and aren't stored in any container's locals, so this +// synthesized table lets someSymbolTableInScope expose them during accessibility checks. +func (c *Checker) getClassExpressionNameTable(location *ast.Node) ast.SymbolTable { + nodeId := ast.GetNodeId(location) + if c.classExpressionNameTables != nil { + if table, ok := c.classExpressionNameTables[nodeId]; ok { + return table + } + } + classSymbol := c.getSymbolOfDeclaration(location) + nameText := location.AsClassExpression().Name().Text() + if len(nameText) == 0 || classSymbol == nil { + return nil + } + table := ast.SymbolTable{nameText: classSymbol} + if c.classExpressionNameTables == nil { + c.classExpressionNameTables = make(map[ast.NodeId]ast.SymbolTable) + } + c.classExpressionNameTables[nodeId] = table + return table +} + /** * Check if the given symbol in given enclosing declaration is accessible and mark all associated alias to be visible if requested * diff --git a/internal/printer/emitresolver.go b/internal/printer/emitresolver.go index 285c424bb89..60c2aead91c 100644 --- a/internal/printer/emitresolver.go +++ b/internal/printer/emitresolver.go @@ -104,6 +104,7 @@ type EmitResolver interface { IsLiteralConstDeclaration(node *ast.Node) bool RequiresAddingImplicitUndefined(node *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool IsDeclarationVisible(node *ast.Node) bool + IsNameResolvedToDeclaration(location *ast.Node, name string, declaration *ast.Node) bool IsImportRequiredByAugmentation(decl *ast.ImportDeclaration) bool IsDefinitelyReferenceToGlobalSymbolObject(node *ast.Node) bool IsImplementationOfOverload(node *ast.SignatureDeclaration) bool diff --git a/internal/transformers/declarations/tracker.go b/internal/transformers/declarations/tracker.go index 2dccce90e1a..db8955f2142 100644 --- a/internal/transformers/declarations/tracker.go +++ b/internal/transformers/declarations/tracker.go @@ -15,6 +15,11 @@ type SymbolTrackerImpl struct { host DeclarationEmitHost fallbackStack []*ast.Node + // For detecting class expression self-references during member serialization. + // When set, TrackSymbol will record usage without reporting accessibility errors. + watchedClassSymbol *ast.Symbol + classSymbolTracked bool + getIsolatedDeclarationError func(node *ast.Node) *ast.Diagnostic } @@ -157,6 +162,13 @@ func (s *SymbolTrackerImpl) TrackSymbol(symbol *ast.Symbol, enclosingDeclaration if symbol.Flags&ast.SymbolFlagsTypeParameter != 0 { return false } + // When watching for a class expression symbol, record its usage without + // reporting accessibility errors — the caller will handle visibility by + // wrapping the class in a namespace. + if s.watchedClassSymbol != nil && symbol == s.watchedClassSymbol { + s.classSymbolTracked = true + return false + } issuedDiagnostic := s.handleSymbolAccessibilityError(s.resolver.IsSymbolAccessible(symbol, enclosingDeclaration, meaning /*shouldComputeAliasToMarkVisible*/, true)) return issuedDiagnostic } diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go index 83feba1e184..8eca614a5bf 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -72,6 +72,8 @@ type DeclarationTransformer struct { expressionVisitor *ast.NodeVisitor exportStrippingVisitor *ast.NodeVisitor thisPropertyVisitor *ast.NodeVisitor + cjsExportAssignmentName *ast.Node // tracks the name node used for `export =` in CJS module.exports assignments + inClassExpressionDeclaration bool // true when serializing members of a class expression kept as a class declaration } // TODO: Convert to transformers.TransformerFactory signature to allow more automatic composition with other transforms @@ -252,6 +254,7 @@ func throwDiagnostic(result printer.SymbolAccessibilityResult) *SymbolAccessibil } func (tx *DeclarationTransformer) visitSourceFile(node *ast.SourceFile) *ast.Node { + tx.cjsExportAssignmentName = nil if node.IsDeclarationFile { return node.AsNode() } @@ -274,11 +277,46 @@ func (tx *DeclarationTransformer) visitSourceFile(node *ast.SourceFile) *ast.Nod tx.state.currentSourceFile = node tx.collectFileReferences(node) tx.resolver.PrecalculateDeclarationEmitVisibility(node) + tx.prescanCJSExportAssignment(node) updated := tx.transformSourceFile(node) tx.state.currentSourceFile = nil return updated } +// prescanCJSExportAssignment scans the source file for a `module.exports = class X {}` +// statement and pre-creates the cjsExportAssignmentName so that all CJS property exports +// (both before and after the assignment in source order) can reference it. +func (tx *DeclarationTransformer) prescanCJSExportAssignment(node *ast.SourceFile) { + if !ast.IsExternalOrCommonJSModule(node) || node.CommonJSModuleIndicator == nil || !ast.IsInJSFile(node.AsNode()) { + return + } + for _, stmt := range node.Statements.Nodes { + if stmt.Kind != ast.KindExpressionStatement { + continue + } + expr := stmt.Expression() + if expr == nil || ast.GetAssignmentDeclarationKind(expr) != ast.JSDeclarationKindModuleExports { + continue + } + rhs := unwrapParenthesizedExpression(expr.AsBinaryExpression().Right) + if !ast.IsClassExpression(rhs) { + continue + } + ce := rhs.AsClassExpression() + if ce.Name() != nil && len(ce.Name().Text()) > 0 { + nameText := ce.Name().Text() + if tx.resolver.IsNameResolvedToDeclaration(node.AsNode(), nameText, nil) { + tx.cjsExportAssignmentName = tx.Factory().NewUniqueNameEx(nameText, printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) + } else { + tx.cjsExportAssignmentName = tx.Factory().NewIdentifier(nameText) + } + } else { + tx.cjsExportAssignmentName = tx.Factory().NewUniqueNameEx("_default", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) + } + return + } +} + func (tx *DeclarationTransformer) collectFileReferences(sourceFile *ast.SourceFile) { tx.rawReferencedFiles = append(tx.rawReferencedFiles, core.Map(sourceFile.ReferencedFiles, func(ref *ast.FileReference) ReferencedFilePair { return ReferencedFilePair{file: sourceFile, ref: ref} })...) tx.rawTypeReferenceDirectives = append(tx.rawTypeReferenceDirectives, sourceFile.TypeReferenceDirectives...) @@ -1106,6 +1144,37 @@ func (tx *DeclarationTransformer) transformExportAssignment(input *ast.Node, ass tx.preserveJsDoc(exportAssignment, input) return exportAssignment } + + // Check if the expression is a class expression - emit as a class declaration + export assignment + if unwrapped := unwrapParenthesizedExpression(expression); ast.IsClassExpression(unwrapped) { + // Reuse the name pre-created by prescanCJSExportAssignment if available + className := tx.cjsExportAssignmentName + if className == nil { + ce := unwrapped.AsClassExpression() + if ce.Name() != nil && len(ce.Name().Text()) > 0 { + nameText := ce.Name().Text() + if tx.resolver.IsNameResolvedToDeclaration(tx.enclosingDeclaration, nameText, nil) { + className = tx.Factory().NewUniqueNameEx(nameText, printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) + } else { + className = tx.Factory().NewIdentifier(nameText) + } + } else { + className = tx.Factory().NewUniqueNameEx("_default", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) + } + tx.cjsExportAssignmentName = className + } + var mods []*ast.Node + if tx.needsDeclare { + mods = append(mods, tx.Factory().NewModifier(ast.KindDeclareKeyword)) + } + classDecl := tx.transformClassExpressionToDeclaration(unwrapped, className, tx.Factory().NewModifierList(mods)) + tx.preserveJsDoc(classDecl, input) + // Reuse the same name node for the export so unique names resolve consistently + exportAssignment := tx.Factory().NewExportAssignment(nil, isExportEquals, nil, className) + tx.removeAllComments(exportAssignment) + return tx.Factory().NewSyntaxList([]*ast.Node{exportAssignment, classDecl}) + } + // expression is non-identifier, create _default typed variable to reference newId := tx.Factory().NewUniqueNameEx("_default", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) tx.state.getSymbolAccessibilityDiagnostic = func(_ printer.SymbolAccessibilityResult) *SymbolAccessibilityDiagnostic { @@ -1156,8 +1225,113 @@ func (tx *DeclarationTransformer) transformCommonJSExport(input *ast.Node, name propertyName = nil } exportSpecifier := tx.Factory().NewExportSpecifier(false, propertyName, name) - return tx.Factory().NewExportDeclaration(nil, false, tx.Factory().NewNamedExports(tx.Factory().NewNodeList([]*ast.Node{exportSpecifier})), nil, nil) - } else if ast.IsIdentifier(name) { + exportDecl := tx.Factory().NewExportDeclaration(nil, false, tx.Factory().NewNamedExports(tx.Factory().NewNodeList([]*ast.Node{exportSpecifier})), nil, nil) + return tx.wrapInCJSExportNamespace(exportDecl) + } + + // Check if the RHS is a class expression - emit as a class declaration instead of a typed variable + if ast.IsBinaryExpression(input) { + if rhs := unwrapParenthesizedExpression(input.AsBinaryExpression().Right); ast.IsClassExpression(rhs) { + ce := rhs.AsClassExpression() + classExprName := ce.Name() + hasExprName := classExprName != nil && len(classExprName.Text()) > 0 + + if hasExprName { + // Set up TrackSymbol watch to detect if the class expression's own + // symbol is referenced during member type serialization. + tx.tracker.watchedClassSymbol = rhs.Symbol() + tx.tracker.classSymbolTracked = false + defer func() { + tx.tracker.watchedClassSymbol = nil + tx.tracker.classSymbolTracked = false + }() + + // Serialize class members using the class expression name, which + // triggers TrackSymbol for any self-referential member types. + className := tx.Factory().NewIdentifier(classExprName.Text()) + classMods := []*ast.Node{tx.Factory().NewModifier(ast.KindExportKeyword)} + classDecl := tx.transformClassExpressionToDeclaration(rhs, className, tx.Factory().NewModifierList(classMods)) + tx.preserveJsDoc(classDecl, input) + + // Determine if namespace isolation is needed: + // - The class expression name differs from the export name, OR + // - The class's own symbol was used in a member's serialized type + namesDiffer := !ast.IsIdentifier(name) || classExprName.Text() != name.Text() + needsIsolation := namesDiffer || tx.tracker.classSymbolTracked + + if needsIsolation { + nsName := tx.Factory().NewUniqueNameEx("_ns", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) + var nsMods []*ast.Node + if tx.needsDeclare { + nsMods = append(nsMods, tx.Factory().NewModifier(ast.KindDeclareKeyword)) + } + nsDecl := tx.Factory().NewModuleDeclaration( + tx.Factory().NewModifierList(nsMods), + ast.KindNamespaceKeyword, + nsName, + tx.Factory().NewModuleBlock(tx.Factory().NewNodeList([]*ast.Node{classDecl})), + ) + + aliasBase := "_exported" + if nameText := name.Text(); ast.IsIdentifier(name) && scanner.IsIdentifierText("_"+nameText, core.LanguageVariantStandard) { + aliasBase = "_" + nameText + } + importAlias := tx.Factory().NewUniqueNameEx(aliasBase, printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) + qualifiedName := tx.Factory().NewQualifiedName(nsName, className) + importDecl := tx.Factory().NewImportEqualsDeclaration(nil, false, importAlias, qualifiedName) + + exportSpecifier := tx.Factory().NewExportSpecifier(false, importAlias, name) + exportDecl := tx.Factory().NewExportDeclaration(nil, false, tx.Factory().NewNamedExports(tx.Factory().NewNodeList([]*ast.Node{exportSpecifier})), nil, nil) + tx.removeAllComments(exportDecl) + + result := []*ast.Node{nsDecl, importDecl} + if tx.cjsExportAssignmentName != nil { + result = append(result, tx.wrapInCJSExportNamespace(exportDecl)) + } else { + result = append(result, exportDecl) + } + return tx.Factory().NewSyntaxList(result) + } + + // No isolation needed: names match and no self-references. + // Update modifiers to include declare if needed. + var mods []*ast.Node + mods = append(mods, tx.Factory().NewModifier(ast.KindExportKeyword)) + if tx.needsDeclare { + mods = append(mods, tx.Factory().NewModifier(ast.KindDeclareKeyword)) + } + classDecl = tx.Factory().UpdateClassDeclaration( + classDecl.AsClassDeclaration(), + tx.Factory().NewModifierList(mods), + classDecl.AsClassDeclaration().Name(), + classDecl.AsClassDeclaration().TypeParameters, + classDecl.AsClassDeclaration().HeritageClauses, + classDecl.AsClassDeclaration().Members, + ) + return classDecl + } + var mods []*ast.Node + mods = append(mods, tx.Factory().NewModifier(ast.KindExportKeyword)) + if tx.needsDeclare { + mods = append(mods, tx.Factory().NewModifier(ast.KindDeclareKeyword)) + } + className := name + if !ast.IsIdentifier(className) { + className = tx.Factory().NewUniqueNameEx("_class", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) + } + classDecl := tx.transformClassExpressionToDeclaration(rhs, className, tx.Factory().NewModifierList(mods)) + tx.preserveJsDoc(classDecl, input) + if !ast.IsIdentifier(name) { + // Non-identifier name: emit class declaration + named export + exportDecl := tx.Factory().NewExportDeclaration(nil, false, tx.Factory().NewNamedExports(tx.Factory().NewNodeList([]*ast.Node{tx.Factory().NewExportSpecifier(false, className, name)})), nil, nil) + tx.removeAllComments(exportDecl) + return tx.Factory().NewSyntaxList([]*ast.Node{classDecl, exportDecl}) + } + return classDecl + } + } + + if ast.IsIdentifier(name) { if name.Text() == "default" { // const _default: Type; export default _default; newId := tx.Factory().NewUniqueNameEx("_default", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) @@ -1227,6 +1401,30 @@ func (tx *DeclarationTransformer) transformCommonJSExport(input *ast.Node, name return tx.Factory().NewSyntaxList([]*ast.Node{statement, assignment}) } +func (tx *DeclarationTransformer) wrapInCJSExportNamespace(content *ast.Node) *ast.Node { + if tx.cjsExportAssignmentName == nil { + return content + } + // Reuse the same name node so unique names resolve consistently with the class/export + nsName := tx.cjsExportAssignmentName + var members []*ast.Node + if content.Kind == ast.KindSyntaxList { + members = content.AsSyntaxList().Children + } else { + members = []*ast.Node{content} + } + var nsMods []*ast.Node + if tx.needsDeclare { + nsMods = append(nsMods, tx.Factory().NewModifier(ast.KindDeclareKeyword)) + } + return tx.Factory().NewModuleDeclaration( + tx.Factory().NewModifierList(nsMods), + ast.KindNamespaceKeyword, + nsName, + tx.Factory().NewModuleBlock(tx.Factory().NewNodeList(members)), + ) +} + func isCommonJSAliasExport(node *ast.Node) bool { if ast.IsBinaryExpression(node) && ast.IsIdentifier(node.AsBinaryExpression().Right) { if symbol := node.Symbol(); symbol != nil && len(symbol.Declarations) == 1 { @@ -1236,6 +1434,36 @@ func isCommonJSAliasExport(node *ast.Node) bool { return false } +// transformClassExpressionToDeclaration converts a class expression into a class declaration +// for use in CJS export declarations (e.g., exports.K = class K {} or module.exports = class Thing {}). +// This delegates to the shared buildClassMembers helper to stay in sync with transformClassDeclaration. +func (tx *DeclarationTransformer) transformClassExpressionToDeclaration(classExpr *ast.Node, className *ast.Node, modifiers *ast.ModifierList) *ast.Node { + previousEnclosingDeclaration := tx.enclosingDeclaration + tx.enclosingDeclaration = classExpr + previousInClassExpressionDeclaration := tx.inClassExpressionDeclaration + tx.inClassExpressionDeclaration = true + defer func() { + tx.enclosingDeclaration = previousEnclosingDeclaration + tx.inClassExpressionDeclaration = previousInClassExpressionDeclaration + }() + + var extraMembers []*ast.Node + if ast.IsInJSFile(classExpr) { + extraMembers = tx.collectThisPropertyAssignments(classExpr) + } + members := tx.buildClassMembers(classExpr, extraMembers...) + typeParameters := tx.ensureTypeParams(classExpr, classExpr.AsClassExpression().TypeParameters) + heritageClauses := tx.Visitor().VisitNodes(classExpr.AsClassExpression().HeritageClauses) + + return tx.Factory().NewClassDeclaration( + modifiers, + className, + typeParameters, + heritageClauses, + members, + ) +} + func (tx *DeclarationTransformer) rewriteModuleSpecifier(parent *ast.Node, input *ast.Node) *ast.Node { if input == nil { return nil @@ -1300,7 +1528,11 @@ func (tx *DeclarationTransformer) ensureType(node *ast.Node, ignorePrivate bool) if tx.state.currentSourceFile.IsJS() { // JS types have a heap of constructs we can't directly emit into .d.ts files; the node builder contains logic to remap those where possible, so we invoke it here // In strada we always built js declarations symbolically, so all js type nodes went through this postprocessing - res := tx.resolver.TryJSTypeNodeToTypeNode(tx.EmitContext(), node.Type(), tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) + jsFlags := declarationEmitNodeBuilderFlags + if tx.inClassExpressionDeclaration { + jsFlags &^= nodebuilder.FlagsWriteClassExpressionAsTypeLiteral + } + res := tx.resolver.TryJSTypeNodeToTypeNode(tx.EmitContext(), node.Type(), tx.enclosingDeclaration, jsFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) if res != nil { return res } @@ -1321,10 +1553,14 @@ func (tx *DeclarationTransformer) ensureType(node *ast.Node, ignorePrivate bool) } var typeNode *ast.Node + flags := declarationEmitNodeBuilderFlags + if tx.inClassExpressionDeclaration { + flags &^= nodebuilder.FlagsWriteClassExpressionAsTypeLiteral + } if ast.HasInferredType(node) { - typeNode = tx.resolver.CreateTypeOfDeclaration(tx.EmitContext(), node, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) + typeNode = tx.resolver.CreateTypeOfDeclaration(tx.EmitContext(), node, tx.enclosingDeclaration, flags, declarationEmitInternalNodeBuilderFlags, tx.tracker) } else if ast.IsFunctionLike(node) { - typeNode = tx.resolver.CreateReturnTypeOfSignatureDeclaration(tx.EmitContext(), node, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker) + typeNode = tx.resolver.CreateReturnTypeOfSignatureDeclaration(tx.EmitContext(), node, tx.enclosingDeclaration, flags, declarationEmitInternalNodeBuilderFlags, tx.tracker) } else { debug.AssertNever(node) } @@ -1559,14 +1795,11 @@ func (tx *DeclarationTransformer) stripExportModifiers(statement *ast.Node) *ast return ast.ReplaceModifiers(tx.Factory().AsNodeFactory(), statement, tx.Factory().NewModifierList(modifiers)) } -func (tx *DeclarationTransformer) transformClassDeclaration(input *ast.ClassDeclaration) *ast.Node { - tx.state.errorNameNode = input.Name() - tx.tracker.PushErrorFallbackNode(input.AsNode()) - defer tx.tracker.PopErrorFallbackNode() - - modifiers := tx.ensureModifiers(input.AsNode()) - typeParameters := tx.ensureTypeParams(input.AsNode(), input.TypeParameters) - ctor := ast.GetFirstConstructorWithBody(input.AsNode()) +// buildClassMembers builds the member list for a class-like node (ClassDeclaration or ClassExpression). +// It handles parameter properties, private identifiers, late-bound index signatures, and visited members. +// Extra members (e.g., this-property assignments from JS files) can be passed via extraMembers. +func (tx *DeclarationTransformer) buildClassMembers(classNode *ast.Node, extraMembers ...*ast.Node) *ast.NodeList { + ctor := ast.GetFirstConstructorWithBody(classNode) var parameterProperties []*ast.Node if ctor != nil { oldDiag := tx.state.getSymbolAccessibilityDiagnostic @@ -1596,43 +1829,54 @@ func (tx *DeclarationTransformer) transformClassDeclaration(input *ast.ClassDecl // When the class has at least one private identifier, create a unique constant identifier to retain the nominal typing behavior // Prevents other classes with the same public members from being used in place of the current class var privateIdentifier *ast.Node - if core.Some(input.Members.Nodes, func(member *ast.Node) bool { return member.Name() != nil && ast.IsPrivateIdentifier(member.Name()) }) { - privateIdentifier = tx.Factory().NewPropertyDeclaration( - nil, - tx.Factory().NewPrivateIdentifier("#private"), - nil, - nil, - nil, - ) - } - - // Collect this.x property assignments from constructors and static blocks in JS files - var thisPropertyAssignments []*ast.Node - if ast.IsInJSFile(input.AsNode()) { - thisPropertyAssignments = tx.collectThisPropertyAssignments(input) + if core.Some(classNode.ClassLikeData().Members.Nodes, func(member *ast.Node) bool { + return member.Name() != nil && ast.IsPrivateIdentifier(member.Name()) + }) { + privateIdentifier = tx.Factory().NewPropertyDeclaration(nil, tx.Factory().NewPrivateIdentifier("#private"), nil, nil, nil) } lateIndexes := tx.resolver.CreateLateBoundIndexSignatures( tx.EmitContext(), - input.AsNode(), + classNode, tx.enclosingDeclaration, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags, tx.tracker, ) - memberNodes := make([]*ast.Node, 0, len(input.Members.Nodes)) + memberNodes := make([]*ast.Node, 0, len(classNode.ClassLikeData().Members.Nodes)) if privateIdentifier != nil { memberNodes = append(memberNodes, privateIdentifier) } memberNodes = append(memberNodes, lateIndexes...) memberNodes = append(memberNodes, parameterProperties...) - memberNodes = append(memberNodes, thisPropertyAssignments...) - visitResult := tx.Visitor().VisitNodes(input.Members) + memberNodes = append(memberNodes, extraMembers...) + visitResult := tx.Visitor().VisitNodes(classNode.ClassLikeData().Members) if visitResult != nil && len(visitResult.Nodes) > 0 { memberNodes = append(memberNodes, visitResult.Nodes...) } - members := tx.Factory().NewNodeList(memberNodes) + return tx.Factory().NewNodeList(memberNodes) +} + +func (tx *DeclarationTransformer) transformClassDeclaration(input *ast.ClassDeclaration) *ast.Node { + previousEnclosingDeclaration := tx.enclosingDeclaration + tx.enclosingDeclaration = input.AsNode() + defer func() { tx.enclosingDeclaration = previousEnclosingDeclaration }() + + tx.state.errorNameNode = input.Name() + tx.tracker.PushErrorFallbackNode(input.AsNode()) + defer tx.tracker.PopErrorFallbackNode() + + modifiers := tx.ensureModifiers(input.AsNode()) + typeParameters := tx.ensureTypeParams(input.AsNode(), input.TypeParameters) + + // Collect this.x property assignments from constructors and static blocks in JS files + var extraMembers []*ast.Node + if ast.IsInJSFile(input.AsNode()) { + extraMembers = tx.collectThisPropertyAssignments(input.AsNode()) + } + + members := tx.buildClassMembers(input.AsNode(), extraMembers...) extendsClause := getEffectiveBaseTypeNode(input.AsNode()) @@ -1798,10 +2042,11 @@ func isClassExtendingNull(node *ast.Node) bool { // collectThisPropertyAssignments finds `this.x = expr` assignments in constructors, methods, and static blocks // of JS classes and synthesizes PropertyDeclaration nodes for each unique property name. -func (tx *DeclarationTransformer) collectThisPropertyAssignments(input *ast.ClassDeclaration) []*ast.Node { +func (tx *DeclarationTransformer) collectThisPropertyAssignments(classNode *ast.Node) []*ast.Node { + members := classNode.ClassLikeData().Members seen := collections.Set[*ast.Node]{} // Pre-populate seen with existing direct member nodes to avoid duplicates - for _, member := range input.Members.Nodes { + for _, member := range members.Nodes { if member.Name() != nil { seen.Add(member) } @@ -1813,7 +2058,7 @@ func (tx *DeclarationTransformer) collectThisPropertyAssignments(input *ast.Clas tx.thisPropertyAssignmentsCollected = nil }() - for _, n := range input.Members.Nodes { + for _, n := range members.Nodes { tx.thisPropertyVisitor.VisitEachChild(n) } return tx.thisPropertyAssignmentsCollected diff --git a/testdata/baselines/reference/compiler/cjsExportClassExpressionNameConflict.js b/testdata/baselines/reference/compiler/cjsExportClassExpressionNameConflict.js new file mode 100644 index 00000000000..f5baf25cfb8 --- /dev/null +++ b/testdata/baselines/reference/compiler/cjsExportClassExpressionNameConflict.js @@ -0,0 +1,40 @@ +//// [tests/cases/compiler/cjsExportClassExpressionNameConflict.ts] //// + +//// [index.js] +module.exports.A = class B {f1 = 1; self = new B();} +module.exports.B = class B {f1 = "ok"; self = new B();} + + +//// [index.js] +"use strict"; +module.exports.A = class B { + constructor() { + this.f1 = 1; + this.self = new B(); + } +}; +module.exports.B = class B { + constructor() { + this.f1 = "ok"; + this.self = new B(); + } +}; + + +//// [index.d.ts] +declare namespace _ns { + export class B { + f1: number; + self: B; + } +} +import _A = _ns.B; +export { _A as A }; +declare namespace _ns_1 { + export class B { + f1: string; + self: B; + } +} +import _B = _ns_1.B; +export { _B as B }; diff --git a/testdata/baselines/reference/compiler/cjsExportClassExpressionNameConflict.symbols b/testdata/baselines/reference/compiler/cjsExportClassExpressionNameConflict.symbols new file mode 100644 index 00000000000..7a89543f29d --- /dev/null +++ b/testdata/baselines/reference/compiler/cjsExportClassExpressionNameConflict.symbols @@ -0,0 +1,25 @@ +//// [tests/cases/compiler/cjsExportClassExpressionNameConflict.ts] //// + +=== index.js === +module.exports.A = class B {f1 = 1; self = new B();} +>module.exports.A : Symbol(A, Decl(index.js, 0, 0)) +>module.exports : Symbol(exports, Decl(index.js, 0, 0)) +>module : Symbol(module, Decl(index.js, 0, 0)) +>exports : Symbol(exports, Decl(index.js, 0, 0)) +>A : Symbol(A, Decl(index.js, 0, 0)) +>B : Symbol(B, Decl(index.js, 0, 18)) +>f1 : Symbol(B.f1, Decl(index.js, 0, 28)) +>self : Symbol(B.self, Decl(index.js, 0, 35)) +>B : Symbol(B, Decl(index.js, 0, 18)) + +module.exports.B = class B {f1 = "ok"; self = new B();} +>module.exports.B : Symbol(B, Decl(index.js, 0, 52)) +>module.exports : Symbol(exports, Decl(index.js, 0, 0)) +>module : Symbol(module, Decl(index.js, 0, 0)) +>exports : Symbol(exports, Decl(index.js, 0, 0)) +>B : Symbol(B, Decl(index.js, 0, 52)) +>B : Symbol(B, Decl(index.js, 1, 18)) +>f1 : Symbol(B.f1, Decl(index.js, 1, 28)) +>self : Symbol(B.self, Decl(index.js, 1, 38)) +>B : Symbol(B, Decl(index.js, 1, 18)) + diff --git a/testdata/baselines/reference/compiler/cjsExportClassExpressionNameConflict.types b/testdata/baselines/reference/compiler/cjsExportClassExpressionNameConflict.types new file mode 100644 index 00000000000..8b324e766a9 --- /dev/null +++ b/testdata/baselines/reference/compiler/cjsExportClassExpressionNameConflict.types @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/cjsExportClassExpressionNameConflict.ts] //// + +=== index.js === +module.exports.A = class B {f1 = 1; self = new B();} +>module.exports.A = class B {f1 = 1; self = new B();} : typeof A +>module.exports.A : typeof A +>module.exports : typeof import(".") +>module : { exports: typeof import("."); } +>exports : typeof import(".") +>A : typeof A +>class B {f1 = 1; self = new B();} : typeof A +>B : typeof B +>f1 : number +>1 : 1 +>self : B +>new B() : B +>B : typeof B + +module.exports.B = class B {f1 = "ok"; self = new B();} +>module.exports.B = class B {f1 = "ok"; self = new B();} : typeof B +>module.exports.B : typeof B +>module.exports : typeof import(".") +>module : { exports: typeof import("."); } +>exports : typeof import(".") +>B : typeof B +>class B {f1 = "ok"; self = new B();} : typeof B +>B : typeof B +>f1 : string +>"ok" : "ok" +>self : B +>new B() : B +>B : typeof B + diff --git a/testdata/baselines/reference/compiler/cjsExportNamedClassExpressionNoAssignment.js b/testdata/baselines/reference/compiler/cjsExportNamedClassExpressionNoAssignment.js new file mode 100644 index 00000000000..c3a3c1f4de0 --- /dev/null +++ b/testdata/baselines/reference/compiler/cjsExportNamedClassExpressionNoAssignment.js @@ -0,0 +1,31 @@ +//// [tests/cases/compiler/cjsExportNamedClassExpressionNoAssignment.ts] //// + +//// [index.js] +// Named class expression exports without module.exports assignment. +// The class expression name B should be preserved via namespace isolation +// even without a module.exports = ... in the file. +module.exports.A = class B {f1 = 1; self = new B();} + + +//// [index.js] +"use strict"; +// Named class expression exports without module.exports assignment. +// The class expression name B should be preserved via namespace isolation +// even without a module.exports = ... in the file. +module.exports.A = class B { + constructor() { + this.f1 = 1; + this.self = new B(); + } +}; + + +//// [index.d.ts] +declare namespace _ns { + export class B { + f1: number; + self: B; + } +} +import _A = _ns.B; +export { _A as A }; diff --git a/testdata/baselines/reference/compiler/cjsExportNamedClassExpressionNoAssignment.symbols b/testdata/baselines/reference/compiler/cjsExportNamedClassExpressionNoAssignment.symbols new file mode 100644 index 00000000000..52d32b2539c --- /dev/null +++ b/testdata/baselines/reference/compiler/cjsExportNamedClassExpressionNoAssignment.symbols @@ -0,0 +1,17 @@ +//// [tests/cases/compiler/cjsExportNamedClassExpressionNoAssignment.ts] //// + +=== index.js === +// Named class expression exports without module.exports assignment. +// The class expression name B should be preserved via namespace isolation +// even without a module.exports = ... in the file. +module.exports.A = class B {f1 = 1; self = new B();} +>module.exports.A : Symbol(A, Decl(index.js, 0, 0)) +>module.exports : Symbol(exports, Decl(index.js, 0, 0)) +>module : Symbol(module, Decl(index.js, 0, 0)) +>exports : Symbol(exports, Decl(index.js, 0, 0)) +>A : Symbol(A, Decl(index.js, 0, 0)) +>B : Symbol(B, Decl(index.js, 3, 18)) +>f1 : Symbol(B.f1, Decl(index.js, 3, 28)) +>self : Symbol(B.self, Decl(index.js, 3, 35)) +>B : Symbol(B, Decl(index.js, 3, 18)) + diff --git a/testdata/baselines/reference/compiler/cjsExportNamedClassExpressionNoAssignment.types b/testdata/baselines/reference/compiler/cjsExportNamedClassExpressionNoAssignment.types new file mode 100644 index 00000000000..950deb2dc58 --- /dev/null +++ b/testdata/baselines/reference/compiler/cjsExportNamedClassExpressionNoAssignment.types @@ -0,0 +1,21 @@ +//// [tests/cases/compiler/cjsExportNamedClassExpressionNoAssignment.ts] //// + +=== index.js === +// Named class expression exports without module.exports assignment. +// The class expression name B should be preserved via namespace isolation +// even without a module.exports = ... in the file. +module.exports.A = class B {f1 = 1; self = new B();} +>module.exports.A = class B {f1 = 1; self = new B();} : typeof A +>module.exports.A : typeof A +>module.exports : typeof import(".") +>module : { exports: typeof import("."); } +>exports : typeof import(".") +>A : typeof A +>class B {f1 = 1; self = new B();} : typeof A +>B : typeof B +>f1 : number +>1 : 1 +>self : B +>new B() : B +>B : typeof B + diff --git a/testdata/baselines/reference/submodule/conformance/commonJSImportExportedClassExpression.js b/testdata/baselines/reference/submodule/conformance/commonJSImportExportedClassExpression.js index 03b6e5ecf90..4585a8a7b1e 100644 --- a/testdata/baselines/reference/submodule/conformance/commonJSImportExportedClassExpression.js +++ b/testdata/baselines/reference/submodule/conformance/commonJSImportExportedClassExpression.js @@ -30,26 +30,8 @@ function f(k) { //// [mod1.d.ts] -export declare var K: { - new (): K; -}; +export declare class K { + values(): void; +} //// [main.d.ts] export {}; - - -//// [DtsFileErrors] - - -out/mod1.d.ts(2,13): error TS2749: 'K' refers to a value, but is being used as a type here. Did you mean 'typeof K'? - - -==== out/main.d.ts (0 errors) ==== - export {}; - -==== out/mod1.d.ts (1 errors) ==== - export declare var K: { - new (): K; - ~ -!!! error TS2749: 'K' refers to a value, but is being used as a type here. Did you mean 'typeof K'? - }; - \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).js index 65aee0cfdc3..28cff80f239 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).js @@ -23,23 +23,11 @@ module.exports = class Thing { //// [index.d.ts] -declare const _default: { - new (p: number): import("."); -}; -export = _default; - - -//// [DtsFileErrors] - - -out/index.d.ts(2,22): error TS1340: Module '.' does not refer to a type, but is used as a type here. Did you mean 'typeof import('.')'? - - -==== out/index.d.ts (1 errors) ==== - declare const _default: { - new (p: number): import("."); - ~~~~~~~~~~~ -!!! error TS1340: Module '.' does not refer to a type, but is used as a type here. Did you mean 'typeof import('.')'? - }; - export = _default; - \ No newline at end of file +export = Thing; +declare class Thing { + t: number; + /** + * @param {number} p + */ + constructor(p: number); +} diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).types b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).types index 30698a1aa32..93b8714f447 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).types +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).types @@ -7,7 +7,7 @@ module.exports = class Thing { >module : { exports: typeof import("."); } >exports : typeof import(".") >class Thing { /** * @param {number} p */ constructor(p) { this.t = 12 + p; }} : typeof import(".") ->Thing : typeof import(".") +>Thing : typeof Thing /** * @param {number} p diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).types.diff b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).types.diff new file mode 100644 index 00000000000..28013b36b07 --- /dev/null +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).types.diff @@ -0,0 +1,11 @@ +--- old.jsDeclarationsExportAssignedClassExpression(target=es2015).types ++++ new.jsDeclarationsExportAssignedClassExpression(target=es2015).types +@@= skipped -6, +6 lines =@@ + >module : { exports: typeof import("."); } + >exports : typeof import(".") + >class Thing { /** * @param {number} p */ constructor(p) { this.t = 12 + p; }} : typeof import(".") +->Thing : typeof import(".") ++>Thing : typeof Thing + + /** + * @param {number} p \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js index 9332c14297d..3124bbc6df4 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js @@ -23,23 +23,11 @@ module.exports = class { //// [index.d.ts] -declare const _default: { - new (p: number): import("."); -}; export = _default; - - -//// [DtsFileErrors] - - -out/index.d.ts(2,22): error TS1340: Module '.' does not refer to a type, but is used as a type here. Did you mean 'typeof import('.')'? - - -==== out/index.d.ts (1 errors) ==== - declare const _default: { - new (p: number): import("."); - ~~~~~~~~~~~ -!!! error TS1340: Module '.' does not refer to a type, but is used as a type here. Did you mean 'typeof import('.')'? - }; - export = _default; - \ No newline at end of file +declare class _default { + t: number; + /** + * @param {number} p + */ + constructor(p: number); +} diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js index ab1ab0a48f1..0eb912771b5 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js @@ -34,12 +34,15 @@ module.exports.Sub = class { //// [index.d.ts] -declare const _default: { - new (p: number): import("."); -}; export = _default; -export declare var Sub: { - new (): { - instance: import("."); - }; -}; +declare class _default { + t: number; + /** + * @param {number} p + */ + constructor(p: number); +} +export declare class Sub { + instance: import("."); + constructor(); +} diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js index c9f8c2ba51b..d4063a549b9 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js @@ -36,11 +36,17 @@ module.exports.Another = Q; //// [index.d.ts] +declare class A { + member: Q; +} declare class Q { x: number; } -declare const _default: { - new (): import("."); -}; -export = _default; -export { Q as Another }; +export = Q_1; +declare class Q_1 { + x: A; + constructor(); +} +declare namespace Q_1 { + export { Q as Another }; +} diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).types b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).types index b14ab1a2b97..df8bd965cf8 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).types +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).types @@ -22,7 +22,7 @@ module.exports = class Q { >module : { exports: typeof import("."); } >exports : typeof import(".") >class Q { constructor() { this.x = new A(); }} : typeof import(".") ->Q : typeof import(".") +>Q : typeof Q constructor() { this.x = new A(); diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).types.diff b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).types.diff index 190b8d7be20..8fba5e57420 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).types.diff +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).types.diff @@ -1,6 +1,15 @@ --- old.jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).types +++ new.jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).types -@@= skipped -35, +35 lines =@@ +@@= skipped -21, +21 lines =@@ + >module : { exports: typeof import("."); } + >exports : typeof import(".") + >class Q { constructor() { this.x = new A(); }} : typeof import(".") +->Q : typeof import(".") ++>Q : typeof Q + + constructor() { + this.x = new A(); +@@= skipped -14, +14 lines =@@ } module.exports.Another = Q; >module.exports.Another = Q : typeof Q diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js index d5491cac5a7..5cc1552e148 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js @@ -36,10 +36,11 @@ module.exports = Container; //// [obj.d.ts] -declare const _default: { - new (): import("./obj"); -}; -export = _default; +export = Obj; +declare class Obj { + x: number; + constructor(); +} //// [index.d.ts] import Obj = require("./obj"); declare class Container { @@ -47,30 +48,3 @@ declare class Container { constructor(); } export = Container; - - -//// [DtsFileErrors] - - -out/index.d.ts(3,12): error TS2749: 'Obj' refers to a value, but is being used as a type here. Did you mean 'typeof Obj'? -out/obj.d.ts(2,13): error TS1340: Module './obj' does not refer to a type, but is used as a type here. Did you mean 'typeof import('./obj')'? - - -==== out/index.d.ts (1 errors) ==== - import Obj = require("./obj"); - declare class Container { - usage: Obj; - ~~~ -!!! error TS2749: 'Obj' refers to a value, but is being used as a type here. Did you mean 'typeof Obj'? - constructor(); - } - export = Container; - -==== out/obj.d.ts (1 errors) ==== - declare const _default: { - new (): import("./obj"); - ~~~~~~~~~~~~~~~ -!!! error TS1340: Module './obj' does not refer to a type, but is used as a type here. Did you mean 'typeof import('./obj')'? - }; - export = _default; - \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).types b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).types index 465fa8c37ec..1ba735460eb 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).types +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).types @@ -35,7 +35,7 @@ module.exports = class Obj { >module : { exports: typeof import("./obj"); } >exports : typeof import("./obj") >class Obj { constructor() { this.x = 12; }} : typeof import("./obj") ->Obj : typeof import("./obj") +>Obj : typeof Obj constructor() { this.x = 12; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).types.diff b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).types.diff new file mode 100644 index 00000000000..ddaa6ad7dd1 --- /dev/null +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).types.diff @@ -0,0 +1,11 @@ +--- old.jsDeclarationsExportAssignedVisibility(target=es2015).types ++++ new.jsDeclarationsExportAssignedVisibility(target=es2015).types +@@= skipped -34, +34 lines =@@ + >module : { exports: typeof import("./obj"); } + >exports : typeof import("./obj") + >class Obj { constructor() { this.x = 12; }} : typeof import("./obj") +->Obj : typeof import("./obj") ++>Obj : typeof Obj + + constructor() { + this.x = 12; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/jsdocImportType2.types b/testdata/baselines/reference/submodule/conformance/jsdocImportType2.types index 1885de6fa5b..efb31e87ad7 100644 --- a/testdata/baselines/reference/submodule/conformance/jsdocImportType2.types +++ b/testdata/baselines/reference/submodule/conformance/jsdocImportType2.types @@ -47,7 +47,7 @@ module.exports = class Chunk { >module : { exports: typeof import("./mod1"); } >exports : typeof import("./mod1") >class Chunk { constructor() { this.chunk = 1; }} : typeof import("./mod1") ->Chunk : typeof import("./mod1") +>Chunk : typeof Chunk constructor() { this.chunk = 1; diff --git a/testdata/baselines/reference/submodule/conformance/jsdocImportType2.types.diff b/testdata/baselines/reference/submodule/conformance/jsdocImportType2.types.diff new file mode 100644 index 00000000000..ef81e91e24c --- /dev/null +++ b/testdata/baselines/reference/submodule/conformance/jsdocImportType2.types.diff @@ -0,0 +1,11 @@ +--- old.jsdocImportType2.types ++++ new.jsdocImportType2.types +@@= skipped -46, +46 lines =@@ + >module : { exports: typeof import("./mod1"); } + >exports : typeof import("./mod1") + >class Chunk { constructor() { this.chunk = 1; }} : typeof import("./mod1") +->Chunk : typeof import("./mod1") ++>Chunk : typeof Chunk + + constructor() { + this.chunk = 1; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.symbols b/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.symbols index 97c9e739066..641e46f8aba 100644 --- a/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.symbols +++ b/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.symbols @@ -11,7 +11,7 @@ module.exports = class C {} >module.exports : Symbol(exports, Decl(bug24024.js, 0, 0)) >module : Symbol(module, Decl(bug24024.js, 0, 0)) >exports : Symbol(exports, Decl(bug24024.js, 0, 0)) ->C : Symbol(wat, Decl(bug24024.js, 2, 16)) +>C : Symbol(C, Decl(bug24024.js, 2, 16)) module.exports.D = class D { } >module.exports : Symbol(exports, Decl(bug24024.js, 0, 0)) diff --git a/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.symbols.diff b/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.symbols.diff index 03fe27468f2..1faa74cead0 100644 --- a/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.symbols.diff +++ b/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.symbols.diff @@ -7,10 +7,11 @@ ->module.exports : Symbol(module.exports, Decl(bug24024.js, 0, 0)) ->module : Symbol(export=, Decl(bug24024.js, 1, 31)) ->exports : Symbol(export=, Decl(bug24024.js, 1, 31)) +->C : Symbol(wat, Decl(bug24024.js, 2, 16)) +>module.exports : Symbol(exports, Decl(bug24024.js, 0, 0)) +>module : Symbol(module, Decl(bug24024.js, 0, 0)) +>exports : Symbol(exports, Decl(bug24024.js, 0, 0)) - >C : Symbol(wat, Decl(bug24024.js, 2, 16)) ++>C : Symbol(C, Decl(bug24024.js, 2, 16)) module.exports.D = class D { } ->module.exports.D : Symbol(wat.D, Decl(bug24024.js, 2, 27)) diff --git a/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.types b/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.types index 60e52601e90..dac731b9678 100644 --- a/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.types +++ b/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.types @@ -14,7 +14,7 @@ module.exports = class C {} >module : { exports: typeof wat; } >exports : typeof wat >class C {} : typeof wat ->C : typeof wat +>C : typeof C module.exports.D = class D { } >module.exports.D = class D { } : typeof D diff --git a/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.types.diff b/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.types.diff index def01d0634f..d7a934597df 100644 --- a/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.types.diff +++ b/testdata/baselines/reference/submodule/conformance/moduleExportAlias4.types.diff @@ -1,7 +1,11 @@ --- old.moduleExportAlias4.types +++ new.moduleExportAlias4.types -@@= skipped -16, +16 lines =@@ - >C : typeof wat +@@= skipped -13, +13 lines =@@ + >module : { exports: typeof wat; } + >exports : typeof wat + >class C {} : typeof wat +->C : typeof wat ++>C : typeof C module.exports.D = class D { } ->module.exports.D = class D { } : typeof wat.D diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/commonJSImportExportedClassExpression.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/commonJSImportExportedClassExpression.js.diff new file mode 100644 index 00000000000..0505e8cc8a1 --- /dev/null +++ b/testdata/baselines/reference/submoduleAccepted/conformance/commonJSImportExportedClassExpression.js.diff @@ -0,0 +1,11 @@ +--- old.commonJSImportExportedClassExpression.js ++++ new.commonJSImportExportedClassExpression.js +@@= skipped -29, +29 lines =@@ + + + //// [mod1.d.ts] +-export class K { ++export declare class K { + values(): void; + } + //// [main.d.ts] \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).js.diff new file mode 100644 index 00000000000..e5f7517321d --- /dev/null +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).js.diff @@ -0,0 +1,13 @@ +--- old.jsDeclarationsExportAssignedClassExpression(target=es2015).js ++++ new.jsDeclarationsExportAssignedClassExpression(target=es2015).js +@@= skipped -24, +24 lines =@@ + //// [index.d.ts] + export = Thing; + declare class Thing { ++ t: number; + /** + * @param {number} p + */ + constructor(p: number); +- t: number; + } \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js.diff new file mode 100644 index 00000000000..5d5ed381ec7 --- /dev/null +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js.diff @@ -0,0 +1,17 @@ +--- old.jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js ++++ new.jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js +@@= skipped -22, +22 lines =@@ + + + //// [index.d.ts] +-export = exports; +-declare class exports { ++export = _default; ++declare class _default { ++ t: number; + /** + * @param {number} p + */ + constructor(p: number); +- t: number; + } \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js.diff similarity index 59% rename from testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js.diff index 6a1c9a1f095..f320f390039 100644 --- a/testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js.diff @@ -6,24 +6,21 @@ //// [index.d.ts] -export = exports; -declare class exports { -- /** -- * @param {number} p -- */ -- constructor(p: number); ++export = _default; ++declare class _default { ++ t: number; + /** + * @param {number} p + */ + constructor(p: number); - t: number; -} -declare namespace exports { - export { Sub }; -} -declare class Sub { -- instance: import("."); --} -+declare const _default: { -+ new (p: number): import("."); -+}; -+export = _default; -+export declare var Sub: { -+ new (): { -+ instance: import("."); -+ }; -+}; \ No newline at end of file ++} ++export declare class Sub { + instance: import("."); ++ constructor(); + } \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js.diff similarity index 62% rename from testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js.diff rename to testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js.diff index 3fc6bb16f05..67e53eb2d5a 100644 --- a/testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js.diff @@ -5,20 +5,24 @@ //// [index.d.ts] -export = Q; - declare class Q { +-declare class Q { - x: A; -} -declare namespace Q { - export { Q_1 as Another }; -} --declare class A { -- member: Q; --} + declare class A { + member: Q; + } -declare class Q_1 { ++declare class Q { x: number; - } -+declare const _default: { -+ new (): import("."); -+}; -+export = _default; -+export { Q as Another }; \ No newline at end of file ++} ++export = Q_1; ++declare class Q_1 { ++ x: A; ++ constructor(); ++} ++declare namespace Q_1 { ++ export { Q as Another }; + } \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js.diff new file mode 100644 index 00000000000..fc4ba1fffee --- /dev/null +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js.diff @@ -0,0 +1,17 @@ +--- old.jsDeclarationsExportAssignedVisibility(target=es2015).js ++++ new.jsDeclarationsExportAssignedVisibility(target=es2015).js +@@= skipped -38, +38 lines =@@ + export = Obj; + declare class Obj { + x: number; ++ constructor(); + } + //// [index.d.ts] +-export = Container; ++import Obj = require("./obj"); + declare class Container { + usage: Obj; ++ constructor(); + } +-import Obj = require("./obj"); ++export = Container; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleTriaged/conformance/commonJSImportExportedClassExpression.js.diff b/testdata/baselines/reference/submoduleTriaged/conformance/commonJSImportExportedClassExpression.js.diff deleted file mode 100644 index 20aa2d5f74b..00000000000 --- a/testdata/baselines/reference/submoduleTriaged/conformance/commonJSImportExportedClassExpression.js.diff +++ /dev/null @@ -1,32 +0,0 @@ ---- old.commonJSImportExportedClassExpression.js -+++ new.commonJSImportExportedClassExpression.js -@@= skipped -29, +29 lines =@@ - - - //// [mod1.d.ts] --export class K { -- values(): void; --} -+export declare var K: { -+ new (): K; -+}; - //// [main.d.ts] - export {}; -+ -+ -+//// [DtsFileErrors] -+ -+ -+out/mod1.d.ts(2,13): error TS2749: 'K' refers to a value, but is being used as a type here. Did you mean 'typeof K'? -+ -+ -+==== out/main.d.ts (0 errors) ==== -+ export {}; -+ -+==== out/mod1.d.ts (1 errors) ==== -+ export declare var K: { -+ new (): K; -+ ~ -+!!! error TS2749: 'K' refers to a value, but is being used as a type here. Did you mean 'typeof K'? -+ }; -+ \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).js.diff b/testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).js.diff deleted file mode 100644 index f72d77aec6e..00000000000 --- a/testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).js.diff +++ /dev/null @@ -1,34 +0,0 @@ ---- old.jsDeclarationsExportAssignedClassExpression(target=es2015).js -+++ new.jsDeclarationsExportAssignedClassExpression(target=es2015).js -@@= skipped -22, +22 lines =@@ - - - //// [index.d.ts] --export = Thing; --declare class Thing { -- /** -- * @param {number} p -- */ -- constructor(p: number); -- t: number; --} -+declare const _default: { -+ new (p: number): import("."); -+}; -+export = _default; -+ -+ -+//// [DtsFileErrors] -+ -+ -+out/index.d.ts(2,22): error TS1340: Module '.' does not refer to a type, but is used as a type here. Did you mean 'typeof import('.')'? -+ -+ -+==== out/index.d.ts (1 errors) ==== -+ declare const _default: { -+ new (p: number): import("."); -+ ~~~~~~~~~~~ -+!!! error TS1340: Module '.' does not refer to a type, but is used as a type here. Did you mean 'typeof import('.')'? -+ }; -+ export = _default; -+ \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js.diff b/testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js.diff deleted file mode 100644 index 74a799f397e..00000000000 --- a/testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js.diff +++ /dev/null @@ -1,34 +0,0 @@ ---- old.jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js -+++ new.jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js -@@= skipped -22, +22 lines =@@ - - - //// [index.d.ts] --export = exports; --declare class exports { -- /** -- * @param {number} p -- */ -- constructor(p: number); -- t: number; --} -+declare const _default: { -+ new (p: number): import("."); -+}; -+export = _default; -+ -+ -+//// [DtsFileErrors] -+ -+ -+out/index.d.ts(2,22): error TS1340: Module '.' does not refer to a type, but is used as a type here. Did you mean 'typeof import('.')'? -+ -+ -+==== out/index.d.ts (1 errors) ==== -+ declare const _default: { -+ new (p: number): import("."); -+ ~~~~~~~~~~~ -+!!! error TS1340: Module '.' does not refer to a type, but is used as a type here. Did you mean 'typeof import('.')'? -+ }; -+ export = _default; -+ \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js.diff b/testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js.diff deleted file mode 100644 index 70b28e59864..00000000000 --- a/testdata/baselines/reference/submoduleTriaged/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js.diff +++ /dev/null @@ -1,50 +0,0 @@ ---- old.jsDeclarationsExportAssignedVisibility(target=es2015).js -+++ new.jsDeclarationsExportAssignedVisibility(target=es2015).js -@@= skipped -35, +35 lines =@@ - - - //// [obj.d.ts] --export = Obj; --declare class Obj { -- x: number; --} -+declare const _default: { -+ new (): import("./obj"); -+}; -+export = _default; - //// [index.d.ts] --export = Container; -+import Obj = require("./obj"); - declare class Container { - usage: Obj; -+ constructor(); - } --import Obj = require("./obj"); -+export = Container; -+ -+ -+//// [DtsFileErrors] -+ -+ -+out/index.d.ts(3,12): error TS2749: 'Obj' refers to a value, but is being used as a type here. Did you mean 'typeof Obj'? -+out/obj.d.ts(2,13): error TS1340: Module './obj' does not refer to a type, but is used as a type here. Did you mean 'typeof import('./obj')'? -+ -+ -+==== out/index.d.ts (1 errors) ==== -+ import Obj = require("./obj"); -+ declare class Container { -+ usage: Obj; -+ ~~~ -+!!! error TS2749: 'Obj' refers to a value, but is being used as a type here. Did you mean 'typeof Obj'? -+ constructor(); -+ } -+ export = Container; -+ -+==== out/obj.d.ts (1 errors) ==== -+ declare const _default: { -+ new (): import("./obj"); -+ ~~~~~~~~~~~~~~~ -+!!! error TS1340: Module './obj' does not refer to a type, but is used as a type here. Did you mean 'typeof import('./obj')'? -+ }; -+ export = _default; -+ \ No newline at end of file diff --git a/testdata/submoduleAccepted.txt b/testdata/submoduleAccepted.txt index ddfbb34d3fd..b9e3d2c233a 100644 --- a/testdata/submoduleAccepted.txt +++ b/testdata/submoduleAccepted.txt @@ -1401,6 +1401,15 @@ conformance/nodeModulesDeclarationEmitWithPackageExports(module=node18).js.diff conformance/nodeModulesDeclarationEmitWithPackageExports(module=node20).js.diff conformance/nodeModulesDeclarationEmitWithPackageExports(module=nodenext).js.diff +# CJS class expressions emit named class declarations instead of anonymous constructor types +## https://github.com/microsoft/typescript-go/issues/3544 +conformance/commonJSImportExportedClassExpression.js.diff +conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).js.diff +conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js.diff +conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js.diff +conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js.diff +conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js.diff + # @callback/@overload variadic intepreted differently than strada ## https://github.com/microsoft/typescript-go/issues/3556 conformance/callbackTagNestedParameter.js.diff diff --git a/testdata/submoduleTriaged.txt b/testdata/submoduleTriaged.txt index ce7a7249ba7..321acdd5586 100644 --- a/testdata/submoduleTriaged.txt +++ b/testdata/submoduleTriaged.txt @@ -39,15 +39,6 @@ compiler/multiLinePropertyAccessAndArrowFunctionIndent1.errors.txt.diff compiler/parseErrorIncorrectReturnToken.errors.txt.diff conformance/parserStatementIsNotAMemberVariableDeclaration1(alwaysstrict=true).errors.txt.diff -# CJS class expression exports now emit anonymous constructor types instead of named class declarations -## https://github.com/microsoft/typescript-go/issues/3544 -conformance/commonJSImportExportedClassExpression.js.diff -conformance/jsDeclarationsExportAssignedClassExpression(target=es2015).js.diff -conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js.diff -conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js.diff -conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js.diff -conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js.diff - # Namespace-to-const restructuring with DtsFileErrors (redeclaration errors) ## https://github.com/microsoft/typescript-go/issues/3547 conformance/typeFromPropertyAssignment39.js.diff diff --git a/testdata/tests/cases/compiler/cjsExportClassExpressionNameConflict.ts b/testdata/tests/cases/compiler/cjsExportClassExpressionNameConflict.ts new file mode 100644 index 00000000000..e0ca8c9e4d2 --- /dev/null +++ b/testdata/tests/cases/compiler/cjsExportClassExpressionNameConflict.ts @@ -0,0 +1,9 @@ +// @allowJs: true +// @checkJs: true +// @target: es2015 +// @outDir: ./out +// @declaration: true + +// @filename: index.js +module.exports.A = class B {f1 = 1; self = new B();} +module.exports.B = class B {f1 = "ok"; self = new B();} diff --git a/testdata/tests/cases/compiler/cjsExportNamedClassExpressionNoAssignment.ts b/testdata/tests/cases/compiler/cjsExportNamedClassExpressionNoAssignment.ts new file mode 100644 index 00000000000..66f9cf78d43 --- /dev/null +++ b/testdata/tests/cases/compiler/cjsExportNamedClassExpressionNoAssignment.ts @@ -0,0 +1,11 @@ +// @allowJs: true +// @checkJs: true +// @target: es2015 +// @outDir: ./out +// @declaration: true + +// @filename: index.js +// Named class expression exports without module.exports assignment. +// The class expression name B should be preserved via namespace isolation +// even without a module.exports = ... in the file. +module.exports.A = class B {f1 = 1; self = new B();} From d42388b295f3aa8e0c61bd2b7b52fe8512a7e2d1 Mon Sep 17 00:00:00 2001 From: Oleksandr Tarasiuk Date: Mon, 15 Jun 2026 20:52:50 +0300 Subject: [PATCH 27/49] fix(4315): preserve modifier order when emitting private js methods (#4318) --- .../transformers/declarations/transform.go | 4 +++- internal/transformers/declarations/util.go | 9 +++++++++ .../jsDeclarationEmitPrivateStaticMethod.js | 20 +++++++++++++++++++ ...DeclarationEmitPrivateStaticMethod.symbols | 15 ++++++++++++++ ...jsDeclarationEmitPrivateStaticMethod.types | 15 ++++++++++++++ .../jsDeclarationEmitPrivateStaticMethod.ts | 11 ++++++++++ 6 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 testdata/baselines/reference/compiler/jsDeclarationEmitPrivateStaticMethod.js create mode 100644 testdata/baselines/reference/compiler/jsDeclarationEmitPrivateStaticMethod.symbols create mode 100644 testdata/baselines/reference/compiler/jsDeclarationEmitPrivateStaticMethod.types create mode 100644 testdata/tests/cases/compiler/jsDeclarationEmitPrivateStaticMethod.ts diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go index 8eca614a5bf..2370dedf68d 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -2200,7 +2200,9 @@ func (tx *DeclarationTransformer) ensureModifiers(node *ast.Node) *ast.ModifierL if mods == nil { return mods } - return tx.Factory().NewModifierList(core.Filter(mods.Nodes, ast.IsModifier)) + if canReuseModifierNodes(mods.Nodes) { + return tx.Factory().NewModifierList(core.Filter(mods.Nodes, ast.IsModifier)) + } } result := ast.CreateModifiersFromModifierFlags(newFlags, tx.Factory().NewModifier) if len(result) == 0 { diff --git a/internal/transformers/declarations/util.go b/internal/transformers/declarations/util.go index 6e40cca0faa..99a12abad1c 100644 --- a/internal/transformers/declarations/util.go +++ b/internal/transformers/declarations/util.go @@ -49,6 +49,15 @@ func canProduceDiagnostics(node *ast.Node) bool { /* ast.IsJSDocTypeAlias(node); */ } +func canReuseModifierNodes(nodes []*ast.Node) bool { + for _, node := range nodes { + if ast.IsModifier(node) && node.Flags&ast.NodeFlagsReparsed != 0 { + return false + } + } + return true +} + func isDeclarationAndNotVisible(emitContext *printer.EmitContext, resolver printer.EmitResolver, node *ast.Node) bool { node = emitContext.ParseNode(node) switch node.Kind { diff --git a/testdata/baselines/reference/compiler/jsDeclarationEmitPrivateStaticMethod.js b/testdata/baselines/reference/compiler/jsDeclarationEmitPrivateStaticMethod.js new file mode 100644 index 00000000000..b0c05be34fa --- /dev/null +++ b/testdata/baselines/reference/compiler/jsDeclarationEmitPrivateStaticMethod.js @@ -0,0 +1,20 @@ +//// [tests/cases/compiler/jsDeclarationEmitPrivateStaticMethod.ts] //// + +//// [a.js] +export class C { + /** @private */ + static foo() {} + /** @protected */ + static bar() {} +} + + + + +//// [a.d.ts] +export declare class C { + /** @private */ + private static foo; + /** @protected */ + protected static bar(): void; +} diff --git a/testdata/baselines/reference/compiler/jsDeclarationEmitPrivateStaticMethod.symbols b/testdata/baselines/reference/compiler/jsDeclarationEmitPrivateStaticMethod.symbols new file mode 100644 index 00000000000..aea45a2b38b --- /dev/null +++ b/testdata/baselines/reference/compiler/jsDeclarationEmitPrivateStaticMethod.symbols @@ -0,0 +1,15 @@ +//// [tests/cases/compiler/jsDeclarationEmitPrivateStaticMethod.ts] //// + +=== a.js === +export class C { +>C : Symbol(C, Decl(a.js, 0, 0)) + + /** @private */ + static foo() {} +>foo : Symbol(C.foo, Decl(a.js, 0, 16)) + + /** @protected */ + static bar() {} +>bar : Symbol(C.bar, Decl(a.js, 2, 19)) +} + diff --git a/testdata/baselines/reference/compiler/jsDeclarationEmitPrivateStaticMethod.types b/testdata/baselines/reference/compiler/jsDeclarationEmitPrivateStaticMethod.types new file mode 100644 index 00000000000..cc91c256b90 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsDeclarationEmitPrivateStaticMethod.types @@ -0,0 +1,15 @@ +//// [tests/cases/compiler/jsDeclarationEmitPrivateStaticMethod.ts] //// + +=== a.js === +export class C { +>C : C + + /** @private */ + static foo() {} +>foo : () => void + + /** @protected */ + static bar() {} +>bar : () => void +} + diff --git a/testdata/tests/cases/compiler/jsDeclarationEmitPrivateStaticMethod.ts b/testdata/tests/cases/compiler/jsDeclarationEmitPrivateStaticMethod.ts new file mode 100644 index 00000000000..453fd0c4651 --- /dev/null +++ b/testdata/tests/cases/compiler/jsDeclarationEmitPrivateStaticMethod.ts @@ -0,0 +1,11 @@ +// @allowJs: true +// @checkJs: true +// @declaration: true +// @emitDeclarationOnly: true +// @filename: a.js +export class C { + /** @private */ + static foo() {} + /** @protected */ + static bar() {} +} From 59ae7d0bfba2e41bed78756858648d225ffa98c8 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Jun 2026 13:08:06 -0700 Subject: [PATCH 28/49] Add native preview Sort Imports commands (#4300) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> Co-authored-by: Daniel Rosenwasser --- _extension/package.json | 20 ++++++++++++++++++++ _extension/package.nls.json | 2 ++ _extension/src/session.ts | 14 ++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/_extension/package.json b/_extension/package.json index b9ef5eb088d..1ac0d5aa83b 100644 --- a/_extension/package.json +++ b/_extension/package.json @@ -153,6 +153,18 @@ "enablement": "typescript.native-preview.serverRunning", "category": "TypeScript Native Preview" }, + { + "command": "typescript.native-preview.sortImports", + "title": "%native-preview.sortImports.title%", + "enablement": "typescript.native-preview.serverRunning && (resourceLangId == typescript || resourceLangId == typescriptreact || resourceLangId == javascript || resourceLangId == javascriptreact)", + "category": "TypeScript Native Preview" + }, + { + "command": "typescript.native-preview.removeUnusedImports", + "title": "%native-preview.removeUnusedImports.title%", + "enablement": "typescript.native-preview.serverRunning && (resourceLangId == typescript || resourceLangId == typescriptreact || resourceLangId == javascript || resourceLangId == javascriptreact)", + "category": "TypeScript Native Preview" + }, { "command": "typescript.native-preview.codeLens.showLocations", "title": "%native-preview.codeLens.showLocations.title%", @@ -200,6 +212,14 @@ { "command": "typescript.native-preview.goToSourceDefinition", "when": "typescript.native-preview.serverRunning && tsSupportsSourceDefinition" + }, + { + "command": "typescript.native-preview.sortImports", + "when": "typescript.native-preview.serverRunning && supportedCodeAction =~ /(\\s|^)source\\.sortImports\\b/ && editorLangId =~ /^(typescript|javascript)(react)?$/" + }, + { + "command": "typescript.native-preview.removeUnusedImports", + "when": "typescript.native-preview.serverRunning && supportedCodeAction =~ /(\\s|^)source\\.removeUnusedImports\\b/ && editorLangId =~ /^(typescript|javascript)(react)?$/" } ], "editor/context": [ diff --git a/_extension/package.nls.json b/_extension/package.nls.json index 434e5452234..7b2379a2877 100644 --- a/_extension/package.nls.json +++ b/_extension/package.nls.json @@ -15,6 +15,8 @@ "native-preview.reportIssue.title": "Report Issue", "native-preview.selectVersion.title": "Select TypeScript Version...", "native-preview.goToSourceDefinition.title": "Go to Source Definition", + "native-preview.sortImports.title": "Sort Imports", + "native-preview.removeUnusedImports.title": "Remove Unused Imports", "native-preview.codeLens.showLocations.title": "Show References of CodeLens", "native-preview.dev.runGC.title": "TypeScript Native Preview: Run Garbage Collection", "native-preview.dev.saveHeapProfile.title": "TypeScript Native Preview: Save Heap Profile", diff --git a/_extension/src/session.ts b/_extension/src/session.ts index fe4b40d3ffe..c9f5c1ad0b5 100644 --- a/_extension/src/session.ts +++ b/_extension/src/session.ts @@ -158,6 +158,20 @@ class Session implements vscode.Disposable { showCommands(this.client); })); + this.disposables.push(vscode.commands.registerCommand("typescript.native-preview.sortImports", () => { + return vscode.commands.executeCommand("editor.action.sourceAction", { + kind: "source.sortImports", + apply: "first", + }); + })); + + this.disposables.push(vscode.commands.registerCommand("typescript.native-preview.removeUnusedImports", () => { + return vscode.commands.executeCommand("editor.action.sourceAction", { + kind: "source.removeUnusedImports", + apply: "first", + }); + })); + this.disposables.push(vscode.commands.registerCommand("typescript.native-preview.reportIssue", () => { this.telemetryReporter.sendTelemetryEvent("command.reportIssue"); vscode.commands.executeCommand("workbench.action.openIssueReporter", { From c90e1d44dd15cdeda4ef1b29a478d58840a87d11 Mon Sep 17 00:00:00 2001 From: Piotr Tomiak Date: Mon, 15 Jun 2026 22:35:33 +0200 Subject: [PATCH 29/49] API: remove `omitempty` from TypeResponse.Value to send empty string (#4320) --- .../native-preview/test/async/api.test.ts | 22 +++++++++++++++++++ .../native-preview/test/sync/api.test.ts | 22 +++++++++++++++++++ internal/api/proto.go | 2 +- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/_packages/native-preview/test/async/api.test.ts b/_packages/native-preview/test/async/api.test.ts index ae2fe5c7af9..83e9ccd02ad 100644 --- a/_packages/native-preview/test/async/api.test.ts +++ b/_packages/native-preview/test/async/api.test.ts @@ -2191,6 +2191,28 @@ describe("IntrinsicType - intrinsicName", () => { }); describe("FreshableType - getFreshType and getRegularType", () => { + test("LiteralType.value is empty string for the empty-string literal type", async () => { + const src = `\nexport const empty: "" = "";\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("empty:")); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + assert.ok(type.flags & TypeFlags.StringLiteral, "Expected StringLiteral"); + const literal = type as LiteralType; + assert.equal(literal.value, "", "value should be empty string, not undefined"); + } + finally { + await api.close(); + } + }); + test("LiteralType.value is accessible via the FreshableType hierarchy", async () => { const src = `\nexport const greeting: "hello" = "hello";\n`; const api = spawnAPI({ diff --git a/_packages/native-preview/test/sync/api.test.ts b/_packages/native-preview/test/sync/api.test.ts index 8aeaa64bdb1..f37e0dd1704 100644 --- a/_packages/native-preview/test/sync/api.test.ts +++ b/_packages/native-preview/test/sync/api.test.ts @@ -2199,6 +2199,28 @@ describe("IntrinsicType - intrinsicName", () => { }); describe("FreshableType - getFreshType and getRegularType", () => { + test("LiteralType.value is empty string for the empty-string literal type", () => { + const src = `\nexport const empty: "" = "";\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("empty:")); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + assert.ok(type.flags & TypeFlags.StringLiteral, "Expected StringLiteral"); + const literal = type as LiteralType; + assert.equal(literal.value, "", "value should be empty string, not undefined"); + } + finally { + api.close(); + } + }); + test("LiteralType.value is accessible via the FreshableType hierarchy", () => { const src = `\nexport const greeting: "hello" = "hello";\n`; const api = spawnAPI({ diff --git a/internal/api/proto.go b/internal/api/proto.go index 65051f202a4..869f64459db 100644 --- a/internal/api/proto.go +++ b/internal/api/proto.go @@ -499,7 +499,7 @@ type TypeResponse struct { ObjectFlags uint32 `json:"objectFlags,omitempty"` // LiteralType data - Value any `json:"value,omitempty"` + Value any `json:"value"` // ObjectType / TypeReference / StringMappingType / IndexType target Target TypeID `json:"target,omitempty"` From 7eb731a93bc57a5db4d4704bb72e6630441d15cb Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 15 Jun 2026 13:42:45 -0700 Subject: [PATCH 30/49] Store API node tables on SourceFile in data bag (#4327) Co-authored-by: Piotr Tomiak Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../native-preview/test/async/api.test.ts | 56 +++++++++++++++++++ .../native-preview/test/sync/api.test.ts | 56 +++++++++++++++++++ internal/api/encoder/encoder.go | 15 ++++- internal/api/session.go | 41 +++----------- internal/ast/ast.go | 51 +++++++++++++++++ 5 files changed, 185 insertions(+), 34 deletions(-) diff --git a/_packages/native-preview/test/async/api.test.ts b/_packages/native-preview/test/async/api.test.ts index 83e9ccd02ad..25bb31a1892 100644 --- a/_packages/native-preview/test/async/api.test.ts +++ b/_packages/native-preview/test/async/api.test.ts @@ -1,5 +1,6 @@ import { cast, + type Expression, getSynthesizedDeepClone, isCallExpression, isFunctionDeclaration, @@ -530,6 +531,61 @@ describe("Source file caching", () => { await api.close(); } }); + + test("node handles from a cached source file should be valid in a new snapshot", async () => { + const { api, fs } = spawnAPIWithFS({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": `function foo(x: number) {}\nfoo(42);`, + "/src/other.ts": `export const x = 1;`, + }); + try { + // Snapshot 1: get a node and verify getContextualType works + const snap1 = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const proj1 = snap1.getProject("/tsconfig.json")!; + + const sf1 = await proj1.program.getSourceFile("/src/main.ts"); + assert.ok(sf1); + + let numLiteral: Expression | undefined; + sf1.forEachChild(function visit(node) { + if (isCallExpression(node)) numLiteral = node.arguments[0]; + node.forEachChild(visit); + }); + assert.ok(numLiteral, "should find the 42 argument"); + + const type1 = await proj1.checker.getContextualType(numLiteral); + assert.ok(type1); + assert.ok(type1.flags & TypeFlags.Number); + + // Snapshot 2: change a different file + fs.writeFile!("/src/other.ts", `export const x = 2;`); + const snap2 = await api.updateSnapshot({ + fileChanges: { changed: ["/src/other.ts"] }, + }); + const proj2 = snap2.getProject("/tsconfig.json")!; + + // main.ts is unchanged — client returns the cached SourceFile (same object) + const sf2 = await proj2.program.getSourceFile("/src/main.ts"); + assert.ok(sf2); + assert.strictEqual(sf1, sf2, "unchanged file should be served from client cache"); + + let numLiteral2: Expression | undefined; + sf2.forEachChild(function visit(node) { + if (isCallExpression(node)) numLiteral2 = node.arguments[0]; + node.forEachChild(visit); + }); + assert.ok(numLiteral2, "should find the 42 argument"); + assert.strictEqual(numLiteral, numLiteral2, "unchanged file should be served from client cache"); + + // A type from new snapshot should be resolved + const type2 = await proj2.checker.getContextualType(numLiteral); + assert.ok(type2); + assert.ok(type2.flags & TypeFlags.Number); + } + finally { + await api.close(); + } + }); }); describe("Snapshot disposal", () => { diff --git a/_packages/native-preview/test/sync/api.test.ts b/_packages/native-preview/test/sync/api.test.ts index f37e0dd1704..5595ad0bfd9 100644 --- a/_packages/native-preview/test/sync/api.test.ts +++ b/_packages/native-preview/test/sync/api.test.ts @@ -8,6 +8,7 @@ // import { cast, + type Expression, getSynthesizedDeepClone, isCallExpression, isFunctionDeclaration, @@ -538,6 +539,61 @@ describe("Source file caching", () => { api.close(); } }); + + test("node handles from a cached source file should be valid in a new snapshot", () => { + const { api, fs } = spawnAPIWithFS({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": `function foo(x: number) {}\nfoo(42);`, + "/src/other.ts": `export const x = 1;`, + }); + try { + // Snapshot 1: get a node and verify getContextualType works + const snap1 = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const proj1 = snap1.getProject("/tsconfig.json")!; + + const sf1 = proj1.program.getSourceFile("/src/main.ts"); + assert.ok(sf1); + + let numLiteral: Expression | undefined; + sf1.forEachChild(function visit(node) { + if (isCallExpression(node)) numLiteral = node.arguments[0]; + node.forEachChild(visit); + }); + assert.ok(numLiteral, "should find the 42 argument"); + + const type1 = proj1.checker.getContextualType(numLiteral); + assert.ok(type1); + assert.ok(type1.flags & TypeFlags.Number); + + // Snapshot 2: change a different file + fs.writeFile!("/src/other.ts", `export const x = 2;`); + const snap2 = api.updateSnapshot({ + fileChanges: { changed: ["/src/other.ts"] }, + }); + const proj2 = snap2.getProject("/tsconfig.json")!; + + // main.ts is unchanged — client returns the cached SourceFile (same object) + const sf2 = proj2.program.getSourceFile("/src/main.ts"); + assert.ok(sf2); + assert.strictEqual(sf1, sf2, "unchanged file should be served from client cache"); + + let numLiteral2: Expression | undefined; + sf2.forEachChild(function visit(node) { + if (isCallExpression(node)) numLiteral2 = node.arguments[0]; + node.forEachChild(visit); + }); + assert.ok(numLiteral2, "should find the 42 argument"); + assert.strictEqual(numLiteral, numLiteral2, "unchanged file should be served from client cache"); + + // A type from new snapshot should be resolved + const type2 = proj2.checker.getContextualType(numLiteral); + assert.ok(type2); + assert.ok(type2.flags & TypeFlags.Number); + } + finally { + api.close(); + } + }); }); describe("Snapshot disposal", () => { diff --git a/internal/api/encoder/encoder.go b/internal/api/encoder/encoder.go index 8bd3e933e8f..b2818a146cc 100644 --- a/internal/api/encoder/encoder.go +++ b/internal/api/encoder/encoder.go @@ -307,6 +307,8 @@ type NodeIndexTable struct { sortedIdx []uint32 // indices into Nodes, sorted by node ID; built lazily } +var nodeIndexTableKey = ast.NewSourceFileDataKey[*NodeIndexTable]() + // GetIndex returns the encoder index for the given node. // On the first call the sortedIdx array is built (O(n log n) sort on a flat []uint32), // then subsequent calls use binary search (O(log n)). This turns out to be much faster than @@ -385,10 +387,21 @@ func BuildNodeIndexTable(sourceFile *ast.SourceFile) *NodeIndexTable { return &NodeIndexTable{Nodes: nodeTable} } +func GetNodeIndexTable(sourceFile *ast.SourceFile) *NodeIndexTable { + return ast.GetOrComputeSourceFileData(sourceFile, nodeIndexTableKey, BuildNodeIndexTable) +} + // EncodeSourceFile encodes an entire source file AST into the binary format. // Returns the encoded bytes and a NodeIndexTable mapping encoder indices to AST nodes. func EncodeSourceFile(sourceFile *ast.SourceFile) ([]byte, *NodeIndexTable, error) { - return encodeTree(sourceFile.AsNode(), sourceFile) + data, nodeTable, err := encodeTree(sourceFile.AsNode(), sourceFile) + if err != nil { + return nil, nil, err + } + nodeTable = ast.GetOrComputeSourceFileData(sourceFile, nodeIndexTableKey, func(*ast.SourceFile) *NodeIndexTable { + return nodeTable + }) + return data, nodeTable, nil } // EncodeNode encodes an arbitrary AST node and its descendants into the binary format. diff --git a/internal/api/session.go b/internal/api/session.go index 7652967c5b0..c897d1a92a2 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -45,12 +45,6 @@ type snapshotData struct { signatureRegistry map[SignatureID]*checker.Signature signatureNextID uint64 signatureRegistryMu sync.RWMutex - - // nodeTablesByPath maps file paths to node index tables used for index-based node handles. - // Tables are populated when a file is encoded (getSourceFile) and may also be built lazily - // on-demand when generating handles before getSourceFile is called. - nodeTablesByPath map[tspath.Path]*encoder.NodeIndexTable - nodeTablesByPathMu sync.RWMutex } // getProgram looks up a program from a project handle within this snapshot. @@ -74,22 +68,7 @@ func (sd *snapshotData) getProgram(projectHandle ProjectID) (*compiler.Program, func (sd *snapshotData) nodeHandleFrom(node *ast.Node) NodeHandle { sourceFile := ast.GetSourceFileOfNode(node) path := sourceFile.Path() - - sd.nodeTablesByPathMu.RLock() - table := sd.nodeTablesByPath[path] - sd.nodeTablesByPathMu.RUnlock() - - if table == nil { - // Eagerly build node index table for this file. - newTable := encoder.BuildNodeIndexTable(sourceFile) - sd.nodeTablesByPathMu.Lock() - if sd.nodeTablesByPath[path] == nil { - sd.nodeTablesByPath[path] = newTable - } - table = sd.nodeTablesByPath[path] - sd.nodeTablesByPathMu.Unlock() - } - + table := encoder.GetNodeIndexTable(sourceFile) idx := table.GetIndex(node) return NodeHandle(fmt.Sprintf("%d.%d.%s", idx, node.Kind, path)) } @@ -636,7 +615,6 @@ func (s *Session) handleUpdateSnapshot(ctx context.Context, params *UpdateSnapsh symbolRegistry: make(map[SymbolID]*ast.Symbol), typeRegistry: make(map[TypeID]*checker.Type), signatureRegistry: make(map[SignatureID]*checker.Signature), - nodeTablesByPath: make(map[tspath.Path]*encoder.NodeIndexTable), } s.snapshots[handle] = sd } @@ -762,16 +740,11 @@ func (s *Session) handleGetSourceFile(ctx context.Context, params *GetSourceFile } // Encode the full source file - data, nodeTable, err := encoder.EncodeSourceFile(sourceFile) + data, _, err := encoder.EncodeSourceFile(sourceFile) if err != nil { return nil, fmt.Errorf("failed to encode source file: %w", err) } - // Store the node table for O(1) handle resolution - sd.nodeTablesByPathMu.Lock() - sd.nodeTablesByPath[sourceFile.Path()] = nodeTable - sd.nodeTablesByPathMu.Unlock() - // Return raw binary for msgpack protocol, or base64 for JSON if s.useBinaryResponses { return RawBinary(data), nil @@ -2008,9 +1981,11 @@ func (sd *snapshotData) resolveNodeHandle(program *compiler.Program, handle Node } path := tspath.Path(s[secondDot+1:]) - sd.nodeTablesByPathMu.RLock() - table := sd.nodeTablesByPath[path] - sd.nodeTablesByPathMu.RUnlock() + sourceFile := program.GetSourceFileByPath(path) + if sourceFile == nil { + return nil, fmt.Errorf("%w: node handle %q could not be resolved (file may not be loaded or handle may be stale)", ErrClientError, handle) + } + table := encoder.GetNodeIndexTable(sourceFile) if table != nil && idx < uint64(len(table.Nodes)) { node := table.Nodes[idx] @@ -2018,7 +1993,7 @@ func (sd *snapshotData) resolveNodeHandle(program *compiler.Program, handle Node return node, nil } } - return nil, fmt.Errorf("%w: node handle %q could not be resolved (file may not be loaded)", ErrClientError, handle) + return nil, fmt.Errorf("%w: node handle %q could not be resolved (file may not be loaded or handle may be stale)", ErrClientError, handle) } // computeSnapshotChanges computes the per-project source file differences between diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 863116f16ce..758ad9ee62b 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -2402,6 +2402,53 @@ type SourceFileMetaData struct { ImpliedNodeFormat core.ResolutionMode } +// SourceFileDataKey identifies lazily-computed data attached to a SourceFile by +// another package. Prefer regular SourceFile fields for ast-owned data. +type SourceFileDataKey[T any] struct { + key sourceFileDataKey + _ [0]T +} + +type sourceFileDataKey uint64 + +var sourceFileDataKeyCounter atomic.Uint64 + +type sourceFileDataCell[T any] struct { + once sync.Once + value T +} + +func NewSourceFileDataKey[T any]() *SourceFileDataKey[T] { + return &SourceFileDataKey[T]{key: sourceFileDataKey(sourceFileDataKeyCounter.Add(1))} +} + +func GetOrComputeSourceFileData[T any](file *SourceFile, key *SourceFileDataKey[T], compute func(*SourceFile) T) T { + cell := getSourceFileDataCell(file, key) + cell.once.Do(func() { + cell.value = compute(file) + }) + return cell.value +} + +func getSourceFileDataCell[T any](file *SourceFile, key *SourceFileDataKey[T]) *sourceFileDataCell[T] { + if key == nil || key.key == 0 { + panic("invalid SourceFileDataKey; use NewSourceFileDataKey") + } + + file.dataMu.Lock() + defer file.dataMu.Unlock() + + if file.data == nil { + file.data = make(map[sourceFileDataKey]any) + } + if cell, ok := file.data[key.key]; ok { + return cell.(*sourceFileDataCell[T]) + } + cell := &sourceFileDataCell[T]{} + file.data[key.key] = cell + return cell +} + type CheckJsDirective struct { Enabled bool Range CommentRange @@ -2430,6 +2477,10 @@ type SourceFile struct { Statements *NodeList // NodeList[*Statement] EndOfFileToken *TokenNode // TokenNode[*EndOfFileToken] + // Fields for lazily-computed data owned by packages outside ast. + dataMu sync.Mutex + data map[sourceFileDataKey]any + // Fields set by parser diagnostics []*Diagnostic jsDiagnostics []*Diagnostic From dd6bceb4d5b5bf4f4045b0f22e471dee2b51058a Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 15 Jun 2026 17:09:20 -0700 Subject: [PATCH 31/49] Fix auto-import crash from unimplemented aliasResolver.IsSourceFileDefaultLibrary (#4323) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com> --- internal/checker/relater.go | 3 + internal/ls/autoimport/aliasresolver.go | 2 +- .../ls/autoimport/aliasresolver_crash_test.go | 73 +++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 internal/ls/autoimport/aliasresolver_crash_test.go diff --git a/internal/checker/relater.go b/internal/checker/relater.go index 4a18da82176..e649e25b10b 100644 --- a/internal/checker/relater.go +++ b/internal/checker/relater.go @@ -441,6 +441,9 @@ func (c *Checker) elaborateError(node *ast.Node, source *Type, target *Type, rel if node == nil || c.isOrHasGenericConditional(target) { return false } + if c.compilerOptions.NoCheck.IsTrue() { + return false + } if c.elaborateDidYouMeanToCallOrConstruct(node, source, target, relation, SignatureKindConstruct, headMessage, diagnosticOutput) || c.elaborateDidYouMeanToCallOrConstruct(node, source, target, relation, SignatureKindCall, headMessage, diagnosticOutput) { return true diff --git a/internal/ls/autoimport/aliasresolver.go b/internal/ls/autoimport/aliasresolver.go index e34b6282255..9da2eeb4ec9 100644 --- a/internal/ls/autoimport/aliasresolver.go +++ b/internal/ls/autoimport/aliasresolver.go @@ -216,7 +216,7 @@ func (r *aliasResolver) GetSourceOfProjectReferenceIfOutputIncluded(file ast.Has // IsSourceFileDefaultLibrary implements checker.Program. func (r *aliasResolver) IsSourceFileDefaultLibrary(path tspath.Path) bool { - panic("unimplemented") + return false } // IsSourceFromProjectReference implements checker.Program. diff --git a/internal/ls/autoimport/aliasresolver_crash_test.go b/internal/ls/autoimport/aliasresolver_crash_test.go new file mode 100644 index 00000000000..897261c15d2 --- /dev/null +++ b/internal/ls/autoimport/aliasresolver_crash_test.go @@ -0,0 +1,73 @@ +package autoimport + +import ( + "context" + "testing" + + "github.com/microsoft/typescript-go/internal/ast" + "github.com/microsoft/typescript-go/internal/binder" + "github.com/microsoft/typescript-go/internal/checker" + "github.com/microsoft/typescript-go/internal/compiler" + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/module" + "github.com/microsoft/typescript-go/internal/packagejson" + "github.com/microsoft/typescript-go/internal/parser" + "github.com/microsoft/typescript-go/internal/tspath" + "github.com/microsoft/typescript-go/internal/vfs" + "github.com/microsoft/typescript-go/internal/vfs/vfstest" +) + +type fakeCloneHost struct { + fs vfs.FS +} + +func (h *fakeCloneHost) FS() vfs.FS { return h.fs } +func (h *fakeCloneHost) GetCurrentDirectory() string { return "/" } +func (h *fakeCloneHost) GetDefaultProject(path tspath.Path) (tspath.Path, *compiler.Program) { + return "", nil +} + +func (h *fakeCloneHost) GetProgramForProject(projectPath tspath.Path) *compiler.Program { return nil } + +func (h *fakeCloneHost) GetPackageJson(fileName string) *packagejson.InfoCacheEntry { return nil } + +func (h *fakeCloneHost) GetSourceFile(fileName string, path tspath.Path) *ast.SourceFile { return nil } +func (h *fakeCloneHost) Dispose() {} + +var _ RegistryCloneHost = (*fakeCloneHost)(nil) + +// Regression test for microsoft/typescript-go#4322. +// +// During auto-import export extraction, the checker is built on top of an +// aliasResolver standing in for a real program. This file has a type error, and +// extracting exports should still complete without crashing. +func TestAliasResolverGetDiagnosticsDoesNotPanic(t *testing.T) { + t.Parallel() + + const fileName = "/pkg/index.ts" + text := "declare function f(arg: { a: string }): () => void;\nexport const x = f({ a: 1 });\n" + + fs := vfstest.FromMap(map[string]string{fileName: text}, true /*useCaseSensitiveFileNames*/) + host := &fakeCloneHost{fs: fs} + + sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{ + FileName: fileName, + Path: tspath.Path(fileName), + }, text, core.ScriptKindTS) + binder.BindSourceFile(sourceFile) + + resolver := module.NewResolver(host, core.EmptyCompilerOptions, "", "") + r := newAliasResolver( + []*ast.SourceFile{sourceFile}, + nil, + host, + resolver, + func(f string) tspath.Path { return tspath.Path(f) }, + func(ast.HasFileName, string) {}, + ) + + ch, _ := checker.NewChecker(r, nil) + + // Type-checking this file's diagnostics must not panic. + ch.GetDiagnostics(context.Background(), sourceFile) +} From 462a1a4f4944a35d19797f9674f97ace28d17fc4 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 15 Jun 2026 21:34:29 -0700 Subject: [PATCH 32/49] Fix redundant undefined wrapping in declaration emit (#4302) --- internal/checker/nodebuilderimpl.go | 32 ++----- internal/checker/pseudotypenodebuilder.go | 19 +++- internal/pseudochecker/lookup.go | 8 +- ...clarationEmitOptionalParameterUndefined.js | 58 +++++++++++ ...tionEmitOptionalParameterUndefined.symbols | 89 +++++++++++++++++ ...rationEmitOptionalParameterUndefined.types | 95 +++++++++++++++++++ ...clarationEmitOptionalParameterUndefined.ts | 37 ++++++++ 7 files changed, 309 insertions(+), 29 deletions(-) create mode 100644 testdata/baselines/reference/compiler/declarationEmitOptionalParameterUndefined.js create mode 100644 testdata/baselines/reference/compiler/declarationEmitOptionalParameterUndefined.symbols create mode 100644 testdata/baselines/reference/compiler/declarationEmitOptionalParameterUndefined.types create mode 100644 testdata/tests/cases/compiler/declarationEmitOptionalParameterUndefined.ts diff --git a/internal/checker/nodebuilderimpl.go b/internal/checker/nodebuilderimpl.go index 61aac48c694..188da07aa6e 100644 --- a/internal/checker/nodebuilderimpl.go +++ b/internal/checker/nodebuilderimpl.go @@ -477,30 +477,6 @@ func (b *NodeBuilderImpl) setCommentRange(node *ast.Node, range_ *ast.Node) { } } -func (b *NodeBuilderImpl) tryReuseExistingTypeNode(typeNode *ast.TypeNode, t *Type, host *ast.Node, addUndefined bool) *ast.TypeNode { - originalType := t - if addUndefined { - t = b.ch.getOptionalType(t, !ast.IsParameterDeclaration(host)) - } - clone := b.tryReuseExistingNonParameterTypeNode(typeNode, t, host, nil) - if clone != nil { - // explicitly add `| undefined` if it's missing from the input type nodes and the type contains `undefined` (and not the missing type) - if addUndefined && containsNonMissingUndefinedType(b.ch, t) && !someType(b.getTypeFromTypeNode(typeNode, false), func(t *Type) bool { - return t.flags&TypeFlagsUndefined != 0 - }) { - return b.f.NewUnionTypeNode(b.f.NewNodeList([]*ast.TypeNode{clone, b.f.NewKeywordTypeNode(ast.KindUndefinedKeyword)})) - } - return clone - } - if addUndefined && originalType != t { - cloneMissingUndefined := b.tryReuseExistingNonParameterTypeNode(typeNode, originalType, host, nil) - if cloneMissingUndefined != nil { - return b.f.NewUnionTypeNode(b.f.NewNodeList([]*ast.TypeNode{cloneMissingUndefined, b.f.NewKeywordTypeNode(ast.KindUndefinedKeyword)})) - } - } - return nil -} - func (b *NodeBuilderImpl) typeNodeIsEquivalentToType(annotatedDeclaration *ast.Node, t *Type, typeFromTypeNode *Type) bool { if typeFromTypeNode == t { return true @@ -2248,7 +2224,15 @@ func (b *NodeBuilderImpl) serializeTypeForDeclaration(declaration *ast.Declarati // typeToTypeNode serialization (mirroring the suppression that // pseudoTypeToNodeWithCheckerFallback provides). reportedInferenceFallback = reportErrors && pt.Kind == pseudochecker.PseudoTypeKindInferred && len(pt.AsPseudoTypeInferred().ErrorNodes) > 0 + shouldAddUndefined := false if requiresAddingUndefined { + if ptt := b.pseudoTypeToType(pt); ptt != nil { + shouldAddUndefined = !containsNonMissingUndefinedType(b.ch, ptt) + } else { + shouldAddUndefined = !pseudochecker.CouldAlreadyReferToUndefinedType(pt) + } + } + if shouldAddUndefined { pt = pseudochecker.NewPseudoTypeUnion([]*pseudochecker.PseudoType{pt, pseudochecker.PseudoTypeUndefined}) if b.pseudoTypeEquivalentToType(pt, t, false, reportErrors) { result = b.pseudoTypeToNodeWithCheckerFallback(pt, t) diff --git a/internal/checker/pseudotypenodebuilder.go b/internal/checker/pseudotypenodebuilder.go index 07dc4c0cb4e..a08187c2478 100644 --- a/internal/checker/pseudotypenodebuilder.go +++ b/internal/checker/pseudotypenodebuilder.go @@ -110,7 +110,24 @@ func (b *NodeBuilderImpl) pseudoTypeToNode(t *pseudochecker.PseudoType) *ast.Nod case pseudochecker.PseudoTypeKindUnion: var res []*ast.Node var hasElidedType bool + var hasUndefined bool members := t.AsPseudoTypeUnion().Types + var appendTypeNode func(node *ast.Node) + appendTypeNode = func(node *ast.Node) { + if ast.IsUnionTypeNode(node) { + for _, node := range node.AsUnionTypeNode().Types.Nodes { + appendTypeNode(node) + } + return + } + if node.Kind == ast.KindUndefinedKeyword { + if hasUndefined { + return + } + hasUndefined = true + } + res = append(res, node) + } for _, m := range members { if !b.ch.strictNullChecks { if m.Kind == pseudochecker.PseudoTypeKindUndefined || m.Kind == pseudochecker.PseudoTypeKindNull { @@ -118,7 +135,7 @@ func (b *NodeBuilderImpl) pseudoTypeToNode(t *pseudochecker.PseudoType) *ast.Nod continue } } - res = append(res, b.pseudoTypeToNode(m)) + appendTypeNode(b.pseudoTypeToNode(m)) } if len(res) == 1 { return res[0] diff --git a/internal/pseudochecker/lookup.go b/internal/pseudochecker/lookup.go index 0bbd9942043..40be9a5cc02 100644 --- a/internal/pseudochecker/lookup.go +++ b/internal/pseudochecker/lookup.go @@ -582,13 +582,13 @@ func typeNodeCouldReferToUndefined(node *ast.Node) bool { } // see this as the inverse of `canAddUndefined` in `expressionToTypeNode` in strada -func couldAlreadyReferToUndefinedType(t *PseudoType) bool { +func CouldAlreadyReferToUndefinedType(t *PseudoType) bool { if t.Kind == PseudoTypeKindNoResult || t.Kind == PseudoTypeKindInferred || isUndefinedPseudoType(t) { return true } if t.Kind == PseudoTypeKindMaybeConstLocation { mc := t.AsPseudoTypeMaybeConstLocation() - return couldAlreadyReferToUndefinedType(mc.RegularType) // if we're even asking this question, it's not a `const` location + return CouldAlreadyReferToUndefinedType(mc.RegularType) // if we're even asking this question, it's not a `const` location } if t.Kind == PseudoTypeKindDirect { // inspect the direct type node @@ -596,7 +596,7 @@ func couldAlreadyReferToUndefinedType(t *PseudoType) bool { return typeNodeCouldReferToUndefined(node) } if t.Kind == PseudoTypeKindUnion { - return core.Some(t.AsPseudoTypeUnion().Types, couldAlreadyReferToUndefinedType) + return core.Some(t.AsPseudoTypeUnion().Types, CouldAlreadyReferToUndefinedType) } return false } @@ -628,7 +628,7 @@ func addUndefinedIfDefinitelyRequired(expr *PseudoType) *PseudoType { // in Strada, this reached into the checker to see if `undefined` was necessary, using `isRequiredOptionalParameter` from the emit resolver, // but that's not required on top of the syntactic checks to get the same behavior. (If we get the type wrong, it'll mismatch later and be discarded // for an inference error since corsa actually validates that pseudotypes semantically match the inferred type the checker produces) - if couldAlreadyReferToUndefinedType(expr) { + if CouldAlreadyReferToUndefinedType(expr) { return expr // will just error later, more like than not, unless the `undefined` is explicit in the pseudo } // Explicitly add an `| undefined` diff --git a/testdata/baselines/reference/compiler/declarationEmitOptionalParameterUndefined.js b/testdata/baselines/reference/compiler/declarationEmitOptionalParameterUndefined.js new file mode 100644 index 00000000000..b356386aadf --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitOptionalParameterUndefined.js @@ -0,0 +1,58 @@ +//// [tests/cases/compiler/declarationEmitOptionalParameterUndefined.ts] //// + +//// [declarationEmitOptionalParameterUndefined.ts] +export function simple_primitive( + foo: number | boolean | null = 2, + _: string, +) {} + +export function simple_primitive_with_explicit_undefined( + foo: number | boolean | null | undefined = 2, + _: string, +) {} + +export function simple_nonPrimitive( + foo: number | RegExp | null = 2, + _: string, +) {} + +export function simple_nonPrimitive_with_explicit_undefined( + foo: number | RegExp | null | undefined = 2, + _: string, +) {} + +export function curry( + foo: number | RegExp | null = 2, + _: string, +) { + return (bar = foo, _: string) => (buzz = bar, _: string) => {} +} + +export function curry_with_explicit_undefined( + foo: number | RegExp | null | undefined = 2, + _: string, +) { + return (bar = foo, _: string) => (buzz = bar, _: string) => {} +} + + +//// [declarationEmitOptionalParameterUndefined.js] +export function simple_primitive(foo = 2, _) { } +export function simple_primitive_with_explicit_undefined(foo = 2, _) { } +export function simple_nonPrimitive(foo = 2, _) { } +export function simple_nonPrimitive_with_explicit_undefined(foo = 2, _) { } +export function curry(foo = 2, _) { + return (bar = foo, _) => (buzz = bar, _) => { }; +} +export function curry_with_explicit_undefined(foo = 2, _) { + return (bar = foo, _) => (buzz = bar, _) => { }; +} + + +//// [declarationEmitOptionalParameterUndefined.d.ts] +export declare function simple_primitive(foo: number | boolean | null | undefined, _: string): void; +export declare function simple_primitive_with_explicit_undefined(foo: number | boolean | null | undefined, _: string): void; +export declare function simple_nonPrimitive(foo: number | RegExp | null | undefined, _: string): void; +export declare function simple_nonPrimitive_with_explicit_undefined(foo: number | RegExp | null | undefined, _: string): void; +export declare function curry(foo: number | RegExp | null | undefined, _: string): (bar: number | RegExp | null | undefined, _: string) => (buzz: number | RegExp | null | undefined, _: string) => void; +export declare function curry_with_explicit_undefined(foo: number | RegExp | null | undefined, _: string): (bar: number | RegExp | null | undefined, _: string) => (buzz: number | RegExp | null | undefined, _: string) => void; diff --git a/testdata/baselines/reference/compiler/declarationEmitOptionalParameterUndefined.symbols b/testdata/baselines/reference/compiler/declarationEmitOptionalParameterUndefined.symbols new file mode 100644 index 00000000000..397de8d6afc --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitOptionalParameterUndefined.symbols @@ -0,0 +1,89 @@ +//// [tests/cases/compiler/declarationEmitOptionalParameterUndefined.ts] //// + +=== declarationEmitOptionalParameterUndefined.ts === +export function simple_primitive( +>simple_primitive : Symbol(simple_primitive, Decl(declarationEmitOptionalParameterUndefined.ts, 0, 0)) + + foo: number | boolean | null = 2, +>foo : Symbol(foo, Decl(declarationEmitOptionalParameterUndefined.ts, 0, 33)) + + _: string, +>_ : Symbol(_, Decl(declarationEmitOptionalParameterUndefined.ts, 1, 37)) + +) {} + +export function simple_primitive_with_explicit_undefined( +>simple_primitive_with_explicit_undefined : Symbol(simple_primitive_with_explicit_undefined, Decl(declarationEmitOptionalParameterUndefined.ts, 3, 4)) + + foo: number | boolean | null | undefined = 2, +>foo : Symbol(foo, Decl(declarationEmitOptionalParameterUndefined.ts, 5, 57)) + + _: string, +>_ : Symbol(_, Decl(declarationEmitOptionalParameterUndefined.ts, 6, 49)) + +) {} + +export function simple_nonPrimitive( +>simple_nonPrimitive : Symbol(simple_nonPrimitive, Decl(declarationEmitOptionalParameterUndefined.ts, 8, 4)) + + foo: number | RegExp | null = 2, +>foo : Symbol(foo, Decl(declarationEmitOptionalParameterUndefined.ts, 10, 36)) +>RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.regexp.d.ts, --, --) ... and 3 more) + + _: string, +>_ : Symbol(_, Decl(declarationEmitOptionalParameterUndefined.ts, 11, 36)) + +) {} + +export function simple_nonPrimitive_with_explicit_undefined( +>simple_nonPrimitive_with_explicit_undefined : Symbol(simple_nonPrimitive_with_explicit_undefined, Decl(declarationEmitOptionalParameterUndefined.ts, 13, 4)) + + foo: number | RegExp | null | undefined = 2, +>foo : Symbol(foo, Decl(declarationEmitOptionalParameterUndefined.ts, 15, 60)) +>RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.regexp.d.ts, --, --) ... and 3 more) + + _: string, +>_ : Symbol(_, Decl(declarationEmitOptionalParameterUndefined.ts, 16, 48)) + +) {} + +export function curry( +>curry : Symbol(curry, Decl(declarationEmitOptionalParameterUndefined.ts, 18, 4)) + + foo: number | RegExp | null = 2, +>foo : Symbol(foo, Decl(declarationEmitOptionalParameterUndefined.ts, 20, 22)) +>RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.regexp.d.ts, --, --) ... and 3 more) + + _: string, +>_ : Symbol(_, Decl(declarationEmitOptionalParameterUndefined.ts, 21, 36)) + +) { + return (bar = foo, _: string) => (buzz = bar, _: string) => {} +>bar : Symbol(bar, Decl(declarationEmitOptionalParameterUndefined.ts, 24, 12)) +>foo : Symbol(foo, Decl(declarationEmitOptionalParameterUndefined.ts, 20, 22)) +>_ : Symbol(_, Decl(declarationEmitOptionalParameterUndefined.ts, 24, 22)) +>buzz : Symbol(buzz, Decl(declarationEmitOptionalParameterUndefined.ts, 24, 38)) +>bar : Symbol(bar, Decl(declarationEmitOptionalParameterUndefined.ts, 24, 12)) +>_ : Symbol(_, Decl(declarationEmitOptionalParameterUndefined.ts, 24, 49)) +} + +export function curry_with_explicit_undefined( +>curry_with_explicit_undefined : Symbol(curry_with_explicit_undefined, Decl(declarationEmitOptionalParameterUndefined.ts, 25, 1)) + + foo: number | RegExp | null | undefined = 2, +>foo : Symbol(foo, Decl(declarationEmitOptionalParameterUndefined.ts, 27, 46)) +>RegExp : Symbol(RegExp, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.core.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.regexp.d.ts, --, --) ... and 3 more) + + _: string, +>_ : Symbol(_, Decl(declarationEmitOptionalParameterUndefined.ts, 28, 48)) + +) { + return (bar = foo, _: string) => (buzz = bar, _: string) => {} +>bar : Symbol(bar, Decl(declarationEmitOptionalParameterUndefined.ts, 31, 12)) +>foo : Symbol(foo, Decl(declarationEmitOptionalParameterUndefined.ts, 27, 46)) +>_ : Symbol(_, Decl(declarationEmitOptionalParameterUndefined.ts, 31, 22)) +>buzz : Symbol(buzz, Decl(declarationEmitOptionalParameterUndefined.ts, 31, 38)) +>bar : Symbol(bar, Decl(declarationEmitOptionalParameterUndefined.ts, 31, 12)) +>_ : Symbol(_, Decl(declarationEmitOptionalParameterUndefined.ts, 31, 49)) +} + diff --git a/testdata/baselines/reference/compiler/declarationEmitOptionalParameterUndefined.types b/testdata/baselines/reference/compiler/declarationEmitOptionalParameterUndefined.types new file mode 100644 index 00000000000..f37b38ea360 --- /dev/null +++ b/testdata/baselines/reference/compiler/declarationEmitOptionalParameterUndefined.types @@ -0,0 +1,95 @@ +//// [tests/cases/compiler/declarationEmitOptionalParameterUndefined.ts] //// + +=== declarationEmitOptionalParameterUndefined.ts === +export function simple_primitive( +>simple_primitive : (foo: number | boolean | null | undefined, _: string) => void + + foo: number | boolean | null = 2, +>foo : number | boolean | null +>2 : 2 + + _: string, +>_ : string + +) {} + +export function simple_primitive_with_explicit_undefined( +>simple_primitive_with_explicit_undefined : (foo: number | boolean | null | undefined, _: string) => void + + foo: number | boolean | null | undefined = 2, +>foo : number | boolean | null | undefined +>2 : 2 + + _: string, +>_ : string + +) {} + +export function simple_nonPrimitive( +>simple_nonPrimitive : (foo: number | RegExp | null | undefined, _: string) => void + + foo: number | RegExp | null = 2, +>foo : number | RegExp | null +>2 : 2 + + _: string, +>_ : string + +) {} + +export function simple_nonPrimitive_with_explicit_undefined( +>simple_nonPrimitive_with_explicit_undefined : (foo: number | RegExp | null | undefined, _: string) => void + + foo: number | RegExp | null | undefined = 2, +>foo : number | RegExp | null | undefined +>2 : 2 + + _: string, +>_ : string + +) {} + +export function curry( +>curry : (foo: number | RegExp | null | undefined, _: string) => (bar: number | RegExp | null | undefined, _: string) => (buzz: number | RegExp | null | undefined, _: string) => void + + foo: number | RegExp | null = 2, +>foo : number | RegExp | null +>2 : 2 + + _: string, +>_ : string + +) { + return (bar = foo, _: string) => (buzz = bar, _: string) => {} +>(bar = foo, _: string) => (buzz = bar, _: string) => {} : (bar: number | RegExp | null | undefined, _: string) => (buzz: number | RegExp | null | undefined, _: string) => void +>bar : number | RegExp | null +>foo : number | RegExp | null +>_ : string +>(buzz = bar, _: string) => {} : (buzz: number | RegExp | null | undefined, _: string) => void +>buzz : number | RegExp | null +>bar : number | RegExp | null +>_ : string +} + +export function curry_with_explicit_undefined( +>curry_with_explicit_undefined : (foo: number | RegExp | null | undefined, _: string) => (bar: number | RegExp | null | undefined, _: string) => (buzz: number | RegExp | null | undefined, _: string) => void + + foo: number | RegExp | null | undefined = 2, +>foo : number | RegExp | null | undefined +>2 : 2 + + _: string, +>_ : string + +) { + return (bar = foo, _: string) => (buzz = bar, _: string) => {} +>(bar = foo, _: string) => (buzz = bar, _: string) => {} : (bar: number | RegExp | null | undefined, _: string) => (buzz: number | RegExp | null | undefined, _: string) => void +>bar : number | RegExp | null +>foo : number | RegExp | null +>_ : string +>(buzz = bar, _: string) => {} : (buzz: number | RegExp | null | undefined, _: string) => void +>buzz : number | RegExp | null +>bar : number | RegExp | null +>_ : string +} + diff --git a/testdata/tests/cases/compiler/declarationEmitOptionalParameterUndefined.ts b/testdata/tests/cases/compiler/declarationEmitOptionalParameterUndefined.ts new file mode 100644 index 00000000000..d541c9fd918 --- /dev/null +++ b/testdata/tests/cases/compiler/declarationEmitOptionalParameterUndefined.ts @@ -0,0 +1,37 @@ +// @declaration: true +// @strict: true +// @stableTypeOrdering: true + +export function simple_primitive( + foo: number | boolean | null = 2, + _: string, +) {} + +export function simple_primitive_with_explicit_undefined( + foo: number | boolean | null | undefined = 2, + _: string, +) {} + +export function simple_nonPrimitive( + foo: number | RegExp | null = 2, + _: string, +) {} + +export function simple_nonPrimitive_with_explicit_undefined( + foo: number | RegExp | null | undefined = 2, + _: string, +) {} + +export function curry( + foo: number | RegExp | null = 2, + _: string, +) { + return (bar = foo, _: string) => (buzz = bar, _: string) => {} +} + +export function curry_with_explicit_undefined( + foo: number | RegExp | null | undefined = 2, + _: string, +) { + return (bar = foo, _: string) => (buzz = bar, _: string) => {} +} From 6894935479f7e424ad93b55be0499e06f35ccaee Mon Sep 17 00:00:00 2001 From: Piotr Tomiak Date: Tue, 16 Jun 2026 18:21:00 +0200 Subject: [PATCH 33/49] API: add Signature implementation and improve object caching on client side (#4312) --- _packages/native-preview/src/api/async/api.ts | 257 ++++--- .../native-preview/src/api/objectRegistry.ts | 20 +- _packages/native-preview/src/api/proto.ts | 2 + _packages/native-preview/src/api/sync/api.ts | 257 ++++--- .../native-preview/test/async/api.test.ts | 159 ++++- .../native-preview/test/sync/api.test.ts | 159 ++++- internal/api/proto.go | 153 +++-- internal/api/session.go | 642 ++++++++++-------- internal/checker/checker.go | 3 + internal/checker/types.go | 10 +- 10 files changed, 1117 insertions(+), 545 deletions(-) diff --git a/_packages/native-preview/src/api/async/api.ts b/_packages/native-preview/src/api/async/api.ts index 5c87a9dda4a..711a191553e 100644 --- a/_packages/native-preview/src/api/async/api.ts +++ b/_packages/native-preview/src/api/async/api.ts @@ -31,7 +31,10 @@ import { readSourceFileHash, RemoteSourceFile, } from "../node/node.ts"; -import { ObjectRegistry } from "../objectRegistry.ts"; +import { + type ObjectFactories, + ObjectRegistry, +} from "../objectRegistry.ts"; import type { APIOptions, LSPConnectionOptions, @@ -101,9 +104,6 @@ export type { APIOptions, ClientSocketOptions, ClientSpawnOptions, DocumentIdent export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts"; -/** Type alias for the snapshot-scoped object registry */ -type SnapshotObjectRegistry = ObjectRegistry; - export class API { private client: Client; private sourceFileCache: SourceFileCache; @@ -248,11 +248,15 @@ export class Snapshot { this.toPath = toPath; this.onDispose = onDispose; - this.objectRegistry = new ObjectRegistry({ - createSymbol: symbolData => new Symbol(symbolData, this.client, this.id, this.objectRegistry), - createType: typeData => new TypeObject(typeData, this.client, this.id, this.objectRegistry), - createSignature: sigData => new Signature(sigData, this.objectRegistry), - }); + this.objectRegistry = new SnapshotObjectRegistry( + { + createSymbol: symbolData => new Symbol(symbolData, this.objectRegistry), + createType: typeData => new TypeObject(typeData, this.objectRegistry), + createSignature: sigData => new Signature(sigData, this.objectRegistry), + }, + client, + this.id, + ); // Create projects this.projectMap = new Map(); @@ -305,6 +309,104 @@ export class Snapshot { } } +class SnapshotObjectRegistry extends ObjectRegistry { + private client: Client; + private snapshotId: number; + + constructor( + factories: ObjectFactories, + client: Client, + snapshotId: number, + ) { + super(factories); + this.client = client; + this.snapshotId = snapshotId; + } + + async fetchType(source: Symbol | Signature | Type, method: string, handle: number | undefined): Promise { + if (!handle) return undefined as unknown as T; + const cached = this.getType(handle); + if (cached) return cached as unknown as T; + + const data = await this.client.apiRequest(method, { + snapshot: this.snapshotId, + objectId: source.id, + }); + if (!data) throw new Error(`${method} returned null type for ${source.constructor.name} ${source.id}`); + return this.getOrCreateType(data) as unknown as T; + } + + async fetchSymbol(source: Symbol | Signature | Type, method: string, handle: number | undefined): Promise { + if (!handle) return undefined as unknown as Symbol; + const cached = this.getSymbol(handle); + if (cached) return cached; + + const data = await this.client.apiRequest(method, { + snapshot: this.snapshotId, + objectId: source.id, + }); + if (!data) throw new Error(`${method} returned null symbol for ${source.constructor.name} ${source.id}`); + return this.getOrCreateSymbol(data); + } + + async fetchSignature(source: Symbol | Signature | Type, method: string, handle: number | undefined): Promise { + if (!handle) return undefined as unknown as Signature; + const cached = this.getSignature(handle); + if (cached) return cached; + + const data = await this.client.apiRequest(method, { + snapshot: this.snapshotId, + objectId: source.id, + }); + if (!data) throw new Error(`${method} returned null signature for ${source.constructor.name} ${source.id}`); + return this.getOrCreateSignature(data); + } + + async fetchTypes(source: Symbol | Signature | Type, method: string, handles?: readonly number[]): Promise { + if (handles) { + const result = new Array(handles.length); + let allCached = true; + for (let i = 0; i < handles.length; i++) { + const cached = this.getType(handles[i]); + if (!cached) { + allCached = false; + break; + } + result[i] = cached; + } + if (allCached) return result; + } + const typesData = await this.client.apiRequest(method, { + snapshot: this.snapshotId, + objectId: source.id, + }); + if (typesData == null) return []; + else return typesData.map(data => this.getOrCreateType(data)); + } + + async fetchSymbols(source: Symbol | Signature | Type, method: string, handles?: readonly number[]): Promise { + if (handles) { + const result = new Array(handles.length); + let allCached = true; + for (let i = 0; i < handles.length; i++) { + const cached = this.getSymbol(handles[i]); + if (!cached) { + allCached = false; + break; + } + result[i] = cached; + } + if (allCached) return result; + } + const symbolData = await this.client.apiRequest(method, { + snapshot: this.snapshotId, + objectId: source.id, + }); + if (symbolData == null) return []; + else return symbolData.map(data => this.getOrCreateSymbol(data)); + } +} + export class Project { readonly id: string; readonly configFileName: string; @@ -1016,8 +1118,6 @@ export interface SignatureUsage { } export class Symbol { - private client: Client; - private snapshotId: number; private objectRegistry: SnapshotObjectRegistry; readonly id: number; @@ -1026,10 +1126,10 @@ export class Symbol { readonly checkFlags: number; readonly declarations: readonly NodeHandle[]; readonly valueDeclaration: NodeHandle | undefined; + readonly parent!: number; + readonly exportSymbol!: number; - constructor(data: SymbolResponse, client: Client, snapshotId: number, objectRegistry: SnapshotObjectRegistry) { - this.client = client; - this.snapshotId = snapshotId; + constructor(data: SymbolResponse, objectRegistry: SnapshotObjectRegistry) { this.objectRegistry = objectRegistry; this.id = data.id; @@ -1038,37 +1138,36 @@ export class Symbol { this.checkFlags = data.checkFlags; this.declarations = (data.declarations ?? []).map(d => new NodeHandle(d)); this.valueDeclaration = data.valueDeclaration ? new NodeHandle(data.valueDeclaration) : undefined; + + if (data.parent !== undefined) this.parent = data.parent; + if (data.exportSymbol !== undefined) this.exportSymbol = data.exportSymbol; } async getParent(): Promise { - const data = await this.client.apiRequest("getParentOfSymbol", { snapshot: this.snapshotId, symbol: this.id }); - return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; + return this.objectRegistry.fetchSymbol(this, "getParentOfSymbol", this.parent); } async getMembers(): Promise { - const data = await this.client.apiRequest("getMembersOfSymbol", { snapshot: this.snapshotId, symbol: this.id }); - return data ? data.map(d => this.objectRegistry.getOrCreateSymbol(d)) : []; + return this.objectRegistry.fetchSymbols(this, "getMembersOfSymbol"); } async getExports(): Promise { - const data = await this.client.apiRequest("getExportsOfSymbol", { snapshot: this.snapshotId, symbol: this.id }); - return data ? data.map(d => this.objectRegistry.getOrCreateSymbol(d)) : []; + return this.objectRegistry.fetchSymbols(this, "getExportsOfSymbol"); } async getExportSymbol(): Promise { - const data = await this.client.apiRequest("getExportSymbolOfSymbol", { snapshot: this.snapshotId, symbol: this.id }); - return this.objectRegistry.getOrCreateSymbol(data); + if (!this.exportSymbol) return this; + return this.objectRegistry.fetchSymbol(this, "getExportSymbolOfSymbol", this.exportSymbol); } } class TypeObject implements Type { - private client: Client; - private snapshotId: number; private objectRegistry: SnapshotObjectRegistry; readonly id: number; readonly flags: TypeFlags; readonly objectFlags!: ObjectFlags; + readonly symbol!: number; readonly value!: string | number | boolean; readonly intrinsicName!: string; readonly isThisType!: boolean; @@ -1091,24 +1190,23 @@ class TypeObject implements Type { readonly baseType!: number; readonly substConstraint!: number; - constructor(data: TypeResponse, client: Client, snapshotId: number, objectRegistry: SnapshotObjectRegistry) { - this.client = client; - this.snapshotId = snapshotId; + constructor(data: TypeResponse, objectRegistry: SnapshotObjectRegistry) { this.objectRegistry = objectRegistry; this.id = data.id; this.flags = data.flags; if (data.objectFlags !== undefined) this.objectFlags = data.objectFlags; + if (data.symbol !== undefined) this.symbol = data.symbol; if (data.value !== undefined) this.value = data.value; if (data.intrinsicName !== undefined) this.intrinsicName = data.intrinsicName; if (data.isThisType !== undefined) this.isThisType = data.isThisType; if (data.freshType !== undefined) this.freshType = data.freshType; if (data.regularType !== undefined) this.regularType = data.regularType; if (data.target !== undefined) this.target = data.target; - if (data.typeParameters !== undefined) this.typeParameters = data.typeParameters; - if (data.outerTypeParameters !== undefined) this.outerTypeParameters = data.outerTypeParameters; - if (data.localTypeParameters !== undefined) this.localTypeParameters = data.localTypeParameters; - if (data.aliasTypeArguments !== undefined) this.aliasTypeArguments = data.aliasTypeArguments; + this.typeParameters = data.typeParameters ?? []; + this.outerTypeParameters = data.outerTypeParameters ?? []; + this.localTypeParameters = data.localTypeParameters ?? []; + this.aliasTypeArguments = data.aliasTypeArguments ?? []; if (data.aliasSymbol !== undefined) this.aliasSymbol = data.aliasSymbol; if (data.elementFlags !== undefined) this.elementFlags = data.elementFlags; if (data.fixedLength !== undefined) this.fixedLength = data.fixedLength; @@ -1123,125 +1221,106 @@ class TypeObject implements Type { } async getSymbol(): Promise { - const data = await this.client.apiRequest("getSymbolOfType", { snapshot: this.snapshotId, type: this.id }); - return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; + return this.objectRegistry.fetchSymbol(this, "getSymbolOfType", this.symbol); } async getAliasSymbol(): Promise { - if (!this.aliasSymbol) return undefined; - const cached = this.objectRegistry.getSymbol(this.aliasSymbol); - if (cached) return cached; - const data = await this.client.apiRequest("getAliasSymbolOfType", { snapshot: this.snapshotId, type: this.id }); - return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; - } - - private async fetchType(handle: number | undefined, method: string): Promise { - const cached = handle !== undefined ? this.objectRegistry.getType(handle) : undefined; - if (cached) return cached as Type; - const data = await this.client.apiRequest(method, { snapshot: this.snapshotId, type: this.id }); - if (!data) throw new Error(`${method} returned null for type ${this.id}`); - return this.objectRegistry.getOrCreateType(data) as Type; - } - - private async fetchTypes(method: string): Promise { - const data = await this.client.apiRequest(method, { snapshot: this.snapshotId, type: this.id }); - return data ? data.map(d => this.objectRegistry.getOrCreateType(d) as Type) : []; + return this.objectRegistry.fetchSymbol(this, "getAliasSymbolOfType", this.aliasSymbol); } async getTarget(): Promise { - return this.fetchType(this.target, "getTargetOfType"); + return this.objectRegistry.fetchType(this, "getTargetOfType", this.target); } async getFreshType(): Promise { - if (!this.freshType) return undefined; - const cached = this.objectRegistry.getType(this.freshType); - if (cached) return cached as FreshableType; - const data = await this.client.apiRequest("getFreshTypeOfType", { snapshot: this.snapshotId, type: this.id }); - return data ? this.objectRegistry.getOrCreateType(data) as FreshableType : undefined; + return this.objectRegistry.fetchType(this, "getFreshTypeOfType", this.freshType); } async getRegularType(): Promise { - if (!this.regularType) return undefined; - const cached = this.objectRegistry.getType(this.regularType); - if (cached) return cached as FreshableType; - const data = await this.client.apiRequest("getRegularTypeOfType", { snapshot: this.snapshotId, type: this.id }); - return data ? this.objectRegistry.getOrCreateType(data) as FreshableType : undefined; + return this.objectRegistry.fetchType(this, "getRegularTypeOfType", this.regularType); } async getTypes(): Promise { - return this.fetchTypes("getTypesOfType"); + return this.objectRegistry.fetchTypes(this, "getTypesOfType"); } async getTypeParameters(): Promise { - return this.fetchTypes("getTypeParametersOfType"); + return this.objectRegistry.fetchTypes(this, "getTypeParametersOfType", this.typeParameters); } async getOuterTypeParameters(): Promise { - return this.fetchTypes("getOuterTypeParametersOfType"); + return this.objectRegistry.fetchTypes(this, "getOuterTypeParametersOfType", this.outerTypeParameters); } async getLocalTypeParameters(): Promise { - return this.fetchTypes("getLocalTypeParametersOfType"); + return this.objectRegistry.fetchTypes(this, "getLocalTypeParametersOfType", this.localTypeParameters); } async getAliasTypeArguments(): Promise { - return this.fetchTypes("getAliasTypeArgumentsOfType"); + return this.objectRegistry.fetchTypes(this, "getAliasTypeArgumentsOfType", this.aliasTypeArguments); } async getObjectType(): Promise { - return this.fetchType(this.objectType, "getObjectTypeOfType"); + return this.objectRegistry.fetchType(this, "getObjectTypeOfType", this.objectType); } async getIndexType(): Promise { - return this.fetchType(this.indexType, "getIndexTypeOfType"); + return this.objectRegistry.fetchType(this, "getIndexTypeOfType", this.indexType); } async getCheckType(): Promise { - return this.fetchType(this.checkType, "getCheckTypeOfType"); + return this.objectRegistry.fetchType(this, "getCheckTypeOfType", this.checkType); } async getExtendsType(): Promise { - return this.fetchType(this.extendsType, "getExtendsTypeOfType"); + return this.objectRegistry.fetchType(this, "getExtendsTypeOfType", this.extendsType); } async getBaseType(): Promise { - return this.fetchType(this.baseType, "getBaseTypeOfType"); + return this.objectRegistry.fetchType(this, "getBaseTypeOfType", this.baseType); } async getConstraint(): Promise { - return this.fetchType(this.substConstraint, "getConstraintOfType"); + return this.objectRegistry.fetchType(this, "getConstraintOfType", this.substConstraint); } } export class Signature { private flags: number; + private objectRegistry: SnapshotObjectRegistry; + readonly id: number; readonly declaration?: NodeHandle | undefined; - readonly typeParameters?: readonly Type[] | undefined; - readonly parameters: readonly Symbol[]; - readonly thisParameter?: Symbol | undefined; - readonly target?: Signature | undefined; + readonly typeParameters?: readonly number[] | undefined; + readonly parameters: readonly number[]; + readonly thisParameter?: number | undefined; + readonly target?: number | undefined; constructor(data: SignatureResponse, objectRegistry: SnapshotObjectRegistry) { this.id = data.id; this.flags = data.flags; + this.objectRegistry = objectRegistry; this.declaration = data.declaration ? new NodeHandle(data.declaration) : undefined; + this.typeParameters = data.typeParameters ?? []; + this.parameters = data.parameters ?? []; + this.thisParameter = data.thisParameter; + this.target = data.target; + } - this.typeParameters = (data.typeParameters ?? []).map(id => { - return objectRegistry.getOrCreateType({ id, flags: 0 }); - }); + async getTypeParameters(): Promise { + return this.objectRegistry.fetchTypes(this, "getTypeParametersOfSignature", this.typeParameters); + } - this.parameters = (data.parameters ?? []).map(id => { - return objectRegistry.getOrCreateSymbol({ id, name: "", flags: 0, checkFlags: 0 }); - }); + async getParameters(): Promise { + return this.objectRegistry.fetchSymbols(this, "getParametersOfSignature", this.parameters); + } - this.thisParameter = data.thisParameter - ? objectRegistry.getOrCreateSymbol({ id: data.thisParameter, name: "", flags: 0, checkFlags: 0 }) - : undefined; + async getThisParameter(): Promise { + return this.objectRegistry.fetchSymbol(this, "getThisParameterOfSignature", this.thisParameter); + } - this.target = data.target - ? objectRegistry.getOrCreateSignature({ id: data.target, flags: 0 }) - : undefined; + async getTarget(): Promise { + return this.objectRegistry.fetchSignature(this, "getTargetOfSignature", this.target); } get hasRestParameter(): boolean { diff --git a/_packages/native-preview/src/api/objectRegistry.ts b/_packages/native-preview/src/api/objectRegistry.ts index 3913c393967..06355b74cdc 100644 --- a/_packages/native-preview/src/api/objectRegistry.ts +++ b/_packages/native-preview/src/api/objectRegistry.ts @@ -66,14 +66,6 @@ export class ObjectRegistry< return type; } - getType(id: number): TType | undefined { - return this.types.get(id); - } - - getSymbol(id: number): TSymbol | undefined { - return this.symbols.get(id); - } - getOrCreateSignature(data: SignatureResponse): TSignature { let signature = this.signatures.get(data.id); if (signature) { @@ -85,6 +77,18 @@ export class ObjectRegistry< return signature; } + getType(id: number): TType | undefined { + return this.types.get(id); + } + + getSymbol(id: number): TSymbol | undefined { + return this.symbols.get(id); + } + + getSignature(id: number): TSignature | undefined { + return this.signatures.get(id); + } + clear(): void { this.symbols.clear(); this.types.clear(); diff --git a/_packages/native-preview/src/api/proto.ts b/_packages/native-preview/src/api/proto.ts index 706f3b4750b..a54e3185834 100644 --- a/_packages/native-preview/src/api/proto.ts +++ b/_packages/native-preview/src/api/proto.ts @@ -134,6 +134,8 @@ export interface SymbolResponse { checkFlags: number; declarations?: string[]; valueDeclaration?: string; + parent?: number; + exportSymbol?: number; } export interface TypeResponse { diff --git a/_packages/native-preview/src/api/sync/api.ts b/_packages/native-preview/src/api/sync/api.ts index 7baa7d05d43..730f538b08f 100644 --- a/_packages/native-preview/src/api/sync/api.ts +++ b/_packages/native-preview/src/api/sync/api.ts @@ -39,7 +39,10 @@ import { readSourceFileHash, RemoteSourceFile, } from "../node/node.ts"; -import { ObjectRegistry } from "../objectRegistry.ts"; +import { + type ObjectFactories, + ObjectRegistry, +} from "../objectRegistry.ts"; import type { APIOptions, LSPConnectionOptions, @@ -109,9 +112,6 @@ export type { APIOptions, ClientSocketOptions, ClientSpawnOptions, DocumentIdent export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts"; -/** Type alias for the snapshot-scoped object registry */ -type SnapshotObjectRegistry = ObjectRegistry; - export class API { private client: Client; private sourceFileCache: SourceFileCache; @@ -256,11 +256,15 @@ export class Snapshot { this.toPath = toPath; this.onDispose = onDispose; - this.objectRegistry = new ObjectRegistry({ - createSymbol: symbolData => new Symbol(symbolData, this.client, this.id, this.objectRegistry), - createType: typeData => new TypeObject(typeData, this.client, this.id, this.objectRegistry), - createSignature: sigData => new Signature(sigData, this.objectRegistry), - }); + this.objectRegistry = new SnapshotObjectRegistry( + { + createSymbol: symbolData => new Symbol(symbolData, this.objectRegistry), + createType: typeData => new TypeObject(typeData, this.objectRegistry), + createSignature: sigData => new Signature(sigData, this.objectRegistry), + }, + client, + this.id, + ); // Create projects this.projectMap = new Map(); @@ -313,6 +317,104 @@ export class Snapshot { } } +class SnapshotObjectRegistry extends ObjectRegistry { + private client: Client; + private snapshotId: number; + + constructor( + factories: ObjectFactories, + client: Client, + snapshotId: number, + ) { + super(factories); + this.client = client; + this.snapshotId = snapshotId; + } + + fetchType(source: Symbol | Signature | Type, method: string, handle: number | undefined): T { + if (!handle) return undefined as unknown as T; + const cached = this.getType(handle); + if (cached) return cached as unknown as T; + + const data = this.client.apiRequest(method, { + snapshot: this.snapshotId, + objectId: source.id, + }); + if (!data) throw new Error(`${method} returned null type for ${source.constructor.name} ${source.id}`); + return this.getOrCreateType(data) as unknown as T; + } + + fetchSymbol(source: Symbol | Signature | Type, method: string, handle: number | undefined): Symbol { + if (!handle) return undefined as unknown as Symbol; + const cached = this.getSymbol(handle); + if (cached) return cached; + + const data = this.client.apiRequest(method, { + snapshot: this.snapshotId, + objectId: source.id, + }); + if (!data) throw new Error(`${method} returned null symbol for ${source.constructor.name} ${source.id}`); + return this.getOrCreateSymbol(data); + } + + fetchSignature(source: Symbol | Signature | Type, method: string, handle: number | undefined): Signature { + if (!handle) return undefined as unknown as Signature; + const cached = this.getSignature(handle); + if (cached) return cached; + + const data = this.client.apiRequest(method, { + snapshot: this.snapshotId, + objectId: source.id, + }); + if (!data) throw new Error(`${method} returned null signature for ${source.constructor.name} ${source.id}`); + return this.getOrCreateSignature(data); + } + + fetchTypes(source: Symbol | Signature | Type, method: string, handles?: readonly number[]): readonly Type[] { + if (handles) { + const result = new Array(handles.length); + let allCached = true; + for (let i = 0; i < handles.length; i++) { + const cached = this.getType(handles[i]); + if (!cached) { + allCached = false; + break; + } + result[i] = cached; + } + if (allCached) return result; + } + const typesData = this.client.apiRequest(method, { + snapshot: this.snapshotId, + objectId: source.id, + }); + if (typesData == null) return []; + else return typesData.map(data => this.getOrCreateType(data)); + } + + fetchSymbols(source: Symbol | Signature | Type, method: string, handles?: readonly number[]): readonly Symbol[] { + if (handles) { + const result = new Array(handles.length); + let allCached = true; + for (let i = 0; i < handles.length; i++) { + const cached = this.getSymbol(handles[i]); + if (!cached) { + allCached = false; + break; + } + result[i] = cached; + } + if (allCached) return result; + } + const symbolData = this.client.apiRequest(method, { + snapshot: this.snapshotId, + objectId: source.id, + }); + if (symbolData == null) return []; + else return symbolData.map(data => this.getOrCreateSymbol(data)); + } +} + export class Project { readonly id: string; readonly configFileName: string; @@ -1024,8 +1126,6 @@ export interface SignatureUsage { } export class Symbol { - private client: Client; - private snapshotId: number; private objectRegistry: SnapshotObjectRegistry; readonly id: number; @@ -1034,10 +1134,10 @@ export class Symbol { readonly checkFlags: number; readonly declarations: readonly NodeHandle[]; readonly valueDeclaration: NodeHandle | undefined; + readonly parent!: number; + readonly exportSymbol!: number; - constructor(data: SymbolResponse, client: Client, snapshotId: number, objectRegistry: SnapshotObjectRegistry) { - this.client = client; - this.snapshotId = snapshotId; + constructor(data: SymbolResponse, objectRegistry: SnapshotObjectRegistry) { this.objectRegistry = objectRegistry; this.id = data.id; @@ -1046,37 +1146,36 @@ export class Symbol { this.checkFlags = data.checkFlags; this.declarations = (data.declarations ?? []).map(d => new NodeHandle(d)); this.valueDeclaration = data.valueDeclaration ? new NodeHandle(data.valueDeclaration) : undefined; + + if (data.parent !== undefined) this.parent = data.parent; + if (data.exportSymbol !== undefined) this.exportSymbol = data.exportSymbol; } getParent(): Symbol | undefined { - const data = this.client.apiRequest("getParentOfSymbol", { snapshot: this.snapshotId, symbol: this.id }); - return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; + return this.objectRegistry.fetchSymbol(this, "getParentOfSymbol", this.parent); } getMembers(): readonly Symbol[] { - const data = this.client.apiRequest("getMembersOfSymbol", { snapshot: this.snapshotId, symbol: this.id }); - return data ? data.map(d => this.objectRegistry.getOrCreateSymbol(d)) : []; + return this.objectRegistry.fetchSymbols(this, "getMembersOfSymbol"); } getExports(): readonly Symbol[] { - const data = this.client.apiRequest("getExportsOfSymbol", { snapshot: this.snapshotId, symbol: this.id }); - return data ? data.map(d => this.objectRegistry.getOrCreateSymbol(d)) : []; + return this.objectRegistry.fetchSymbols(this, "getExportsOfSymbol"); } getExportSymbol(): Symbol { - const data = this.client.apiRequest("getExportSymbolOfSymbol", { snapshot: this.snapshotId, symbol: this.id }); - return this.objectRegistry.getOrCreateSymbol(data); + if (!this.exportSymbol) return this; + return this.objectRegistry.fetchSymbol(this, "getExportSymbolOfSymbol", this.exportSymbol); } } class TypeObject implements Type { - private client: Client; - private snapshotId: number; private objectRegistry: SnapshotObjectRegistry; readonly id: number; readonly flags: TypeFlags; readonly objectFlags!: ObjectFlags; + readonly symbol!: number; readonly value!: string | number | boolean; readonly intrinsicName!: string; readonly isThisType!: boolean; @@ -1099,24 +1198,23 @@ class TypeObject implements Type { readonly baseType!: number; readonly substConstraint!: number; - constructor(data: TypeResponse, client: Client, snapshotId: number, objectRegistry: SnapshotObjectRegistry) { - this.client = client; - this.snapshotId = snapshotId; + constructor(data: TypeResponse, objectRegistry: SnapshotObjectRegistry) { this.objectRegistry = objectRegistry; this.id = data.id; this.flags = data.flags; if (data.objectFlags !== undefined) this.objectFlags = data.objectFlags; + if (data.symbol !== undefined) this.symbol = data.symbol; if (data.value !== undefined) this.value = data.value; if (data.intrinsicName !== undefined) this.intrinsicName = data.intrinsicName; if (data.isThisType !== undefined) this.isThisType = data.isThisType; if (data.freshType !== undefined) this.freshType = data.freshType; if (data.regularType !== undefined) this.regularType = data.regularType; if (data.target !== undefined) this.target = data.target; - if (data.typeParameters !== undefined) this.typeParameters = data.typeParameters; - if (data.outerTypeParameters !== undefined) this.outerTypeParameters = data.outerTypeParameters; - if (data.localTypeParameters !== undefined) this.localTypeParameters = data.localTypeParameters; - if (data.aliasTypeArguments !== undefined) this.aliasTypeArguments = data.aliasTypeArguments; + this.typeParameters = data.typeParameters ?? []; + this.outerTypeParameters = data.outerTypeParameters ?? []; + this.localTypeParameters = data.localTypeParameters ?? []; + this.aliasTypeArguments = data.aliasTypeArguments ?? []; if (data.aliasSymbol !== undefined) this.aliasSymbol = data.aliasSymbol; if (data.elementFlags !== undefined) this.elementFlags = data.elementFlags; if (data.fixedLength !== undefined) this.fixedLength = data.fixedLength; @@ -1131,125 +1229,106 @@ class TypeObject implements Type { } getSymbol(): Symbol | undefined { - const data = this.client.apiRequest("getSymbolOfType", { snapshot: this.snapshotId, type: this.id }); - return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; + return this.objectRegistry.fetchSymbol(this, "getSymbolOfType", this.symbol); } getAliasSymbol(): Symbol | undefined { - if (!this.aliasSymbol) return undefined; - const cached = this.objectRegistry.getSymbol(this.aliasSymbol); - if (cached) return cached; - const data = this.client.apiRequest("getAliasSymbolOfType", { snapshot: this.snapshotId, type: this.id }); - return data ? this.objectRegistry.getOrCreateSymbol(data) : undefined; - } - - private fetchType(handle: number | undefined, method: string): Type { - const cached = handle !== undefined ? this.objectRegistry.getType(handle) : undefined; - if (cached) return cached as Type; - const data = this.client.apiRequest(method, { snapshot: this.snapshotId, type: this.id }); - if (!data) throw new Error(`${method} returned null for type ${this.id}`); - return this.objectRegistry.getOrCreateType(data) as Type; - } - - private fetchTypes(method: string): readonly Type[] { - const data = this.client.apiRequest(method, { snapshot: this.snapshotId, type: this.id }); - return data ? data.map(d => this.objectRegistry.getOrCreateType(d) as Type) : []; + return this.objectRegistry.fetchSymbol(this, "getAliasSymbolOfType", this.aliasSymbol); } getTarget(): Type { - return this.fetchType(this.target, "getTargetOfType"); + return this.objectRegistry.fetchType(this, "getTargetOfType", this.target); } getFreshType(): FreshableType | undefined { - if (!this.freshType) return undefined; - const cached = this.objectRegistry.getType(this.freshType); - if (cached) return cached as FreshableType; - const data = this.client.apiRequest("getFreshTypeOfType", { snapshot: this.snapshotId, type: this.id }); - return data ? this.objectRegistry.getOrCreateType(data) as FreshableType : undefined; + return this.objectRegistry.fetchType(this, "getFreshTypeOfType", this.freshType); } getRegularType(): FreshableType | undefined { - if (!this.regularType) return undefined; - const cached = this.objectRegistry.getType(this.regularType); - if (cached) return cached as FreshableType; - const data = this.client.apiRequest("getRegularTypeOfType", { snapshot: this.snapshotId, type: this.id }); - return data ? this.objectRegistry.getOrCreateType(data) as FreshableType : undefined; + return this.objectRegistry.fetchType(this, "getRegularTypeOfType", this.regularType); } getTypes(): readonly Type[] { - return this.fetchTypes("getTypesOfType"); + return this.objectRegistry.fetchTypes(this, "getTypesOfType"); } getTypeParameters(): readonly Type[] { - return this.fetchTypes("getTypeParametersOfType"); + return this.objectRegistry.fetchTypes(this, "getTypeParametersOfType", this.typeParameters); } getOuterTypeParameters(): readonly Type[] { - return this.fetchTypes("getOuterTypeParametersOfType"); + return this.objectRegistry.fetchTypes(this, "getOuterTypeParametersOfType", this.outerTypeParameters); } getLocalTypeParameters(): readonly Type[] { - return this.fetchTypes("getLocalTypeParametersOfType"); + return this.objectRegistry.fetchTypes(this, "getLocalTypeParametersOfType", this.localTypeParameters); } getAliasTypeArguments(): readonly Type[] { - return this.fetchTypes("getAliasTypeArgumentsOfType"); + return this.objectRegistry.fetchTypes(this, "getAliasTypeArgumentsOfType", this.aliasTypeArguments); } getObjectType(): Type { - return this.fetchType(this.objectType, "getObjectTypeOfType"); + return this.objectRegistry.fetchType(this, "getObjectTypeOfType", this.objectType); } getIndexType(): Type { - return this.fetchType(this.indexType, "getIndexTypeOfType"); + return this.objectRegistry.fetchType(this, "getIndexTypeOfType", this.indexType); } getCheckType(): Type { - return this.fetchType(this.checkType, "getCheckTypeOfType"); + return this.objectRegistry.fetchType(this, "getCheckTypeOfType", this.checkType); } getExtendsType(): Type { - return this.fetchType(this.extendsType, "getExtendsTypeOfType"); + return this.objectRegistry.fetchType(this, "getExtendsTypeOfType", this.extendsType); } getBaseType(): Type { - return this.fetchType(this.baseType, "getBaseTypeOfType"); + return this.objectRegistry.fetchType(this, "getBaseTypeOfType", this.baseType); } getConstraint(): Type { - return this.fetchType(this.substConstraint, "getConstraintOfType"); + return this.objectRegistry.fetchType(this, "getConstraintOfType", this.substConstraint); } } export class Signature { private flags: number; + private objectRegistry: SnapshotObjectRegistry; + readonly id: number; readonly declaration?: NodeHandle | undefined; - readonly typeParameters?: readonly Type[] | undefined; - readonly parameters: readonly Symbol[]; - readonly thisParameter?: Symbol | undefined; - readonly target?: Signature | undefined; + readonly typeParameters?: readonly number[] | undefined; + readonly parameters: readonly number[]; + readonly thisParameter?: number | undefined; + readonly target?: number | undefined; constructor(data: SignatureResponse, objectRegistry: SnapshotObjectRegistry) { this.id = data.id; this.flags = data.flags; + this.objectRegistry = objectRegistry; this.declaration = data.declaration ? new NodeHandle(data.declaration) : undefined; + this.typeParameters = data.typeParameters ?? []; + this.parameters = data.parameters ?? []; + this.thisParameter = data.thisParameter; + this.target = data.target; + } - this.typeParameters = (data.typeParameters ?? []).map(id => { - return objectRegistry.getOrCreateType({ id, flags: 0 }); - }); + getTypeParameters(): readonly Type[] { + return this.objectRegistry.fetchTypes(this, "getTypeParametersOfSignature", this.typeParameters); + } - this.parameters = (data.parameters ?? []).map(id => { - return objectRegistry.getOrCreateSymbol({ id, name: "", flags: 0, checkFlags: 0 }); - }); + getParameters(): readonly Symbol[] { + return this.objectRegistry.fetchSymbols(this, "getParametersOfSignature", this.parameters); + } - this.thisParameter = data.thisParameter - ? objectRegistry.getOrCreateSymbol({ id: data.thisParameter, name: "", flags: 0, checkFlags: 0 }) - : undefined; + getThisParameter(): Symbol | undefined { + return this.objectRegistry.fetchSymbol(this, "getThisParameterOfSignature", this.thisParameter); + } - this.target = data.target - ? objectRegistry.getOrCreateSignature({ id: data.target, flags: 0 }) - : undefined; + getTarget(): Signature | undefined { + return this.objectRegistry.fetchSignature(this, "getTargetOfSignature", this.target); } get hasRestParameter(): boolean { diff --git a/_packages/native-preview/test/async/api.test.ts b/_packages/native-preview/test/async/api.test.ts index 25bb31a1892..c2e2e847510 100644 --- a/_packages/native-preview/test/async/api.test.ts +++ b/_packages/native-preview/test/async/api.test.ts @@ -946,6 +946,161 @@ export class Cache { await api.close(); } }); + + test("getSignaturesOfType - signature type parameters", async () => { + const mainFile = ` + interface Operator { + } + export declare class Observable { + lift(operator: Operator): Observable; + } + `; + const api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": mainFile, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const liftPos = mainFile.indexOf("lift"); + const type = await project.checker.getTypeAtPosition("/src/main.ts", liftPos); + assert.ok(type); + const callSigs = await project.checker.getSignaturesOfType(type, SignatureKind.Call); + assert.ok(callSigs.length === 1, "should have exactly one call signature, found: " + callSigs.length); + const sig = callSigs[0]; + assert.ok(sig.typeParameters?.length === 1, "should have exactly one type parameter, found: " + sig.typeParameters?.length); + const typeParams = await sig.getTypeParameters(); + const typeParam = typeParams[0]; + assert.ok(typeParam, "should have type parameter"); + const name = (await typeParam.getSymbol())?.name; + assert.ok(name === "R", "should be named R, instead: " + name); + assert.ok(typeParam.flags & TypeFlags.TypeParameter, "should be a type parameter, instead flags: " + typeParam.flags); + } + finally { + await api.close(); + } + }); + + test("Signature.getParameters() returns parameter symbols with correct names", async () => { + const api = spawnAPI(checkerFiles); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const src = checkerFiles["/src/main.ts"]; + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("add(")); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const sigs = await project.checker.getSignaturesOfType(type, SignatureKind.Call); + assert.ok(sigs.length > 0); + const params = await sigs[0].getParameters(); + assert.equal(params.length, 3); + assert.equal(params[0].name, "a"); + assert.equal(params[1].name, "b"); + assert.equal(params[2].name, "rest"); + assert.ok(params[0].flags & SymbolFlags.FunctionScopedVariable, `expected FunctionScopedVariable on 'a', got ${params[0].flags}`); + } + finally { + await api.close(); + } + }); + + test("Signature.getThisParameter() returns undefined when no explicit this parameter", async () => { + const api = spawnAPI(checkerFiles); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const src = checkerFiles["/src/main.ts"]; + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("add(")); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const sigs = await project.checker.getSignaturesOfType(type, SignatureKind.Call); + assert.ok(sigs.length > 0); + const thisParam = await sigs[0].getThisParameter(); + assert.strictEqual(thisParam, undefined, "add() has no explicit this parameter"); + } + finally { + await api.close(); + } + }); + + test("Signature.getThisParameter() returns symbol for explicit this parameter", async () => { + const src = `export function foo(this: { n: number }, x: string): void {}`; + const api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("foo(")); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const sigs = await project.checker.getSignaturesOfType(type, SignatureKind.Call); + assert.ok(sigs.length > 0); + const thisParam = await sigs[0].getThisParameter(); + assert.ok(thisParam, "foo has an explicit this parameter"); + assert.equal(thisParam.name, "this"); + assert.ok(thisParam.flags & SymbolFlags.FunctionScopedVariable, `expected FunctionScopedVariable, got ${thisParam.flags}`); + } + finally { + await api.close(); + } + }); + + test("Signature.getTarget() returns undefined for a non-instantiated signature", async () => { + const api = spawnAPI(checkerFiles); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const src = checkerFiles["/src/main.ts"]; + const symbol = await project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("add(")); + assert.ok(symbol); + const type = await project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const sigs = await project.checker.getSignaturesOfType(type, SignatureKind.Call); + assert.ok(sigs.length > 0); + const target = await sigs[0].getTarget(); + assert.strictEqual(target, undefined, "add() is not an instantiated signature"); + } + finally { + await api.close(); + } + }); + + test("Signature.getTarget() returns the generic source signature for an instantiated call", async () => { + const src = ` + function identity(x: T): T { return x; } + identity("hello"); + `; + const api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const sourceFile = await project.program.getSourceFile("/src/main.ts"); + assert.ok(sourceFile); + let callNode: Node | undefined; + sourceFile.forEachChild(function visit(node) { + if (isCallExpression(node)) callNode = node; + node.forEachChild(visit); + }); + assert.ok(callNode, "should find a call expression"); + const sig = await project.checker.getResolvedSignature(callNode); + assert.ok(sig, "should resolve a signature for the call"); + assert.ok(sig.target !== undefined, "instantiated call should have a target ID"); + const target = await sig.getTarget(); + assert.ok(target, "getTarget() should return the generic signature"); + assert.ok(target.typeParameters && target.typeParameters.length > 0, "target should have type parameters"); + } + finally { + await api.close(); + } + }); }); describe("Symbol - parent, members, exports", () => { @@ -1989,8 +2144,8 @@ describe("Checker - getConstraintOfTypeParameter", () => { assert.ok(type); const sigs = await project.checker.getSignaturesOfType(type, SignatureKind.Call); assert.ok(sigs.length > 0); - const typeParams = sigs[0].typeParameters; - assert.ok(typeParams && typeParams.length > 0, "Should have type parameters"); + assert.ok(sigs[0].typeParameters && sigs[0].typeParameters.length > 0, "Should have type parameters"); + const typeParams = await sigs[0].getTypeParameters(); const constraint = await project.checker.getConstraintOfTypeParameter(typeParams[0]); assert.ok(constraint); assert.ok(constraint.flags & TypeFlags.String, `Expected string constraint, got flags ${constraint.flags}`); diff --git a/_packages/native-preview/test/sync/api.test.ts b/_packages/native-preview/test/sync/api.test.ts index 5595ad0bfd9..113538c1df6 100644 --- a/_packages/native-preview/test/sync/api.test.ts +++ b/_packages/native-preview/test/sync/api.test.ts @@ -954,6 +954,161 @@ export class Cache { api.close(); } }); + + test("getSignaturesOfType - signature type parameters", () => { + const mainFile = ` + interface Operator { + } + export declare class Observable { + lift(operator: Operator): Observable; + } + `; + const api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": mainFile, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const liftPos = mainFile.indexOf("lift"); + const type = project.checker.getTypeAtPosition("/src/main.ts", liftPos); + assert.ok(type); + const callSigs = project.checker.getSignaturesOfType(type, SignatureKind.Call); + assert.ok(callSigs.length === 1, "should have exactly one call signature, found: " + callSigs.length); + const sig = callSigs[0]; + assert.ok(sig.typeParameters?.length === 1, "should have exactly one type parameter, found: " + sig.typeParameters?.length); + const typeParams = sig.getTypeParameters(); + const typeParam = typeParams[0]; + assert.ok(typeParam, "should have type parameter"); + const name = (typeParam.getSymbol())?.name; + assert.ok(name === "R", "should be named R, instead: " + name); + assert.ok(typeParam.flags & TypeFlags.TypeParameter, "should be a type parameter, instead flags: " + typeParam.flags); + } + finally { + api.close(); + } + }); + + test("Signature.getParameters() returns parameter symbols with correct names", () => { + const api = spawnAPI(checkerFiles); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const src = checkerFiles["/src/main.ts"]; + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("add(")); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const sigs = project.checker.getSignaturesOfType(type, SignatureKind.Call); + assert.ok(sigs.length > 0); + const params = sigs[0].getParameters(); + assert.equal(params.length, 3); + assert.equal(params[0].name, "a"); + assert.equal(params[1].name, "b"); + assert.equal(params[2].name, "rest"); + assert.ok(params[0].flags & SymbolFlags.FunctionScopedVariable, `expected FunctionScopedVariable on 'a', got ${params[0].flags}`); + } + finally { + api.close(); + } + }); + + test("Signature.getThisParameter() returns undefined when no explicit this parameter", () => { + const api = spawnAPI(checkerFiles); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const src = checkerFiles["/src/main.ts"]; + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("add(")); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const sigs = project.checker.getSignaturesOfType(type, SignatureKind.Call); + assert.ok(sigs.length > 0); + const thisParam = sigs[0].getThisParameter(); + assert.strictEqual(thisParam, undefined, "add() has no explicit this parameter"); + } + finally { + api.close(); + } + }); + + test("Signature.getThisParameter() returns symbol for explicit this parameter", () => { + const src = `export function foo(this: { n: number }, x: string): void {}`; + const api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("foo(")); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const sigs = project.checker.getSignaturesOfType(type, SignatureKind.Call); + assert.ok(sigs.length > 0); + const thisParam = sigs[0].getThisParameter(); + assert.ok(thisParam, "foo has an explicit this parameter"); + assert.equal(thisParam.name, "this"); + assert.ok(thisParam.flags & SymbolFlags.FunctionScopedVariable, `expected FunctionScopedVariable, got ${thisParam.flags}`); + } + finally { + api.close(); + } + }); + + test("Signature.getTarget() returns undefined for a non-instantiated signature", () => { + const api = spawnAPI(checkerFiles); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const src = checkerFiles["/src/main.ts"]; + const symbol = project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("add(")); + assert.ok(symbol); + const type = project.checker.getTypeOfSymbol(symbol); + assert.ok(type); + const sigs = project.checker.getSignaturesOfType(type, SignatureKind.Call); + assert.ok(sigs.length > 0); + const target = sigs[0].getTarget(); + assert.strictEqual(target, undefined, "add() is not an instantiated signature"); + } + finally { + api.close(); + } + }); + + test("Signature.getTarget() returns the generic source signature for an instantiated call", () => { + const src = ` + function identity(x: T): T { return x; } + identity("hello"); + `; + const api = spawnAPI({ + "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + const sourceFile = project.program.getSourceFile("/src/main.ts"); + assert.ok(sourceFile); + let callNode: Node | undefined; + sourceFile.forEachChild(function visit(node) { + if (isCallExpression(node)) callNode = node; + node.forEachChild(visit); + }); + assert.ok(callNode, "should find a call expression"); + const sig = project.checker.getResolvedSignature(callNode); + assert.ok(sig, "should resolve a signature for the call"); + assert.ok(sig.target !== undefined, "instantiated call should have a target ID"); + const target = sig.getTarget(); + assert.ok(target, "getTarget() should return the generic signature"); + assert.ok(target.typeParameters && target.typeParameters.length > 0, "target should have type parameters"); + } + finally { + api.close(); + } + }); }); describe("Symbol - parent, members, exports", () => { @@ -1997,8 +2152,8 @@ describe("Checker - getConstraintOfTypeParameter", () => { assert.ok(type); const sigs = project.checker.getSignaturesOfType(type, SignatureKind.Call); assert.ok(sigs.length > 0); - const typeParams = sigs[0].typeParameters; - assert.ok(typeParams && typeParams.length > 0, "Should have type parameters"); + assert.ok(sigs[0].typeParameters && sigs[0].typeParameters.length > 0, "Should have type parameters"); + const typeParams = sigs[0].getTypeParameters(); const constraint = project.checker.getConstraintOfTypeParameter(typeParams[0]); assert.ok(constraint); assert.ok(constraint.flags & TypeFlags.String, `Expected string constraint, got flags ${constraint.flags}`); diff --git a/internal/api/proto.go b/internal/api/proto.go index 869f64459db..42573a235c3 100644 --- a/internal/api/proto.go +++ b/internal/api/proto.go @@ -45,8 +45,8 @@ func TypeHandle(t *checker.Type) TypeID { return TypeID(t.Id()) } -func SignatureHandle(id uint64) SignatureID { - return SignatureID(id) +func SignatureHandle(sig *checker.Signature) SignatureID { + return SignatureID(sig.Id()) } func parseProjectHandle(handle ProjectID) tspath.Path { @@ -69,11 +69,6 @@ const ( MethodGetDeclaredTypeOfSymbol Method = "getDeclaredTypeOfSymbol" MethodGetSourceFile Method = "getSourceFile" MethodResolveName Method = "resolveName" - MethodGetParentOfSymbol Method = "getParentOfSymbol" - MethodGetMembersOfSymbol Method = "getMembersOfSymbol" - MethodGetExportsOfSymbol Method = "getExportsOfSymbol" - MethodGetExportSymbolOfSymbol Method = "getExportSymbolOfSymbol" - MethodGetSymbolOfType Method = "getSymbolOfType" MethodGetSignaturesOfType Method = "getSignaturesOfType" MethodGetResolvedSignature Method = "getResolvedSignature" MethodGetTypeAtLocation Method = "getTypeAtLocation" @@ -81,7 +76,14 @@ const ( MethodGetTypeAtPosition Method = "getTypeAtPosition" MethodGetTypesAtPositions Method = "getTypesAtPositions" + // Symbol sub-property methods + MethodGetParentOfSymbol Method = "getParentOfSymbol" + MethodGetMembersOfSymbol Method = "getMembersOfSymbol" + MethodGetExportsOfSymbol Method = "getExportsOfSymbol" + MethodGetExportSymbolOfSymbol Method = "getExportSymbolOfSymbol" + // Type sub-property methods + MethodGetSymbolOfType Method = "getSymbolOfType" MethodGetTargetOfType Method = "getTargetOfType" MethodGetFreshTypeOfType Method = "getFreshTypeOfType" MethodGetRegularTypeOfType Method = "getRegularTypeOfType" @@ -98,6 +100,12 @@ const ( MethodGetBaseTypeOfType Method = "getBaseTypeOfType" MethodGetConstraintOfType Method = "getConstraintOfType" + // Signature sub-property methods + MethodGetTypeParametersOfSignature Method = "getTypeParametersOfSignature" + MethodGetParametersOfSignature Method = "getParametersOfSignature" + MethodGetThisParameterOfSignature Method = "getThisParameterOfSignature" + MethodGetTargetOfSignature Method = "getTargetOfSignature" + // Checker methods MethodGetContextualType Method = "getContextualType" MethodGetBaseTypeOfLiteralType Method = "getBaseTypeOfLiteralType" @@ -312,11 +320,6 @@ var unmarshalers = map[Method]func([]byte) (any, error){ MethodGetTypesOfSymbols: unmarshallerFor[GetTypesOfSymbolsParams], MethodGetDeclaredTypeOfSymbol: unmarshallerFor[GetTypeOfSymbolParams], MethodResolveName: unmarshallerFor[ResolveNameParams], - MethodGetParentOfSymbol: unmarshallerFor[GetParentOfSymbolParams], - MethodGetMembersOfSymbol: unmarshallerFor[GetMembersOfSymbolParams], - MethodGetExportsOfSymbol: unmarshallerFor[GetExportsOfSymbolParams], - MethodGetExportSymbolOfSymbol: unmarshallerFor[GetExportSymbolOfSymbolParams], - MethodGetSymbolOfType: unmarshallerFor[GetSymbolOfTypeParams], MethodGetSignaturesOfType: unmarshallerFor[GetSignaturesOfTypeParams], MethodGetResolvedSignature: unmarshallerFor[GetResolvedSignatureParams], MethodGetTypeAtLocation: unmarshallerFor[GetTypeAtLocationParams], @@ -324,21 +327,33 @@ var unmarshalers = map[Method]func([]byte) (any, error){ MethodGetTypeAtPosition: unmarshallerFor[GetTypeAtPositionParams], MethodGetTypesAtPositions: unmarshallerFor[GetTypesAtPositionsParams], - MethodGetTargetOfType: unmarshallerFor[GetTypePropertyParams], - MethodGetFreshTypeOfType: unmarshallerFor[GetTypePropertyParams], - MethodGetRegularTypeOfType: unmarshallerFor[GetTypePropertyParams], - MethodGetTypesOfType: unmarshallerFor[GetTypePropertyParams], - MethodGetTypeParametersOfType: unmarshallerFor[GetTypePropertyParams], - MethodGetOuterTypeParametersOfType: unmarshallerFor[GetTypePropertyParams], - MethodGetLocalTypeParametersOfType: unmarshallerFor[GetTypePropertyParams], - MethodGetAliasTypeArgumentsOfType: unmarshallerFor[GetTypePropertyParams], - MethodGetAliasSymbolOfType: unmarshallerFor[GetTypePropertyParams], - MethodGetObjectTypeOfType: unmarshallerFor[GetTypePropertyParams], - MethodGetIndexTypeOfType: unmarshallerFor[GetTypePropertyParams], - MethodGetCheckTypeOfType: unmarshallerFor[GetTypePropertyParams], - MethodGetExtendsTypeOfType: unmarshallerFor[GetTypePropertyParams], - MethodGetBaseTypeOfType: unmarshallerFor[GetTypePropertyParams], - MethodGetConstraintOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetParentOfSymbol: unmarshallerFor[GetSymbolPropertyParams], + MethodGetMembersOfSymbol: unmarshallerFor[GetSymbolPropertyParams], + MethodGetExportsOfSymbol: unmarshallerFor[GetSymbolPropertyParams], + MethodGetExportSymbolOfSymbol: unmarshallerFor[GetSymbolPropertyParams], + + MethodGetSymbolOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetTargetOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetFreshTypeOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetRegularTypeOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetTypesOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetTypeParametersOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetOuterTypeParametersOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetLocalTypeParametersOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetAliasTypeArgumentsOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetAliasSymbolOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetObjectTypeOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetIndexTypeOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetCheckTypeOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetExtendsTypeOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetBaseTypeOfType: unmarshallerFor[GetTypePropertyParams], + MethodGetConstraintOfType: unmarshallerFor[GetTypePropertyParams], + + MethodGetTypeParametersOfSignature: unmarshallerFor[GetSignaturePropertyParams], + MethodGetParametersOfSignature: unmarshallerFor[GetSignaturePropertyParams], + MethodGetThisParameterOfSignature: unmarshallerFor[GetSignaturePropertyParams], + MethodGetTargetOfSignature: unmarshallerFor[GetSignaturePropertyParams], + MethodGetContextualType: unmarshallerFor[GetContextualTypeParams], MethodGetBaseTypeOfLiteralType: unmarshallerFor[GetBaseTypeOfLiteralTypeParams], MethodGetNonNullableType: unmarshallerFor[GetNonNullableTypeParams], @@ -463,22 +478,19 @@ type SymbolResponse struct { CheckFlags uint32 `json:"checkFlags"` Declarations []NodeHandle `json:"declarations,omitempty"` ValueDeclaration NodeHandle `json:"valueDeclaration,omitempty"` + Parent SymbolID `json:"parent,omitzero"` + ExportSymbol SymbolID `json:"exportSymbol,omitzero"` } -func NewSymbolResponse(symbol *ast.Symbol) *SymbolResponse { - resp := &SymbolResponse{ - Id: SymbolHandle(symbol), - Name: symbol.Name, - Flags: uint32(symbol.Flags), - CheckFlags: uint32(symbol.CheckFlags), +func symbolHandles(symbols []*ast.Symbol) []SymbolID { + if len(symbols) == 0 { + return nil } - - // Allocate declaration slices; callers fill in the handles. - if len(symbol.Declarations) > 0 { - resp.Declarations = make([]NodeHandle, len(symbol.Declarations)) + handles := make([]SymbolID, len(symbols)) + for i, t := range symbols { + handles[i] = SymbolHandle(t) } - - return resp + return handles } type GetTypeOfSymbolParams struct { @@ -502,7 +514,7 @@ type TypeResponse struct { Value any `json:"value"` // ObjectType / TypeReference / StringMappingType / IndexType target - Target TypeID `json:"target,omitempty"` + Target TypeID `json:"target,omitzero"` // InterfaceType type parameters TypeParameters []TypeID `json:"typeParameters,omitempty"` @@ -515,23 +527,23 @@ type TypeResponse struct { TupleReadonly *bool `json:"readonly,omitempty"` // IndexedAccessType data - ObjectType TypeID `json:"objectType,omitempty"` - IndexType TypeID `json:"indexType,omitempty"` + ObjectType TypeID `json:"objectType,omitzero"` + IndexType TypeID `json:"indexType,omitzero"` // ConditionalType data - CheckType TypeID `json:"checkType,omitempty"` - ExtendsType TypeID `json:"extendsType,omitempty"` + CheckType TypeID `json:"checkType,omitzero"` + ExtendsType TypeID `json:"extendsType,omitzero"` // SubstitutionType data - BaseType TypeID `json:"baseType,omitempty"` - SubstConstraint TypeID `json:"substConstraint,omitempty"` + BaseType TypeID `json:"baseType,omitzero"` + SubstConstraint TypeID `json:"substConstraint,omitzero"` // TemplateLiteralType text segments Texts []string `json:"texts,omitempty"` // FreshableType data (LiteralType and computed enum types) - FreshType TypeID `json:"freshType,omitempty"` - RegularType TypeID `json:"regularType,omitempty"` + FreshType TypeID `json:"freshType,omitzero"` + RegularType TypeID `json:"regularType,omitzero"` // TypeParameter data IsThisType bool `json:"isThisType,omitempty"` @@ -541,15 +553,15 @@ type TypeResponse struct { // TypeAlias data AliasTypeArguments []TypeID `json:"aliasTypeArguments,omitempty"` - AliasSymbol SymbolID `json:"aliasSymbol,omitempty"` + AliasSymbol SymbolID `json:"aliasSymbol,omitzero"` // Symbol associated with structured types - Symbol SymbolID `json:"symbol,omitempty"` + Symbol SymbolID `json:"symbol,omitzero"` } -func newTypeData(t *checker.Type) *TypeResponse { +func newTypeResponse(t *checker.Type, id TypeID) *TypeResponse { resp := &TypeResponse{ - Id: TypeHandle(t), + Id: id, Flags: uint32(t.Flags()), } @@ -665,8 +677,8 @@ type SignatureResponse struct { Declaration NodeHandle `json:"declaration,omitempty"` TypeParameters []TypeID `json:"typeParameters,omitempty"` Parameters []SymbolID `json:"parameters,omitempty"` - ThisParameter SymbolID `json:"thisParameter,omitempty"` - Target SignatureID `json:"target,omitempty"` + ThisParameter SymbolID `json:"thisParameter,omitzero"` + Target SignatureID `json:"target,omitzero"` } type GetSourceFileParams struct { @@ -686,35 +698,22 @@ type ResolveNameParams struct { ExcludeGlobals bool `json:"excludeGlobals,omitempty"` // Whether to exclude global symbols } -type GetParentOfSymbolParams struct { - Snapshot SnapshotID `json:"snapshot"` - Symbol SymbolID `json:"symbol"` -} - -type GetMembersOfSymbolParams struct { - Snapshot SnapshotID `json:"snapshot"` - Symbol SymbolID `json:"symbol"` -} - -type GetExportsOfSymbolParams struct { - Snapshot SnapshotID `json:"snapshot"` - Symbol SymbolID `json:"symbol"` -} - -type GetExportSymbolOfSymbolParams struct { +// GetTypePropertyParams is used for all type sub-property endpoints. +type GetTypePropertyParams struct { Snapshot SnapshotID `json:"snapshot"` - Symbol SymbolID `json:"symbol"` + Type TypeID `json:"objectId"` } -type GetSymbolOfTypeParams struct { +// GetSymbolPropertyParams is used for all symbol sub-property endpoints. +type GetSymbolPropertyParams struct { Snapshot SnapshotID `json:"snapshot"` - Type TypeID `json:"type"` + Symbol SymbolID `json:"objectId"` } -// GetTypePropertyParams is used for all type sub-property endpoints. -type GetTypePropertyParams struct { - Snapshot SnapshotID `json:"snapshot"` - Type TypeID `json:"type"` +// GetSignaturePropertyParams is used for all signature sub-property endpoints. +type GetSignaturePropertyParams struct { + Snapshot SnapshotID `json:"snapshot"` + Signature SignatureID `json:"objectId"` } // GetContextualTypeParams returns the contextual type for a node. diff --git a/internal/api/session.go b/internal/api/session.go index c897d1a92a2..6ebed37a9ba 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -73,40 +73,84 @@ func (sd *snapshotData) nodeHandleFrom(node *ast.Node) NodeHandle { return NodeHandle(fmt.Sprintf("%d.%d.%s", idx, node.Kind, path)) } -// registerSymbol registers a symbol in this snapshot's registry and returns the response. -func (sd *snapshotData) registerSymbol(symbol *ast.Symbol) *SymbolResponse { +// newSymbolResponse registers a symbol in this snapshot's registry and returns the response. +func (sd *snapshotData) newSymbolResponse(symbol *ast.Symbol) *SymbolResponse { if symbol == nil { return nil } - resp := NewSymbolResponse(symbol) - // Override declarations with index-based handles - for i, decl := range symbol.Declarations { - resp.Declarations[i] = sd.nodeHandleFrom(decl) + resp := &SymbolResponse{ + Id: sd.registerSymbol(symbol), + Name: symbol.Name, + Flags: uint32(symbol.Flags), + CheckFlags: uint32(symbol.CheckFlags), } + + if len(symbol.Declarations) > 0 { + resp.Declarations = make([]NodeHandle, len(symbol.Declarations)) + for i, decl := range symbol.Declarations { + resp.Declarations[i] = sd.nodeHandleFrom(decl) + } + } + if symbol.ValueDeclaration != nil { resp.ValueDeclaration = sd.nodeHandleFrom(symbol.ValueDeclaration) } - sd.symbolRegistryMu.Lock() - sd.symbolRegistry[resp.Id] = symbol - sd.symbolRegistryMu.Unlock() + if symbol.Parent != nil { + resp.Parent = SymbolHandle(symbol.Parent) + } + + if symbol.ExportSymbol != nil { + resp.ExportSymbol = SymbolHandle(symbol.ExportSymbol) + } return resp } -// registerType registers a type in this snapshot's registry and returns the response. -func (sd *snapshotData) registerType(t *checker.Type) *TypeResponse { +func (sd *snapshotData) registerSymbol(symbol *ast.Symbol) SymbolID { + if symbol == nil { + return 0 + } + id := SymbolHandle(symbol) + sd.symbolRegistryMu.Lock() + defer sd.symbolRegistryMu.Unlock() + existing := sd.symbolRegistry[id] + if existing != nil { + if existing != symbol { + panic("duplicate symbol") + } + return id + } + sd.symbolRegistry[id] = symbol + return id +} + +// newTypeResponse registers a type in this snapshot's registry and returns the response. +func (sd *snapshotData) newTypeResponse(t *checker.Type) *TypeResponse { if t == nil { return nil } - resp := newTypeData(t) + return newTypeResponse(t, sd.registerType(t)) +} +func (sd *snapshotData) registerType(t *checker.Type) TypeID { + if t == nil { + return 0 + } + id := TypeHandle(t) sd.typeRegistryMu.Lock() - sd.typeRegistry[resp.Id] = t - sd.typeRegistryMu.Unlock() + defer sd.typeRegistryMu.Unlock() + existing := sd.typeRegistry[id] - return resp + if existing != nil { + if existing != t { + panic("duplicate type") + } + return id + } + sd.typeRegistry[id] = t + return id } // resolveSymbolHandle resolves a symbol handle to a symbol within this snapshot. @@ -160,21 +204,13 @@ func (sd *snapshotData) resolveSignatureHandle(handle SignatureID) (*checker.Sig return sig, nil } -// registerSignature registers a signature in this snapshot's registry and returns the response. -func (sd *snapshotData) registerSignature(sig *checker.Signature) *SignatureResponse { +// newSignatureResponse registers a signature in this snapshot's registry and returns the response. +func (sd *snapshotData) newSignatureResponse(sig *checker.Signature) *SignatureResponse { if sig == nil { return nil } - - sd.signatureRegistryMu.Lock() - sd.signatureNextID++ - id := sd.signatureNextID - handle := SignatureHandle(id) - sd.signatureRegistry[handle] = sig - sd.signatureRegistryMu.Unlock() - resp := &SignatureResponse{ - Id: handle, + Id: sd.registerSignature(sig), Flags: uint32(sig.Flags()), } @@ -183,42 +219,44 @@ func (sd *snapshotData) registerSignature(sig *checker.Signature) *SignatureResp } if len(sig.TypeParameters()) > 0 { - resp.TypeParameters = make([]TypeID, len(sig.TypeParameters())) - for i, tp := range sig.TypeParameters() { - resp.TypeParameters[i] = TypeHandle(tp) - sd.typeRegistryMu.Lock() - sd.typeRegistry[resp.TypeParameters[i]] = tp - sd.typeRegistryMu.Unlock() - } + resp.TypeParameters = typeHandles(sig.TypeParameters()) } if len(sig.Parameters()) > 0 { - resp.Parameters = make([]SymbolID, len(sig.Parameters())) - for i, param := range sig.Parameters() { - resp.Parameters[i] = SymbolHandle(param) - sd.symbolRegistryMu.Lock() - sd.symbolRegistry[resp.Parameters[i]] = param - sd.symbolRegistryMu.Unlock() - } + resp.Parameters = symbolHandles(sig.Parameters()) } if sig.ThisParameter() != nil { resp.ThisParameter = SymbolHandle(sig.ThisParameter()) - sd.symbolRegistryMu.Lock() - sd.symbolRegistry[resp.ThisParameter] = sig.ThisParameter() - sd.symbolRegistryMu.Unlock() } if sig.Target() != nil { - targetResp := sd.registerSignature(sig.Target()) - if targetResp != nil { - resp.Target = targetResp.Id - } + resp.Target = SignatureHandle(sig.Target()) } return resp } +func (sd *snapshotData) registerSignature(sig *checker.Signature) SignatureID { + if sig == nil { + return 0 + } + id := SignatureHandle(sig) + + sd.signatureRegistryMu.Lock() + defer sd.signatureRegistryMu.Unlock() + existing := sd.signatureRegistry[id] + + if existing != nil { + if existing != sig { + panic("duplicate signature") + } + return id + } + sd.signatureRegistry[id] = sig + return id +} + // Session represents an API session that provides programmatic access // to TypeScript language services through the LSP server. // It implements the Handler interface to process incoming API requests. @@ -381,16 +419,6 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json. return s.handleGetDeclaredTypeOfSymbol(ctx, parsed.(*GetTypeOfSymbolParams)) case string(MethodResolveName): return s.handleResolveName(ctx, parsed.(*ResolveNameParams)) - case string(MethodGetParentOfSymbol): - return s.handleGetParentOfSymbol(ctx, parsed.(*GetParentOfSymbolParams)) - case string(MethodGetMembersOfSymbol): - return s.handleGetMembersOfSymbol(ctx, parsed.(*GetMembersOfSymbolParams)) - case string(MethodGetExportsOfSymbol): - return s.handleGetExportsOfSymbol(ctx, parsed.(*GetExportsOfSymbolParams)) - case string(MethodGetExportSymbolOfSymbol): - return s.handleGetExportSymbolOfSymbol(ctx, parsed.(*GetExportSymbolOfSymbolParams)) - case string(MethodGetSymbolOfType): - return s.handleGetSymbolOfType(ctx, parsed.(*GetSymbolOfTypeParams)) case string(MethodGetSignaturesOfType): return s.handleGetSignaturesOfType(ctx, parsed.(*GetSignaturesOfTypeParams)) case string(MethodGetResolvedSignature): @@ -403,6 +431,16 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json. return s.handleGetTypeAtPosition(ctx, parsed.(*GetTypeAtPositionParams)) case string(MethodGetTypesAtPositions): return s.handleGetTypesAtPositions(ctx, parsed.(*GetTypesAtPositionsParams)) + case string(MethodGetParentOfSymbol): + return s.handleGetParentOfSymbol(ctx, parsed.(*GetSymbolPropertyParams)) + case string(MethodGetMembersOfSymbol): + return s.handleGetMembersOfSymbol(ctx, parsed.(*GetSymbolPropertyParams)) + case string(MethodGetExportsOfSymbol): + return s.handleGetExportsOfSymbol(ctx, parsed.(*GetSymbolPropertyParams)) + case string(MethodGetExportSymbolOfSymbol): + return s.handleGetExportSymbolOfSymbol(ctx, parsed.(*GetSymbolPropertyParams)) + case string(MethodGetSymbolOfType): + return s.handleGetSymbolOfType(ctx, parsed.(*GetTypePropertyParams)) case string(MethodGetTargetOfType): return s.handleGetTargetOfType(ctx, parsed.(*GetTypePropertyParams)) case string(MethodGetFreshTypeOfType): @@ -433,6 +471,14 @@ func (s *Session) HandleRequest(ctx context.Context, method string, params json. return s.handleGetBaseTypeOfType(ctx, parsed.(*GetTypePropertyParams)) case string(MethodGetConstraintOfType): return s.handleGetConstraintOfType(ctx, parsed.(*GetTypePropertyParams)) + case string(MethodGetTypeParametersOfSignature): + return s.handleGetTypeParametersOfSignature(ctx, parsed.(*GetSignaturePropertyParams)) + case string(MethodGetParametersOfSignature): + return s.handleGetParametersOfSignature(ctx, parsed.(*GetSignaturePropertyParams)) + case string(MethodGetThisParameterOfSignature): + return s.handleGetThisParameterOfSignature(ctx, parsed.(*GetSignaturePropertyParams)) + case string(MethodGetTargetOfSignature): + return s.handleGetTargetOfSignature(ctx, parsed.(*GetSignaturePropertyParams)) case string(MethodGetContextualType): return s.handleGetContextualType(ctx, parsed.(*GetContextualTypeParams)) case string(MethodGetBaseTypeOfLiteralType): @@ -778,7 +824,7 @@ func (s *Session) handleGetSymbolAtPosition(ctx context.Context, params *GetSymb return nil, nil } - return setup.sd.registerSymbol(symbol), nil + return setup.sd.newSymbolResponse(symbol), nil } // handleGetSymbolsAtPositions returns symbols at multiple positions in a file. @@ -803,7 +849,7 @@ func (s *Session) handleGetSymbolsAtPositions(ctx context.Context, params *GetSy } symbol := setup.checker.GetSymbolAtLocation(node) if symbol != nil { - results[i] = setup.sd.registerSymbol(symbol) + results[i] = setup.sd.newSymbolResponse(symbol) } } @@ -831,7 +877,7 @@ func (s *Session) handleGetSymbolAtLocation(ctx context.Context, params *GetSymb return nil, nil } - return setup.sd.registerSymbol(symbol), nil + return setup.sd.newSymbolResponse(symbol), nil } // handleGetSymbolsAtLocations returns symbols at multiple node locations. @@ -853,7 +899,7 @@ func (s *Session) handleGetSymbolsAtLocations(ctx context.Context, params *GetSy } symbol := setup.checker.GetSymbolAtLocation(node) if symbol != nil { - results[i] = setup.sd.registerSymbol(symbol) + results[i] = setup.sd.newSymbolResponse(symbol) } } @@ -881,7 +927,7 @@ func (s *Session) handleGetTypeOfSymbol(ctx context.Context, params *GetTypeOfSy return nil, nil } - return setup.sd.registerType(t), nil + return setup.sd.newTypeResponse(t), nil } // handleGetTypesOfSymbols returns the types of multiple symbols. @@ -903,7 +949,7 @@ func (s *Session) handleGetTypesOfSymbols(ctx context.Context, params *GetTypesO } t := setup.checker.GetTypeOfSymbol(symbol) if t != nil { - results[i] = setup.sd.registerType(t) + results[i] = setup.sd.newTypeResponse(t) } } @@ -931,7 +977,7 @@ func (s *Session) handleGetDeclaredTypeOfSymbol(ctx context.Context, params *Get return nil, nil } - return setup.sd.registerType(t), nil + return setup.sd.newTypeResponse(t), nil } // handleResolveName resolves a name to a symbol at a given location. @@ -962,114 +1008,7 @@ func (s *Session) handleResolveName(ctx context.Context, params *ResolveNamePara return nil, nil } - return setup.sd.registerSymbol(symbol), nil -} - -// handleGetParentOfSymbol returns the parent of a symbol. -func (s *Session) handleGetParentOfSymbol(ctx context.Context, params *GetParentOfSymbolParams) (*SymbolResponse, error) { - sd, err := s.getSnapshotData(params.Snapshot) - if err != nil { - return nil, err - } - - symbol, err := sd.resolveSymbolHandle(params.Symbol) - if err != nil { - return nil, err - } - - parent := symbol.Parent - if parent == nil { - return nil, nil - } - - return sd.registerSymbol(parent), nil -} - -// handleGetMembersOfSymbol returns the members of a symbol. -func (s *Session) handleGetMembersOfSymbol(ctx context.Context, params *GetMembersOfSymbolParams) ([]*SymbolResponse, error) { - sd, err := s.getSnapshotData(params.Snapshot) - if err != nil { - return nil, err - } - - symbol, err := sd.resolveSymbolHandle(params.Symbol) - if err != nil { - return nil, err - } - - if symbol.Members == nil { - return nil, nil - } - - results := make([]*SymbolResponse, 0, len(symbol.Members)) - for _, member := range symbol.Members { - results = append(results, sd.registerSymbol(member)) - } - - return results, nil -} - -// handleGetExportsOfSymbol returns the exports of a symbol. -func (s *Session) handleGetExportsOfSymbol(ctx context.Context, params *GetExportsOfSymbolParams) ([]*SymbolResponse, error) { - sd, err := s.getSnapshotData(params.Snapshot) - if err != nil { - return nil, err - } - - symbol, err := sd.resolveSymbolHandle(params.Symbol) - if err != nil { - return nil, err - } - - if symbol.Exports == nil { - return nil, nil - } - - results := make([]*SymbolResponse, 0, len(symbol.Exports)) - for _, exp := range symbol.Exports { - results = append(results, sd.registerSymbol(exp)) - } - - return results, nil -} - -// handleGetExportSymbolOfSymbol returns the export symbol of a symbol. -func (s *Session) handleGetExportSymbolOfSymbol(ctx context.Context, params *GetExportSymbolOfSymbolParams) (*SymbolResponse, error) { - sd, err := s.getSnapshotData(params.Snapshot) - if err != nil { - return nil, err - } - - symbol, err := sd.resolveSymbolHandle(params.Symbol) - if err != nil { - return nil, err - } - - if symbol.ExportSymbol != nil { - return sd.registerSymbol(symbol.ExportSymbol), nil - } - - return sd.registerSymbol(symbol), nil -} - -// handleGetSymbolOfType returns the symbol associated with a type. -func (s *Session) handleGetSymbolOfType(ctx context.Context, params *GetSymbolOfTypeParams) (*SymbolResponse, error) { - sd, err := s.getSnapshotData(params.Snapshot) - if err != nil { - return nil, err - } - - t, err := sd.resolveTypeHandle(params.Type) - if err != nil { - return nil, err - } - - symbol := t.Symbol() - if symbol == nil { - return nil, nil - } - - return sd.registerSymbol(symbol), nil + return setup.sd.newSymbolResponse(symbol), nil } // handleGetSignaturesOfType returns the call or construct signatures of a type. @@ -1088,7 +1027,7 @@ func (s *Session) handleGetSignaturesOfType(ctx context.Context, params *GetSign sigs := setup.checker.GetSignaturesOfType(t, checker.SignatureKind(params.Kind)) results := make([]*SignatureResponse, len(sigs)) for i, sig := range sigs { - results[i] = setup.sd.registerSignature(sig) + results[i] = setup.sd.newSignatureResponse(sig) } return results, nil @@ -1111,7 +1050,7 @@ func (s *Session) handleGetResolvedSignature(ctx context.Context, params *GetRes } sig := setup.checker.GetResolvedSignature(node) - return setup.sd.registerSignature(sig), nil + return setup.sd.newSignatureResponse(sig), nil } // handleGetTypeAtLocation returns the type at a node location. @@ -1135,7 +1074,7 @@ func (s *Session) handleGetTypeAtLocation(ctx context.Context, params *GetTypeAt return nil, nil } - return setup.sd.registerType(t), nil + return setup.sd.newTypeResponse(t), nil } // handleGetTypeAtLocations returns types at multiple node locations. @@ -1157,7 +1096,7 @@ func (s *Session) handleGetTypeAtLocations(ctx context.Context, params *GetTypeA } t := setup.checker.GetTypeAtLocation(node) if t != nil { - results[i] = setup.sd.registerType(t) + results[i] = setup.sd.newTypeResponse(t) } } @@ -1188,7 +1127,7 @@ func (s *Session) handleGetTypeAtPosition(ctx context.Context, params *GetTypeAt return nil, nil } - return setup.sd.registerType(t), nil + return setup.sd.newTypeResponse(t), nil } // handleGetTypesAtPositions returns types at multiple positions in a file. @@ -1213,15 +1152,125 @@ func (s *Session) handleGetTypesAtPositions(ctx context.Context, params *GetType } t := setup.checker.GetTypeAtLocation(node) if t != nil { - results[i] = setup.sd.registerType(t) + results[i] = setup.sd.newTypeResponse(t) } } return results, nil } -// resolveTypeProperty resolves a type handle and returns a single sub-type. -func (s *Session) resolveTypeProperty(params *GetTypePropertyParams, getter func(*checker.Type) *checker.Type) (*TypeResponse, error) { +func (s *Session) handleGetParentOfSymbol(_ context.Context, params *GetSymbolPropertyParams) (*SymbolResponse, error) { + return s.resolveSymbolPropertyOfSymbol(params, func(sym *ast.Symbol) *ast.Symbol { return sym.Parent }) +} + +func (s *Session) handleGetMembersOfSymbol(_ context.Context, params *GetSymbolPropertyParams) ([]*SymbolResponse, error) { + return s.resolveSymbolTablePropertyOfSymbol(params, func(symbol *ast.Symbol) ast.SymbolTable { + return symbol.Members + }) +} + +func (s *Session) handleGetExportsOfSymbol(_ context.Context, params *GetSymbolPropertyParams) ([]*SymbolResponse, error) { + return s.resolveSymbolTablePropertyOfSymbol(params, func(symbol *ast.Symbol) ast.SymbolTable { + return symbol.Exports + }) +} + +func (s *Session) handleGetExportSymbolOfSymbol(_ context.Context, params *GetSymbolPropertyParams) (*SymbolResponse, error) { + return s.resolveSymbolPropertyOfSymbol(params, func(sym *ast.Symbol) *ast.Symbol { return sym.ExportSymbol }) +} + +func (s *Session) handleGetSymbolOfType(_ context.Context, params *GetTypePropertyParams) (*SymbolResponse, error) { + return s.resolveSymbolPropertyOfType(params, (*checker.Type).Symbol) +} + +func (s *Session) handleGetTargetOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { + return s.resolveTypePropertyOfType(params, (*checker.Type).Target) +} + +func (s *Session) handleGetFreshTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { + return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsLiteralType().FreshType() }) +} + +func (s *Session) handleGetRegularTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { + return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsLiteralType().RegularType() }) +} + +func (s *Session) handleGetTypesOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { + return s.resolveTypeArrayPropertyOfType(params, (*checker.Type).Types) +} + +func (s *Session) handleGetTypeParametersOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { + return s.resolveTypeArrayPropertyOfType(params, func(t *checker.Type) []*checker.Type { return t.AsInterfaceType().TypeParameters() }) +} + +func (s *Session) handleGetOuterTypeParametersOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { + return s.resolveTypeArrayPropertyOfType(params, func(t *checker.Type) []*checker.Type { return t.AsInterfaceType().OuterTypeParameters() }) +} + +func (s *Session) handleGetLocalTypeParametersOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { + return s.resolveTypeArrayPropertyOfType(params, func(t *checker.Type) []*checker.Type { return t.AsInterfaceType().LocalTypeParameters() }) +} + +func (s *Session) handleGetAliasTypeArgumentsOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { + return s.resolveTypeArrayPropertyOfType(params, func(t *checker.Type) []*checker.Type { + if t.Alias() == nil { + return nil + } + return t.Alias().TypeArguments() + }) +} + +func (s *Session) handleGetAliasSymbolOfType(_ context.Context, params *GetTypePropertyParams) (*SymbolResponse, error) { + return s.resolveSymbolPropertyOfType(params, func(t *checker.Type) *ast.Symbol { + if t.Alias() == nil { + return nil + } + return t.Alias().Symbol() + }) +} + +func (s *Session) handleGetObjectTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { + return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsIndexedAccessType().ObjectType() }) +} + +func (s *Session) handleGetIndexTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { + return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsIndexedAccessType().IndexType() }) +} + +func (s *Session) handleGetCheckTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { + return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsConditionalType().CheckType() }) +} + +func (s *Session) handleGetExtendsTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { + return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsConditionalType().ExtendsType() }) +} + +func (s *Session) handleGetBaseTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { + return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsSubstitutionType().BaseType() }) +} + +func (s *Session) handleGetConstraintOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { + return s.resolveTypePropertyOfType(params, func(t *checker.Type) *checker.Type { return t.AsSubstitutionType().SubstConstraint() }) +} + +func (s *Session) handleGetTypeParametersOfSignature(_ context.Context, params *GetSignaturePropertyParams) ([]*TypeResponse, error) { + return s.resolveTypeArrayPropertyOfSignature(params, (*checker.Signature).TypeParameters) +} + +func (s *Session) handleGetParametersOfSignature(_ context.Context, params *GetSignaturePropertyParams) ([]*SymbolResponse, error) { + return s.resolveSymbolArrayPropertyOfSignature(params, (*checker.Signature).Parameters) +} + +func (s *Session) handleGetThisParameterOfSignature(_ context.Context, params *GetSignaturePropertyParams) (*SymbolResponse, error) { + return s.resolveSymbolPropertyOfSignature(params, (*checker.Signature).ThisParameter) +} + +func (s *Session) handleGetTargetOfSignature(_ context.Context, params *GetSignaturePropertyParams) (*SignatureResponse, error) { + return s.resolveSignaturePropertyOfSignature(params, (*checker.Signature).Target) +} + +// resolveTypePropertyOfType resolves a type property of type `Type` and returns a type response. +func (s *Session) resolveTypePropertyOfType(params *GetTypePropertyParams, getter func(*checker.Type) *checker.Type) (*TypeResponse, error) { sd, err := s.getSnapshotData(params.Snapshot) if err != nil { return nil, err @@ -1237,11 +1286,11 @@ func (s *Session) resolveTypeProperty(params *GetTypePropertyParams, getter func return nil, nil } - return sd.registerType(result), nil + return sd.newTypeResponse(result), nil } -// resolveTypeArrayProperty resolves a type handle and returns an array of sub-types. -func (s *Session) resolveTypeArrayProperty(params *GetTypePropertyParams, getter func(*checker.Type) []*checker.Type) ([]*TypeResponse, error) { +// resolveTypeArrayPropertyOfType resolves a type property of an array of types and returns an array of type responses. +func (s *Session) resolveTypeArrayPropertyOfType(params *GetTypePropertyParams, getter func(*checker.Type) []*checker.Type) ([]*TypeResponse, error) { sd, err := s.getSnapshotData(params.Snapshot) if err != nil { return nil, err @@ -1259,116 +1308,155 @@ func (s *Session) resolveTypeArrayProperty(params *GetTypePropertyParams, getter results := make([]*TypeResponse, len(types)) for i, sub := range types { - results[i] = sd.registerType(sub) + results[i] = sd.newTypeResponse(sub) } return results, nil } -func (s *Session) handleGetTargetOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { - return s.resolveTypeProperty(params, (*checker.Type).Target) -} +// resolveSymbolPropertyOfType resolves a type property of type `Symbol` and returns a symbol response. +func (s *Session) resolveSymbolPropertyOfType(params *GetTypePropertyParams, getter func(*checker.Type) *ast.Symbol) (*SymbolResponse, error) { + sd, err := s.getSnapshotData(params.Snapshot) + if err != nil { + return nil, err + } -func (s *Session) handleGetFreshTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { - return s.resolveTypeProperty(params, func(t *checker.Type) *checker.Type { - if t.Flags()&checker.TypeFlagsFreshable == 0 { - return nil - } - return t.AsLiteralType().FreshType() - }) -} + t, err := sd.resolveTypeHandle(params.Type) + if err != nil { + return nil, err + } -func (s *Session) handleGetRegularTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { - return s.resolveTypeProperty(params, func(t *checker.Type) *checker.Type { - if t.Flags()&checker.TypeFlagsFreshable == 0 { - return nil - } - return t.AsLiteralType().RegularType() - }) + result := getter(t) + if result == nil { + return nil, nil + } + return sd.newSymbolResponse(result), nil } -func (s *Session) handleGetTypesOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { - return s.resolveTypeArrayProperty(params, (*checker.Type).Types) -} +// resolveSymbolTablePropertyOfSymbol resolves a symbol property of type `Symbol` and returns a symbol response. +func (s *Session) resolveSymbolPropertyOfSymbol(params *GetSymbolPropertyParams, getter func(*ast.Symbol) *ast.Symbol) (*SymbolResponse, error) { + sd, err := s.getSnapshotData(params.Snapshot) + if err != nil { + return nil, err + } -func (s *Session) handleGetTypeParametersOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { - return s.resolveTypeArrayProperty(params, func(t *checker.Type) []*checker.Type { - return t.AsInterfaceType().TypeParameters() - }) -} + symbol, err := sd.resolveSymbolHandle(params.Symbol) + if err != nil { + return nil, err + } -func (s *Session) handleGetOuterTypeParametersOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { - return s.resolveTypeArrayProperty(params, func(t *checker.Type) []*checker.Type { - return t.AsInterfaceType().OuterTypeParameters() - }) + result := getter(symbol) + if result == nil { + return nil, nil + } + return sd.newSymbolResponse(result), nil } -func (s *Session) handleGetLocalTypeParametersOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { - return s.resolveTypeArrayProperty(params, func(t *checker.Type) []*checker.Type { - return t.AsInterfaceType().LocalTypeParameters() - }) -} +// resolveSymbolTablePropertyOfSymbol resolves a symbol property of type `SymbolTable` and returns an array of symbol responses. +func (s *Session) resolveSymbolTablePropertyOfSymbol(params *GetSymbolPropertyParams, getter func(*ast.Symbol) ast.SymbolTable) ([]*SymbolResponse, error) { + sd, err := s.getSnapshotData(params.Snapshot) + if err != nil { + return nil, err + } -func (s *Session) handleGetAliasTypeArgumentsOfType(_ context.Context, params *GetTypePropertyParams) ([]*TypeResponse, error) { - return s.resolveTypeArrayProperty(params, func(t *checker.Type) []*checker.Type { - if t.Alias() == nil { - return nil - } - return t.Alias().TypeArguments() - }) + symbol, err := sd.resolveSymbolHandle(params.Symbol) + if err != nil { + return nil, err + } + + symbolTable := getter(symbol) + if symbolTable == nil || len(symbolTable) == 0 { + return nil, nil + } + + results := make([]*SymbolResponse, 0, len(symbolTable)) + for _, sub := range symbolTable { + results = append(results, sd.newSymbolResponse(sub)) + } + return results, nil } -func (s *Session) handleGetAliasSymbolOfType(_ context.Context, params *GetTypePropertyParams) (*SymbolResponse, error) { +// resolveSymbolArrayPropertyOfSignature resolves a signature property of an array of symbols and returns an array of symbol responses. +func (s *Session) resolveSymbolArrayPropertyOfSignature(params *GetSignaturePropertyParams, getter func(*checker.Signature) []*ast.Symbol) ([]*SymbolResponse, error) { sd, err := s.getSnapshotData(params.Snapshot) if err != nil { return nil, err } - t, err := sd.resolveTypeHandle(params.Type) + sig, err := sd.resolveSignatureHandle(params.Signature) if err != nil { return nil, err } - if t.Alias() == nil || t.Alias().Symbol() == nil { + symbols := getter(sig) + if len(symbols) == 0 { return nil, nil } - return sd.registerSymbol(t.Alias().Symbol()), nil + results := make([]*SymbolResponse, len(symbols)) + for i, sym := range symbols { + results[i] = sd.newSymbolResponse(sym) + } + return results, nil } -func (s *Session) handleGetObjectTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { - return s.resolveTypeProperty(params, func(t *checker.Type) *checker.Type { - return t.AsIndexedAccessType().ObjectType() - }) -} +// resolveSymbolPropertyOfSignature resolves a signature property of type `Symbol` and returns a symbol response. +func (s *Session) resolveSymbolPropertyOfSignature(params *GetSignaturePropertyParams, getter func(*checker.Signature) *ast.Symbol) (*SymbolResponse, error) { + sd, err := s.getSnapshotData(params.Snapshot) + if err != nil { + return nil, err + } -func (s *Session) handleGetIndexTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { - return s.resolveTypeProperty(params, func(t *checker.Type) *checker.Type { - return t.AsIndexedAccessType().IndexType() - }) -} + sig, err := sd.resolveSignatureHandle(params.Signature) + if err != nil { + return nil, err + } -func (s *Session) handleGetCheckTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { - return s.resolveTypeProperty(params, func(t *checker.Type) *checker.Type { - return t.AsConditionalType().CheckType() - }) + result := getter(sig) + if result == nil { + return nil, nil + } + return sd.newSymbolResponse(result), nil } -func (s *Session) handleGetExtendsTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { - return s.resolveTypeProperty(params, func(t *checker.Type) *checker.Type { - return t.AsConditionalType().ExtendsType() - }) -} +func (s *Session) resolveTypeArrayPropertyOfSignature(params *GetSignaturePropertyParams, getter func(signature *checker.Signature) []*checker.Type) ([]*TypeResponse, error) { + sd, err := s.getSnapshotData(params.Snapshot) + if err != nil { + return nil, err + } -func (s *Session) handleGetBaseTypeOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { - return s.resolveTypeProperty(params, func(t *checker.Type) *checker.Type { - return t.AsSubstitutionType().BaseType() - }) + sig, err := sd.resolveSignatureHandle(params.Signature) + if err != nil { + return nil, err + } + + types := getter(sig) + if len(types) == 0 { + return nil, nil + } + + results := make([]*TypeResponse, len(types)) + for i, sub := range types { + results[i] = sd.newTypeResponse(sub) + } + return results, nil } -func (s *Session) handleGetConstraintOfType(_ context.Context, params *GetTypePropertyParams) (*TypeResponse, error) { - return s.resolveTypeProperty(params, func(t *checker.Type) *checker.Type { - return t.AsSubstitutionType().SubstConstraint() - }) +func (s *Session) resolveSignaturePropertyOfSignature(params *GetSignaturePropertyParams, getter func(*checker.Signature) *checker.Signature) (*SignatureResponse, error) { + sd, err := s.getSnapshotData(params.Snapshot) + if err != nil { + return nil, err + } + + sig, err := sd.resolveSignatureHandle(params.Signature) + if err != nil { + return nil, err + } + + result := getter(sig) + if result == nil { + return nil, nil + } + return sd.newSignatureResponse(result), nil } // handleGetContextualType returns the contextual type for a node. @@ -1392,7 +1480,7 @@ func (s *Session) handleGetContextualType(ctx context.Context, params *GetContex return nil, nil } - return setup.sd.registerType(t), nil + return setup.sd.newTypeResponse(t), nil } // handleGetBaseTypeOfLiteralType returns the base type of a literal type (e.g. number for 42). @@ -1413,7 +1501,7 @@ func (s *Session) handleGetBaseTypeOfLiteralType(ctx context.Context, params *Ge return nil, nil } - return setup.sd.registerType(result), nil + return setup.sd.newTypeResponse(result), nil } // handleGetNonNullableType returns the type with null and undefined removed. @@ -1434,7 +1522,7 @@ func (s *Session) handleGetNonNullableType(ctx context.Context, params *GetNonNu return nil, nil } - return setup.sd.registerType(result), nil + return setup.sd.newTypeResponse(result), nil } // handleGetTypeFromTypeNode returns the type for a type node. @@ -1458,7 +1546,7 @@ func (s *Session) handleGetTypeFromTypeNode(ctx context.Context, params *GetType return nil, nil } - return setup.sd.registerType(t), nil + return setup.sd.newTypeResponse(t), nil } // handleGetWidenedType returns the widened type. @@ -1479,7 +1567,7 @@ func (s *Session) handleGetWidenedType(ctx context.Context, params *GetWidenedTy return nil, nil } - return setup.sd.registerType(result), nil + return setup.sd.newTypeResponse(result), nil } // handleGetParameterType returns the type of a parameter at a given index in a signature. @@ -1504,7 +1592,7 @@ func (s *Session) handleGetParameterType(ctx context.Context, params *GetParamet return nil, nil } - return setup.sd.registerType(t), nil + return setup.sd.newTypeResponse(t), nil } // handleIsArrayLikeType returns whether a type is array-like. @@ -1564,7 +1652,7 @@ func (s *Session) handleGetShorthandAssignmentValueSymbol(ctx context.Context, p return nil, nil } - return setup.sd.registerSymbol(symbol), nil + return setup.sd.newSymbolResponse(symbol), nil } // handleGetTypeOfSymbolAtLocation returns the narrowed type of a symbol at a specific location. @@ -1596,7 +1684,7 @@ func (s *Session) handleGetTypeOfSymbolAtLocation(ctx context.Context, params *G return nil, nil } - return setup.sd.registerType(t), nil + return setup.sd.newTypeResponse(t), nil } // handleTypeToTypeNode converts a Type to a TypeNode AST and returns it as binary-encoded data. @@ -1736,7 +1824,7 @@ func (s *Session) handleGetIntrinsicType(ctx context.Context, params *GetIntrins return nil, nil } - return setup.sd.registerType(t), nil + return setup.sd.newTypeResponse(t), nil } // handleIsContextSensitive returns whether a node is context-sensitive. @@ -1776,7 +1864,7 @@ func (s *Session) handleGetReturnTypeOfSignature(ctx context.Context, params *Ch return nil, nil } - return setup.sd.registerType(t), nil + return setup.sd.newTypeResponse(t), nil } // handleGetRestTypeOfSignature returns the rest type of a signature. @@ -1797,7 +1885,7 @@ func (s *Session) handleGetRestTypeOfSignature(ctx context.Context, params *Chec return nil, nil } - return setup.sd.registerType(t), nil + return setup.sd.newTypeResponse(t), nil } // handleGetTypePredicateOfSignature returns the type predicate of a signature. @@ -1824,7 +1912,7 @@ func (s *Session) handleGetTypePredicateOfSignature(ctx context.Context, params ParameterName: pred.ParameterName(), } if pred.Type() != nil { - resp.Type = setup.sd.registerType(pred.Type()) + resp.Type = setup.sd.newTypeResponse(pred.Type()) } return resp, nil @@ -1850,7 +1938,7 @@ func (s *Session) handleGetBaseTypes(ctx context.Context, params *CheckerTypePar results := make([]*TypeResponse, len(baseTypes)) for i, bt := range baseTypes { - results[i] = setup.sd.registerType(bt) + results[i] = setup.sd.newTypeResponse(bt) } return results, nil @@ -1876,7 +1964,7 @@ func (s *Session) handleGetPropertiesOfType(ctx context.Context, params *Checker results := make([]*SymbolResponse, len(props)) for i, prop := range props { - results[i] = setup.sd.registerSymbol(prop) + results[i] = setup.sd.newSymbolResponse(prop) } return results, nil @@ -1903,8 +1991,8 @@ func (s *Session) handleGetIndexInfosOfType(ctx context.Context, params *Checker results := make([]*IndexInfoResponse, len(infos)) for i, info := range infos { results[i] = &IndexInfoResponse{ - KeyType: *setup.sd.registerType(info.KeyType()), - ValueType: *setup.sd.registerType(info.ValueType()), + KeyType: *setup.sd.newTypeResponse(info.KeyType()), + ValueType: *setup.sd.newTypeResponse(info.ValueType()), IsReadonly: info.IsReadonly(), } if info.Declaration() != nil { @@ -1933,7 +2021,7 @@ func (s *Session) handleGetConstraintOfTypeParameter(ctx context.Context, params return nil, nil } - return setup.sd.registerType(constraint), nil + return setup.sd.newTypeResponse(constraint), nil } // handleGetTypeArguments returns the type arguments of a type reference. @@ -1956,7 +2044,7 @@ func (s *Session) handleGetTypeArguments(ctx context.Context, params *CheckerTyp results := make([]*TypeResponse, len(typeArgs)) for i, ta := range typeArgs { - results[i] = setup.sd.registerType(ta) + results[i] = setup.sd.newTypeResponse(ta) } return results, nil @@ -2331,7 +2419,7 @@ func (s *Session) handleGetCompletionsAtPosition(ctx context.Context, params *Ge } } if item.Symbol != nil { - entry.Symbol = sd.registerSymbol(item.Symbol) + entry.Symbol = sd.newSymbolResponse(item.Symbol) } entries = append(entries, entry) } @@ -2388,7 +2476,7 @@ func (s *Session) handleGetReferencedSymbolsForNode(ctx context.Context, params References: refs, } if sym := entry.DefinitionSymbol(); sym != nil { - re.Symbol = sd.registerSymbol(sym) + re.Symbol = sd.newSymbolResponse(sym) } result = append(result, re) } diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 95c650ff352..ff8eeb13fc8 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -586,6 +586,7 @@ type Checker struct { compareSymbolChains func([]*ast.Symbol, []*ast.Symbol) int TypeCount uint32 SymbolCount uint32 + SignatureCount uint32 TotalInstantiationCount uint32 instantiationCount uint32 instantiationDepth uint32 @@ -25128,7 +25129,9 @@ func (c *Checker) newSubstitutionType(baseType *Type, constraint *Type) *Type { } func (c *Checker) newSignature(flags SignatureFlags, declaration *ast.Node, typeParameters []*Type, thisParameter *ast.Symbol, parameters []*ast.Symbol, resolvedReturnType *Type, resolvedTypePredicate *TypePredicate, minArgumentCount int) *Signature { + c.SignatureCount++ sig := c.signatureArena.New() + sig.id = SignatureId(c.SignatureCount) sig.flags = flags sig.declaration = declaration sig.typeParameters = typeParameters diff --git a/internal/checker/types.go b/internal/checker/types.go index bf728413f13..bd13420922e 100644 --- a/internal/checker/types.go +++ b/internal/checker/types.go @@ -153,7 +153,10 @@ const externalHelpersModuleNameText = "tslib" // Ids -type TypeId uint32 +type ( + TypeId uint32 + SignatureId uint32 +) // Links for referenced symbols @@ -1271,6 +1274,7 @@ const ( // Signature type Signature struct { + id SignatureId flags SignatureFlags minArgumentCount int32 resolvedMinArgumentCount int32 @@ -1286,6 +1290,10 @@ type Signature struct { composite *CompositeSignature } +func (s *Signature) Id() SignatureId { + return s.id +} + func (s *Signature) Flags() SignatureFlags { return s.flags } From 900f95a921b2b627d63a522d01304050f8494874 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Tue, 16 Jun 2026 12:01:50 -0700 Subject: [PATCH 34/49] Note `tsc` for RC and above in README.me (#4340) --- README.md | 2 ++ _packages/native-preview/README.md | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 01e9662c66c..5fa8bc5220c 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ npm install @typescript/native-preview npx tsgo # Use this as you would tsc. ``` +For TypeScript 7.0 RC and later, the command name is `tsc`. + A preview VS Code extension is [available on the VS Code marketplace](https://marketplace.visualstudio.com/items?itemName=TypeScriptTeam.native-preview). To use this, set this in your VS Code settings: diff --git a/_packages/native-preview/README.md b/_packages/native-preview/README.md index afd47c6fd52..f3a3831c643 100644 --- a/_packages/native-preview/README.md +++ b/_packages/native-preview/README.md @@ -8,7 +8,9 @@ It will eventually be replaced by the official TypeScript package. ## Usage -Use the `tsgo` command just like you would use `tsc`: +For TypeScript 7.0 RC and later, use `tsc` just like for TypeScript 6.0. + +For other builds, use the `tsgo` command just like you would use `tsc`: ```sh npx tsgo --help From 2fb5d4ce13935aef1f3c2896fad76d0ab4d43604 Mon Sep 17 00:00:00 2001 From: John Favret <64748847+johnfav03@users.noreply.github.com> Date: Tue, 16 Jun 2026 19:31:03 -0500 Subject: [PATCH 35/49] Rebuilt build mode around `fswatch` package (#4326) --- internal/execute/build/buildtask.go | 60 --- internal/execute/build/orchestrator.go | 267 ++++++++++++- .../execute/tsctests/mock_watch_backend.go | 6 +- internal/execute/tsctests/sys.go | 3 +- .../execute/tsctests/watcher_race_test.go | 2 +- internal/execute/watcher.go | 370 ++---------------- internal/execute/watchmanager/watchbackend.go | 81 ++++ internal/execute/watchmanager/watchmanager.go | 298 ++++++++++++++ .../reports-syntax-errors-in-config-file.js | 20 + .../demo/updates-with-bad-reference.js | 18 + .../demo/updates-with-circular-reference.js | 15 + .../extends/configDir-template.js | 10 + .../with-config-with-libReplacement.js | 11 + .../libraryResolution/with-config.js | 7 + ...for-changes-to-package-json-main-fields.js | 15 + ...se-different-module-resolution-settings.js | 20 + ...t-correctly-with-cts-and-mts-extensions.js | 25 ++ ...n-no-files-are-emitted-with-incremental.js | 12 + ...when-watching-when-no-files-are-emitted.js | 9 + .../dts-errors-with-incremental-as-modules.js | 28 ++ .../noEmit/dts-errors-with-incremental.js | 28 ++ ...dts-enabled-with-incremental-as-modules.js | 28 ++ ...rs-without-dts-enabled-with-incremental.js | 28 ++ .../noEmit/dts-errors-without-dts-enabled.js | 21 + .../tsbuildWatch/noEmit/dts-errors.js | 21 + ...ntic-errors-with-incremental-as-modules.js | 28 ++ .../semantic-errors-with-incremental.js | 28 ++ .../tsbuildWatch/noEmit/semantic-errors.js | 21 + ...ntax-errors-with-incremental-as-modules.js | 28 ++ .../noEmit/syntax-errors-with-incremental.js | 28 ++ .../tsbuildWatch/noEmit/syntax-errors.js | 21 + ...Error-with-declaration-with-incremental.js | 48 +++ .../noEmitOnError-with-declaration.js | 36 ++ .../noEmitOnError-with-incremental.js | 48 +++ .../noEmitOnError/noEmitOnError.js | 36 ++ ...ceError-when-file-with-no-error-changes.js | 12 + ...ing-errors-only-changed-file-is-emitted.js | 12 + ...tErrors-when-file-with-no-error-changes.js | 8 + ...ixing-error-files-all-files-are-emitted.js | 8 + .../programUpdates/tsbuildinfo-has-error.js | 4 + ...e-down-stream-project-and-then-fixes-it.js | 15 + ...project-with-extended-config-is-removed.js | 8 + ...hen-noUnusedParameters-changes-to-false.js | 6 + .../works-with-extended-source-files.js | 32 ++ ...rojects-in-a-solution-with---builders-3.js | 135 +++++++ ...hen-there-are-23-projects-in-a-solution.js | 135 +++++++ ...rojects-in-a-solution-with---builders-1.js | 35 ++ ...when-there-are-3-projects-in-a-solution.js | 35 ++ ...rojects-in-a-solution-with---builders-2.js | 45 +++ ...when-there-are-5-projects-in-a-solution.js | 45 +++ ...rojects-in-a-solution-with---builders-3.js | 60 +++ ...when-there-are-8-projects-in-a-solution.js | 60 +++ .../reexport/Reports-errors-correctly.js | 18 + ...-referenced-project-and-shared-is-first.js | 54 +++ ...en-root-file-is-from-referenced-project.js | 54 +++ ...equent-updates-with-circular-references.js | 18 + ...le-is-added,-and-its-subsequent-updates.js | 18 + ...errors-message-with-circular-references.js | 24 ++ ...hanges-and-reports-found-errors-message.js | 24 ++ .../incremental-updates-in-verbose-mode.js | 18 + ...ncing-projects-with-circular-references.js | 12 + ...not-start-build-of-referencing-projects.js | 12 + ...rs-when-preserveWatchOutput-is-not-used.js | 24 ++ ...veWatchOutput-is-passed-on-command-line.js | 24 ++ ...BuildOnErrors-is-passed-on-command-line.js | 24 ++ ...e-of-program-emit-with-outDir-specified.js | 16 + ...r-recompilation-because-of-program-emit.js | 16 + ...rrors-when-test-does-not-reference-core.js | 12 + ...ects-have-errors-with-stopBuildOnErrors.js | 12 + ...tches-config-files-that-are-not-present.js | 12 + 70 files changed, 2359 insertions(+), 413 deletions(-) create mode 100644 internal/execute/watchmanager/watchbackend.go create mode 100644 internal/execute/watchmanager/watchmanager.go diff --git a/internal/execute/build/buildtask.go b/internal/execute/build/buildtask.go index 5b69910d808..0a64695d947 100644 --- a/internal/execute/build/buildtask.go +++ b/internal/execute/build/buildtask.go @@ -2,7 +2,6 @@ package build import ( "fmt" - "slices" "strings" "sync" "sync/atomic" @@ -19,14 +18,6 @@ import ( "github.com/microsoft/typescript-go/internal/tspath" ) -type updateKind uint - -const ( - updateKindNone updateKind = iota - updateKindConfig - updateKindUpdate -) - type buildKind uint const ( @@ -70,11 +61,6 @@ type BuildTask struct { prevReporter *BuildTask reportDone chan struct{} - // Watching things - configTime time.Time - extendedConfigTimes []time.Time - inputFiles []time.Time - buildInfoEntry *buildInfoEntry buildInfoEntryMu sync.Mutex @@ -710,14 +696,7 @@ func (t *BuildTask) cleanProjectOutput(orchestrator *Orchestrator, outputFile st } func (t *BuildTask) updateWatch(orchestrator *Orchestrator, oldCache *collections.SyncMap[tspath.Path, time.Time]) { - t.configTime = orchestrator.host.loadOrStoreMTime(t.config, oldCache, false) if t.resolved != nil { - t.extendedConfigTimes = core.Map(t.resolved.ExtendedSourceFiles(), func(p string) time.Time { - return orchestrator.host.loadOrStoreMTime(p, oldCache, false) - }) - t.inputFiles = core.Map(t.resolved.FileNames(), func(p string) time.Time { - return orchestrator.host.loadOrStoreMTime(p, oldCache, false) - }) if t.canUpdateJsDtsOutputTimestamps() { for outputFile := range t.resolved.GetOutputFileNames() { orchestrator.host.storeMTimeFromOldCache(outputFile, oldCache) @@ -737,45 +716,6 @@ func (t *BuildTask) resetConfig(orchestrator *Orchestrator, path tspath.Path) { orchestrator.host.resolvedReferences.delete(path) } -func (t *BuildTask) hasUpdate(orchestrator *Orchestrator, path tspath.Path) updateKind { - var needsConfigUpdate bool - var needsUpdate bool - if configTime := orchestrator.host.GetMTime(t.config); configTime != t.configTime { - t.resetConfig(orchestrator, path) - needsConfigUpdate = true - } - if t.resolved != nil { - for index, file := range t.resolved.ExtendedSourceFiles() { - if orchestrator.host.GetMTime(file) != t.extendedConfigTimes[index] { - t.resetConfig(orchestrator, path) - needsConfigUpdate = true - } - } - for index, file := range t.resolved.FileNames() { - if orchestrator.host.GetMTime(file) != t.inputFiles[index] { - t.resetStatus() - needsUpdate = true - } - } - if !needsConfigUpdate { - configStart := orchestrator.opts.Sys.Now() - newConfig := t.resolved.ReloadFileNamesOfParsedCommandLine(orchestrator.host.FS()) - configTime := orchestrator.opts.Sys.Now().Sub(configStart) - // Make new channels if needed later - t.reportDone = make(chan struct{}) - t.done = make(chan struct{}) - if !slices.Equal(t.resolved.FileNames(), newConfig.FileNames()) { - orchestrator.host.resolvedReferences.store(path, newConfig) - orchestrator.host.configTimes.Store(path, configTime) - t.resolved = newConfig - t.resetStatus() - needsUpdate = true - } - } - } - return core.IfElse(needsConfigUpdate, updateKindConfig, core.IfElse(needsUpdate, updateKindUpdate, updateKindNone)) -} - func (t *BuildTask) loadOrStoreBuildInfo(orchestrator *Orchestrator, configPath tspath.Path, buildInfoFileName string) (*incremental.BuildInfo, time.Time) { path := orchestrator.toPath(buildInfoFileName) t.buildInfoEntryMu.Lock() diff --git a/internal/execute/build/orchestrator.go b/internal/execute/build/orchestrator.go index 99a2e1179cb..6afdf077022 100644 --- a/internal/execute/build/orchestrator.go +++ b/internal/execute/build/orchestrator.go @@ -2,7 +2,9 @@ package build import ( "context" + "fmt" "io" + "slices" "strings" "sync/atomic" "time" @@ -13,6 +15,8 @@ import ( "github.com/microsoft/typescript-go/internal/core" "github.com/microsoft/typescript-go/internal/diagnostics" "github.com/microsoft/typescript-go/internal/execute/tsc" + "github.com/microsoft/typescript-go/internal/execute/watchmanager" + "github.com/microsoft/typescript-go/internal/fswatch" "github.com/microsoft/typescript-go/internal/tsoptions" "github.com/microsoft/typescript-go/internal/tspath" "github.com/microsoft/typescript-go/internal/vfs/cachedvfs" @@ -66,6 +70,9 @@ type Orchestrator struct { errorSummaryReporter tsc.DiagnosticsReporter watchStatusReporter tsc.DiagnosticReporter + + // fswatch event-based watching + wm *watchmanager.WatchManager } var _ tsc.Watcher = (*Orchestrator)(nil) @@ -78,6 +85,13 @@ func (o *Orchestrator) toPath(fileName string) tspath.Path { return tspath.ToPath(fileName, o.comparePathsOptions.CurrentDirectory, o.comparePathsOptions.UseCaseSensitiveFileNames) } +func (o *Orchestrator) resolveBuildInfoFileName(fileName string, buildInfoDir string) string { + if !strings.HasPrefix(fileName, ".") { + return tspath.CombinePaths(o.host.DefaultLibraryPath(), fileName) + } + return tspath.GetNormalizedAbsolutePath(fileName, buildInfoDir) +} + func (o *Orchestrator) Order() []string { return o.order } @@ -223,22 +237,27 @@ func (o *Orchestrator) Start(ctx context.Context) tsc.CommandLineResult { } func (o *Orchestrator) Watch(ctx context.Context) { + o.wm.Lock() + + if o.opts.Testing == nil { + if o.opts.Sys.GetEnvironmentVariable("TS_WATCH_DEBUG") != "" { + o.wm.DebugLog = o.opts.Sys.Writer() + } + o.wm.EnsureDefaultBackend() + } + o.updateWatch() + desiredDirs := o.computeDesiredWatches() + if err := o.wm.ReconcileWatches(desiredDirs); err != nil { + fmt.Fprintf(o.opts.Sys.Writer(), "%v\n", err) + o.wm.ForceOverflow() + } o.resetCaches() - // Start watching for file changes + o.wm.Unlock() + if o.opts.Testing == nil { - watchInterval := o.opts.Command.WatchOptions.WatchInterval() - ticker := time.NewTicker(watchInterval) - defer ticker.Stop() - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - o.DoCycle() - } - } + o.wm.RunLoop(ctx, o.DoCycle) } } @@ -259,21 +278,216 @@ func (o *Orchestrator) resetCaches() { o.host.configTimes = collections.SyncMap[tspath.Path, time.Duration]{} } -func (o *Orchestrator) DoCycle() { - var needsConfigUpdate atomic.Bool - var needsUpdate atomic.Bool - mTimes := o.host.mTimes.Clone() - o.rangeTask(func(path tspath.Path, task *BuildTask) { - if updateKind := task.hasUpdate(o, path); updateKind != updateKindNone { +func (o *Orchestrator) checkTasksForEventChanges(changedPaths map[string]fswatch.EventKind, needsConfigUpdate, needsUpdate *atomic.Bool) { + normalizedPaths := make(map[tspath.Path]struct{}, len(changedPaths)) + for eventPath := range changedPaths { + normalizedPaths[o.toPath(eventPath)] = struct{}{} + } + + for i := range o.order { + config := o.order[i] + path := o.toPath(config) + task := o.getTask(path) + + configPath := o.toPath(task.config) + if _, changed := normalizedPaths[configPath]; changed { + task.resetConfig(o, path) + needsConfigUpdate.Store(true) needsUpdate.Store(true) - if updateKind == updateKindConfig { + continue + } + + if task.resolved == nil { + continue + } + + configChanged := false + for _, file := range task.resolved.ExtendedSourceFiles() { + fp := o.toPath(file) + if _, changed := normalizedPaths[fp]; changed { + task.resetConfig(o, path) needsConfigUpdate.Store(true) + needsUpdate.Store(true) + configChanged = true + break } } - }) + if configChanged { + continue + } + + rootChanged := false + fileNames := task.resolved.FileNames() + roots := collections.NewSetWithSizeHint[tspath.Path](len(fileNames)) + for _, file := range fileNames { + fp := o.toPath(file) + roots.Add(fp) + if !rootChanged { + if _, changed := normalizedPaths[fp]; changed { + task.resetStatus() + needsUpdate.Store(true) + rootChanged = true + } + } + } + + if !rootChanged { + task.buildInfoEntryMu.Lock() + bi := task.buildInfoEntry + task.buildInfoEntryMu.Unlock() + if bi != nil && bi.buildInfo != nil { + buildInfoDir := tspath.GetDirectoryPath(string(bi.path)) + for _, fileName := range bi.buildInfo.FileNames { + fp := o.toPath(o.resolveBuildInfoFileName(fileName, buildInfoDir)) + if roots.Has(fp) { + continue + } + if _, changed := normalizedPaths[fp]; changed { + task.resetStatus() + needsUpdate.Store(true) + break + } + } + } + } + + task.reportDone = make(chan struct{}) + task.done = make(chan struct{}) + + newConfig := task.resolved.ReloadFileNamesOfParsedCommandLine(o.host.FS()) + if !slices.Equal(task.resolved.FileNames(), newConfig.FileNames()) { + o.host.resolvedReferences.store(path, newConfig) + task.resolved = newConfig + task.resetStatus() + needsUpdate.Store(true) + } + } + + if !needsUpdate.Load() { + opts := o.comparePathsOptions + for eventPath := range changedPaths { + if o.host.FS().DirectoryExists(eventPath) { + if o.wm.IsPathUnderWatch(eventPath, opts) { + o.rangeTask(func(path tspath.Path, task *BuildTask) { + task.resetStatus() + task.reportDone = make(chan struct{}) + task.done = make(chan struct{}) + }) + needsUpdate.Store(true) + break + } + } + } + } +} + +func (o *Orchestrator) computeDesiredWatches() map[string]bool { + desiredDirs := make(map[string]bool) + + for i := range o.order { + config := o.order[i] + path := o.toPath(config) + task := o.getTask(path) + + // Watch config file directory + configDir := tspath.GetDirectoryPath(task.config) + realConfigDir := o.host.FS().Realpath(configDir) + if _, has := desiredDirs[realConfigDir]; !has { + desiredDirs[realConfigDir] = false + } + + if task.resolved == nil { + continue + } + + // Extended config file directories + for _, cfgPath := range task.resolved.ExtendedSourceFiles() { + realPath := o.host.FS().Realpath(cfgPath) + dir := tspath.GetDirectoryPath(realPath) + if _, has := desiredDirs[dir]; !has { + desiredDirs[dir] = false + } + } + + // Wildcard directories from tsconfig + for dir, recursive := range task.resolved.WildcardDirectories() { + realDir := o.host.FS().Realpath(dir) + if existing, has := desiredDirs[realDir]; has { + desiredDirs[realDir] = existing || recursive + } else { + desiredDirs[realDir] = recursive + } + } + + // Input file directories not already covered + for _, fileName := range task.resolved.FileNames() { + absPath := tspath.GetNormalizedAbsolutePath(fileName, o.opts.Sys.GetCurrentDirectory()) + dir := tspath.GetDirectoryPath(absPath) + if !watchmanager.IsDirCoveredByWatch(desiredDirs, dir, o.comparePathsOptions) { + if watchmanager.CanWatchDirectory(dir) { + desiredDirs[dir] = false + } + } + } + + // Non-root dependency directories from buildinfo (e.g. node_modules .d.ts files). + task.buildInfoEntryMu.Lock() + bi := task.buildInfoEntry + task.buildInfoEntryMu.Unlock() + if bi != nil && bi.buildInfo != nil { + buildInfoDir := tspath.GetDirectoryPath(string(bi.path)) + roots := collections.NewSetFromItems(core.Map(task.resolved.FileNames(), o.toPath)...) + for _, fileName := range bi.buildInfo.FileNames { + absPath := o.resolveBuildInfoFileName(fileName, buildInfoDir) + fp := o.toPath(absPath) + if roots.Has(fp) { + continue + } + dir := tspath.GetDirectoryPath(absPath) + if !watchmanager.IsDirCoveredByWatch(desiredDirs, dir, o.comparePathsOptions) { + if watchmanager.CanWatchDirectory(dir) { + desiredDirs[dir] = false + } + } + } + } + } + + return o.wm.ResolveDesiredDirs(desiredDirs) +} + +func (o *Orchestrator) DoCycle() { + o.wm.Lock() + defer o.wm.Unlock() + + changedPaths, overflow := o.wm.DrainEvents() + hasEvents := len(changedPaths) > 0 || overflow + + if !hasEvents { + if o.wm.DebugLog != nil { + fmt.Fprintf(o.wm.DebugLog, "[watch] DoCycle: no events, skipping\n") + } + return + } + + var needsConfigUpdate atomic.Bool + var needsUpdate atomic.Bool + + if overflow { + // Overflow: reset all tasks to force a full rebuild. + o.rangeTask(func(path tspath.Path, task *BuildTask) { + task.resetConfig(o, path) + task.reportDone = make(chan struct{}) + task.done = make(chan struct{}) + }) + needsConfigUpdate.Store(true) + needsUpdate.Store(true) + } else { + // Event-driven: check only tasks affected by changed paths + o.checkTasksForEventChanges(changedPaths, &needsConfigUpdate, &needsUpdate) + } if !needsUpdate.Load() { - o.host.mTimes = mTimes o.resetCaches() return } @@ -286,6 +500,12 @@ func (o *Orchestrator) DoCycle() { o.buildOrClean() o.updateWatch() + desiredDirs := o.computeDesiredWatches() + if err := o.wm.ReconcileWatches(desiredDirs); err != nil { + fmt.Fprintf(o.opts.Sys.Writer(), "%v\n", err) + // Mark overflow so the next event triggers a full rebuild + o.wm.ForceOverflow() + } o.resetCaches() } @@ -381,6 +601,7 @@ func (o *Orchestrator) createDiagnosticReporter(task *BuildTask) tsc.DiagnosticR } func NewOrchestrator(opts Options) *Orchestrator { + wm := watchmanager.NewWatchManager(opts.Sys.Writer(), opts.Sys.FS().DirectoryExists) orchestrator := &Orchestrator{ opts: opts, comparePathsOptions: tspath.ComparePathsOptions{ @@ -388,6 +609,7 @@ func NewOrchestrator(opts Options) *Orchestrator { UseCaseSensitiveFileNames: opts.Sys.FS().UseCaseSensitiveFileNames(), }, tasks: &collections.SyncMap[tspath.Path, *BuildTask]{}, + wm: wm, } orchestrator.host = &host{ orchestrator: orchestrator, @@ -402,6 +624,9 @@ func NewOrchestrator(opts Options) *Orchestrator { } if opts.Command.CompilerOptions.Watch.IsTrue() { orchestrator.watchStatusReporter = tsc.CreateWatchStatusReporter(opts.Sys, opts.Command.Locale(), opts.Command.CompilerOptions, opts.Testing) + if t, ok := opts.Testing.(watchmanager.CommandLineTestingWithWatchBackend); ok { + wm.SetBackend(t.WatchBackend()) + } } else { orchestrator.errorSummaryReporter = tsc.CreateReportErrorSummary(opts.Sys, opts.Command.Locale(), opts.Command.CompilerOptions) } diff --git a/internal/execute/tsctests/mock_watch_backend.go b/internal/execute/tsctests/mock_watch_backend.go index 6d549994b39..eca507a0772 100644 --- a/internal/execute/tsctests/mock_watch_backend.go +++ b/internal/execute/tsctests/mock_watch_backend.go @@ -8,12 +8,12 @@ import ( "strings" "sync" - "github.com/microsoft/typescript-go/internal/execute" + "github.com/microsoft/typescript-go/internal/execute/watchmanager" "github.com/microsoft/typescript-go/internal/fswatch" "github.com/microsoft/typescript-go/internal/testutil/fsbaselineutil" ) -// MockWatchBackend implements execute.WatchBackend for testing. It +// MockWatchBackend implements watchmanager.WatchBackend for testing. It // records all WatchDirectory calls so tests can verify that // the correct watches are registered. Events can be delivered through // SendEvents, which routes them only through watches whose paths @@ -24,7 +24,7 @@ type MockWatchBackend struct { DirectoryExists func(string) bool // if set, WatchDirectory fails for non-existent dirs } -var _ execute.WatchBackend = (*MockWatchBackend)(nil) +var _ watchmanager.WatchBackend = (*MockWatchBackend)(nil) // NewMockWatchBackend creates a ready-to-use mock backend. func NewMockWatchBackend() *MockWatchBackend { diff --git a/internal/execute/tsctests/sys.go b/internal/execute/tsctests/sys.go index 6f7fe60ffbf..2d75a36d4ca 100644 --- a/internal/execute/tsctests/sys.go +++ b/internal/execute/tsctests/sys.go @@ -17,6 +17,7 @@ import ( "github.com/microsoft/typescript-go/internal/execute" "github.com/microsoft/typescript-go/internal/execute/incremental" "github.com/microsoft/typescript-go/internal/execute/tsc" + "github.com/microsoft/typescript-go/internal/execute/watchmanager" "github.com/microsoft/typescript-go/internal/locale" "github.com/microsoft/typescript-go/internal/testutil/fsbaselineutil" "github.com/microsoft/typescript-go/internal/testutil/harnessutil" @@ -317,7 +318,7 @@ func (s *TestSys) writeHeaderToBaseline(builder *strings.Builder, program *incre } } -func (s *TestSys) WatchBackend() execute.WatchBackend { +func (s *TestSys) WatchBackend() watchmanager.WatchBackend { return s.mockWatchBackend } diff --git a/internal/execute/tsctests/watcher_race_test.go b/internal/execute/tsctests/watcher_race_test.go index d917e666fa8..691b2605b6a 100644 --- a/internal/execute/tsctests/watcher_race_test.go +++ b/internal/execute/tsctests/watcher_race_test.go @@ -279,7 +279,7 @@ func TestBuildWatchStopsWhenContextIsCancelled(t *testing.T) { resultCh := make(chan tsc.CommandLineResult, 1) go func() { - resultCh <- execute.CommandLine(ctx, sys, []string{"--build", "--watch", "--watchInterval", "60000"}, nil) + resultCh <- execute.CommandLine(ctx, sys, []string{"--build", "--watch", "--watchInterval", "60000"}, sys) }() select { diff --git a/internal/execute/watcher.go b/internal/execute/watcher.go index 7ea8ee9aa5b..9b11c2a4dd4 100644 --- a/internal/execute/watcher.go +++ b/internal/execute/watcher.go @@ -2,12 +2,8 @@ package execute import ( "context" - "errors" "fmt" - "io" "reflect" - "strings" - "sync" "time" "github.com/microsoft/typescript-go/internal/ast" @@ -17,6 +13,7 @@ import ( "github.com/microsoft/typescript-go/internal/diagnostics" "github.com/microsoft/typescript-go/internal/execute/incremental" "github.com/microsoft/typescript-go/internal/execute/tsc" + "github.com/microsoft/typescript-go/internal/execute/watchmanager" "github.com/microsoft/typescript-go/internal/fswatch" "github.com/microsoft/typescript-go/internal/tsoptions" "github.com/microsoft/typescript-go/internal/tspath" @@ -24,33 +21,6 @@ import ( "github.com/microsoft/typescript-go/internal/vfs/trackingvfs" ) -// WatchBackend abstracts fswatch.Watcher for testing -type WatchBackend interface { - WatchDirectory(dir string, fn fswatch.WatchCallback, recursive bool, ignore func(string) bool) (io.Closer, error) -} - -type commandLineTestingWithWatchBackend interface { - WatchBackend() WatchBackend -} - -type fswatchBackend struct{ inner fswatch.Watcher } - -func (b *fswatchBackend) WatchDirectory(dir string, fn fswatch.WatchCallback, recursive bool, ignore func(string) bool) (io.Closer, error) { - var opts []fswatch.WatchOption - if recursive { - opts = append(opts, fswatch.WithRecursive()) - } - if ignore != nil { - opts = append(opts, fswatch.WithIgnore(ignore)) - } - return b.inner.WatchDirectory(dir, fn, opts...) -} - -type watchedDir struct { - closer io.Closer - recursive bool -} - type cachedSourceFile struct { file *ast.SourceFile modTime time.Time @@ -85,7 +55,6 @@ func (h *watchCompilerHost) GetSourceFile(opts ast.SourceFileParseOptions) *ast. } type Watcher struct { - mu sync.Mutex sys tsc.System configFileName string config *tsoptions.ParsedCommandLine @@ -104,16 +73,9 @@ type Watcher struct { sourceFileCache *collections.SyncMap[tspath.Path, *cachedSourceFile] - backend WatchBackend - watchedDirs map[string]*watchedDir // dir path → watch state + wm *watchmanager.WatchManager seenFiles *collections.Set[tspath.Path] // all build dependencies (for event filtering) configMtimes map[string]time.Time - doCycleCh chan struct{} - debugLog io.Writer // nil = silent; set via TS_WATCH_DEBUG - - changedMu sync.Mutex - changedPaths map[string]fswatch.EventKind // event path → last event kind - changedOverflow bool // true on ErrOverflow; forces full scan fallback } var _ tsc.Watcher = (*Watcher)(nil) @@ -127,6 +89,10 @@ func createWatcher( reportErrorSummary tsc.DiagnosticsReporter, testing tsc.CommandLineTesting, ) *Watcher { + wm := watchmanager.NewWatchManager(sys.Writer(), sys.FS().DirectoryExists) + if t, ok := testing.(watchmanager.CommandLineTestingWithWatchBackend); ok { + wm.SetBackend(t.WatchBackend()) + } w := &Watcher{ sys: sys, config: configParseResult, @@ -137,20 +103,16 @@ func createWatcher( reportWatchStatus: tsc.CreateWatchStatusReporter(sys, configParseResult.Locale(), configParseResult.CompilerOptions(), testing), testing: testing, sourceFileCache: &collections.SyncMap[tspath.Path, *cachedSourceFile]{}, - doCycleCh: make(chan struct{}, 1), - watchedDirs: make(map[string]*watchedDir), + wm: wm, } if configParseResult.ConfigFile != nil { w.configFileName = configParseResult.ConfigFile.SourceFile.FileName() } - if t, ok := testing.(commandLineTestingWithWatchBackend); ok { - w.backend = t.WatchBackend() - } return w } func (w *Watcher) start(ctx context.Context) { - w.mu.Lock() + w.wm.Lock() w.extendedConfigCache = &tsc.ExtendedConfigCache{} host := compiler.NewCompilerHost(w.sys.GetCurrentDirectory(), w.sys.FS(), w.sys.DefaultLibraryPath(), w.extendedConfigCache, getTraceFromSys(w.sys, w.config.Locale(), w.testing)) w.program = incremental.ReadBuildInfoProgram(w.config, incremental.NewBuildInfoReader(host), host) @@ -160,31 +122,21 @@ func (w *Watcher) start(ctx context.Context) { } if w.sys.GetEnvironmentVariable("TS_WATCH_DEBUG") != "" { - w.debugLog = w.sys.Writer() + w.wm.DebugLog = w.sys.Writer() } - if w.testing == nil && w.backend == nil { - fsw := fswatch.Default() - w.backend = &fswatchBackend{inner: fsw} - if w.debugLog != nil { - fmt.Fprintf(w.debugLog, "[watch] using %s backend\n", fsw.Name()) - } + if w.testing == nil { + w.wm.EnsureDefaultBackend() } w.reportWatchStatus(ast.NewCompilerDiagnostic(diagnostics.Starting_compilation_in_watch_mode)) - w.doBuild() - w.mu.Unlock() + if err := w.doBuild(); err != nil { + w.wm.ForceOverflow() + } + w.wm.Unlock() if w.testing == nil { - for { - select { - case <-ctx.Done(): - w.closeAllWatches() - return - case <-w.doCycleCh: - w.DoCycle() - } - } + w.wm.RunLoop(ctx, w.DoCycle) } } @@ -230,68 +182,25 @@ func (w *Watcher) computeDesiredWatches(seenFilePaths []string) map[string]bool // Add parent directories for seen files not covered by existing dir watches. // Resolve ancestor fallbacks first so coverage checks use final dirs. - resolvedDirs := w.resolveDesiredDirs(desiredDirs) + resolvedDirs := w.wm.ResolveDesiredDirs(desiredDirs) opts := w.comparePathsOptions() for _, filePath := range seenFilePaths { dir := tspath.GetDirectoryPath(filePath) - covered := false - for wdir, recursive := range resolvedDirs { - if recursive { - if tspath.ContainsPath(wdir, dir, opts) { - covered = true - break - } - } else if dir == wdir { - covered = true - break - } - } - if !covered { - if canWatchDirectory(dir) { + if !watchmanager.IsDirCoveredByWatch(resolvedDirs, dir, opts) { + if watchmanager.CanWatchDirectory(dir) { resolvedDirs[dir] = false } } } // Re-resolve in case newly added dirs don't exist - return w.resolveDesiredDirs(resolvedDirs) + return w.wm.ResolveDesiredDirs(resolvedDirs) } -func (w *Watcher) reconcileWatches(seenFilePaths []string) { - if w.backend == nil { - return - } - +func (w *Watcher) reconcileWatches(seenFilePaths []string) error { desiredDirs := w.computeDesiredWatches(seenFilePaths) - - // Reconcile directory watches using DiffMaps, performing effects inline - core.DiffMapsFunc( - w.watchedDirs, - desiredDirs, - func(wd *watchedDir, recursive bool) bool { return wd.recursive == recursive }, - func(dir string, recursive bool) { - if w.debugLog != nil { - fmt.Fprintf(w.debugLog, "[watch] watching directory %s (recursive=%v)\n", dir, recursive) - } - w.createDirWatch(dir, recursive) - }, - func(dir string, wd *watchedDir) { - if w.debugLog != nil { - fmt.Fprintf(w.debugLog, "[watch] closing stale dir watch: %s\n", dir) - } - wd.closer.Close() - delete(w.watchedDirs, dir) - }, - func(dir string, wd *watchedDir, recursive bool) { - if w.debugLog != nil { - fmt.Fprintf(w.debugLog, "[watch] recreating dir watch %s (recursive %v→%v)\n", dir, wd.recursive, recursive) - } - wd.closer.Close() - delete(w.watchedDirs, dir) - w.createDirWatch(dir, recursive) - }, - ) + return w.wm.ReconcileWatches(desiredDirs) } func (w *Watcher) comparePathsOptions() tspath.ComparePathsOptions { @@ -301,204 +210,11 @@ func (w *Watcher) comparePathsOptions() tspath.ComparePathsOptions { } } -func (w *Watcher) resolveDesiredDirs(desiredDirs map[string]bool) map[string]bool { - resolved := make(map[string]bool, len(desiredDirs)) - for dir, recursive := range desiredDirs { - watchDir := dir - watchRecursive := recursive - for !w.sys.FS().DirectoryExists(watchDir) { - parent := tspath.GetDirectoryPath(watchDir) - if parent == watchDir { - break - } - watchDir = parent - watchRecursive = false // ancestor fallbacks are always non-recursive - } - if !w.sys.FS().DirectoryExists(watchDir) || !canWatchDirectory(watchDir) { - if w.debugLog != nil { - fmt.Fprintf(w.debugLog, "[watch] no watchable ancestor for %s\n", dir) - } - continue - } - if watchDir != dir && w.debugLog != nil { - fmt.Fprintf(w.debugLog, "[watch] resolved %s to ancestor %s\n", dir, watchDir) - } - if existing, has := resolved[watchDir]; has { - resolved[watchDir] = existing || watchRecursive - } else { - resolved[watchDir] = watchRecursive - } - } - return resolved -} - -func canWatchDirectory(dir string) bool { - components := tspath.GetPathComponents(dir, "") - length := len(components) - if length <= 2 { - return false - } - rootLength := perceivedOsRootLengthForWatching(components) - return length > rootLength+1 -} - -func perceivedOsRootLengthForWatching(components []string) int { - length := len(components) - if length <= 1 { - return 1 - } - root := components[0] - indexAfterOsRoot := 1 - isDosStyle := len(root) >= 2 && tspath.IsVolumeCharacter(root[0]) && root[1] == ':' - - if root != "/" && !isDosStyle && len(components) > 1 { - // Check for UNC-like paths: //server/c$/... - if len(components[1]) >= 2 && tspath.IsVolumeCharacter(components[1][0]) && strings.HasSuffix(components[1], "$") { - if length == 2 { - return 2 - } - indexAfterOsRoot = 2 - isDosStyle = true - } - } - - if isDosStyle && (indexAfterOsRoot >= length || !strings.EqualFold(components[indexAfterOsRoot], "users")) { - return indexAfterOsRoot - } - - if indexAfterOsRoot < length && strings.EqualFold(components[indexAfterOsRoot], "workspaces") { - // Codespaces: /workspaces repos are hoisted here - return indexAfterOsRoot + 1 - } - - // /home/username or C:/Users/username - return indexAfterOsRoot + 2 -} - -func (w *Watcher) createDirWatch(dir string, recursive bool) { - entry := &watchedDir{recursive: recursive} - cb := func(events []fswatch.Event, err error) { - if err != nil && errors.Is(err, fswatch.ErrWatchTerminated) { - w.handleWatchTerminated(dir, entry) - return - } - w.onWatchEvents(events, err) - } - watch, err := w.backend.WatchDirectory(dir, cb, recursive, shouldIgnoreWatchPath) - if err != nil { - if w.debugLog != nil { - fmt.Fprintf(w.debugLog, "[watch] failed to watch directory %s: %v\n", dir, err) - } - return - } - entry.closer = watch - w.watchedDirs[dir] = entry -} - -func (w *Watcher) closeAllWatches() { - w.mu.Lock() - dirs := make([]io.Closer, 0, len(w.watchedDirs)) - for dir, wd := range w.watchedDirs { - dirs = append(dirs, wd.closer) - delete(w.watchedDirs, dir) - } - w.mu.Unlock() - for _, c := range dirs { - c.Close() - } -} - -func (w *Watcher) handleWatchTerminated(dir string, identity *watchedDir) { - if w.debugLog != nil { - fmt.Fprintf(w.debugLog, "[watch] watch terminated: %s\n", dir) - } - var staleCloser io.Closer - w.mu.Lock() - if wd, ok := w.watchedDirs[dir]; ok && wd == identity { - staleCloser = wd.closer - delete(w.watchedDirs, dir) - } - w.mu.Unlock() - if staleCloser != nil { - staleCloser.Close() - } - w.changedMu.Lock() - w.changedOverflow = true - w.changedMu.Unlock() - w.signalDoCycle() -} - -func shouldIgnoreWatchPath(path string) bool { - p := tspath.NormalizeSlashes(path) - return strings.HasSuffix(p, "/.git") || - strings.Contains(p, "/.git/") || - strings.Contains(p, "/node_modules/.") || - strings.Contains(p, "/.#") -} - -func (w *Watcher) onWatchEvents(events []fswatch.Event, err error) { - if err != nil { - if errors.Is(err, fswatch.ErrOverflow) { - if w.debugLog != nil { - fmt.Fprintf(w.debugLog, "[watch] event overflow, triggering rebuild\n") - } - w.changedMu.Lock() - w.changedOverflow = true - w.changedMu.Unlock() - w.signalDoCycle() - return - } - fmt.Fprintf(w.sys.Writer(), "Warning: File watch error: %v\n", err) - return - } - - if len(events) > 0 { - if w.debugLog != nil { - fmt.Fprintf(w.debugLog, "[watch] %d event(s): ", len(events)) - for i, e := range events { - if i > 0 { - fmt.Fprint(w.debugLog, ", ") - } - if i >= 5 { - fmt.Fprintf(w.debugLog, "... and %d more", len(events)-i) - break - } - fmt.Fprintf(w.debugLog, "%s %s", e.Kind, e.Path) - } - fmt.Fprintln(w.debugLog) - } - w.changedMu.Lock() - if w.changedPaths == nil { - w.changedPaths = make(map[string]fswatch.EventKind, len(events)) - } - for _, e := range events { - w.changedPaths[e.Path] = e.Kind - } - w.changedMu.Unlock() - w.signalDoCycle() - } -} - -func (w *Watcher) signalDoCycle() { - select { - case w.doCycleCh <- struct{}{}: - // Signal sent; the DoCycle loop will pick it up. - default: - // A signal is already pending; coalesced. - } -} - func (w *Watcher) DoCycle() { - w.mu.Lock() - defer w.mu.Unlock() - - w.changedMu.Lock() - changedPaths := w.changedPaths - overflow := w.changedOverflow - w.changedPaths = nil - w.changedOverflow = false - w.changedMu.Unlock() + w.wm.Lock() + defer w.wm.Unlock() + changedPaths, overflow := w.wm.DrainEvents() hasEvents := len(changedPaths) > 0 || overflow if w.recheckTsConfig() { @@ -510,8 +226,8 @@ func (w *Watcher) DoCycle() { if w.isRelevantChange(changedPaths) { w.evictChangedSourceFiles(changedPaths) } else { - if w.debugLog != nil { - fmt.Fprintf(w.debugLog, "[watch] DoCycle: %d event(s) not relevant to compilation, skipping rebuild\n", len(changedPaths)) + if w.wm.DebugLog != nil { + fmt.Fprintf(w.wm.DebugLog, "[watch] DoCycle: %d event(s) not relevant to compilation, skipping rebuild\n", len(changedPaths)) } if w.testing != nil { w.testing.OnProgram(w.program) @@ -523,8 +239,8 @@ func (w *Watcher) DoCycle() { w.sourceFileCache = &collections.SyncMap[tspath.Path, *cachedSourceFile]{} } else if !hasEvents && !w.configModified { // No events and no config change - if w.debugLog != nil { - fmt.Fprintf(w.debugLog, "[watch] DoCycle: no events, skipping\n") + if w.wm.DebugLog != nil { + fmt.Fprintf(w.wm.DebugLog, "[watch] DoCycle: no events, skipping\n") } if w.testing != nil { w.testing.OnProgram(w.program) @@ -533,7 +249,10 @@ func (w *Watcher) DoCycle() { } w.reportWatchStatus(ast.NewCompilerDiagnostic(diagnostics.File_change_detected_Starting_incremental_compilation)) - w.doBuild() + if err := w.doBuild(); err != nil { + // Mid-cycle watch failure; force a full rebuild on the next event + w.wm.ForceOverflow() + } } func (w *Watcher) isRelevantChange(changedPaths map[string]fswatch.EventKind) bool { @@ -551,23 +270,16 @@ func (w *Watcher) isRelevantChange(changedPaths map[string]fswatch.EventKind) bo if w.config.ConfigFile != nil && w.config.PossiblyMatchesDirectoryName(p) { return true } - // If a directory was created under an ancestor fallback watch, - // treat it as relevant — it may be on the path to a previously - // non-existent directory we want to watch. Err on the side of - // false positives (unnecessary rebuild) over false negatives - // (missed rebuild). if w.sys.FS().DirectoryExists(eventPath) { - for dir := range w.watchedDirs { - if tspath.ContainsPath(dir, eventPath, opts) { - return true - } + if w.wm.IsPathUnderWatch(eventPath, opts) { + return true } } } return false } -func (w *Watcher) doBuild() { +func (w *Watcher) doBuild() error { if w.configModified { w.sourceFileCache = &collections.SyncMap[tspath.Path, *cachedSourceFile]{} } @@ -614,7 +326,10 @@ func (w *Watcher) doBuild() { } } - w.reconcileWatches(seenSlice) + if err := w.reconcileWatches(seenSlice); err != nil { + fmt.Fprintf(w.sys.Writer(), "%v\n", err) + return err + } w.configModified = false programFiles := w.program.GetProgram().FilesByPath() @@ -635,6 +350,7 @@ func (w *Watcher) doBuild() { if w.testing != nil { w.testing.OnProgram(w.program) } + return nil } func (w *Watcher) evictChangedSourceFiles(changedPaths map[string]fswatch.EventKind) { @@ -643,8 +359,8 @@ func (w *Watcher) evictChangedSourceFiles(changedPaths map[string]fswatch.EventK for eventPath := range changedPaths { p := tspath.ToPath(eventPath, cwd, caseSensitive) if _, ok := w.sourceFileCache.Load(p); ok { - if w.debugLog != nil { - fmt.Fprintf(w.debugLog, "[watch] evicting cached source file: %s\n", p) + if w.wm.DebugLog != nil { + fmt.Fprintf(w.wm.DebugLog, "[watch] evicting cached source file: %s\n", p) } w.sourceFileCache.Delete(p) } diff --git a/internal/execute/watchmanager/watchbackend.go b/internal/execute/watchmanager/watchbackend.go new file mode 100644 index 00000000000..64551574cb7 --- /dev/null +++ b/internal/execute/watchmanager/watchbackend.go @@ -0,0 +1,81 @@ +package watchmanager + +import ( + "io" + "strings" + + "github.com/microsoft/typescript-go/internal/fswatch" + "github.com/microsoft/typescript-go/internal/tspath" +) + +// WatchBackend abstracts fswatch.Watcher for testing +type WatchBackend interface { + WatchDirectory(dir string, fn fswatch.WatchCallback, recursive bool, ignore func(string) bool) (io.Closer, error) +} + +// CommandLineTestingWithWatchBackend is an optional extension of +// [CommandLineTesting] that supplies a [WatchBackend] for test mode +type CommandLineTestingWithWatchBackend interface { + WatchBackend() WatchBackend +} + +type FSWatchBackend struct{ Inner fswatch.Watcher } + +func (b *FSWatchBackend) WatchDirectory(dir string, fn fswatch.WatchCallback, recursive bool, ignore func(string) bool) (io.Closer, error) { + var opts []fswatch.WatchOption + if recursive { + opts = append(opts, fswatch.WithRecursive()) + } + if ignore != nil { + opts = append(opts, fswatch.WithIgnore(ignore)) + } + return b.Inner.WatchDirectory(dir, fn, opts...) +} + +func ShouldIgnoreWatchPath(path string) bool { + p := tspath.NormalizeSlashes(path) + return strings.HasSuffix(p, "/.git") || + strings.Contains(p, "/.git/") || + strings.Contains(p, "/node_modules/.") || + strings.Contains(p, "/.#") +} + +func CanWatchDirectory(dir string) bool { + components := tspath.GetPathComponents(dir, "") + length := len(components) + if length <= 2 { + return false + } + rootLength := PerceivedOsRootLengthForWatching(components) + return length > rootLength+1 +} + +func PerceivedOsRootLengthForWatching(components []string) int { + length := len(components) + if length <= 1 { + return 1 + } + root := components[0] + indexAfterOsRoot := 1 + isDosStyle := len(root) >= 2 && tspath.IsVolumeCharacter(root[0]) && root[1] == ':' + + if root != "/" && !isDosStyle && len(components) > 1 { + if len(components[1]) >= 2 && tspath.IsVolumeCharacter(components[1][0]) && strings.HasSuffix(components[1], "$") { + if length == 2 { + return 2 + } + indexAfterOsRoot = 2 + isDosStyle = true + } + } + + if isDosStyle && (indexAfterOsRoot >= length || !strings.EqualFold(components[indexAfterOsRoot], "users")) { + return indexAfterOsRoot + } + + if indexAfterOsRoot < length && strings.EqualFold(components[indexAfterOsRoot], "workspaces") { + return indexAfterOsRoot + 1 + } + + return indexAfterOsRoot + 2 +} diff --git a/internal/execute/watchmanager/watchmanager.go b/internal/execute/watchmanager/watchmanager.go new file mode 100644 index 00000000000..b5fe9a89bab --- /dev/null +++ b/internal/execute/watchmanager/watchmanager.go @@ -0,0 +1,298 @@ +package watchmanager + +import ( + "context" + "errors" + "fmt" + "io" + "sync" + + "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/fswatch" + "github.com/microsoft/typescript-go/internal/tspath" +) + +type watchedDir struct { + closer io.Closer + recursive bool +} + +// WatchManager manages fswatch directory watches, event accumulation, +// and DoCycle signaling. It is shared by the CLI watcher and the build +// mode orchestrator. +// +// Locking contract: +// - Call Lock/Unlock around the entire DoCycle body. +// - ReconcileWatches must be called under Lock. +// - CloseAllWatches and handleWatchTerminated manage their own locking. +type WatchManager struct { + mu sync.Mutex + backend WatchBackend + watchedDirs map[string]*watchedDir + doCycleCh chan struct{} + + // DebugLog receives verbose watch diagnostics when non-nil + DebugLog io.Writer + + warnWriter io.Writer + dirExists func(string) bool + + changedMu sync.Mutex + changedPaths map[string]fswatch.EventKind + changedOverflow bool +} + +func NewWatchManager(warnWriter io.Writer, dirExists func(string) bool) *WatchManager { + return &WatchManager{ + watchedDirs: make(map[string]*watchedDir), + doCycleCh: make(chan struct{}, 1), + warnWriter: warnWriter, + dirExists: dirExists, + } +} + +func (wm *WatchManager) SetBackend(b WatchBackend) { wm.backend = b } + +func (wm *WatchManager) Backend() WatchBackend { return wm.backend } + +func (wm *WatchManager) EnsureDefaultBackend() { + if wm.backend == nil { + fsw := fswatch.Default() + wm.backend = &FSWatchBackend{Inner: fsw} + if wm.DebugLog != nil { + fmt.Fprintf(wm.DebugLog, "[watch] using %s backend\n", fsw.Name()) + } + } +} + +func (wm *WatchManager) Lock() { wm.mu.Lock() } + +func (wm *WatchManager) Unlock() { wm.mu.Unlock() } + +func (wm *WatchManager) DoCycleCh() <-chan struct{} { return wm.doCycleCh } + +func (wm *WatchManager) DrainEvents() (changed map[string]fswatch.EventKind, overflow bool) { + wm.changedMu.Lock() + changed = wm.changedPaths + overflow = wm.changedOverflow + wm.changedPaths = nil + wm.changedOverflow = false + wm.changedMu.Unlock() + return +} + +func (wm *WatchManager) ForceOverflow() { + wm.changedMu.Lock() + wm.changedOverflow = true + wm.changedMu.Unlock() +} + +func (wm *WatchManager) signalDoCycle() { + select { + case wm.doCycleCh <- struct{}{}: + // Signal sent; the DoCycle loop will pick it up. + default: + // A signal is already pending; coalesced. + } +} + +func (wm *WatchManager) onWatchEvents(events []fswatch.Event, err error) { + if err != nil { + if errors.Is(err, fswatch.ErrOverflow) { + if wm.DebugLog != nil { + fmt.Fprintf(wm.DebugLog, "[watch] event overflow, triggering rebuild\n") + } + wm.changedMu.Lock() + wm.changedOverflow = true + wm.changedMu.Unlock() + wm.signalDoCycle() + return + } + fmt.Fprintf(wm.warnWriter, "Warning: File watch error: %v\n", err) + return + } + + if len(events) > 0 { + if wm.DebugLog != nil { + fmt.Fprintf(wm.DebugLog, "[watch] %d event(s): ", len(events)) + for i, e := range events { + if i > 0 { + fmt.Fprint(wm.DebugLog, ", ") + } + if i >= 5 { + fmt.Fprintf(wm.DebugLog, "... and %d more", len(events)-i) + break + } + fmt.Fprintf(wm.DebugLog, "%s %s", e.Kind, e.Path) + } + fmt.Fprintln(wm.DebugLog) + } + wm.changedMu.Lock() + if wm.changedPaths == nil { + wm.changedPaths = make(map[string]fswatch.EventKind, len(events)) + } + for _, e := range events { + wm.changedPaths[e.Path] = e.Kind + } + wm.changedMu.Unlock() + wm.signalDoCycle() + } +} + +func (wm *WatchManager) handleWatchTerminated(dir string, identity *watchedDir) { + if wm.DebugLog != nil { + fmt.Fprintf(wm.DebugLog, "[watch] watch terminated: %s\n", dir) + } + var staleCloser io.Closer + wm.mu.Lock() + if wd, ok := wm.watchedDirs[dir]; ok && wd == identity { + staleCloser = wd.closer + delete(wm.watchedDirs, dir) + } + wm.mu.Unlock() + if staleCloser != nil { + staleCloser.Close() + } + wm.changedMu.Lock() + wm.changedOverflow = true + wm.changedMu.Unlock() + wm.signalDoCycle() +} + +func (wm *WatchManager) CloseAllWatches() { + wm.mu.Lock() + closers := make([]io.Closer, 0, len(wm.watchedDirs)) + for dir, wd := range wm.watchedDirs { + closers = append(closers, wd.closer) + delete(wm.watchedDirs, dir) + } + wm.mu.Unlock() + for _, c := range closers { + c.Close() + } +} + +func (wm *WatchManager) createDirWatch(dir string, recursive bool) error { + entry := &watchedDir{recursive: recursive} + cb := func(events []fswatch.Event, err error) { + if err != nil && errors.Is(err, fswatch.ErrWatchTerminated) { + wm.handleWatchTerminated(dir, entry) + return + } + wm.onWatchEvents(events, err) + } + watch, err := wm.backend.WatchDirectory(dir, cb, recursive, ShouldIgnoreWatchPath) + if err != nil { + if wm.DebugLog != nil { + fmt.Fprintf(wm.DebugLog, "[watch] failed to watch directory %s: %v\n", dir, err) + } + return fmt.Errorf("failed to watch directory %s: %w", dir, err) + } + entry.closer = watch + wm.watchedDirs[dir] = entry + return nil +} + +func (wm *WatchManager) ResolveDesiredDirs(desiredDirs map[string]bool) map[string]bool { + resolved := make(map[string]bool, len(desiredDirs)) + for dir, recursive := range desiredDirs { + watchDir := dir + watchRecursive := recursive + for !wm.dirExists(watchDir) { + parent := tspath.GetDirectoryPath(watchDir) + if parent == watchDir { + break + } + watchDir = parent + watchRecursive = false // ancestor fallbacks are always non-recursive + } + if !wm.dirExists(watchDir) || !CanWatchDirectory(watchDir) { + if wm.DebugLog != nil { + fmt.Fprintf(wm.DebugLog, "[watch] no watchable ancestor for %s\n", dir) + } + continue + } + if watchDir != dir && wm.DebugLog != nil { + fmt.Fprintf(wm.DebugLog, "[watch] resolved %s to ancestor %s\n", dir, watchDir) + } + if existing, has := resolved[watchDir]; has { + resolved[watchDir] = existing || watchRecursive + } else { + resolved[watchDir] = watchRecursive + } + } + return resolved +} + +func (wm *WatchManager) ReconcileWatches(desiredDirs map[string]bool) error { + if wm.backend == nil { + return nil + } + + var watchErr error + core.DiffMapsFunc( + wm.watchedDirs, + desiredDirs, + func(wd *watchedDir, recursive bool) bool { return wd.recursive == recursive }, + func(dir string, recursive bool) { + if wm.DebugLog != nil { + fmt.Fprintf(wm.DebugLog, "[watch] watching directory %s (recursive=%v)\n", dir, recursive) + } + if err := wm.createDirWatch(dir, recursive); err != nil && watchErr == nil { + watchErr = err + } + }, + func(dir string, wd *watchedDir) { + if wm.DebugLog != nil { + fmt.Fprintf(wm.DebugLog, "[watch] closing stale dir watch: %s\n", dir) + } + wd.closer.Close() + delete(wm.watchedDirs, dir) + }, + func(dir string, wd *watchedDir, recursive bool) { + if wm.DebugLog != nil { + fmt.Fprintf(wm.DebugLog, "[watch] recreating dir watch %s (recursive %v→%v)\n", dir, wd.recursive, recursive) + } + wd.closer.Close() + delete(wm.watchedDirs, dir) + if err := wm.createDirWatch(dir, recursive); err != nil && watchErr == nil { + watchErr = err + } + }, + ) + return watchErr +} + +func IsDirCoveredByWatch(dirs map[string]bool, dir string, opts tspath.ComparePathsOptions) bool { + for wdir, recursive := range dirs { + if recursive { + if tspath.ContainsPath(wdir, dir, opts) { + return true + } + } else if dir == wdir { + return true + } + } + return false +} + +func (wm *WatchManager) IsPathUnderWatch(path string, opts tspath.ComparePathsOptions) bool { + for dir := range wm.watchedDirs { + if tspath.ContainsPath(dir, path, opts) { + return true + } + } + return false +} + +func (wm *WatchManager) RunLoop(ctx context.Context, doCycle func()) { + for { + select { + case <-ctx.Done(): + wm.CloseAllWatches() + return + case <-wm.doCycleCh: + doCycle() + } + } +} diff --git a/testdata/baselines/reference/tsbuildWatch/configFileErrors/reports-syntax-errors-in-config-file.js b/testdata/baselines/reference/tsbuildWatch/configFileErrors/reports-syntax-errors-in-config-file.js index e85a51f69f0..9a7fd401ab2 100644 --- a/testdata/baselines/reference/tsbuildWatch/configFileErrors/reports-syntax-errors-in-config-file.js +++ b/testdata/baselines/reference/tsbuildWatch/configFileErrors/reports-syntax-errors-in-config-file.js @@ -129,6 +129,10 @@ export function bar() { } "size": 1320 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -163,6 +167,10 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -258,6 +266,10 @@ export function fooBar() { } "size": 1408 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/project/a.ts @@ -280,6 +292,10 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -369,6 +385,10 @@ Output:: "size": 1394 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/project tsconfig.json:: SemanticDiagnostics:: Signatures:: diff --git a/testdata/baselines/reference/tsbuildWatch/demo/updates-with-bad-reference.js b/testdata/baselines/reference/tsbuildWatch/demo/updates-with-bad-reference.js index df7cbed5241..e82e630c5d4 100644 --- a/testdata/baselines/reference/tsbuildWatch/demo/updates-with-bad-reference.js +++ b/testdata/baselines/reference/tsbuildWatch/demo/updates-with-bad-reference.js @@ -662,6 +662,15 @@ function createZoo() { } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/demo + /user/username/projects/demo/animals (recursive) + /user/username/projects/demo/core (recursive) + /user/username/projects/demo/lib/animals + /user/username/projects/demo/lib/core + /user/username/projects/demo/zoo (recursive) core/tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.d.ts @@ -886,6 +895,15 @@ Output:: //// [/user/username/projects/demo/lib/core/utilities.js] *rewrite with same content* //// [/user/username/projects/demo/lib/zoo/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/demo + /user/username/projects/demo/animals (recursive) + /user/username/projects/demo/core (recursive) + /user/username/projects/demo/lib/animals + /user/username/projects/demo/lib/core + /user/username/projects/demo/zoo (recursive) core/tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/demo/updates-with-circular-reference.js b/testdata/baselines/reference/tsbuildWatch/demo/updates-with-circular-reference.js index 83d070cf3ec..4a4953c179b 100644 --- a/testdata/baselines/reference/tsbuildWatch/demo/updates-with-circular-reference.js +++ b/testdata/baselines/reference/tsbuildWatch/demo/updates-with-circular-reference.js @@ -132,6 +132,12 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /user/username/projects/demo + /user/username/projects/demo/animals (recursive) + /user/username/projects/demo/core (recursive) + /user/username/projects/demo/zoo (recursive) Edit [0]:: Fix error @@ -584,6 +590,15 @@ function createZoo() { } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/demo + /user/username/projects/demo/animals (recursive) + /user/username/projects/demo/core (recursive) + /user/username/projects/demo/lib/animals + /user/username/projects/demo/lib/core + /user/username/projects/demo/zoo (recursive) core/tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/extends/configDir-template.js b/testdata/baselines/reference/tsbuildWatch/extends/configDir-template.js index 2177b5d31d6..b88f944061c 100644 --- a/testdata/baselines/reference/tsbuildWatch/extends/configDir-template.js +++ b/testdata/baselines/reference/tsbuildWatch/extends/configDir-template.js @@ -120,6 +120,11 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/projects/configs/first + /home/src/projects/configs/second + /home/src/projects/myproject tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -174,6 +179,11 @@ main.ts //// [/home/src/projects/myproject/outDir/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* //// [/home/src/projects/myproject/outDir/types/sometype.js] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/configs/first + /home/src/projects/configs/second + /home/src/projects/myproject tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/libraryResolution/with-config-with-libReplacement.js b/testdata/baselines/reference/tsbuildWatch/libraryResolution/with-config-with-libReplacement.js index 7e311d75eb8..a41c7510fe4 100644 --- a/testdata/baselines/reference/tsbuildWatch/libraryResolution/with-config-with-libReplacement.js +++ b/testdata/baselines/reference/tsbuildWatch/libraryResolution/with-config-with-libReplacement.js @@ -840,6 +840,17 @@ export const z = 10; "size": 1564 } +Watch Registrations:: +Directory watches:: + /home/src/workspace/projects/node_modules/@typescript/lib-dom + /home/src/workspace/projects/node_modules/@typescript/lib-es5 + /home/src/workspace/projects/node_modules/@typescript/lib-esnext + /home/src/workspace/projects/node_modules/@typescript/lib-scripthost + /home/src/workspace/projects/node_modules/@typescript/lib-webworker + /home/src/workspace/projects/project1 (recursive) + /home/src/workspace/projects/project2 (recursive) + /home/src/workspace/projects/project3 (recursive) + /home/src/workspace/projects/project4 (recursive) project1/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspace/projects/node_modules/@typescript/lib-webworker/index.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/libraryResolution/with-config.js b/testdata/baselines/reference/tsbuildWatch/libraryResolution/with-config.js index db11d6cb112..7b6be85aa92 100644 --- a/testdata/baselines/reference/tsbuildWatch/libraryResolution/with-config.js +++ b/testdata/baselines/reference/tsbuildWatch/libraryResolution/with-config.js @@ -603,6 +603,13 @@ export const z = 10; "size": 1462 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspace/projects/project1 (recursive) + /home/src/workspace/projects/project2 (recursive) + /home/src/workspace/projects/project3 (recursive) + /home/src/workspace/projects/project4 (recursive) project1/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es5.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/moduleResolution/build-mode-watches-for-changes-to-package-json-main-fields.js b/testdata/baselines/reference/tsbuildWatch/moduleResolution/build-mode-watches-for-changes-to-package-json-main-fields.js index b52e1f6a79e..6f0efd314b8 100644 --- a/testdata/baselines/reference/tsbuildWatch/moduleResolution/build-mode-watches-for-changes-to-package-json-main-fields.js +++ b/testdata/baselines/reference/tsbuildWatch/moduleResolution/build-mode-watches-for-changes-to-package-json-main-fields.js @@ -243,6 +243,11 @@ export {}; "size": 1576 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject/packages/pkg1 (recursive) + /user/username/projects/myproject/packages/pkg2 (recursive) packages/pkg2/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -274,6 +279,11 @@ Edit [0]:: reports import errors after change to package file Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject/packages/pkg1 (recursive) + /user/username/projects/myproject/packages/pkg2 (recursive) Diff:: Package.json watch pending, so no change detected yet @@ -297,3 +307,8 @@ Edit [1]:: removes those errors when a package file is changed back Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject/packages/pkg1 (recursive) + /user/username/projects/myproject/packages/pkg2 (recursive) diff --git a/testdata/baselines/reference/tsbuildWatch/moduleResolution/handles-the-cache-correctly-when-two-projects-use-different-module-resolution-settings.js b/testdata/baselines/reference/tsbuildWatch/moduleResolution/handles-the-cache-correctly-when-two-projects-use-different-module-resolution-settings.js index aceb2ec7752..3453333f920 100644 --- a/testdata/baselines/reference/tsbuildWatch/moduleResolution/handles-the-cache-correctly-when-two-projects-use-different-module-resolution-settings.js +++ b/testdata/baselines/reference/tsbuildWatch/moduleResolution/handles-the-cache-correctly-when-two-projects-use-different-module-resolution-settings.js @@ -249,6 +249,16 @@ Object.defineProperty(exports, "__esModule", { value: true }); "size": 1344 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject + /user/username/projects/myproject/node_modules/@types/bar + /user/username/projects/myproject/node_modules/@types/foo + /user/username/projects/myproject/project1 + /user/username/projects/myproject/project1/node_modules/file + /user/username/projects/myproject/project2 + /user/username/projects/myproject/project2/node_modules/file project1/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -372,6 +382,16 @@ export {}; "size": 1444 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject + /user/username/projects/myproject/node_modules/@types/bar + /user/username/projects/myproject/node_modules/@types/foo + /user/username/projects/myproject/project1 + /user/username/projects/myproject/project1/node_modules/file + /user/username/projects/myproject/project2 + /user/username/projects/myproject/project2/node_modules/file project1/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/project1/index.ts diff --git a/testdata/baselines/reference/tsbuildWatch/moduleResolution/resolves-specifier-in-output-declaration-file-from-referenced-project-correctly-with-cts-and-mts-extensions.js b/testdata/baselines/reference/tsbuildWatch/moduleResolution/resolves-specifier-in-output-declaration-file-from-referenced-project-correctly-with-cts-and-mts-extensions.js index 31fd6787c95..fdbf634b8c2 100644 --- a/testdata/baselines/reference/tsbuildWatch/moduleResolution/resolves-specifier-in-output-declaration-file-from-referenced-project-correctly-with-cts-and-mts-extensions.js +++ b/testdata/baselines/reference/tsbuildWatch/moduleResolution/resolves-specifier-in-output-declaration-file-from-referenced-project-correctly-with-cts-and-mts-extensions.js @@ -225,6 +225,11 @@ export {}; "size": 1403 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject/packages/pkg1 (recursive) + /user/username/projects/myproject/packages/pkg2 (recursive) packages/pkg2/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -255,6 +260,11 @@ Edit [0]:: reports import errors after change to package file Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject/packages/pkg1 (recursive) + /user/username/projects/myproject/packages/pkg2 (recursive) Diff:: Package.json watch pending, so no change detected yet @@ -288,6 +298,11 @@ Edit [1]:: removes those errors when a package file is changed back Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject/packages/pkg1 (recursive) + /user/username/projects/myproject/packages/pkg2 (recursive) Edit [2]:: reports import errors after change to package file @@ -302,6 +317,11 @@ Edit [2]:: reports import errors after change to package file Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject/packages/pkg1 (recursive) + /user/username/projects/myproject/packages/pkg2 (recursive) Diff:: Package.json watch pending, so no change detected yet @@ -482,6 +502,11 @@ export type { TheNum } from './const.cjs'; "size": 1404 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject/packages/pkg1 (recursive) + /user/username/projects/myproject/packages/pkg2 (recursive) packages/pkg2/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/packages/pkg2/index.cts diff --git a/testdata/baselines/reference/tsbuildWatch/noEmit/does-not-go-in-loop-when-watching-when-no-files-are-emitted-with-incremental.js b/testdata/baselines/reference/tsbuildWatch/noEmit/does-not-go-in-loop-when-watching-when-no-files-are-emitted-with-incremental.js index 7754838d081..984f2509620 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmit/does-not-go-in-loop-when-watching-when-no-files-are-emitted-with-incremental.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmit/does-not-go-in-loop-when-watching-when-no-files-are-emitted-with-incremental.js @@ -116,6 +116,10 @@ declare const console: { log(msg: any): void; }; "size": 1013 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -142,6 +146,10 @@ Output:: //// [/user/username/projects/myproject/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) Edit [1]:: change @@ -234,6 +242,10 @@ Output:: "size": 1157 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/noEmit/does-not-go-in-loop-when-watching-when-no-files-are-emitted.js b/testdata/baselines/reference/tsbuildWatch/noEmit/does-not-go-in-loop-when-watching-when-no-files-are-emitted.js index 4704c118f9e..71fb2bbffe7 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmit/does-not-go-in-loop-when-watching-when-no-files-are-emitted.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmit/does-not-go-in-loop-when-watching-when-no-files-are-emitted.js @@ -72,6 +72,9 @@ declare const console: { log(msg: any): void; }; "size": 54 } +Watch Registrations:: +Directory watches:: + /user/username/projects/myproject (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -99,6 +102,9 @@ Output:: //// [/user/username/projects/myproject/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/myproject/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/myproject (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -127,6 +133,9 @@ Output:: //// [/user/username/projects/myproject/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/myproject/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/myproject (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-with-incremental-as-modules.js b/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-with-incremental-as-modules.js index 18cfda645c1..10644647acf 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-with-incremental-as-modules.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-with-incremental-as-modules.js @@ -160,6 +160,10 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -262,6 +266,10 @@ Output:: "size": 1193 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/projects/project/a.ts @@ -367,6 +375,10 @@ export const b = 10; "size": 1262 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -399,6 +411,10 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -535,6 +551,10 @@ Output:: "size": 1875 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -675,6 +695,10 @@ const a = class { "size": 1839 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -712,6 +736,10 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: diff --git a/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-with-incremental.js b/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-with-incremental.js index b11025dc415..df3c75166c5 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-with-incremental.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-with-incremental.js @@ -145,6 +145,10 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -229,6 +233,10 @@ Output:: "size": 1129 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -316,6 +324,10 @@ const a = "hello"; "size": 1093 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -347,6 +359,10 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -467,6 +483,10 @@ Output:: "size": 1694 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -591,6 +611,10 @@ const a = class { "size": 1658 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -628,6 +652,10 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: diff --git a/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-without-dts-enabled-with-incremental-as-modules.js b/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-without-dts-enabled-with-incremental-as-modules.js index a668f44548f..bcb7ed3b854 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-without-dts-enabled-with-incremental-as-modules.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-without-dts-enabled-with-incremental-as-modules.js @@ -116,6 +116,10 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -212,6 +216,10 @@ Output:: "size": 1184 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/projects/project/a.ts @@ -306,6 +314,10 @@ export const b = 10; "size": 1151 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -337,6 +349,10 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -427,6 +443,10 @@ Output:: "size": 1421 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -524,6 +544,10 @@ const a = class { "size": 1390 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -555,6 +579,10 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: diff --git a/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-without-dts-enabled-with-incremental.js b/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-without-dts-enabled-with-incremental.js index d6c2e09dbb7..deac4c5b699 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-without-dts-enabled-with-incremental.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-without-dts-enabled-with-incremental.js @@ -104,6 +104,10 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -185,6 +189,10 @@ Output:: "size": 1125 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -269,6 +277,10 @@ const a = "hello"; "size": 1094 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -300,6 +312,10 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -379,6 +395,10 @@ Output:: "size": 1352 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -465,6 +485,10 @@ const a = class { "size": 1321 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -496,6 +520,10 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: diff --git a/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-without-dts-enabled.js b/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-without-dts-enabled.js index 6f88b8b118e..21e54b7e8d8 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-without-dts-enabled.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors-without-dts-enabled.js @@ -64,6 +64,9 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -91,6 +94,9 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -127,6 +133,9 @@ const a = "hello"; //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -159,6 +168,9 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -186,6 +198,9 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -224,6 +239,9 @@ const a = class { //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -256,6 +274,9 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors.js b/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors.js index 560ceaefd52..d5ca457a8b0 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmit/dts-errors.js @@ -74,6 +74,9 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -114,6 +117,9 @@ Output:: "size": 45 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -153,6 +159,9 @@ const a = "hello"; //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -185,6 +194,9 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -235,6 +247,9 @@ Output:: "size": 59 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -285,6 +300,9 @@ const a = class { //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -326,6 +344,9 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/noEmit/semantic-errors-with-incremental-as-modules.js b/testdata/baselines/reference/tsbuildWatch/noEmit/semantic-errors-with-incremental-as-modules.js index fa9b3645600..5ee9ae7e99f 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmit/semantic-errors-with-incremental-as-modules.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmit/semantic-errors-with-incremental-as-modules.js @@ -139,6 +139,10 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -235,6 +239,10 @@ Output:: "size": 1184 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/projects/project/a.ts @@ -329,6 +337,10 @@ export const b = 10; "size": 1151 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -360,6 +372,10 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -473,6 +489,10 @@ Output:: "size": 1366 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -591,6 +611,10 @@ const a = "hello"; "size": 1335 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -624,6 +648,10 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: diff --git a/testdata/baselines/reference/tsbuildWatch/noEmit/semantic-errors-with-incremental.js b/testdata/baselines/reference/tsbuildWatch/noEmit/semantic-errors-with-incremental.js index b1d7a4108da..c316b724962 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmit/semantic-errors-with-incremental.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmit/semantic-errors-with-incremental.js @@ -127,6 +127,10 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -208,6 +212,10 @@ Output:: "size": 1125 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -292,6 +300,10 @@ const a = "hello"; "size": 1094 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -323,6 +335,10 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -425,6 +441,10 @@ Output:: "size": 1297 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -529,6 +549,10 @@ Output:: "size": 1266 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -562,6 +586,10 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: diff --git a/testdata/baselines/reference/tsbuildWatch/noEmit/semantic-errors.js b/testdata/baselines/reference/tsbuildWatch/noEmit/semantic-errors.js index 8e8a2bcf394..cf1668937cc 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmit/semantic-errors.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmit/semantic-errors.js @@ -70,6 +70,9 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -110,6 +113,9 @@ Output:: "size": 45 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -146,6 +152,9 @@ const a = "hello"; //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -178,6 +187,9 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -224,6 +236,9 @@ Output:: "semanticErrors": true } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -262,6 +277,9 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -299,6 +317,9 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/noEmit/syntax-errors-with-incremental-as-modules.js b/testdata/baselines/reference/tsbuildWatch/noEmit/syntax-errors-with-incremental-as-modules.js index 28780099793..a2b040d182f 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmit/syntax-errors-with-incremental-as-modules.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmit/syntax-errors-with-incremental-as-modules.js @@ -127,6 +127,10 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -223,6 +227,10 @@ Output:: "size": 1184 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -319,6 +327,10 @@ export const b = 10; "size": 1151 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -350,6 +362,10 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -442,6 +458,10 @@ Output:: "size": 1201 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/projects/project/a.ts @@ -545,6 +565,10 @@ const a = "hello; "size": 1209 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -581,6 +605,10 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/noEmit/syntax-errors-with-incremental.js b/testdata/baselines/reference/tsbuildWatch/noEmit/syntax-errors-with-incremental.js index df718aa040b..feeea664e65 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmit/syntax-errors-with-incremental.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmit/syntax-errors-with-incremental.js @@ -114,6 +114,10 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -195,6 +199,10 @@ Output:: "size": 1125 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -279,6 +287,10 @@ const a = "hello"; "size": 1094 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -310,6 +322,10 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -391,6 +407,10 @@ Output:: "size": 1125 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/projects/project/a.ts @@ -482,6 +502,10 @@ const a = "hello; "size": 1138 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/projects/project/a.ts @@ -517,6 +541,10 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) + /home/src/tslibs/TS/Lib tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/projects/project/a.ts diff --git a/testdata/baselines/reference/tsbuildWatch/noEmit/syntax-errors.js b/testdata/baselines/reference/tsbuildWatch/noEmit/syntax-errors.js index 9f9b991de61..6cb3e50d333 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmit/syntax-errors.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmit/syntax-errors.js @@ -70,6 +70,9 @@ interface Symbol { } declare const console: { log(msg: any): void; }; +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -110,6 +113,9 @@ Output:: "size": 45 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -146,6 +152,9 @@ const a = "hello"; //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -178,6 +187,9 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -224,6 +236,9 @@ Output:: "size": 59 } +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -265,6 +280,9 @@ const a = "hello; //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -302,6 +320,9 @@ Output:: //// [/home/src/projects/project/tsconfig.tsbuildinfo] *rewrite with same content* //// [/home/src/projects/project/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError-with-declaration-with-incremental.js b/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError-with-declaration-with-incremental.js index 8f67e10f449..167a5b6ded6 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError-with-declaration-with-incremental.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError-with-declaration-with-incremental.js @@ -163,6 +163,10 @@ declare const console: { log(msg: any): void; }; "size": 1381 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -194,6 +198,10 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -336,6 +344,10 @@ export {}; "size": 1608 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -366,6 +378,10 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) Edit [3]:: semantic errors @@ -505,6 +521,10 @@ Output:: "size": 1794 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/noEmitOnError/src/main.ts @@ -534,6 +554,10 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -651,6 +675,10 @@ export {}; "size": 1599 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/noEmitOnError/src/main.ts @@ -676,6 +704,10 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) Edit [7]:: dts errors @@ -833,6 +865,10 @@ Output:: "size": 2230 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/noEmitOnError/src/main.ts @@ -866,6 +902,10 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -990,6 +1030,10 @@ export const a = class { "size": 1668 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/noEmitOnError/src/main.ts @@ -1015,3 +1059,7 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) diff --git a/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError-with-declaration.js b/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError-with-declaration.js index 4cd4780e418..430db82e70a 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError-with-declaration.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError-with-declaration.js @@ -94,6 +94,9 @@ declare const console: { log(msg: any): void; }; "size": 109 } +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -127,6 +130,9 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -208,6 +214,9 @@ export {}; "size": 95 } +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -242,6 +251,9 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -303,6 +315,9 @@ Output:: "semanticErrors": true } +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -336,6 +351,9 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -400,6 +418,9 @@ export {}; "size": 95 } +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -434,6 +455,9 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -499,6 +523,9 @@ Output:: "size": 109 } +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -536,6 +563,9 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -607,6 +637,9 @@ export const a = class { "size": 95 } +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -641,6 +674,9 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError-with-incremental.js b/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError-with-incremental.js index 358ed4fdae3..85fbf31c434 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError-with-incremental.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError-with-incremental.js @@ -163,6 +163,10 @@ declare const console: { log(msg: any): void; }; "size": 1382 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -194,6 +198,10 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -315,6 +323,10 @@ export {}; "size": 1389 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -343,6 +355,10 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) Edit [3]:: semantic errors @@ -472,6 +488,10 @@ Output:: "size": 1575 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/noEmitOnError/src/main.ts @@ -501,6 +521,10 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -607,6 +631,10 @@ export {}; "size": 1380 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/noEmitOnError/src/main.ts @@ -632,6 +660,10 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) Edit [7]:: dts errors @@ -736,6 +768,10 @@ export const a = class { "size": 1633 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/noEmitOnError/src/main.ts @@ -761,6 +797,10 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) Edit [9]:: Fix dts errors @@ -861,6 +901,10 @@ Output:: "size": 1449 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/noEmitOnError/src/main.ts @@ -886,3 +930,7 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/noEmitOnError (recursive) diff --git a/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError.js b/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError.js index 76c4398164e..88bb93181c1 100644 --- a/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError.js +++ b/testdata/baselines/reference/tsbuildWatch/noEmitOnError/noEmitOnError.js @@ -94,6 +94,9 @@ declare const console: { log(msg: any): void; }; "size": 109 } +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -127,6 +130,9 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *not cached* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -197,6 +203,9 @@ export {}; "size": 95 } +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -228,6 +237,9 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -289,6 +301,9 @@ Output:: "semanticErrors": true } +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -322,6 +337,9 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -383,6 +401,9 @@ export {}; "size": 95 } +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -414,6 +435,9 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -451,6 +475,9 @@ export const a = class { //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -482,6 +509,9 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -515,6 +545,9 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -546,6 +579,9 @@ Output:: //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/noEmitOnError/dev-build/tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /user/username/projects/noEmitOnError (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-introduceError-when-file-with-no-error-changes.js b/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-introduceError-when-file-with-no-error-changes.js index b10f8d088a9..9730ae2c877 100644 --- a/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-introduceError-when-file-with-no-error-changes.js +++ b/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-introduceError-when-file-with-no-error-changes.js @@ -130,6 +130,10 @@ export class myClass { "size": 1430 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/solution/app (recursive) app/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -272,6 +276,10 @@ export var myClassWithError = class { "size": 2167 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/solution/app (recursive) app/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/solution/app/fileWithError.ts @@ -410,6 +418,10 @@ export class myClass2 { "size": 2169 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/solution/app (recursive) app/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/solution/app/fileWithoutError.ts diff --git a/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-introduceError-when-fixing-errors-only-changed-file-is-emitted.js b/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-introduceError-when-fixing-errors-only-changed-file-is-emitted.js index 78312e4c50f..ebedc41d1cf 100644 --- a/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-introduceError-when-fixing-errors-only-changed-file-is-emitted.js +++ b/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-introduceError-when-fixing-errors-only-changed-file-is-emitted.js @@ -130,6 +130,10 @@ export class myClass { "size": 1430 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/solution/app (recursive) app/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -272,6 +276,10 @@ export var myClassWithError = class { "size": 2167 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/solution/app (recursive) app/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/solution/app/fileWithError.ts @@ -362,6 +370,10 @@ export var myClassWithError = class { "size": 1434 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/solution/app (recursive) app/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/solution/app/fileWithError.ts diff --git a/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-when-file-with-no-error-changes.js b/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-when-file-with-no-error-changes.js index 4f146e2d4ca..e008d8d1a28 100644 --- a/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-when-file-with-no-error-changes.js +++ b/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-when-file-with-no-error-changes.js @@ -163,6 +163,10 @@ export class myClass { "size": 1654 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/solution/app (recursive) app/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -294,6 +298,10 @@ export class myClass2 { "size": 1656 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/solution/app (recursive) app/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/solution/app/fileWithoutError.ts diff --git a/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-when-fixing-error-files-all-files-are-emitted.js b/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-when-fixing-error-files-all-files-are-emitted.js index 4669ae31a8a..e481ff2ee41 100644 --- a/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-when-fixing-error-files-all-files-are-emitted.js +++ b/testdata/baselines/reference/tsbuildWatch/programUpdates/declarationEmitErrors-when-fixing-error-files-all-files-are-emitted.js @@ -163,6 +163,10 @@ export class myClass { "size": 1654 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/solution/app (recursive) app/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -262,6 +266,10 @@ export var myClassWithError = class { "size": 1431 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/solution/app (recursive) app/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/solution/app/fileWithError.ts diff --git a/testdata/baselines/reference/tsbuildWatch/programUpdates/tsbuildinfo-has-error.js b/testdata/baselines/reference/tsbuildWatch/programUpdates/tsbuildinfo-has-error.js index 5c7818babf7..191815a32e6 100644 --- a/testdata/baselines/reference/tsbuildWatch/programUpdates/tsbuildinfo-has-error.js +++ b/testdata/baselines/reference/tsbuildWatch/programUpdates/tsbuildinfo-has-error.js @@ -81,6 +81,10 @@ export const x = 10; "size": 927 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/project (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/programUpdates/when-referenced-project-change-introduces-error-in-the-down-stream-project-and-then-fixes-it.js b/testdata/baselines/reference/tsbuildWatch/programUpdates/when-referenced-project-change-introduces-error-in-the-down-stream-project-and-then-fixes-it.js index aa9269a1f14..9d6818368db 100644 --- a/testdata/baselines/reference/tsbuildWatch/programUpdates/when-referenced-project-change-introduces-error-in-the-down-stream-project-and-then-fixes-it.js +++ b/testdata/baselines/reference/tsbuildWatch/programUpdates/when-referenced-project-change-introduces-error-in-the-down-stream-project-and-then-fixes-it.js @@ -139,6 +139,11 @@ export function createSomeObject() { "size": 1338 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/App (recursive) + /user/username/projects/sample1/Library (recursive) Library/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -263,6 +268,11 @@ export function createSomeObject() { "size": 1341 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/App (recursive) + /user/username/projects/sample1/Library (recursive) Library/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/Library/library.ts @@ -376,6 +386,11 @@ export function createSomeObject() { "size": 1338 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/App (recursive) + /user/username/projects/sample1/Library (recursive) Library/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/Library/library.ts diff --git a/testdata/baselines/reference/tsbuildWatch/programUpdates/works-correctly-when-project-with-extended-config-is-removed.js b/testdata/baselines/reference/tsbuildWatch/programUpdates/works-correctly-when-project-with-extended-config-is-removed.js index c644f965ba1..a83d4be774b 100644 --- a/testdata/baselines/reference/tsbuildWatch/programUpdates/works-correctly-when-project-with-extended-config-is-removed.js +++ b/testdata/baselines/reference/tsbuildWatch/programUpdates/works-correctly-when-project-with-extended-config-is-removed.js @@ -233,6 +233,10 @@ let z = 0; "size": 1131 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/project project1.tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -272,3 +276,7 @@ Output:: [HH:MM:SS AM] Found 0 errors. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/project diff --git a/testdata/baselines/reference/tsbuildWatch/programUpdates/works-when-noUnusedParameters-changes-to-false.js b/testdata/baselines/reference/tsbuildWatch/programUpdates/works-when-noUnusedParameters-changes-to-false.js index 7cccf268b6f..ebf291b0639 100644 --- a/testdata/baselines/reference/tsbuildWatch/programUpdates/works-when-noUnusedParameters-changes-to-false.js +++ b/testdata/baselines/reference/tsbuildWatch/programUpdates/works-when-noUnusedParameters-changes-to-false.js @@ -66,6 +66,9 @@ const fn = (a, b) => b; "semanticErrors": true } +Watch Registrations:: +Directory watches:: + /user/username/projects/myproject (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -104,6 +107,9 @@ Output:: "size": 49 } +Watch Registrations:: +Directory watches:: + /user/username/projects/myproject (recursive) tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts diff --git a/testdata/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js b/testdata/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js index 9cec3fd438b..9d62bab02b1 100644 --- a/testdata/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js +++ b/testdata/baselines/reference/tsbuildWatch/programUpdates/works-with-extended-source-files.js @@ -268,6 +268,10 @@ let k = 0; "size": 50 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/project project1.tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -325,6 +329,10 @@ Output:: //// [/user/username/projects/project/project1.tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/project/project2.tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/project project1.tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -408,6 +416,10 @@ Output:: "size": 1132 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/project project2.tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -454,6 +466,10 @@ Output:: "size": 49 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/project project2.tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -491,6 +507,10 @@ Output:: //// [/user/username/projects/project/project2.tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/project/project2.tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/project project1.tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -527,6 +547,10 @@ Output:: //// [/user/username/projects/project/project3.tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/project/project3.tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/project project3.tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -561,6 +585,10 @@ Output:: //// [/user/username/projects/project/project3.tsconfig.tsbuildinfo] *rewrite with same content* //// [/user/username/projects/project/project3.tsconfig.tsbuildinfo.readable.baseline.txt] *rewrite with same content* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/project project3.tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -586,3 +614,7 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/project diff --git a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-23-projects-in-a-solution-with---builders-3.js b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-23-projects-in-a-solution-with---builders-3.js index 31d57f2b3d0..66bd41fb433 100644 --- a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-23-projects-in-a-solution-with---builders-3.js +++ b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-23-projects-in-a-solution-with---builders-3.js @@ -1925,6 +1925,33 @@ export const pkg9 = 9; "size": 3576 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg10 (recursive) + /user/username/projects/myproject/pkg11 (recursive) + /user/username/projects/myproject/pkg12 (recursive) + /user/username/projects/myproject/pkg13 (recursive) + /user/username/projects/myproject/pkg14 (recursive) + /user/username/projects/myproject/pkg15 (recursive) + /user/username/projects/myproject/pkg16 (recursive) + /user/username/projects/myproject/pkg17 (recursive) + /user/username/projects/myproject/pkg18 (recursive) + /user/username/projects/myproject/pkg19 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg20 (recursive) + /user/username/projects/myproject/pkg21 (recursive) + /user/username/projects/myproject/pkg22 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) + /user/username/projects/myproject/pkg8 (recursive) + /user/username/projects/myproject/pkg9 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -2323,6 +2350,33 @@ const someConst2 = 10; //// [/user/username/projects/myproject/pkg9/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/myproject/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg10 (recursive) + /user/username/projects/myproject/pkg11 (recursive) + /user/username/projects/myproject/pkg12 (recursive) + /user/username/projects/myproject/pkg13 (recursive) + /user/username/projects/myproject/pkg14 (recursive) + /user/username/projects/myproject/pkg15 (recursive) + /user/username/projects/myproject/pkg16 (recursive) + /user/username/projects/myproject/pkg17 (recursive) + /user/username/projects/myproject/pkg18 (recursive) + /user/username/projects/myproject/pkg19 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg20 (recursive) + /user/username/projects/myproject/pkg21 (recursive) + /user/username/projects/myproject/pkg22 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) + /user/username/projects/myproject/pkg8 (recursive) + /user/username/projects/myproject/pkg9 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -2335,6 +2389,33 @@ Edit [1]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg10 (recursive) + /user/username/projects/myproject/pkg11 (recursive) + /user/username/projects/myproject/pkg12 (recursive) + /user/username/projects/myproject/pkg13 (recursive) + /user/username/projects/myproject/pkg14 (recursive) + /user/username/projects/myproject/pkg15 (recursive) + /user/username/projects/myproject/pkg16 (recursive) + /user/username/projects/myproject/pkg17 (recursive) + /user/username/projects/myproject/pkg18 (recursive) + /user/username/projects/myproject/pkg19 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg20 (recursive) + /user/username/projects/myproject/pkg21 (recursive) + /user/username/projects/myproject/pkg22 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) + /user/username/projects/myproject/pkg8 (recursive) + /user/username/projects/myproject/pkg9 (recursive) Edit [2]:: dts change @@ -2931,6 +3012,33 @@ export const someConst = 10; "size": 3614 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg10 (recursive) + /user/username/projects/myproject/pkg11 (recursive) + /user/username/projects/myproject/pkg12 (recursive) + /user/username/projects/myproject/pkg13 (recursive) + /user/username/projects/myproject/pkg14 (recursive) + /user/username/projects/myproject/pkg15 (recursive) + /user/username/projects/myproject/pkg16 (recursive) + /user/username/projects/myproject/pkg17 (recursive) + /user/username/projects/myproject/pkg18 (recursive) + /user/username/projects/myproject/pkg19 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg20 (recursive) + /user/username/projects/myproject/pkg21 (recursive) + /user/username/projects/myproject/pkg22 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) + /user/username/projects/myproject/pkg8 (recursive) + /user/username/projects/myproject/pkg9 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -3037,3 +3145,30 @@ Edit [3]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg10 (recursive) + /user/username/projects/myproject/pkg11 (recursive) + /user/username/projects/myproject/pkg12 (recursive) + /user/username/projects/myproject/pkg13 (recursive) + /user/username/projects/myproject/pkg14 (recursive) + /user/username/projects/myproject/pkg15 (recursive) + /user/username/projects/myproject/pkg16 (recursive) + /user/username/projects/myproject/pkg17 (recursive) + /user/username/projects/myproject/pkg18 (recursive) + /user/username/projects/myproject/pkg19 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg20 (recursive) + /user/username/projects/myproject/pkg21 (recursive) + /user/username/projects/myproject/pkg22 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) + /user/username/projects/myproject/pkg8 (recursive) + /user/username/projects/myproject/pkg9 (recursive) diff --git a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-23-projects-in-a-solution.js b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-23-projects-in-a-solution.js index d960714cb3b..b16634a6a73 100644 --- a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-23-projects-in-a-solution.js +++ b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-23-projects-in-a-solution.js @@ -1925,6 +1925,33 @@ export const pkg9 = 9; "size": 3576 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg10 (recursive) + /user/username/projects/myproject/pkg11 (recursive) + /user/username/projects/myproject/pkg12 (recursive) + /user/username/projects/myproject/pkg13 (recursive) + /user/username/projects/myproject/pkg14 (recursive) + /user/username/projects/myproject/pkg15 (recursive) + /user/username/projects/myproject/pkg16 (recursive) + /user/username/projects/myproject/pkg17 (recursive) + /user/username/projects/myproject/pkg18 (recursive) + /user/username/projects/myproject/pkg19 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg20 (recursive) + /user/username/projects/myproject/pkg21 (recursive) + /user/username/projects/myproject/pkg22 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) + /user/username/projects/myproject/pkg8 (recursive) + /user/username/projects/myproject/pkg9 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -2323,6 +2350,33 @@ const someConst2 = 10; //// [/user/username/projects/myproject/pkg9/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/myproject/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg10 (recursive) + /user/username/projects/myproject/pkg11 (recursive) + /user/username/projects/myproject/pkg12 (recursive) + /user/username/projects/myproject/pkg13 (recursive) + /user/username/projects/myproject/pkg14 (recursive) + /user/username/projects/myproject/pkg15 (recursive) + /user/username/projects/myproject/pkg16 (recursive) + /user/username/projects/myproject/pkg17 (recursive) + /user/username/projects/myproject/pkg18 (recursive) + /user/username/projects/myproject/pkg19 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg20 (recursive) + /user/username/projects/myproject/pkg21 (recursive) + /user/username/projects/myproject/pkg22 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) + /user/username/projects/myproject/pkg8 (recursive) + /user/username/projects/myproject/pkg9 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -2335,6 +2389,33 @@ Edit [1]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg10 (recursive) + /user/username/projects/myproject/pkg11 (recursive) + /user/username/projects/myproject/pkg12 (recursive) + /user/username/projects/myproject/pkg13 (recursive) + /user/username/projects/myproject/pkg14 (recursive) + /user/username/projects/myproject/pkg15 (recursive) + /user/username/projects/myproject/pkg16 (recursive) + /user/username/projects/myproject/pkg17 (recursive) + /user/username/projects/myproject/pkg18 (recursive) + /user/username/projects/myproject/pkg19 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg20 (recursive) + /user/username/projects/myproject/pkg21 (recursive) + /user/username/projects/myproject/pkg22 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) + /user/username/projects/myproject/pkg8 (recursive) + /user/username/projects/myproject/pkg9 (recursive) Edit [2]:: dts change @@ -2931,6 +3012,33 @@ export const someConst = 10; "size": 3614 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg10 (recursive) + /user/username/projects/myproject/pkg11 (recursive) + /user/username/projects/myproject/pkg12 (recursive) + /user/username/projects/myproject/pkg13 (recursive) + /user/username/projects/myproject/pkg14 (recursive) + /user/username/projects/myproject/pkg15 (recursive) + /user/username/projects/myproject/pkg16 (recursive) + /user/username/projects/myproject/pkg17 (recursive) + /user/username/projects/myproject/pkg18 (recursive) + /user/username/projects/myproject/pkg19 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg20 (recursive) + /user/username/projects/myproject/pkg21 (recursive) + /user/username/projects/myproject/pkg22 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) + /user/username/projects/myproject/pkg8 (recursive) + /user/username/projects/myproject/pkg9 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -3037,3 +3145,30 @@ Edit [3]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg10 (recursive) + /user/username/projects/myproject/pkg11 (recursive) + /user/username/projects/myproject/pkg12 (recursive) + /user/username/projects/myproject/pkg13 (recursive) + /user/username/projects/myproject/pkg14 (recursive) + /user/username/projects/myproject/pkg15 (recursive) + /user/username/projects/myproject/pkg16 (recursive) + /user/username/projects/myproject/pkg17 (recursive) + /user/username/projects/myproject/pkg18 (recursive) + /user/username/projects/myproject/pkg19 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg20 (recursive) + /user/username/projects/myproject/pkg21 (recursive) + /user/username/projects/myproject/pkg22 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) + /user/username/projects/myproject/pkg8 (recursive) + /user/username/projects/myproject/pkg9 (recursive) diff --git a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-3-projects-in-a-solution-with---builders-1.js b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-3-projects-in-a-solution-with---builders-1.js index 0b91c320941..934296ffd16 100644 --- a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-3-projects-in-a-solution-with---builders-1.js +++ b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-3-projects-in-a-solution-with---builders-1.js @@ -325,6 +325,13 @@ export const pkg2 = 2; "size": 1245 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -443,6 +450,13 @@ const someConst2 = 10; //// [/user/username/projects/myproject/pkg2/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/myproject/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -455,6 +469,13 @@ Edit [1]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) Edit [2]:: dts change @@ -631,6 +652,13 @@ export const someConst = 10; "size": 1283 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -657,3 +685,10 @@ Edit [3]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) diff --git a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-3-projects-in-a-solution.js b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-3-projects-in-a-solution.js index d7c644cec19..29933802241 100644 --- a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-3-projects-in-a-solution.js +++ b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-3-projects-in-a-solution.js @@ -325,6 +325,13 @@ export const pkg2 = 2; "size": 1245 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -443,6 +450,13 @@ const someConst2 = 10; //// [/user/username/projects/myproject/pkg2/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/myproject/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -455,6 +469,13 @@ Edit [1]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) Edit [2]:: dts change @@ -631,6 +652,13 @@ export const someConst = 10; "size": 1283 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -657,3 +685,10 @@ Edit [3]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) diff --git a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-5-projects-in-a-solution-with---builders-2.js b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-5-projects-in-a-solution-with---builders-2.js index a655e3e136a..60d7f0474a2 100644 --- a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-5-projects-in-a-solution-with---builders-2.js +++ b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-5-projects-in-a-solution-with---builders-2.js @@ -485,6 +485,15 @@ export const pkg4 = 4; "size": 1471 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -631,6 +640,15 @@ const someConst2 = 10; //// [/user/username/projects/myproject/pkg4/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/myproject/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -643,6 +661,15 @@ Edit [1]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) Edit [2]:: dts change @@ -861,6 +888,15 @@ export const someConst = 10; "size": 1509 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -895,3 +931,12 @@ Edit [3]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) diff --git a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-5-projects-in-a-solution.js b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-5-projects-in-a-solution.js index 4749a53c86e..21ba5cd0ba5 100644 --- a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-5-projects-in-a-solution.js +++ b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-5-projects-in-a-solution.js @@ -485,6 +485,15 @@ export const pkg4 = 4; "size": 1471 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -631,6 +640,15 @@ const someConst2 = 10; //// [/user/username/projects/myproject/pkg4/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/myproject/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -643,6 +661,15 @@ Edit [1]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) Edit [2]:: dts change @@ -861,6 +888,15 @@ export const someConst = 10; "size": 1509 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -895,3 +931,12 @@ Edit [3]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) diff --git a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-8-projects-in-a-solution-with---builders-3.js b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-8-projects-in-a-solution-with---builders-3.js index a65e4749ff8..9571a088b01 100644 --- a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-8-projects-in-a-solution-with---builders-3.js +++ b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-8-projects-in-a-solution-with---builders-3.js @@ -725,6 +725,18 @@ export const pkg7 = 7; "size": 1813 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -913,6 +925,18 @@ const someConst2 = 10; //// [/user/username/projects/myproject/pkg7/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/myproject/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -925,6 +949,18 @@ Edit [1]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) Edit [2]:: dts change @@ -1206,6 +1242,18 @@ export const someConst = 10; "size": 1851 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -1252,3 +1300,15 @@ Edit [3]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) diff --git a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-8-projects-in-a-solution.js b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-8-projects-in-a-solution.js index b0d00753cd2..3b60dcb059b 100644 --- a/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-8-projects-in-a-solution.js +++ b/testdata/baselines/reference/tsbuildWatch/projectsBuilding/when-there-are-8-projects-in-a-solution.js @@ -725,6 +725,18 @@ export const pkg7 = 7; "size": 1813 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -913,6 +925,18 @@ const someConst2 = 10; //// [/user/username/projects/myproject/pkg7/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/myproject/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -925,6 +949,18 @@ Edit [1]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) Edit [2]:: dts change @@ -1206,6 +1242,18 @@ export const someConst = 10; "size": 1851 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) pkg0/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/myproject/pkg0/index.ts @@ -1252,3 +1300,15 @@ Edit [3]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/myproject (recursive) + /user/username/projects/myproject/pkg0 (recursive) + /user/username/projects/myproject/pkg1 (recursive) + /user/username/projects/myproject/pkg2 (recursive) + /user/username/projects/myproject/pkg3 (recursive) + /user/username/projects/myproject/pkg4 (recursive) + /user/username/projects/myproject/pkg5 (recursive) + /user/username/projects/myproject/pkg6 (recursive) + /user/username/projects/myproject/pkg7 (recursive) diff --git a/testdata/baselines/reference/tsbuildWatch/reexport/Reports-errors-correctly.js b/testdata/baselines/reference/tsbuildWatch/reexport/Reports-errors-correctly.js index d6409b9568b..02f6096237e 100644 --- a/testdata/baselines/reference/tsbuildWatch/reexport/Reports-errors-correctly.js +++ b/testdata/baselines/reference/tsbuildWatch/reexport/Reports-errors-correctly.js @@ -193,6 +193,12 @@ export {}; "size": 1475 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/reexport/src + /user/username/projects/reexport/src/main (recursive) + /user/username/projects/reexport/src/pure (recursive) src/pure/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -348,6 +354,12 @@ export interface Session { "size": 1492 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/reexport/src + /user/username/projects/reexport/src/main (recursive) + /user/username/projects/reexport/src/pure (recursive) src/pure/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/reexport/src/pure/session.ts @@ -491,6 +503,12 @@ export interface Session { "size": 1477 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/reexport/src + /user/username/projects/reexport/src/main (recursive) + /user/username/projects/reexport/src/pure (recursive) src/pure/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/reexport/src/pure/session.ts diff --git a/testdata/baselines/reference/tsbuildWatch/roots/when-root-file-is-from-referenced-project-and-shared-is-first.js b/testdata/baselines/reference/tsbuildWatch/roots/when-root-file-is-from-referenced-project-and-shared-is-first.js index 128fc07342a..6e2b9ee3c1c 100644 --- a/testdata/baselines/reference/tsbuildWatch/roots/when-root-file-is-from-referenced-project-and-shared-is-first.js +++ b/testdata/baselines/reference/tsbuildWatch/roots/when-root-file-is-from-referenced-project-and-shared-is-first.js @@ -336,6 +336,15 @@ export function randomFn(str) { "size": 1649 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/solution + /home/src/workspaces/solution/projects/server + /home/src/workspaces/solution/projects/server/src (recursive) + /home/src/workspaces/solution/projects/shared + /home/src/workspaces/solution/projects/shared/dist/src + /home/src/workspaces/solution/projects/shared/src (recursive) projects/shared/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -363,6 +372,15 @@ Edit [0]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/solution + /home/src/workspaces/solution/projects/server + /home/src/workspaces/solution/projects/server/src (recursive) + /home/src/workspaces/solution/projects/shared + /home/src/workspaces/solution/projects/shared/dist/src + /home/src/workspaces/solution/projects/shared/src (recursive) Edit [1]:: edit logging file @@ -614,6 +632,15 @@ export const x = 10; "size": 1700 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/solution + /home/src/workspaces/solution/projects/server + /home/src/workspaces/solution/projects/server/src (recursive) + /home/src/workspaces/solution/projects/shared + /home/src/workspaces/solution/projects/shared/dist/src + /home/src/workspaces/solution/projects/shared/src (recursive) projects/shared/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/solution/projects/shared/src/logging.ts @@ -632,6 +659,15 @@ Edit [2]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/solution + /home/src/workspaces/solution/projects/server + /home/src/workspaces/solution/projects/server/src (recursive) + /home/src/workspaces/solution/projects/shared + /home/src/workspaces/solution/projects/shared/dist/src + /home/src/workspaces/solution/projects/shared/src (recursive) Edit [3]:: delete random file @@ -839,6 +875,15 @@ projects/server/src/server.ts "size": 1444 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/solution + /home/src/workspaces/solution/projects/server + /home/src/workspaces/solution/projects/server/src (recursive) + /home/src/workspaces/solution/projects/shared + /home/src/workspaces/solution/projects/shared/dist/src + /home/src/workspaces/solution/projects/shared/src (recursive) projects/shared/tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -853,3 +898,12 @@ Edit [4]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/solution + /home/src/workspaces/solution/projects/server + /home/src/workspaces/solution/projects/server/src (recursive) + /home/src/workspaces/solution/projects/shared + /home/src/workspaces/solution/projects/shared/dist/src + /home/src/workspaces/solution/projects/shared/src (recursive) diff --git a/testdata/baselines/reference/tsbuildWatch/roots/when-root-file-is-from-referenced-project.js b/testdata/baselines/reference/tsbuildWatch/roots/when-root-file-is-from-referenced-project.js index 1d5eb3cbbaa..b238722a135 100644 --- a/testdata/baselines/reference/tsbuildWatch/roots/when-root-file-is-from-referenced-project.js +++ b/testdata/baselines/reference/tsbuildWatch/roots/when-root-file-is-from-referenced-project.js @@ -336,6 +336,15 @@ export function randomFn(str) { "size": 1649 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/solution + /home/src/workspaces/solution/projects/server + /home/src/workspaces/solution/projects/server/src (recursive) + /home/src/workspaces/solution/projects/shared + /home/src/workspaces/solution/projects/shared/dist/src + /home/src/workspaces/solution/projects/shared/src (recursive) projects/shared/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -363,6 +372,15 @@ Edit [0]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/solution + /home/src/workspaces/solution/projects/server + /home/src/workspaces/solution/projects/server/src (recursive) + /home/src/workspaces/solution/projects/shared + /home/src/workspaces/solution/projects/shared/dist/src + /home/src/workspaces/solution/projects/shared/src (recursive) Edit [1]:: edit logging file @@ -614,6 +632,15 @@ export const x = 10; "size": 1700 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/solution + /home/src/workspaces/solution/projects/server + /home/src/workspaces/solution/projects/server/src (recursive) + /home/src/workspaces/solution/projects/shared + /home/src/workspaces/solution/projects/shared/dist/src + /home/src/workspaces/solution/projects/shared/src (recursive) projects/shared/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/workspaces/solution/projects/shared/src/logging.ts @@ -632,6 +659,15 @@ Edit [2]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/solution + /home/src/workspaces/solution/projects/server + /home/src/workspaces/solution/projects/server/src (recursive) + /home/src/workspaces/solution/projects/shared + /home/src/workspaces/solution/projects/shared/dist/src + /home/src/workspaces/solution/projects/shared/src (recursive) Edit [3]:: delete random file @@ -839,6 +875,15 @@ projects/shared/dist/src/logging.d.ts "size": 1444 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/solution + /home/src/workspaces/solution/projects/server + /home/src/workspaces/solution/projects/server/src (recursive) + /home/src/workspaces/solution/projects/shared + /home/src/workspaces/solution/projects/shared/dist/src + /home/src/workspaces/solution/projects/shared/src (recursive) projects/shared/tsconfig.json:: SemanticDiagnostics:: Signatures:: @@ -853,3 +898,12 @@ Edit [4]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /home/src/workspaces/solution + /home/src/workspaces/solution/projects/server + /home/src/workspaces/solution/projects/server/src (recursive) + /home/src/workspaces/solution/projects/shared + /home/src/workspaces/solution/projects/shared/dist/src + /home/src/workspaces/solution/projects/shared/src (recursive) diff --git a/testdata/baselines/reference/tsbuildWatch/sample/builds-when-new-file-is-added,-and-its-subsequent-updates-with-circular-references.js b/testdata/baselines/reference/tsbuildWatch/sample/builds-when-new-file-is-added,-and-its-subsequent-updates-with-circular-references.js index 0a1206d613e..d29f94e7e7d 100644 --- a/testdata/baselines/reference/tsbuildWatch/sample/builds-when-new-file-is-added,-and-its-subsequent-updates-with-circular-references.js +++ b/testdata/baselines/reference/tsbuildWatch/sample/builds-when-new-file-is-added,-and-its-subsequent-updates-with-circular-references.js @@ -382,6 +382,12 @@ export const m = mod; "size": 1972 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -523,6 +529,12 @@ export const newFileConst = 30; //// [/user/username/projects/sample1/logic/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/sample1/tests/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/newfile.ts @@ -652,6 +664,12 @@ export class someClass2 { //// [/user/username/projects/sample1/logic/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/sample1/tests/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/newfile.ts diff --git a/testdata/baselines/reference/tsbuildWatch/sample/builds-when-new-file-is-added,-and-its-subsequent-updates.js b/testdata/baselines/reference/tsbuildWatch/sample/builds-when-new-file-is-added,-and-its-subsequent-updates.js index d17a1b98245..81ae9ea03f1 100644 --- a/testdata/baselines/reference/tsbuildWatch/sample/builds-when-new-file-is-added,-and-its-subsequent-updates.js +++ b/testdata/baselines/reference/tsbuildWatch/sample/builds-when-new-file-is-added,-and-its-subsequent-updates.js @@ -387,6 +387,12 @@ export const m = mod; "size": 2050 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -532,6 +538,12 @@ export const newFileConst = 30; //// [/user/username/projects/sample1/logic/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/sample1/tests/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/newfile.ts @@ -665,6 +677,12 @@ export class someClass2 { //// [/user/username/projects/sample1/logic/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/sample1/tests/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/newfile.ts diff --git a/testdata/baselines/reference/tsbuildWatch/sample/change-builds-changes-and-reports-found-errors-message-with-circular-references.js b/testdata/baselines/reference/tsbuildWatch/sample/change-builds-changes-and-reports-found-errors-message-with-circular-references.js index 0e193423cdd..9f9d297cb38 100644 --- a/testdata/baselines/reference/tsbuildWatch/sample/change-builds-changes-and-reports-found-errors-message-with-circular-references.js +++ b/testdata/baselines/reference/tsbuildWatch/sample/change-builds-changes-and-reports-found-errors-message-with-circular-references.js @@ -382,6 +382,12 @@ export const m = mod; "size": 1972 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -690,6 +696,12 @@ export class someClass { "size": 2009 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/index.ts @@ -986,6 +998,12 @@ export function multiply(a, b) { return a * b; } "size": 1972 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/index.ts @@ -1292,6 +1310,12 @@ export class someClass2 { "size": 2047 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/index.ts diff --git a/testdata/baselines/reference/tsbuildWatch/sample/change-builds-changes-and-reports-found-errors-message.js b/testdata/baselines/reference/tsbuildWatch/sample/change-builds-changes-and-reports-found-errors-message.js index e5a5c323253..bc14d3d9ffb 100644 --- a/testdata/baselines/reference/tsbuildWatch/sample/change-builds-changes-and-reports-found-errors-message.js +++ b/testdata/baselines/reference/tsbuildWatch/sample/change-builds-changes-and-reports-found-errors-message.js @@ -387,6 +387,12 @@ export const m = mod; "size": 2050 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -699,6 +705,12 @@ export class someClass { "size": 2087 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/index.ts @@ -999,6 +1011,12 @@ export function multiply(a, b) { return a * b; } "size": 2050 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/index.ts @@ -1309,6 +1327,12 @@ export class someClass2 { "size": 2125 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/index.ts diff --git a/testdata/baselines/reference/tsbuildWatch/sample/incremental-updates-in-verbose-mode.js b/testdata/baselines/reference/tsbuildWatch/sample/incremental-updates-in-verbose-mode.js index d0cc605b198..7a30f75d5fe 100644 --- a/testdata/baselines/reference/tsbuildWatch/sample/incremental-updates-in-verbose-mode.js +++ b/testdata/baselines/reference/tsbuildWatch/sample/incremental-updates-in-verbose-mode.js @@ -404,6 +404,12 @@ export const m = mod; "size": 2050 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -553,6 +559,12 @@ function someFn() { } } //// [/user/username/projects/sample1/tests/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests logic/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/logic/index.ts @@ -776,6 +788,12 @@ export function someFn() { } "size": 2091 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests logic/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/logic/index.ts diff --git a/testdata/baselines/reference/tsbuildWatch/sample/non-local-change-does-not-start-build-of-referencing-projects-with-circular-references.js b/testdata/baselines/reference/tsbuildWatch/sample/non-local-change-does-not-start-build-of-referencing-projects-with-circular-references.js index f0659ff8ae1..5e98547c541 100644 --- a/testdata/baselines/reference/tsbuildWatch/sample/non-local-change-does-not-start-build-of-referencing-projects-with-circular-references.js +++ b/testdata/baselines/reference/tsbuildWatch/sample/non-local-change-does-not-start-build-of-referencing-projects-with-circular-references.js @@ -382,6 +382,12 @@ export const m = mod; "size": 1972 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -513,6 +519,12 @@ function foo() { } //// [/user/username/projects/sample1/logic/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/sample1/tests/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/index.ts diff --git a/testdata/baselines/reference/tsbuildWatch/sample/non-local-change-does-not-start-build-of-referencing-projects.js b/testdata/baselines/reference/tsbuildWatch/sample/non-local-change-does-not-start-build-of-referencing-projects.js index cdeee568a28..dfc20a258f3 100644 --- a/testdata/baselines/reference/tsbuildWatch/sample/non-local-change-does-not-start-build-of-referencing-projects.js +++ b/testdata/baselines/reference/tsbuildWatch/sample/non-local-change-does-not-start-build-of-referencing-projects.js @@ -387,6 +387,12 @@ export const m = mod; "size": 2050 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -521,6 +527,12 @@ function foo() { } //// [/user/username/projects/sample1/logic/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/sample1/tests/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/index.ts diff --git a/testdata/baselines/reference/tsbuildWatch/sample/reportErrors-when-preserveWatchOutput-is-not-used.js b/testdata/baselines/reference/tsbuildWatch/sample/reportErrors-when-preserveWatchOutput-is-not-used.js index 472a16585f0..2ce1a4975be 100644 --- a/testdata/baselines/reference/tsbuildWatch/sample/reportErrors-when-preserveWatchOutput-is-not-used.js +++ b/testdata/baselines/reference/tsbuildWatch/sample/reportErrors-when-preserveWatchOutput-is-not-used.js @@ -387,6 +387,12 @@ export const m = mod; "size": 2050 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -546,6 +552,12 @@ let y = 10; } //// [/user/username/projects/sample1/tests/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests logic/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/logic/index.ts @@ -685,6 +697,12 @@ let x = 10; //// [/user/username/projects/sample1/logic/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/sample1/tests/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/index.ts @@ -805,6 +823,12 @@ export const m = mod; } //// [/user/username/projects/sample1/tests/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests logic/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/logic/index.ts diff --git a/testdata/baselines/reference/tsbuildWatch/sample/reportErrors-when-preserveWatchOutput-is-passed-on-command-line.js b/testdata/baselines/reference/tsbuildWatch/sample/reportErrors-when-preserveWatchOutput-is-passed-on-command-line.js index 574fd73e5b0..3344a761254 100644 --- a/testdata/baselines/reference/tsbuildWatch/sample/reportErrors-when-preserveWatchOutput-is-passed-on-command-line.js +++ b/testdata/baselines/reference/tsbuildWatch/sample/reportErrors-when-preserveWatchOutput-is-passed-on-command-line.js @@ -387,6 +387,12 @@ export const m = mod; "size": 2050 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -546,6 +552,12 @@ let y = 10; } //// [/user/username/projects/sample1/tests/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests logic/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/logic/index.ts @@ -685,6 +697,12 @@ let x = 10; //// [/user/username/projects/sample1/logic/tsconfig.tsbuildinfo] *mTime changed* //// [/user/username/projects/sample1/tests/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/index.ts @@ -805,6 +823,12 @@ export const m = mod; } //// [/user/username/projects/sample1/tests/tsconfig.tsbuildinfo] *mTime changed* +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests logic/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/logic/index.ts diff --git a/testdata/baselines/reference/tsbuildWatch/sample/reportErrors-when-stopBuildOnErrors-is-passed-on-command-line.js b/testdata/baselines/reference/tsbuildWatch/sample/reportErrors-when-stopBuildOnErrors-is-passed-on-command-line.js index 511d489ca4c..9aa5cf34566 100644 --- a/testdata/baselines/reference/tsbuildWatch/sample/reportErrors-when-stopBuildOnErrors-is-passed-on-command-line.js +++ b/testdata/baselines/reference/tsbuildWatch/sample/reportErrors-when-stopBuildOnErrors-is-passed-on-command-line.js @@ -387,6 +387,12 @@ export const m = mod; "size": 2050 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -545,6 +551,12 @@ let y = 10; "size": 2085 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests logic/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/logic/index.ts @@ -682,6 +694,12 @@ let x = 10; "size": 2024 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/index.ts @@ -724,3 +742,9 @@ Output:: [HH:MM:SS AM] Found 1 error. Watching for file changes. +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests diff --git a/testdata/baselines/reference/tsbuildWatch/sample/should-not-trigger-recompilation-because-of-program-emit-with-outDir-specified.js b/testdata/baselines/reference/tsbuildWatch/sample/should-not-trigger-recompilation-because-of-program-emit-with-outDir-specified.js index a9a06fea4d6..29e1548ecd4 100644 --- a/testdata/baselines/reference/tsbuildWatch/sample/should-not-trigger-recompilation-because-of-program-emit-with-outDir-specified.js +++ b/testdata/baselines/reference/tsbuildWatch/sample/should-not-trigger-recompilation-because-of-program-emit-with-outDir-specified.js @@ -191,6 +191,10 @@ export function multiply(a, b) { return a * b; } "size": 1779 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) core/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -207,6 +211,10 @@ Edit [0]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) Edit [1]:: Add new file @@ -325,6 +333,10 @@ export const y = 10; "size": 1961 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/file3.ts @@ -337,3 +349,7 @@ Edit [2]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) diff --git a/testdata/baselines/reference/tsbuildWatch/sample/should-not-trigger-recompilation-because-of-program-emit.js b/testdata/baselines/reference/tsbuildWatch/sample/should-not-trigger-recompilation-because-of-program-emit.js index c310fe8fad3..210ff58f461 100644 --- a/testdata/baselines/reference/tsbuildWatch/sample/should-not-trigger-recompilation-because-of-program-emit.js +++ b/testdata/baselines/reference/tsbuildWatch/sample/should-not-trigger-recompilation-because-of-program-emit.js @@ -199,6 +199,10 @@ export function multiply(a, b) { return a * b; } "size": 1830 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) core/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -215,6 +219,10 @@ Edit [0]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) Edit [1]:: Add new file @@ -337,6 +345,10 @@ export const y = 10; "size": 2011 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/file3.ts @@ -349,3 +361,7 @@ Edit [2]:: no change Output:: +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) diff --git a/testdata/baselines/reference/tsbuildWatch/sample/skips-builds-downstream-projects-if-upstream-projects-have-errors-with-stopBuildOnErrors-when-test-does-not-reference-core.js b/testdata/baselines/reference/tsbuildWatch/sample/skips-builds-downstream-projects-if-upstream-projects-have-errors-with-stopBuildOnErrors-when-test-does-not-reference-core.js index 41f07286e0a..185618cabcf 100644 --- a/testdata/baselines/reference/tsbuildWatch/sample/skips-builds-downstream-projects-if-upstream-projects-have-errors-with-stopBuildOnErrors-when-test-does-not-reference-core.js +++ b/testdata/baselines/reference/tsbuildWatch/sample/skips-builds-downstream-projects-if-upstream-projects-have-errors-with-stopBuildOnErrors-when-test-does-not-reference-core.js @@ -248,6 +248,12 @@ multiply(); "size": 2145 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -571,6 +577,12 @@ export const m = mod; "size": 2050 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/index.ts diff --git a/testdata/baselines/reference/tsbuildWatch/sample/skips-builds-downstream-projects-if-upstream-projects-have-errors-with-stopBuildOnErrors.js b/testdata/baselines/reference/tsbuildWatch/sample/skips-builds-downstream-projects-if-upstream-projects-have-errors-with-stopBuildOnErrors.js index 6a06c4cb0e7..e9053b92b1f 100644 --- a/testdata/baselines/reference/tsbuildWatch/sample/skips-builds-downstream-projects-if-upstream-projects-have-errors-with-stopBuildOnErrors.js +++ b/testdata/baselines/reference/tsbuildWatch/sample/skips-builds-downstream-projects-if-upstream-projects-have-errors-with-stopBuildOnErrors.js @@ -249,6 +249,12 @@ multiply(); "size": 2145 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -572,6 +578,12 @@ export const m = mod; "size": 2050 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /user/username/projects/sample1/core/index.ts diff --git a/testdata/baselines/reference/tsbuildWatch/sample/watches-config-files-that-are-not-present.js b/testdata/baselines/reference/tsbuildWatch/sample/watches-config-files-that-are-not-present.js index 38a93543421..512fce8dceb 100644 --- a/testdata/baselines/reference/tsbuildWatch/sample/watches-config-files-that-are-not-present.js +++ b/testdata/baselines/reference/tsbuildWatch/sample/watches-config-files-that-are-not-present.js @@ -317,6 +317,12 @@ export const m = mod; "size": 2364 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic + /user/username/projects/sample1/tests core/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts @@ -540,6 +546,12 @@ export const m = mod; "size": 2050 } +Watch Registrations:: +Directory watches:: + /home/src/tslibs/TS/Lib + /user/username/projects/sample1/core (recursive) + /user/username/projects/sample1/logic (recursive) + /user/username/projects/sample1/tests logic/tsconfig.json:: SemanticDiagnostics:: *refresh* /home/src/tslibs/TS/Lib/lib.es2025.full.d.ts From 241df12a5fbeb9268c2adabcbc70f33341bee685 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 17 Jun 2026 08:33:09 -0700 Subject: [PATCH 36/49] Update NOTICE and ensure it's inside packages (#4348) --- Herebyfile.mjs | 4 +++- NOTICE.txt | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Herebyfile.mjs b/Herebyfile.mjs index 93dee2dcc1d..389811acd40 100644 --- a/Herebyfile.mjs +++ b/Herebyfile.mjs @@ -1555,6 +1555,7 @@ async function runBuildNativePreviewPackages() { const inputPackageJson = JSON.parse(fs.readFileSync(path.join(inputDir, "package.json"), "utf8")); inputPackageJson.version = getVersion(); delete inputPackageJson.private; + inputPackageJson.files = [...new Set([...(inputPackageJson.files ?? []), "NOTICE.txt"])]; stripSourceConditions(inputPackageJson); const { stdout: gitHead } = await $pipe`git rev-parse HEAD`; @@ -1575,7 +1576,7 @@ async function runBuildNativePreviewPackages() { await fs.promises.writeFile(path.join(mainPackageDir, "package.json"), JSON.stringify(mainPackage, undefined, 4)); await fs.promises.copyFile("LICENSE", path.join(mainPackageDir, "LICENSE")); - // No NOTICE.txt here; does not ship the binary or libs. If this changes, we should add it. + await fs.promises.copyFile("NOTICE.txt", path.join(mainPackageDir, "NOTICE.txt")); // Build JS API and copy dist into the package. await $`npm run -w @typescript/native-preview build`; @@ -1611,6 +1612,7 @@ async function runBuildNativePreviewPackages() { const packageJson = { ...inputPackageJson, bin: undefined, + files: ["lib", "NOTICE.txt"], imports: undefined, dependencies: undefined, name: npmPackageName, diff --git a/NOTICE.txt b/NOTICE.txt index 550e268da2c..7f19b1c39e6 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -400,3 +400,37 @@ limitations under the License. --------------------------------------------------------- +--------------------------------------------------------- + +parcel-bundler/watcher 8926bb8b281733bbfcaf69bb4e62ab7a1431c42a - MIT + + +Copyright Node.js contributors +Copyright 2012-2020 Facebook, Inc. +Copyright (c) 2017-present Devon Govett + +MIT License + +Copyright (c) 2017-present Devon Govett + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + From 3af952d7586f82da238563d0ffcb9a460402f86f Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 18 Jun 2026 10:33:13 -0700 Subject: [PATCH 37/49] Deduplicate declaration emit for this-assigned methods (#4351) --- internal/checker/checker.go | 20 +- .../transformers/declarations/transform.go | 29 ++- ...tionEmitThisAssignmentDuplicatingMethod.js | 99 ++++++++++ ...mitThisAssignmentDuplicatingMethod.symbols | 149 +++++++++++++++ ...nEmitThisAssignmentDuplicatingMethod.types | 174 ++++++++++++++++++ ...tionEmitThisAssignmentDuplicatingMethod.ts | 63 +++++++ 6 files changed, 528 insertions(+), 6 deletions(-) create mode 100644 testdata/baselines/reference/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.js create mode 100644 testdata/baselines/reference/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.symbols create mode 100644 testdata/baselines/reference/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.types create mode 100644 testdata/tests/cases/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.ts diff --git a/internal/checker/checker.go b/internal/checker/checker.go index ff8eeb13fc8..a8223d2bac9 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -14254,6 +14254,9 @@ func getExcludedSymbolFlags(flags ast.SymbolFlags) ast.SymbolFlags { if flags&ast.SymbolFlagsAlias != 0 { result |= ast.SymbolFlagsAliasExcludes } + if flags&ast.SymbolFlagsReplaceableByMethod != 0 { + result &^= ast.SymbolFlagsMethod + } return result } @@ -15990,15 +15993,30 @@ func (c *Checker) lateBindIndexSignature(parent *ast.Symbol, earlySymbols ast.Sy } } +func isNotReplacableByMethod(decl *ast.Node) bool { + return decl.Symbol().Flags&ast.SymbolFlagsReplaceableByMethod == 0 +} + // Adds a declaration to a late-bound dynamic member. This performs the same function for // late-bound members that `addDeclarationToSymbol` in binder.ts performs for early-bound // members. func (c *Checker) addDeclarationToLateBoundSymbol(symbol *ast.Symbol, member *ast.Node, symbolFlags ast.SymbolFlags) { debug.Assert(symbol.CheckFlags&ast.CheckFlagsLate != 0, "Expected a late-bound symbol.") - symbol.Flags |= symbolFlags c.lateBoundLinks.Get(member.Symbol()).lateSymbol = symbol if len(symbol.Declarations) == 0 || member.Symbol().Flags&ast.SymbolFlagsReplaceableByMethod == 0 { + symbol.Flags |= symbolFlags symbol.Declarations = append(symbol.Declarations, member) + } else if symbol.Flags&ast.SymbolFlagsReplaceableByMethod != 0 && member.Symbol().Flags&ast.SymbolFlagsMethod != 0 { + // Remove all replacable-by-method members, along with their flags. + symbol.Declarations = append(core.Filter(symbol.Declarations, isNotReplacableByMethod), member) + oldFlags := symbol.Flags + symbol.Flags = ast.SymbolFlagsNone + for _, d := range symbol.Declarations { + symbol.Flags |= d.Symbol().Flags + } + if oldFlags&ast.SymbolFlagsAccessor != 0 { + symbol.Flags |= ast.SymbolFlagsAccessor + } } if symbolFlags&ast.SymbolFlagsValue != 0 { binder.SetValueDeclaration(symbol, member) diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go index 2370dedf68d..aa2462654e0 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -43,6 +43,23 @@ type DeclarationEmitHost interface { GetEmitResolver() printer.EmitResolver } +type thisPropertyAssignmentKey struct { + name string + node *ast.Node + isStatic bool + isPrivate bool +} + +func getThisPropertyAssignmentKey(name *ast.Node, node *ast.Node, isStatic bool) thisPropertyAssignmentKey { + isPrivate := ast.IsPrivateIdentifier(name) + if name != nil && !ast.IsDynamicName(name) { + if nameText, ok := ast.TryGetTextOfPropertyName(name); ok { + return thisPropertyAssignmentKey{name: nameText, isStatic: isStatic, isPrivate: isPrivate} + } + } + return thisPropertyAssignmentKey{node: node, isStatic: isStatic, isPrivate: isPrivate} +} + type DeclarationTransformer struct { transformers.Transformer host DeclarationEmitHost @@ -63,7 +80,7 @@ type DeclarationTransformer struct { lateStatementReplacementMap map[ast.NodeId]*ast.Node expandoHosts map[ast.NodeId]*ast.Node // store the result of transforming expando hosts so they can be inserted later if the host is actually referenced expandoMembers map[ast.NodeId][]*ast.Node // store any found expando _members_ after transforming them so *if* the host is referenced, they can be emitted alongside it - seenProperties collections.Set[*ast.Node] + seenProperties collections.Set[thisPropertyAssignmentKey] thisPropertyAssignmentsCollected []*ast.Node rawReferencedFiles []ReferencedFilePair rawTypeReferenceDirectives []*ast.FileReference @@ -1970,10 +1987,11 @@ caseBlock: case ast.JSDeclarationKindThisProperty: name := ast.GetNameOfDeclaration(node) base := tx.resolver.GetReferencedMemberValueDeclaration(node) - if base == nil || tx.seenProperties.Has(base) { + key := getThisPropertyAssignmentKey(name, node, isStatic) + if base == nil || tx.seenProperties.Has(key) { break } - tx.seenProperties.Add(base) + tx.seenProperties.Add(key) // problem: this prop might be overriding a prop from a base type. The checker has special bails for override compat comparisons for binary expression properties, // but what we transform to won't - so we either need to match the base type (for example, if it's a getter/setter) or emit nothing @@ -2044,11 +2062,12 @@ func isClassExtendingNull(node *ast.Node) bool { // of JS classes and synthesizes PropertyDeclaration nodes for each unique property name. func (tx *DeclarationTransformer) collectThisPropertyAssignments(classNode *ast.Node) []*ast.Node { members := classNode.ClassLikeData().Members - seen := collections.Set[*ast.Node]{} + seen := collections.Set[thisPropertyAssignmentKey]{} // Pre-populate seen with existing direct member nodes to avoid duplicates for _, member := range members.Nodes { if member.Name() != nil { - seen.Add(member) + isStatic := ast.IsStatic(member) + seen.Add(getThisPropertyAssignmentKey(member.Name(), member, isStatic)) } } tx.seenProperties = seen diff --git a/testdata/baselines/reference/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.js b/testdata/baselines/reference/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.js new file mode 100644 index 00000000000..824d0822657 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.js @@ -0,0 +1,99 @@ +//// [tests/cases/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.ts] //// + +//// [main.js] +export class A { + constructor() { + this.foo = this.foo.bind(this); + } + foo() {} +} + +export class B { + constructor() { + this.#foo = this.#foo.bind(this); + } + #foo = () => {} +} + +const sym = Symbol(); +export class C { + constructor() { + this[sym] = this[sym].bind(this); + } + [sym]() {} +} + +export class D { + constructor() { + this.bar = 1; + } + static bar() {} +} + +export class E { + static init() { + this.baz = 1; + } + baz() {} +} + +export class F { + static #foo = () => {} + static { + this.#foo = this.#foo.bind(this); + } +} + +const sym2 = Symbol(); +export class G { + static { + this[sym2] = this[sym2].bind(this); + } + static[sym2]() {} +} + +export class H { + static foo = () => {} + static { + this.foo = this.foo.bind(this); + } +} + + + + +//// [main.d.ts] +export declare class A { + constructor(); + foo(): void; +} +export declare class B { + #private; + constructor(); +} +declare const sym: unique symbol; +export declare class C { + constructor(); + [sym](): void; +} +export declare class D { + bar: number; + constructor(); + static bar(): void; +} +export declare class E { + static baz: number | undefined; + static init(): void; + baz(): void; +} +export declare class F { + #private; +} +declare const sym2: unique symbol; +export declare class G { + static [sym2](): void; +} +export declare class H { + static foo: () => void; +} +export {}; diff --git a/testdata/baselines/reference/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.symbols b/testdata/baselines/reference/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.symbols new file mode 100644 index 00000000000..2b23f850ec4 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.symbols @@ -0,0 +1,149 @@ +//// [tests/cases/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.ts] //// + +=== main.js === +export class A { +>A : Symbol(A, Decl(main.js, 0, 0)) + + constructor() { + this.foo = this.foo.bind(this); +>this.foo : Symbol(A.foo, Decl(main.js, 3, 5)) +>this : Symbol(A, Decl(main.js, 0, 0)) +>foo : Symbol(A.foo, Decl(main.js, 3, 5)) +>this.foo.bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>this.foo : Symbol(A.foo, Decl(main.js, 3, 5)) +>this : Symbol(A, Decl(main.js, 0, 0)) +>foo : Symbol(A.foo, Decl(main.js, 3, 5)) +>bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>this : Symbol(A, Decl(main.js, 0, 0)) + } + foo() {} +>foo : Symbol(A.foo, Decl(main.js, 3, 5)) +} + +export class B { +>B : Symbol(B, Decl(main.js, 5, 1)) + + constructor() { + this.#foo = this.#foo.bind(this); +>this.#foo : Symbol(B.#foo, Decl(main.js, 10, 5)) +>this : Symbol(B, Decl(main.js, 5, 1)) +>this.#foo.bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>this.#foo : Symbol(B.#foo, Decl(main.js, 10, 5)) +>this : Symbol(B, Decl(main.js, 5, 1)) +>bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>this : Symbol(B, Decl(main.js, 5, 1)) + } + #foo = () => {} +>#foo : Symbol(B.#foo, Decl(main.js, 10, 5)) +} + +const sym = Symbol(); +>sym : Symbol(sym, Decl(main.js, 14, 5)) +>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --)) + +export class C { +>C : Symbol(C, Decl(main.js, 14, 21)) + + constructor() { + this[sym] = this[sym].bind(this); +>this : Symbol(C, Decl(main.js, 14, 21)) +>sym : Symbol(sym, Decl(main.js, 14, 5)) +>this[sym].bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>this : Symbol(C, Decl(main.js, 14, 21)) +>sym : Symbol(sym, Decl(main.js, 14, 5)) +>bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>this : Symbol(C, Decl(main.js, 14, 21)) + } + [sym]() {} +>[sym] : Symbol(C[sym], Decl(main.js, 18, 5)) +>sym : Symbol(sym, Decl(main.js, 14, 5)) +} + +export class D { +>D : Symbol(D, Decl(main.js, 20, 1)) + + constructor() { + this.bar = 1; +>this.bar : Symbol(D.bar, Decl(main.js, 23, 19)) +>this : Symbol(D, Decl(main.js, 20, 1)) +>bar : Symbol(D.bar, Decl(main.js, 23, 19)) + } + static bar() {} +>bar : Symbol(D.bar, Decl(main.js, 25, 5)) +} + +export class E { +>E : Symbol(E, Decl(main.js, 27, 1)) + + static init() { +>init : Symbol(E.init, Decl(main.js, 29, 16)) + + this.baz = 1; +>this.baz : Symbol(E.baz, Decl(main.js, 30, 19)) +>this : Symbol(E, Decl(main.js, 27, 1)) +>baz : Symbol(E.baz, Decl(main.js, 30, 19)) + } + baz() {} +>baz : Symbol(E.baz, Decl(main.js, 32, 5)) +} + +export class F { +>F : Symbol(F, Decl(main.js, 34, 1)) + + static #foo = () => {} +>#foo : Symbol(F.#foo, Decl(main.js, 36, 16)) + + static { + this.#foo = this.#foo.bind(this); +>this.#foo : Symbol(F.#foo, Decl(main.js, 36, 16)) +>this : Symbol(F, Decl(main.js, 34, 1)) +>this.#foo.bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>this.#foo : Symbol(F.#foo, Decl(main.js, 36, 16)) +>this : Symbol(F, Decl(main.js, 34, 1)) +>bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>this : Symbol(F, Decl(main.js, 34, 1)) + } +} + +const sym2 = Symbol(); +>sym2 : Symbol(sym2, Decl(main.js, 43, 5)) +>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --)) + +export class G { +>G : Symbol(G, Decl(main.js, 43, 22)) + + static { + this[sym2] = this[sym2].bind(this); +>this : Symbol(G, Decl(main.js, 43, 22)) +>sym2 : Symbol(sym2, Decl(main.js, 43, 5)) +>this[sym2].bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>this : Symbol(G, Decl(main.js, 43, 22)) +>sym2 : Symbol(sym2, Decl(main.js, 43, 5)) +>bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>this : Symbol(G, Decl(main.js, 43, 22)) + } + static[sym2]() {} +>[sym2] : Symbol(G[sym2], Decl(main.js, 47, 5)) +>sym2 : Symbol(sym2, Decl(main.js, 43, 5)) +} + +export class H { +>H : Symbol(H, Decl(main.js, 49, 1)) + + static foo = () => {} +>foo : Symbol(H.foo, Decl(main.js, 51, 16)) + + static { + this.foo = this.foo.bind(this); +>this.foo : Symbol(H.foo, Decl(main.js, 51, 16)) +>this : Symbol(H, Decl(main.js, 49, 1)) +>foo : Symbol(H.foo, Decl(main.js, 51, 16)) +>this.foo.bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>this.foo : Symbol(H.foo, Decl(main.js, 51, 16)) +>this : Symbol(H, Decl(main.js, 49, 1)) +>foo : Symbol(H.foo, Decl(main.js, 51, 16)) +>bind : Symbol(CallableFunction.bind, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>this : Symbol(H, Decl(main.js, 49, 1)) + } +} + diff --git a/testdata/baselines/reference/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.types b/testdata/baselines/reference/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.types new file mode 100644 index 00000000000..eec534f8f16 --- /dev/null +++ b/testdata/baselines/reference/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.types @@ -0,0 +1,174 @@ +//// [tests/cases/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.ts] //// + +=== main.js === +export class A { +>A : A + + constructor() { + this.foo = this.foo.bind(this); +>this.foo = this.foo.bind(this) : () => void +>this.foo : () => void +>this : this +>foo : () => void +>this.foo.bind(this) : () => void +>this.foo.bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>this.foo : () => void +>this : this +>foo : () => void +>bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>this : this + } + foo() {} +>foo : () => void +} + +export class B { +>B : B + + constructor() { + this.#foo = this.#foo.bind(this); +>this.#foo = this.#foo.bind(this) : () => void +>this.#foo : () => void +>this : this +>this.#foo.bind(this) : () => void +>this.#foo.bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>this.#foo : () => void +>this : this +>bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>this : this + } + #foo = () => {} +>#foo : () => void +>() => {} : () => void +} + +const sym = Symbol(); +>sym : unique symbol +>Symbol() : unique symbol +>Symbol : SymbolConstructor + +export class C { +>C : C + + constructor() { + this[sym] = this[sym].bind(this); +>this[sym] = this[sym].bind(this) : () => void +>this[sym] : () => void +>this : this +>sym : unique symbol +>this[sym].bind(this) : () => void +>this[sym].bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>this[sym] : () => void +>this : this +>sym : unique symbol +>bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>this : this + } + [sym]() {} +>[sym] : () => void +>sym : unique symbol +} + +export class D { +>D : D + + constructor() { + this.bar = 1; +>this.bar = 1 : 1 +>this.bar : any +>this : this +>bar : any +>1 : 1 + } + static bar() {} +>bar : () => void +} + +export class E { +>E : E + + static init() { +>init : () => void + + this.baz = 1; +>this.baz = 1 : 1 +>this.baz : number | undefined +>this : typeof E +>baz : number | undefined +>1 : 1 + } + baz() {} +>baz : () => void +} + +export class F { +>F : F + + static #foo = () => {} +>#foo : () => void +>() => {} : () => void + + static { + this.#foo = this.#foo.bind(this); +>this.#foo = this.#foo.bind(this) : () => void +>this.#foo : () => void +>this : typeof F +>this.#foo.bind(this) : () => void +>this.#foo.bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>this.#foo : () => void +>this : typeof F +>bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>this : typeof F + } +} + +const sym2 = Symbol(); +>sym2 : unique symbol +>Symbol() : unique symbol +>Symbol : SymbolConstructor + +export class G { +>G : G + + static { + this[sym2] = this[sym2].bind(this); +>this[sym2] = this[sym2].bind(this) : () => void +>this[sym2] : () => void +>this : typeof G +>sym2 : unique symbol +>this[sym2].bind(this) : () => void +>this[sym2].bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>this[sym2] : () => void +>this : typeof G +>sym2 : unique symbol +>bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>this : typeof G + } + static[sym2]() {} +>[sym2] : () => void +>sym2 : unique symbol +} + +export class H { +>H : H + + static foo = () => {} +>foo : () => void +>() => {} : () => void + + static { + this.foo = this.foo.bind(this); +>this.foo = this.foo.bind(this) : () => void +>this.foo : () => void +>this : typeof H +>foo : () => void +>this.foo.bind(this) : () => void +>this.foo.bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>this.foo : () => void +>this : typeof H +>foo : () => void +>bind : { (this: T, thisArg: ThisParameterType): OmitThisParameter; (this: (this: T, ...args: [...A, ...B]) => R, thisArg: T, ...args: A): (...args: B) => R; } +>this : typeof H + } +} + diff --git a/testdata/tests/cases/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.ts b/testdata/tests/cases/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.ts new file mode 100644 index 00000000000..3e4392fc936 --- /dev/null +++ b/testdata/tests/cases/compiler/jsDeclarationEmitThisAssignmentDuplicatingMethod.ts @@ -0,0 +1,63 @@ +// @allowJs: true +// @checkJs: true +// @declaration: true +// @emitDeclarationOnly: true +// @target: esnext +// @filename: main.js +export class A { + constructor() { + this.foo = this.foo.bind(this); + } + foo() {} +} + +export class B { + constructor() { + this.#foo = this.#foo.bind(this); + } + #foo = () => {} +} + +const sym = Symbol(); +export class C { + constructor() { + this[sym] = this[sym].bind(this); + } + [sym]() {} +} + +export class D { + constructor() { + this.bar = 1; + } + static bar() {} +} + +export class E { + static init() { + this.baz = 1; + } + baz() {} +} + +export class F { + static #foo = () => {} + static { + this.#foo = this.#foo.bind(this); + } +} + +const sym2 = Symbol(); +export class G { + static { + this[sym2] = this[sym2].bind(this); + } + static[sym2]() {} +} + +export class H { + static foo = () => {} + static { + this.foo = this.foo.bind(this); + } +} From e578159b7ae473127056a65748d7b3a4daa9a93f Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 18 Jun 2026 12:31:24 -0700 Subject: [PATCH 38/49] Re-allow nested cjs export assignments in declaration emit (#4356) --- .../src/enums/outerExpressionKinds.enum.ts | 4 + .../src/enums/outerExpressionKinds.ts | 4 + internal/ast/ast.go | 1 - internal/ast/utilities.go | 37 +- internal/binder/binder.go | 10 - internal/checker/emitresolver.go | 18 +- internal/printer/emitresolver.go | 2 +- .../transformers/declarations/transform.go | 318 ++++++++++++------ .../transformers/tstransforms/typeeraser.go | 2 +- .../declarationEmitExpandoFunction.js | 6 +- ...arationEmitNestedExpandoPropsRegression.js | 7 +- .../compiler/exportAssignmentMerging5.js | 10 +- ...ationsRequireImportForms(target=es2015).js | 4 +- ...ultipleModuleExportsAssignments.errors.txt | 8 +- .../typedefModuleExportsIndirect1.js | 2 +- .../typedefModuleExportsIndirect2.js | 2 +- .../typedefModuleExportsIndirect3.js | 2 +- .../amdLikeInputDeclarationEmit.errors.txt | 34 -- .../compiler/amdLikeInputDeclarationEmit.js | 39 +++ ...onEmitDefaultExportWithStaticAssignment.js | 14 +- ...rationEmitExportAliasVisibiilityMarking.js | 4 +- ...nEmitExportAliasVisibiilityMarking.js.diff | 16 + .../expandoFunctionNestedAssigments.js | 47 +-- .../expandoFunctionNestedAssigments.js.diff | 58 ++++ .../jsDeclarationEmitExportAssignedArray.js | 4 +- ...ssignedFunctionWithExtraTypedefsMembers.js | 10 +- .../jsExportAssignmentNonMutableLocation.js | 8 +- ...xDeclarationsWithEsModuleInteropNoCrash.js | 14 +- .../submodule/compiler/modulePreserve4.js | 2 +- .../assignmentToVoidZero1.errors.txt | 11 - .../conformance/assignmentToVoidZero1.js | 5 + .../conformance/commonJSAliasedExport.js | 4 +- .../commonJSImportClassTypeReference.js | 2 +- ...nsClassExtendsVisibility(target=es2015).js | 6 +- ...sDeclarationsClassStatic(target=es2015).js | 4 +- .../jsDeclarationsCommonjsRelativePath.js | 12 +- ...eclarationsComputedNames(target=es2015).js | 8 +- ...clarationsCrossfileMerge(target=es2015).js | 8 +- ...tionsDocCommentsOnConsts(target=es2015).js | 10 +- ...ClassExpressionAnonymous(target=es2015).js | 4 +- ...pressionAnonymousWithSub(target=es2015).js | 12 +- ...ClassExpressionShadowing(target=es2015).js | 12 +- ...rtAssignedClassInstance1(target=es2015).js | 4 +- ...rtAssignedClassInstance2(target=es2015).js | 4 +- ...rtAssignedClassInstance3(target=es2015).js | 8 +- ...nstructorFunctionWithSub(target=es2015).js | 8 +- ...ctorFunctionWithSub(target=es2015).js.diff | 8 +- ...ExportAssignedVisibility(target=es2015).js | 2 +- ...tExpressionPlusSecondary(target=es2015).js | 16 +- ...ssignmentWithKeywordName(target=es2015).js | 4 +- .../jsDeclarationsExportDefinePropertyEmit.js | 26 +- ...ExportDoubleAssignmentInClosure.errors.txt | 22 -- ...tDoubleAssignmentInClosure.errors.txt.diff | 26 -- ...arationsExportDoubleAssignmentInClosure.js | 8 + ...tionsExportDoubleAssignmentInClosure.types | 16 +- ...ExportDoubleAssignmentInClosure.types.diff | 30 +- ...sDeclarationsExportForms(target=es2015).js | 12 +- ...ionsExportSubAssignments(target=es2015).js | 4 +- .../jsDeclarationsExportedClassAliases.js | 12 +- ...assesCjsExportAssignment(target=es2015).js | 6 +- ...ionKeywordPropExhaustive(target=es2015).js | 3 +- ...ationsFunctionWithDefaultAssignedMember.js | 7 +- .../conformance/jsDeclarationsFunctions.js | 2 +- .../conformance/jsDeclarationsFunctionsCjs.js | 12 +- ...onsImportAliasExposedWithinNamespaceCjs.js | 12 +- .../jsDeclarationsJson(target=es2015).js | 6 +- ...sDeclarationsPackageJson(target=es2015).js | 6 +- ...tionsParameterTagReusesInputNodeInEmit1.js | 4 +- ...tionsParameterTagReusesInputNodeInEmit2.js | 34 +- ...ationsReexportedCjsAlias(target=es2015).js | 14 +- ...ationsReferenceToClassInstanceCrossFile.js | 16 +- .../conformance/jsDeclarationsTypeAliases.js | 10 +- ...arationsTypeReassignmentFromDeclaration.js | 2 +- ...clarationsTypeReferences(target=es2015).js | 4 +- ...larationsTypeReferences2(target=es2015).js | 4 +- .../jsDeclarationsTypedefAndImportTypes.js | 10 +- ...tionsTypedefPropertyAndExportAssignment.js | 18 +- ...oduleExportAliasElementAccessExpression.js | 2 +- .../nestedDestructuringOfRequire.js | 2 +- ...sAllowJsExportAssignment(module=node16).js | 2 +- ...sAllowJsExportAssignment(module=node18).js | 2 +- ...sAllowJsExportAssignment(module=node20).js | 2 +- ...llowJsExportAssignment(module=nodenext).js | 2 +- .../submodule/conformance/nullPropertyName.js | 3 +- ...mdLikeInputDeclarationEmit.errors.txt.diff | 38 --- .../amdLikeInputDeclarationEmit.js.diff | 48 +-- ...tDefaultExportWithStaticAssignment.js.diff | 23 +- ...DeclarationEmitExportAssignedArray.js.diff | 14 - ...edFunctionWithExtraTypedefsMembers.js.diff | 10 +- ...ExportAssignmentNonMutableLocation.js.diff | 8 +- ...arationsWithEsModuleInteropNoCrash.js.diff | 21 +- .../compiler/modulePreserve4.js.diff | 10 +- .../assignmentToVoidZero1.errors.txt.diff | 17 +- .../conformance/assignmentToVoidZero1.js.diff | 15 +- .../conformance/commonJSAliasedExport.js.diff | 8 +- .../commonJSImportClassTypeReference.js.diff | 5 +- ...ssExtendsVisibility(target=es2015).js.diff | 15 +- ...arationsClassStatic(target=es2015).js.diff | 8 +- ...jsDeclarationsCommonjsRelativePath.js.diff | 12 +- ...ationsComputedNames(target=es2015).js.diff | 22 +- ...tionsCrossfileMerge(target=es2015).js.diff | 24 +- ...DocCommentsOnConsts(target=es2015).js.diff | 19 +- ...ExpressionAnonymous(target=es2015).js.diff | 4 +- ...ionAnonymousWithSub(target=es2015).js.diff | 13 +- ...ExpressionShadowing(target=es2015).js.diff | 19 +- ...ignedClassInstance1(target=es2015).js.diff | 12 - ...ignedClassInstance2(target=es2015).js.diff | 14 - ...ignedClassInstance3(target=es2015).js.diff | 10 +- ...tAssignedVisibility(target=es2015).js.diff | 5 +- ...essionPlusSecondary(target=es2015).js.diff | 16 +- ...mentWithKeywordName(target=es2015).js.diff | 4 +- ...clarationsExportDefinePropertyEmit.js.diff | 28 +- ...onsExportDoubleAssignmentInClosure.js.diff | 19 +- ...arationsExportForms(target=es2015).js.diff | 17 +- ...xportSubAssignments(target=es2015).js.diff | 12 +- ...jsDeclarationsExportedClassAliases.js.diff | 12 +- ...CjsExportAssignment(target=es2015).js.diff | 19 +- ...ywordPropExhaustive(target=es2015).js.diff | 3 +- ...sFunctionWithDefaultAssignedMember.js.diff | 16 - .../jsDeclarationsFunctions.js.diff | 11 +- .../jsDeclarationsFunctionsCjs.js.diff | 17 +- ...portAliasExposedWithinNamespaceCjs.js.diff | 29 +- .../jsDeclarationsJson(target=es2015).js.diff | 9 +- ...arationsPackageJson(target=es2015).js.diff | 9 +- ...ParameterTagReusesInputNodeInEmit1.js.diff | 14 +- ...ParameterTagReusesInputNodeInEmit2.js.diff | 44 +-- ...sReexportedCjsAlias(target=es2015).js.diff | 19 +- ...sReferenceToClassInstanceCrossFile.js.diff | 18 +- .../jsDeclarationsTypeAliases.js.diff | 13 +- ...onsTypeReassignmentFromDeclaration.js.diff | 10 - ...tionsTypeReferences(target=es2015).js.diff | 4 +- ...ionsTypeReferences2(target=es2015).js.diff | 4 +- ...sDeclarationsTypedefAndImportTypes.js.diff | 12 +- ...TypedefPropertyAndExportAssignment.js.diff | 36 +- ...ExportAliasElementAccessExpression.js.diff | 4 +- .../nestedDestructuringOfRequire.js.diff | 2 +- ...wJsExportAssignment(module=node16).js.diff | 3 +- ...wJsExportAssignment(module=node18).js.diff | 3 +- ...wJsExportAssignment(module=node20).js.diff | 3 +- ...sExportAssignment(module=nodenext).js.diff | 3 +- .../conformance/nullPropertyName.js.diff | 11 +- ...based-projects-and-emits-them-correctly.js | 24 +- testdata/submoduleAccepted.txt | 6 - 143 files changed, 973 insertions(+), 1055 deletions(-) delete mode 100644 testdata/baselines/reference/submodule/compiler/amdLikeInputDeclarationEmit.errors.txt create mode 100644 testdata/baselines/reference/submodule/compiler/amdLikeInputDeclarationEmit.js create mode 100644 testdata/baselines/reference/submodule/compiler/declarationEmitExportAliasVisibiilityMarking.js.diff create mode 100644 testdata/baselines/reference/submodule/compiler/expandoFunctionNestedAssigments.js.diff delete mode 100644 testdata/baselines/reference/submodule/conformance/assignmentToVoidZero1.errors.txt delete mode 100644 testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.errors.txt delete mode 100644 testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.errors.txt.diff delete mode 100644 testdata/baselines/reference/submoduleAccepted/compiler/amdLikeInputDeclarationEmit.errors.txt.diff delete mode 100644 testdata/baselines/reference/submoduleAccepted/compiler/jsDeclarationEmitExportAssignedArray.js.diff delete mode 100644 testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassInstance1(target=es2015).js.diff delete mode 100644 testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassInstance2(target=es2015).js.diff delete mode 100644 testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionWithDefaultAssignedMember.js.diff delete mode 100644 testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeReassignmentFromDeclaration.js.diff diff --git a/_packages/native-preview/src/enums/outerExpressionKinds.enum.ts b/_packages/native-preview/src/enums/outerExpressionKinds.enum.ts index b451813e98f..d9d67bf7acd 100644 --- a/_packages/native-preview/src/enums/outerExpressionKinds.enum.ts +++ b/_packages/native-preview/src/enums/outerExpressionKinds.enum.ts @@ -8,6 +8,10 @@ export enum OuterExpressionKinds { ExpressionsWithTypeArguments = 1 << 4, Satisfies = 1 << 5, ExcludeJSDocTypeAssertion = 1 << 6, + Assignments = 1 << 7, + Comma = 1 << 8, Assertions = TypeAssertions | NonNullAssertions | Satisfies, All = Parentheses | Assertions | PartiallyEmittedExpressions | ExpressionsWithTypeArguments, + AllExceptAssertionsOrExpressionsWithTypeArguments = All & ~Assertions & ~ExpressionsWithTypeArguments, + ExpressionTypePassthrough = Parentheses | Assignments | Comma, } diff --git a/_packages/native-preview/src/enums/outerExpressionKinds.ts b/_packages/native-preview/src/enums/outerExpressionKinds.ts index 960bca7cd08..47dcf8504b9 100644 --- a/_packages/native-preview/src/enums/outerExpressionKinds.ts +++ b/_packages/native-preview/src/enums/outerExpressionKinds.ts @@ -8,6 +8,10 @@ export var OuterExpressionKinds: any; OuterExpressionKinds[OuterExpressionKinds["ExpressionsWithTypeArguments"] = 16] = "ExpressionsWithTypeArguments"; OuterExpressionKinds[OuterExpressionKinds["Satisfies"] = 32] = "Satisfies"; OuterExpressionKinds[OuterExpressionKinds["ExcludeJSDocTypeAssertion"] = 64] = "ExcludeJSDocTypeAssertion"; + OuterExpressionKinds[OuterExpressionKinds["Assignments"] = 128] = "Assignments"; + OuterExpressionKinds[OuterExpressionKinds["Comma"] = 256] = "Comma"; OuterExpressionKinds[OuterExpressionKinds["Assertions"] = 38] = "Assertions"; OuterExpressionKinds[OuterExpressionKinds["All"] = 63] = "All"; + OuterExpressionKinds[OuterExpressionKinds["AllExceptAssertionsOrExpressionsWithTypeArguments"] = 9] = "AllExceptAssertionsOrExpressionsWithTypeArguments"; + OuterExpressionKinds[OuterExpressionKinds["ExpressionTypePassthrough"] = 385] = "ExpressionTypePassthrough"; })(OuterExpressionKinds || (OuterExpressionKinds = {})); diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 758ad9ee62b..f4fd6dcde8a 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -2522,7 +2522,6 @@ type SourceFile struct { SymbolCount int ClassifiableNames collections.Set[string] PatternAmbientModules []*PatternAmbientModule - NestedCJSExports []*Node GlobalExports SymbolTable // Fields set by ECMALineMap diff --git a/internal/ast/utilities.go b/internal/ast/utilities.go index 8554c14276a..c25190a4ad3 100644 --- a/internal/ast/utilities.go +++ b/internal/ast/utilities.go @@ -769,18 +769,22 @@ func IsPrologueDirective(node *Node) bool { node.Expression().Kind == KindStringLiteral } -type OuterExpressionKinds int16 +type OuterExpressionKinds uint16 const ( - OEKParentheses OuterExpressionKinds = 1 << 0 - OEKTypeAssertions OuterExpressionKinds = 1 << 1 - OEKNonNullAssertions OuterExpressionKinds = 1 << 2 - OEKPartiallyEmittedExpressions OuterExpressionKinds = 1 << 3 - OEKExpressionsWithTypeArguments OuterExpressionKinds = 1 << 4 - OEKSatisfies OuterExpressionKinds = 1 << 5 - OEKExcludeJSDocTypeAssertion = 1 << 6 - OEKAssertions = OEKTypeAssertions | OEKNonNullAssertions | OEKSatisfies - OEKAll = OEKParentheses | OEKAssertions | OEKPartiallyEmittedExpressions | OEKExpressionsWithTypeArguments + OEKParentheses OuterExpressionKinds = 1 << 0 + OEKTypeAssertions OuterExpressionKinds = 1 << 1 + OEKNonNullAssertions OuterExpressionKinds = 1 << 2 + OEKPartiallyEmittedExpressions OuterExpressionKinds = 1 << 3 + OEKExpressionsWithTypeArguments OuterExpressionKinds = 1 << 4 + OEKSatisfies OuterExpressionKinds = 1 << 5 + OEKExcludeJSDocTypeAssertion OuterExpressionKinds = 1 << 6 + OEKAssignments OuterExpressionKinds = 1 << 7 + OEKComma OuterExpressionKinds = 1 << 8 + OEKAssertions = OEKTypeAssertions | OEKNonNullAssertions | OEKSatisfies + OEKAll = OEKParentheses | OEKAssertions | OEKPartiallyEmittedExpressions | OEKExpressionsWithTypeArguments + OEKAllExceptAssertionsOrExpressionsWithTypeArguments = OEKAll &^ OEKAssertions &^ OEKExpressionsWithTypeArguments + OEKExpressionTypePassthrough = OEKParentheses | OEKAssignments | OEKComma ) // Determines whether node is an "outer expression" of the provided kinds @@ -798,6 +802,13 @@ func IsOuterExpression(node *Expression, kinds OuterExpressionKinds) bool { return kinds&OEKNonNullAssertions != 0 case KindPartiallyEmittedExpression: return kinds&OEKPartiallyEmittedExpressions != 0 + case KindBinaryExpression: + switch node.AsBinaryExpression().OperatorToken.Kind { + case KindEqualsToken: + return kinds&OEKAssignments != 0 + case KindCommaToken: + return kinds&OEKComma != 0 + } } return false } @@ -805,7 +816,11 @@ func IsOuterExpression(node *Expression, kinds OuterExpressionKinds) bool { // Descends into an expression, skipping past "outer expressions" of the provided kinds func SkipOuterExpressions(node *Expression, kinds OuterExpressionKinds) *Expression { for IsOuterExpression(node, kinds) { - node = node.Expression() + if IsBinaryExpression(node) { + node = node.AsBinaryExpression().Right + } else { + node = node.Expression() + } } return node } diff --git a/internal/binder/binder.go b/internal/binder/binder.go index 28de0e95104..c029ef3b23b 100644 --- a/internal/binder/binder.go +++ b/internal/binder/binder.go @@ -80,7 +80,6 @@ type Binder struct { flowListArena core.Arena[ast.FlowList] singleDeclarationsArena core.Arena[*ast.Node] expandoAssignments []ExpandoAssignmentInfo - nestedCJSExports []*ast.Node } type ActiveLabel struct { @@ -129,7 +128,6 @@ func bindSourceFile(file *ast.SourceFile) { b.bindDeferredExpandoAssignments() file.SymbolCount = b.symbolCount file.ClassifiableNames = b.classifiableNames - file.NestedCJSExports = b.nestedCJSExports }) } @@ -876,12 +874,6 @@ func (b *Binder) bindExportAssignment(node *ast.Node) { } } -func (b *Binder) trackNestedCJSExport(node *ast.Node) { - if !(ast.IsSourceFile(node.Parent) || ast.IsExpressionStatement(node.Parent) && ast.IsSourceFile(node.Parent.Parent)) { - b.nestedCJSExports = append(b.nestedCJSExports, node) - } -} - func (b *Binder) bindJsxAttributes(node *ast.Node) { b.bindAnonymousDeclaration(node, ast.SymbolFlagsObjectLiteral, ast.InternalSymbolNameJSXAttributes) } @@ -1023,7 +1015,6 @@ func (b *Binder) addLateBoundAssignmentDeclarationToSymbol(node *ast.Node, symbo func (b *Binder) bindModuleExportsAssignment(node *ast.Node) { if b.setCommonJSModuleIndicator(node) { - b.trackNestedCJSExport(node) container := b.file.AsNode() flags := core.IfElse(ast.ExpressionIsAlias(node.AsBinaryExpression().Right), ast.SymbolFlagsAlias, ast.SymbolFlagsProperty) symbol := b.declareSymbol(ast.GetExports(container.Symbol()), container.Symbol(), node, flags, 0) @@ -1094,7 +1085,6 @@ func getParentOfPropertyAssignment(node *ast.Node) *ast.Node { func (b *Binder) bindExportsOrObjectDefineProperty(node *ast.Node) { if b.setCommonJSModuleIndicator(node) { - b.trackNestedCJSExport(node) container := b.file.AsNode() flags := core.IfElse(ast.IsBinaryExpression(node) && ast.ExpressionIsAlias(node.AsBinaryExpression().Right), ast.SymbolFlagsAlias, ast.SymbolFlagsFunctionScopedVariable) b.declareSymbol(ast.GetExports(container.Symbol()), container.Symbol(), node, flags, ast.SymbolFlagsFunctionScopedVariableExcludes) diff --git a/internal/checker/emitresolver.go b/internal/checker/emitresolver.go index 572ac70dc2c..7bf3719e58f 100644 --- a/internal/checker/emitresolver.go +++ b/internal/checker/emitresolver.go @@ -313,7 +313,8 @@ func getMeaningOfEntityNameReference(entityName *ast.Node) ast.SymbolFlags { if entityName.Parent.Kind == ast.KindTypeQuery || entityName.Parent.Kind == ast.KindExpressionWithTypeArguments && !ast.IsPartOfTypeNode(entityName.Parent) || entityName.Parent.Kind == ast.KindComputedPropertyName || - entityName.Parent.Kind == ast.KindTypePredicate && entityName.Parent.AsTypePredicateNode().ParameterName == entityName { + entityName.Parent.Kind == ast.KindTypePredicate && entityName.Parent.AsTypePredicateNode().ParameterName == entityName || + entityName.Parent.Kind == ast.KindBinaryExpression { // Typeof value return ast.SymbolFlagsValue | ast.SymbolFlagsExportValue } @@ -896,22 +897,13 @@ func (r *EmitResolver) GetReferencedValueDeclarations(node *ast.IdentifierNode) return r.getReferenceResolver().GetReferencedValueDeclarations(node) } -// IsNameResolvedToDeclaration checks whether `name` resolved at `location` -// resolves to a symbol that includes `declaration` among its declarations. -// When `declaration` is nil, checks whether the name resolves to any symbol. -// This is used by the declaration emitter to check for naming conflicts at file scope. -func (r *EmitResolver) IsNameResolvedToDeclaration(location *ast.Node, name string, declaration *ast.Node) bool { +// IsNameResolvable returns `true` if the given `name` resolves to any symbol at `location` +func (r *EmitResolver) IsNameResolvable(location *ast.Node, name string) bool { r.checkerMu.Lock() defer r.checkerMu.Unlock() symbol := r.checker.resolveName(location, name, ast.SymbolFlagsValue|ast.SymbolFlagsType|ast.SymbolFlagsNamespace, nil /*nameNotFoundMessage*/, false /*isUse*/, false /*excludeGlobals*/) - if symbol == nil { - return false - } - if declaration == nil { - return true - } - return slices.Contains(symbol.Declarations, declaration) + return symbol != nil } func (r *EmitResolver) GetElementAccessExpressionName(expression *ast.ElementAccessExpression) string { diff --git a/internal/printer/emitresolver.go b/internal/printer/emitresolver.go index 60c2aead91c..189fe88cce8 100644 --- a/internal/printer/emitresolver.go +++ b/internal/printer/emitresolver.go @@ -104,7 +104,7 @@ type EmitResolver interface { IsLiteralConstDeclaration(node *ast.Node) bool RequiresAddingImplicitUndefined(node *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node) bool IsDeclarationVisible(node *ast.Node) bool - IsNameResolvedToDeclaration(location *ast.Node, name string, declaration *ast.Node) bool + IsNameResolvable(location *ast.Node, name string) bool IsImportRequiredByAugmentation(decl *ast.ImportDeclaration) bool IsDefinitelyReferenceToGlobalSymbolObject(node *ast.Node) bool IsImplementationOfOverload(node *ast.SignatureDeclaration) bool diff --git a/internal/transformers/declarations/transform.go b/internal/transformers/declarations/transform.go index aa2462654e0..b6596e0d77b 100644 --- a/internal/transformers/declarations/transform.go +++ b/internal/transformers/declarations/transform.go @@ -87,10 +87,15 @@ type DeclarationTransformer struct { rawLibReferenceDirectives []*ast.FileReference bindingNameVisitor *ast.NodeVisitor expressionVisitor *ast.NodeVisitor + cjsExportAssignmentVisitor *ast.NodeVisitor exportStrippingVisitor *ast.NodeVisitor thisPropertyVisitor *ast.NodeVisitor - cjsExportAssignmentName *ast.Node // tracks the name node used for `export =` in CJS module.exports assignments - inClassExpressionDeclaration bool // true when serializing members of a class expression kept as a class declaration + + cjsExportAssignment *ast.Node + cjsExportMembers []*ast.Node + cjsExportAssignmentName *ast.Node // tracks the name node used for `export =` in CJS module.exports assignments + declareStrippingVisitor *ast.NodeVisitor + inClassExpressionDeclaration bool // true when serializing members of a class expression kept as a class declaration } // TODO: Convert to transformers.TransformerFactory signature to allow more automatic composition with other transforms @@ -128,6 +133,8 @@ func NewDeclarationTransformer(host DeclarationEmitHost, context *printer.EmitCo tx.expressionVisitor = tx.EmitContext().NewNodeVisitor(tx.visitNestedExpression) tx.exportStrippingVisitor = tx.EmitContext().NewNodeVisitor(tx.stripExportModifiers) tx.thisPropertyVisitor = tx.EmitContext().NewNodeVisitor(tx.visitThisPropertyAssignments) + tx.cjsExportAssignmentVisitor = tx.EmitContext().NewNodeVisitor(tx.visitCJSExportAssignments) + tx.declareStrippingVisitor = tx.EmitContext().NewNodeVisitor(tx.stripDeclareModifiers) return tx } @@ -256,10 +263,9 @@ func (tx *DeclarationTransformer) visit(node *ast.Node) *ast.Node { ast.KindWithStatement, ast.KindNotEmittedStatement, ast.KindBlock, - ast.KindMissingDeclaration: + ast.KindMissingDeclaration, + ast.KindExpressionStatement: return nil - case ast.KindExpressionStatement: - return tx.visitExpressionStatement(node) // parts of things, things we just visit children of default: return tx.visitDeclarationSubtree(node) @@ -294,57 +300,57 @@ func (tx *DeclarationTransformer) visitSourceFile(node *ast.SourceFile) *ast.Nod tx.state.currentSourceFile = node tx.collectFileReferences(node) tx.resolver.PrecalculateDeclarationEmitVisibility(node) - tx.prescanCJSExportAssignment(node) updated := tx.transformSourceFile(node) tx.state.currentSourceFile = nil return updated } -// prescanCJSExportAssignment scans the source file for a `module.exports = class X {}` -// statement and pre-creates the cjsExportAssignmentName so that all CJS property exports -// (both before and after the assignment in source order) can reference it. -func (tx *DeclarationTransformer) prescanCJSExportAssignment(node *ast.SourceFile) { - if !ast.IsExternalOrCommonJSModule(node) || node.CommonJSModuleIndicator == nil || !ast.IsInJSFile(node.AsNode()) { - return - } - for _, stmt := range node.Statements.Nodes { - if stmt.Kind != ast.KindExpressionStatement { - continue - } - expr := stmt.Expression() - if expr == nil || ast.GetAssignmentDeclarationKind(expr) != ast.JSDeclarationKindModuleExports { - continue - } - rhs := unwrapParenthesizedExpression(expr.AsBinaryExpression().Right) - if !ast.IsClassExpression(rhs) { - continue - } - ce := rhs.AsClassExpression() - if ce.Name() != nil && len(ce.Name().Text()) > 0 { - nameText := ce.Name().Text() - if tx.resolver.IsNameResolvedToDeclaration(node.AsNode(), nameText, nil) { - tx.cjsExportAssignmentName = tx.Factory().NewUniqueNameEx(nameText, printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) - } else { - tx.cjsExportAssignmentName = tx.Factory().NewIdentifier(nameText) - } - } else { - tx.cjsExportAssignmentName = tx.Factory().NewUniqueNameEx("_default", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) - } - return - } -} - func (tx *DeclarationTransformer) collectFileReferences(sourceFile *ast.SourceFile) { tx.rawReferencedFiles = append(tx.rawReferencedFiles, core.Map(sourceFile.ReferencedFiles, func(ref *ast.FileReference) ReferencedFilePair { return ReferencedFilePair{file: sourceFile, ref: ref} })...) tx.rawTypeReferenceDirectives = append(tx.rawTypeReferenceDirectives, sourceFile.TypeReferenceDirectives...) tx.rawLibReferenceDirectives = append(tx.rawLibReferenceDirectives, sourceFile.LibReferenceDirectives...) } +func nodeOrSyntaxListChildren(node *ast.Node) []*ast.Node { + if ast.IsSyntaxList(node) { + return node.AsSyntaxList().Children + } + return []*ast.Node{node} +} + +func flattenSyntaxLists(nodes []*ast.Node) []*ast.Node { + return core.FlatMap(nodes, nodeOrSyntaxListChildren) +} + +func (tx *DeclarationTransformer) appendCjsExports(combinedStatements *ast.StatementList) *ast.StatementList { + result := []*ast.Node{} + if tx.cjsExportAssignment != nil { + result = append(result, tx.cjsExportAssignment) + } + result = append(result, tx.cjsExportMembers...) + result = append(result, combinedStatements.Nodes...) + statementNodes := flattenSyntaxLists(result) + if len(statementNodes) != len(combinedStatements.Nodes) { + combinedStatements = tx.Factory().NewNodeList(statementNodes) + } + return combinedStatements +} + func (tx *DeclarationTransformer) transformSourceFile(node *ast.SourceFile) *ast.Node { - tx.expressionVisitor.VisitNode(node.AsNode()) // collect expando members + tx.cjsExportAssignment = nil + tx.cjsExportAssignmentName = nil + tx.cjsExportMembers = nil + defer func() { + tx.cjsExportAssignment = nil + tx.cjsExportAssignmentName = nil + tx.cjsExportMembers = nil + }() + tx.cjsExportAssignmentVisitor.VisitNode(node.AsNode()) // collect nested module.exports= assignments + tx.expressionVisitor.VisitNode(node.AsNode()) // collect expando members (requires any export assignment be located in advance) var combinedStatements *ast.StatementList statements := tx.Visitor().VisitNodes(node.Statements) combinedStatements = tx.transformAndReplaceLatePaintedStatements(statements) + combinedStatements = tx.appendCjsExports(combinedStatements) combinedStatements.Loc = statements.Loc // setTextRange if ast.IsExternalOrCommonJSModule(node) { if ast.IsInJSFile(node.AsNode()) { @@ -353,9 +359,6 @@ func (tx *DeclarationTransformer) transformSourceFile(node *ast.SourceFile) *ast tx.state.addDiagnostic(createDiagnosticForNode(node, diagnostics.Multiple_module_exports_assignments_cannot_be_serialized_for_declaration_emit)) } } - for _, node := range node.NestedCJSExports { - tx.state.addDiagnostic(createDiagnosticForNode(node, diagnostics.Nested_CommonJS_export_constructs_cannot_be_serialized_for_declaration_emit)) - } } if !tx.resultHasExternalModuleIndicator || (tx.needsScopeFixMarker && !tx.resultHasScopeMarker) { marker := createEmptyExports(tx.Factory().AsNodeFactory()) @@ -1151,55 +1154,88 @@ func (tx *DeclarationTransformer) visitDeclarationStatements(input *ast.Node) *a } } +func (tx *DeclarationTransformer) tryGetNameOfAssignedExpression(unwrapped *ast.Node) *ast.Node { + var nameNode *ast.Node + var nameText string + if !ast.IsPropertyAccessExpression(unwrapped) && unwrapped.Name() != nil { + nameText = unwrapped.Name().Text() + } else if ast.IsIdentifier(unwrapped) { + nameText = unwrapped.Text() + } + if nameText != "" && nameText != "default" { + if tx.resolver.IsNameResolvable(tx.enclosingDeclaration, nameText) { + // create a unique name that shares the same text as its' base + nameNode = tx.Factory().NewUniqueNameEx(nameText, printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) + } else { + // use the node's name as-is, since it's not otherwise in-scope + nameNode = tx.Factory().NewIdentifier(nameText) + } + } + return nameNode +} + +func (tx *DeclarationTransformer) getNameOfExportedAssignedExpression(unwrapped *ast.Node, isExportEquals bool) *ast.Node { + nameNode := tx.tryGetNameOfAssignedExpression(unwrapped) + if nameNode == nil { + // fallback to a default name + if isExportEquals && ast.IsSourceFileJS(tx.state.currentSourceFile) { + // only JS files prefer to use `_exports` for export assignments - TS has always used `_default` for both `export=` and `export default` + nameNode = tx.Factory().NewUniqueNameEx("_exports", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) + } else { + nameNode = tx.Factory().NewUniqueNameEx("_default", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) + } + } + tx.cjsExportAssignmentName = nameNode + return nameNode +} + func (tx *DeclarationTransformer) transformExportAssignment(input *ast.Node, assignment *ast.Node, expression *ast.Node, isExportEquals bool) *ast.Node { if ast.IsSourceFile(input.Parent) { tx.resultHasExternalModuleIndicator = true } tx.resultHasScopeMarker = true - if ast.IsIdentifier(expression) { + if ast.IsIdentifier(expression) && (ast.IsSourceFile(input.Parent) || ast.IsModuleBlock(input.Parent)) { exportAssignment := tx.Factory().NewExportAssignment(nil, isExportEquals, nil, expression) tx.preserveJsDoc(exportAssignment, input) return exportAssignment } // Check if the expression is a class expression - emit as a class declaration + export assignment - if unwrapped := unwrapParenthesizedExpression(expression); ast.IsClassExpression(unwrapped) { - // Reuse the name pre-created by prescanCJSExportAssignment if available - className := tx.cjsExportAssignmentName - if className == nil { - ce := unwrapped.AsClassExpression() - if ce.Name() != nil && len(ce.Name().Text()) > 0 { - nameText := ce.Name().Text() - if tx.resolver.IsNameResolvedToDeclaration(tx.enclosingDeclaration, nameText, nil) { - className = tx.Factory().NewUniqueNameEx(nameText, printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) - } else { - className = tx.Factory().NewIdentifier(nameText) - } - } else { - className = tx.Factory().NewUniqueNameEx("_default", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) - } - tx.cjsExportAssignmentName = className - } + unwrapped := ast.SkipOuterExpressions(expression, ast.OEKExpressionTypePassthrough) + newId := tx.getNameOfExportedAssignedExpression(unwrapped, isExportEquals) + if ast.IsClassExpression(unwrapped) { var mods []*ast.Node if tx.needsDeclare { mods = append(mods, tx.Factory().NewModifier(ast.KindDeclareKeyword)) } - classDecl := tx.transformClassExpressionToDeclaration(unwrapped, className, tx.Factory().NewModifierList(mods)) + classDecl := tx.transformClassExpressionToDeclaration(unwrapped, newId, tx.Factory().NewModifierList(mods)) tx.preserveJsDoc(classDecl, input) // Reuse the same name node for the export so unique names resolve consistently - exportAssignment := tx.Factory().NewExportAssignment(nil, isExportEquals, nil, className) + exportAssignment := tx.Factory().NewExportAssignment(nil, isExportEquals, nil, newId) tx.removeAllComments(exportAssignment) return tx.Factory().NewSyntaxList([]*ast.Node{exportAssignment, classDecl}) + } else if ast.IsFunctionLike(unwrapped) { + // Promote function or arrow function expressions to a function declaration + var mods []*ast.Node + if tx.needsDeclare { + mods = append(mods, tx.Factory().NewModifier(ast.KindDeclareKeyword)) + } + funcDecl := tx.transformFunctionLikeToDeclaration(unwrapped, newId, tx.Factory().NewModifierList(mods)) + tx.preserveJsDoc(funcDecl, input) + // Reuse the same name node for the export so unique names resolve consistently + exportAssignment := tx.Factory().NewExportAssignment(nil, isExportEquals, nil, newId) + tx.removeAllComments(exportAssignment) + return tx.Factory().NewSyntaxList([]*ast.Node{exportAssignment, funcDecl}) } // expression is non-identifier, create _default typed variable to reference - newId := tx.Factory().NewUniqueNameEx("_default", printer.AutoGenerateOptions{Flags: printer.GeneratedIdentifierFlagsOptimistic}) tx.state.getSymbolAccessibilityDiagnostic = func(_ printer.SymbolAccessibilityResult) *SymbolAccessibilityDiagnostic { return &SymbolAccessibilityDiagnostic{ diagnosticMessage: diagnostics.Default_export_of_the_module_has_or_is_using_private_name_0, errorNode: input, } } + tx.cjsExportAssignmentName = newId tx.tracker.PushErrorFallbackNode(assignment) var type_, initializer *ast.Node if ast.IsPrimitiveLiteralValue(unwrapParenthesizedExpression(expression), true) { @@ -1223,7 +1259,48 @@ func (tx *DeclarationTransformer) transformExportAssignment(input *ast.Node, ass return tx.Factory().NewSyntaxList([]*ast.Node{statement, exportAssignment}) } +func (tx *DeclarationTransformer) transformFunctionLikeToDeclaration(unwrapped *ast.Node, funcName *ast.Node, mods *ast.ModifierList) *ast.Node { + d := unwrapped.FunctionLikeData() + return tx.Factory().NewFunctionDeclaration( + mods, + nil, + funcName, + tx.ensureTypeParams(unwrapped, d.TypeParameters), + tx.updateParamList(unwrapped, d.Parameters), + tx.ensureType(unwrapped, false), + tx.Visitor().VisitNode(d.FullSignature), + nil, + ) +} + +func (tx *DeclarationTransformer) transformBinaryExpressionToExportDeclaration(input *ast.Node, name *ast.Node) *ast.Node { + propertyName := input.AsBinaryExpression().Right + + // track alias target so referenced declarations are included in the output + tx.tracker.handleSymbolAccessibilityError(tx.resolver.IsEntityNameVisible(propertyName, tx.enclosingDeclaration)) + + if ast.IsIdentifier(name) && propertyName.Text() == name.Text() { + propertyName = nil + } + + return tx.Factory().NewExportDeclaration( + nil, + false, + tx.Factory().NewNamedExports(tx.Factory().NewNodeList([]*ast.Node{tx.Factory().NewExportSpecifier(false, propertyName, name)})), + nil, + nil, + ) +} + func (tx *DeclarationTransformer) transformCommonJSExport(input *ast.Node, name *ast.Node) *ast.Node { + res := tx.transformCommonJSExportWorker(input, name) + if res == nil { + return res + } + return tx.wrapInCJSExportNamespace(res) +} + +func (tx *DeclarationTransformer) transformCommonJSExportWorker(input *ast.Node, name *ast.Node) *ast.Node { var nameText string if ast.IsIdentifier(name) || ast.IsStringLiteral(name) { nameText = name.Text() @@ -1234,16 +1311,11 @@ func (tx *DeclarationTransformer) transformCommonJSExport(input *ast.Node, name tx.witnessedCjsExports.Add(nameText) tx.resultHasExternalModuleIndicator = true tx.resultHasScopeMarker = true - if isCommonJSAliasExport(input) { + // only transform cjs exports to shorthand at the top-level of a source file, otherwise we uniformly emit nested exports with a type annotation + if isCommonJSAliasExport(input) && ast.IsExpressionStatement(input.Parent) && ast.IsSourceFile(input.Parent.Parent) { // export { name } // export { source as name } - propertyName := input.AsBinaryExpression().Right - if ast.IsIdentifier(name) && propertyName.Text() == name.Text() { - propertyName = nil - } - exportSpecifier := tx.Factory().NewExportSpecifier(false, propertyName, name) - exportDecl := tx.Factory().NewExportDeclaration(nil, false, tx.Factory().NewNamedExports(tx.Factory().NewNodeList([]*ast.Node{exportSpecifier})), nil, nil) - return tx.wrapInCJSExportNamespace(exportDecl) + return tx.transformBinaryExpressionToExportDeclaration(input, name) } // Check if the RHS is a class expression - emit as a class declaration instead of a typed variable @@ -1301,13 +1373,7 @@ func (tx *DeclarationTransformer) transformCommonJSExport(input *ast.Node, name exportDecl := tx.Factory().NewExportDeclaration(nil, false, tx.Factory().NewNamedExports(tx.Factory().NewNodeList([]*ast.Node{exportSpecifier})), nil, nil) tx.removeAllComments(exportDecl) - result := []*ast.Node{nsDecl, importDecl} - if tx.cjsExportAssignmentName != nil { - result = append(result, tx.wrapInCJSExportNamespace(exportDecl)) - } else { - result = append(result, exportDecl) - } - return tx.Factory().NewSyntaxList(result) + return tx.Factory().NewSyntaxList(append([]*ast.Node{nsDecl, importDecl}, exportDecl)) } // No isolation needed: names match and no self-references. @@ -1434,6 +1500,7 @@ func (tx *DeclarationTransformer) wrapInCJSExportNamespace(content *ast.Node) *a if tx.needsDeclare { nsMods = append(nsMods, tx.Factory().NewModifier(ast.KindDeclareKeyword)) } + members, _ = tx.declareStrippingVisitor.VisitSlice(members) return tx.Factory().NewModuleDeclaration( tx.Factory().NewModifierList(nsMods), ast.KindNamespaceKeyword, @@ -2550,35 +2617,66 @@ func (tx *DeclarationTransformer) getNameExpressionPreferringIdentifier(nameExpr return nameExpr } -func (tx *DeclarationTransformer) visitExpressionStatement(node *ast.Node) *ast.Node { - if expression := node.Expression(); expression != nil { +func isNotDeclareModifier(mod *ast.Modifier) bool { + return mod.Kind != ast.KindDeclareKeyword +} + +func (tx *DeclarationTransformer) stripDeclareModifiers(node *ast.Node) *ast.Node { + if node == nil { + return nil + } + mods := node.Modifiers() + if mods != nil { + flags := node.ModifierFlags() + if flags&ast.ModifierFlagsAmbient != 0 { + filtered := core.Filter(mods.Nodes, isNotDeclareModifier) + node.AsMutable().SetModifiers(tx.Factory().NewModifierList(filtered)) + } + } + return node // no need to recur into children, only strip at top-level +} + +func (tx *DeclarationTransformer) visitCJSExportAssignments(expression *ast.Node) *ast.Node { + if expression != nil { _, cleanupDiagnosticContext := tx.setupDiagnosticContext(expression) defer cleanupDiagnosticContext() switch ast.GetAssignmentDeclarationKind(expression) { case ast.JSDeclarationKindModuleExports: - if ast.IsSourceFile(node.Parent) && node.Parent.AsSourceFile().CommonJSModuleIndicator != nil { - return tx.transformExportAssignment(node, expression, expression.AsBinaryExpression().Right, true /*isExportEquals*/) - } - case ast.JSDeclarationKindExportsProperty: - if ast.IsSourceFile(node.Parent) && node.Parent.AsSourceFile().CommonJSModuleIndicator != nil { - return tx.transformCommonJSExport(expression, tx.getNameExpressionPreferringIdentifier(ast.GetElementOrPropertyAccessName(expression.AsBinaryExpression().Left))) - } - case ast.JSDeclarationKindProperty: - return nil // handled in pre-walk - case ast.JSDeclarationKindObjectDefinePropertyExports: - if ast.IsSourceFile(node.Parent) && node.Parent.AsSourceFile().CommonJSModuleIndicator != nil { - return tx.transformCommonJSExport(expression, tx.getNameExpressionPreferringIdentifier(expression.Arguments()[1])) + if tx.state.currentSourceFile.CommonJSModuleIndicator != nil { + result := tx.transformExportAssignment(expression.Parent, expression, expression.AsBinaryExpression().Right, true /*isExportEquals*/) + if result != nil { + tx.cjsExportAssignment = result + tx.resultHasScopeMarker = true + tx.resultHasExternalModuleIndicator = true + } } } + return tx.cjsExportAssignmentVisitor.VisitEachChild(expression) // recur through the whole tree, looking for module.exports= } return nil } func (tx *DeclarationTransformer) visitNestedExpression(expression *ast.Node) *ast.Node { if expression != nil { + _, cleanupDiagnosticContext := tx.setupDiagnosticContext(expression) + defer cleanupDiagnosticContext() switch ast.GetAssignmentDeclarationKind(expression) { case ast.JSDeclarationKindProperty: tx.transformExpandoAssignment(expression.AsBinaryExpression()) + case ast.JSDeclarationKindExportsProperty: + if tx.state.currentSourceFile.CommonJSModuleIndicator != nil { + result := tx.transformCommonJSExport(expression, tx.getNameExpressionPreferringIdentifier(ast.GetElementOrPropertyAccessName(expression.AsBinaryExpression().Left))) + if result != nil { + tx.cjsExportMembers = append(tx.cjsExportMembers, result) + } + } + case ast.JSDeclarationKindObjectDefinePropertyExports: + if tx.state.currentSourceFile.CommonJSModuleIndicator != nil { + result := tx.transformCommonJSExport(expression, tx.getNameExpressionPreferringIdentifier(expression.Arguments()[1])) + if result != nil { + tx.cjsExportMembers = append(tx.cjsExportMembers, result) + } + } } return tx.expressionVisitor.VisitEachChild(expression) // recur through the whole tree, looking for special assignments } @@ -2640,10 +2738,28 @@ func (tx *DeclarationTransformer) transformExpandoAssignment(node *ast.BinaryExp tx.transformExpandoHost(name, declaration) - isNonContextualKeywordName := ast.IsNonContextualKeyword(scanner.StringToToken(property)) - exportName := core.IfElse(isNonContextualKeywordName, tx.Factory().NewGeneratedNameForNode(left), tx.Factory().NewIdentifier(property)) + exportName := tx.Factory().NewIdentifier(property) + localName := tx.tryGetNameOfAssignedExpression(node.AsNode()) + if localName == nil && !tx.resolver.IsNameResolvable(tx.enclosingDeclaration, property) && !ast.IsNonContextualKeyword(scanner.StringToToken(exportName.Text())) { + // use exportName as localName if there won't be any conflicts or keyword issues + localName = exportName + } + if localName == nil || ast.IsNonContextualKeyword(scanner.StringToToken(localName.Text())) { + // fallback to a generated name if the localName doesn't exist or is a keyword + localName = tx.Factory().NewGeneratedNameForNode(node.AsNode()) + } hostId := tx.getExpandoHostId(declaration) + _, cleanupDiagnosticContext := tx.setupDiagnosticContext(node.AsNode()) + defer cleanupDiagnosticContext() + + if ast.IsIdentifier(node.Right) { + // alias-like, emit an `export {name}` or `export {name as alias}` + result := tx.transformBinaryExpressionToExportDeclaration(node.AsNode(), exportName) + tx.expandoMembers[hostId] = append(tx.expandoMembers[hostId], result) + return + } + preexistingExpandoHasExport := core.Some(tx.expandoMembers[hostId], ast.IsExportDeclaration) var varModifiers *ast.ModifierList @@ -2657,6 +2773,7 @@ func (tx *DeclarationTransformer) transformExpandoAssignment(node *ast.BinaryExp declarationData.Symbol = host containerData := synthesizedNamespace.LocalsContainerData() containerData.Locals = make(ast.SymbolTable, 0) + containerData.Locals[localName.Text()] = symbol oldEnclosing := tx.enclosingDeclaration tx.enclosingDeclaration = synthesizedNamespace @@ -2664,25 +2781,22 @@ func (tx *DeclarationTransformer) transformExpandoAssignment(node *ast.BinaryExp tx.enclosingDeclaration = oldEnclosing }() - _, cleanupDiagnosticContext := tx.setupDiagnosticContext(node.AsNode()) - defer cleanupDiagnosticContext() - statements := []*ast.Statement{ tx.Factory().NewVariableStatement( varModifiers, tx.Factory().NewVariableDeclarationList( tx.Factory().NewNodeList([]*ast.Node{ - tx.Factory().NewVariableDeclaration(exportName, nil /*exclamationToken*/, tx.ensureType(node.AsNode(), false), nil /*initializer*/), + tx.Factory().NewVariableDeclaration(localName, nil /*exclamationToken*/, tx.ensureType(node.AsNode(), false), nil /*initializer*/), }), ast.NodeFlagsNone, ), ), } - if isNonContextualKeywordName { + if localName.Text() != exportName.Text() { namedExports := tx.Factory().NewNamedExports(tx.Factory().NewNodeList( []*ast.Node{ - tx.Factory().NewExportSpecifier(false /*isTypeOnly*/, exportName, tx.Factory().NewIdentifier(property)), + tx.Factory().NewExportSpecifier(false /*isTypeOnly*/, localName, exportName), }, )) statements = append(statements, tx.Factory().NewExportDeclaration(nil /*modifiers*/, false /*isTypeOnly*/, namedExports, nil /*moduleSpecifier*/, nil /*attributes*/)) diff --git a/internal/transformers/tstransforms/typeeraser.go b/internal/transformers/tstransforms/typeeraser.go index ae87f885a49..6f145b2678b 100644 --- a/internal/transformers/tstransforms/typeeraser.go +++ b/internal/transformers/tstransforms/typeeraser.go @@ -269,7 +269,7 @@ func (tx *TypeEraserTransformer) visit(node *ast.Node) *ast.Node { case ast.KindParenthesizedExpression: if !ast.IsJSDocTypeAssertion(node) { n := node.AsParenthesizedExpression() - expression := ast.SkipOuterExpressions(n.Expression, ^(ast.OEKAssertions | ast.OEKExpressionsWithTypeArguments)) + expression := ast.SkipOuterExpressions(n.Expression, ast.OEKAllExceptAssertionsOrExpressionsWithTypeArguments) if ast.IsAssertionExpression(expression) || ast.IsSatisfiesExpression(expression) { partial := tx.Factory().NewPartiallyEmittedExpression(tx.Visitor().VisitNode(n.Expression)) tx.EmitContext().SetOriginal(partial, node) diff --git a/testdata/baselines/reference/compiler/declarationEmitExpandoFunction.js b/testdata/baselines/reference/compiler/declarationEmitExpandoFunction.js index e0b394a54bf..7abec60cc97 100644 --- a/testdata/baselines/reference/compiler/declarationEmitExpandoFunction.js +++ b/testdata/baselines/reference/compiler/declarationEmitExpandoFunction.js @@ -38,12 +38,12 @@ B.c = C; //// [declarationEmitExpandoFunction.d.ts] export declare function A(): string; export declare namespace A { - var a: typeof C; - var b: typeof C; + export { C as a }; + export { C as b }; } export declare function B(): string; export declare namespace B { - var c: typeof C; + export { C as c }; } export declare enum C { C = 0 diff --git a/testdata/baselines/reference/compiler/declarationEmitNestedExpandoPropsRegression.js b/testdata/baselines/reference/compiler/declarationEmitNestedExpandoPropsRegression.js index 1801f5e7f01..24deb08eec3 100644 --- a/testdata/baselines/reference/compiler/declarationEmitNestedExpandoPropsRegression.js +++ b/testdata/baselines/reference/compiler/declarationEmitNestedExpandoPropsRegression.js @@ -21,8 +21,9 @@ if (true) { //// [declarationEmitNestedExpandoPropsRegression.d.ts] declare function Foo(): void; declare namespace Foo { - var top: number; - var inInitializer: number; - var inBlock: number; + var _a: number; + export { _a as top }; + export var inInitializer: number; + export var inBlock: number; } declare let d: number; diff --git a/testdata/baselines/reference/compiler/exportAssignmentMerging5.js b/testdata/baselines/reference/compiler/exportAssignmentMerging5.js index 0195f74de80..4f0f0afc8a7 100644 --- a/testdata/baselines/reference/compiler/exportAssignmentMerging5.js +++ b/testdata/baselines/reference/compiler/exportAssignmentMerging5.js @@ -29,16 +29,16 @@ let v1 = { x: "test" }; //// [a.d.ts] -export type Foo = { - x: string; -}; /** * @typedef {{x: string}} Foo */ -declare const _default: { +declare const _exports: { a: number; b: string; }; -export = _default; +export = _exports; +export type Foo = { + x: string; +}; //// [b.d.ts] export {}; diff --git a/testdata/baselines/reference/compiler/jsDeclarationsRequireImportForms(target=es2015).js b/testdata/baselines/reference/compiler/jsDeclarationsRequireImportForms(target=es2015).js index 5dc4a3afa1f..f4c3fe58636 100644 --- a/testdata/baselines/reference/compiler/jsDeclarationsRequireImportForms(target=es2015).js +++ b/testdata/baselines/reference/compiler/jsDeclarationsRequireImportForms(target=es2015).js @@ -42,12 +42,13 @@ module.exports = Container; //// [obj.d.ts] +export { Obj }; declare class Obj { x: number; constructor(); } -export { Obj }; //// [index.d.ts] +export = Container; import { Obj, Obj as Other } from "./obj"; declare class Container { usage: Obj; @@ -55,4 +56,3 @@ declare class Container { usage2: Other; constructor(); } -export = Container; diff --git a/testdata/baselines/reference/compiler/multipleModuleExportsAssignments.errors.txt b/testdata/baselines/reference/compiler/multipleModuleExportsAssignments.errors.txt index 844115f1242..ced25319fbe 100644 --- a/testdata/baselines/reference/compiler/multipleModuleExportsAssignments.errors.txt +++ b/testdata/baselines/reference/compiler/multipleModuleExportsAssignments.errors.txt @@ -1,7 +1,5 @@ x.js(2,5): error TS6424: Multiple 'module.exports' assignments cannot be serialized for declaration emit. -x.js(2,5): error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. x.js(5,5): error TS6424: Multiple 'module.exports' assignments cannot be serialized for declaration emit. -x.js(5,5): error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. ==== y.js (0 errors) ==== @@ -9,18 +7,14 @@ x.js(5,5): error TS6425: Nested CommonJS export constructs cannot be serialized const a = x.a; const b = x.b; -==== x.js (4 errors) ==== +==== x.js (2 errors) ==== if (!!true) { module.exports = { a: 1 }; ~~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS6424: Multiple 'module.exports' assignments cannot be serialized for declaration emit. - ~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. } else { module.exports = { b: "hello" }; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ !!! error TS6424: Multiple 'module.exports' assignments cannot be serialized for declaration emit. - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. } \ No newline at end of file diff --git a/testdata/baselines/reference/conformance/typedefModuleExportsIndirect1.js b/testdata/baselines/reference/conformance/typedefModuleExportsIndirect1.js index 87bb9a90ab4..835bca08c60 100644 --- a/testdata/baselines/reference/conformance/typedefModuleExportsIndirect1.js +++ b/testdata/baselines/reference/conformance/typedefModuleExportsIndirect1.js @@ -23,13 +23,13 @@ var c; //// [typedefModuleExportsIndirect1.d.ts] +export = dummy; export type C = { a: 1; m: 1; }; /** @typedef {{ a: 1, m: 1 }} C */ declare const dummy = 0; -export = dummy; //// [use.d.ts] type C = import('./typedefModuleExportsIndirect1').C; /** @typedef {import('./typedefModuleExportsIndirect1').C} C */ diff --git a/testdata/baselines/reference/conformance/typedefModuleExportsIndirect2.js b/testdata/baselines/reference/conformance/typedefModuleExportsIndirect2.js index 05a640acd83..66fdb1e30a5 100644 --- a/testdata/baselines/reference/conformance/typedefModuleExportsIndirect2.js +++ b/testdata/baselines/reference/conformance/typedefModuleExportsIndirect2.js @@ -23,13 +23,13 @@ var c; //// [typedefModuleExportsIndirect2.d.ts] +export = f; export type C = { a: 1; m: 1; }; /** @typedef {{ a: 1, m: 1 }} C */ declare const f: () => void; -export = f; //// [use.d.ts] type C = import('./typedefModuleExportsIndirect2').C; /** @typedef {import('./typedefModuleExportsIndirect2').C} C */ diff --git a/testdata/baselines/reference/conformance/typedefModuleExportsIndirect3.js b/testdata/baselines/reference/conformance/typedefModuleExportsIndirect3.js index 0978674adcb..5d7dae4e723 100644 --- a/testdata/baselines/reference/conformance/typedefModuleExportsIndirect3.js +++ b/testdata/baselines/reference/conformance/typedefModuleExportsIndirect3.js @@ -23,13 +23,13 @@ var c; //// [typedefModuleExportsIndirect3.d.ts] +export = o; export type C = { a: 1; m: 1; }; /** @typedef {{ a: 1, m: 1 }} C */ declare const o: {}; -export = o; //// [use.d.ts] type C = import('./typedefModuleExportsIndirect3').C; /** @typedef {import('./typedefModuleExportsIndirect3').C} C */ diff --git a/testdata/baselines/reference/submodule/compiler/amdLikeInputDeclarationEmit.errors.txt b/testdata/baselines/reference/submodule/compiler/amdLikeInputDeclarationEmit.errors.txt deleted file mode 100644 index 22fd9401a79..00000000000 --- a/testdata/baselines/reference/submodule/compiler/amdLikeInputDeclarationEmit.errors.txt +++ /dev/null @@ -1,34 +0,0 @@ -ExtendedClass.js(17,5): error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. - - -==== typing.d.ts (0 errors) ==== - declare function define(name: string, modules: string[], ready: (...modules: unknown[]) => T); -==== deps/BaseClass.d.ts (0 errors) ==== - declare module "deps/BaseClass" { - class BaseClass { - static extends(a: A): new () => A & BaseClass; - } - export = BaseClass; - } -==== ExtendedClass.js (1 errors) ==== - define("lib/ExtendedClass", ["deps/BaseClass"], - /** - * {typeof import("deps/BaseClass")} - * @param {typeof import("deps/BaseClass")} BaseClass - * @returns - */ - (BaseClass) => { - - const ExtendedClass = BaseClass.extends({ - f: function() { - return "something"; - } - }); - - // Exports the module in a way tsc recognize class export - const module = {}; - module.exports = ExtendedClass - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. - return module.exports; - }); \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/amdLikeInputDeclarationEmit.js b/testdata/baselines/reference/submodule/compiler/amdLikeInputDeclarationEmit.js new file mode 100644 index 00000000000..1ffd0c5ce47 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/amdLikeInputDeclarationEmit.js @@ -0,0 +1,39 @@ +//// [tests/cases/compiler/amdLikeInputDeclarationEmit.ts] //// + +//// [typing.d.ts] +declare function define(name: string, modules: string[], ready: (...modules: unknown[]) => T); +//// [BaseClass.d.ts] +declare module "deps/BaseClass" { + class BaseClass { + static extends(a: A): new () => A & BaseClass; + } + export = BaseClass; +} +//// [ExtendedClass.js] +define("lib/ExtendedClass", ["deps/BaseClass"], +/** + * {typeof import("deps/BaseClass")} + * @param {typeof import("deps/BaseClass")} BaseClass + * @returns + */ +(BaseClass) => { + + const ExtendedClass = BaseClass.extends({ + f: function() { + return "something"; + } + }); + + // Exports the module in a way tsc recognize class export + const module = {}; + module.exports = ExtendedClass + return module.exports; +}); + + + +//// [ExtendedClass.d.ts] +declare const ExtendedClass: new () => { + f: () => "something"; +} & import("deps/BaseClass"); +export = ExtendedClass; diff --git a/testdata/baselines/reference/submodule/compiler/declarationEmitDefaultExportWithStaticAssignment.js b/testdata/baselines/reference/submodule/compiler/declarationEmitDefaultExportWithStaticAssignment.js index b0303116d6a..731f8ff4b52 100644 --- a/testdata/baselines/reference/submodule/compiler/declarationEmitDefaultExportWithStaticAssignment.js +++ b/testdata/baselines/reference/submodule/compiler/declarationEmitDefaultExportWithStaticAssignment.js @@ -82,10 +82,11 @@ C.B = B; export declare class Foo { } //// [index1.d.ts] +import { Foo } from './foo'; declare function Example(): void; export default Example; declare namespace Example { - var Foo: typeof import("./foo").Foo; + export { Foo }; } //// [index2.d.ts] import { Foo } from './foo'; @@ -93,7 +94,7 @@ export { Foo }; declare function Example(): void; export default Example; declare namespace Example { - var Foo: typeof import("./foo").Foo; + export { Foo }; } //// [index3.d.ts] export declare class Bar { @@ -101,11 +102,14 @@ export declare class Bar { declare function Example(): void; export default Example; declare namespace Example { - var Bar: typeof import("./index3").Bar; + export { Bar }; } //// [index4.d.ts] +declare function A(): void; +declare function B(): void; export declare function C(): any; export declare namespace C { - var A: () => void; - var B: () => void; + export { A }; + export { B }; } +export {}; diff --git a/testdata/baselines/reference/submodule/compiler/declarationEmitExportAliasVisibiilityMarking.js b/testdata/baselines/reference/submodule/compiler/declarationEmitExportAliasVisibiilityMarking.js index 7061c8bff55..1ce57094265 100644 --- a/testdata/baselines/reference/submodule/compiler/declarationEmitExportAliasVisibiilityMarking.js +++ b/testdata/baselines/reference/submodule/compiler/declarationEmitExportAliasVisibiilityMarking.js @@ -68,11 +68,11 @@ type Rank = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 'Jack' | 'Queen' | 'Kin export { Suit, Rank }; //// [Card.d.ts] import { Suit, Rank } from './Types'; -declare const _default: (suit: Suit, rank: Rank) => { +export default _default; +declare function _default(suit: Suit, rank: Rank): { suit: Suit; rank: Rank; }; -export default _default; //// [index.d.ts] export declare let lazyCard: () => Promise<(suit: import("./Types").Suit, rank: import("./Types").Rank) => { suit: import("./Types").Suit; diff --git a/testdata/baselines/reference/submodule/compiler/declarationEmitExportAliasVisibiilityMarking.js.diff b/testdata/baselines/reference/submodule/compiler/declarationEmitExportAliasVisibiilityMarking.js.diff new file mode 100644 index 00000000000..08c92f1d058 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/declarationEmitExportAliasVisibiilityMarking.js.diff @@ -0,0 +1,16 @@ +--- old.declarationEmitExportAliasVisibiilityMarking.js ++++ new.declarationEmitExportAliasVisibiilityMarking.js +@@= skipped -67, +67 lines =@@ + export { Suit, Rank }; + //// [Card.d.ts] + import { Suit, Rank } from './Types'; +-declare const _default: (suit: Suit, rank: Rank) => { ++export default _default; ++declare function _default(suit: Suit, rank: Rank): { + suit: Suit; + rank: Rank; + }; +-export default _default; + //// [index.d.ts] + export declare let lazyCard: () => Promise<(suit: import("./Types").Suit, rank: import("./Types").Rank) => { + suit: import("./Types").Suit; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/expandoFunctionNestedAssigments.js b/testdata/baselines/reference/submodule/compiler/expandoFunctionNestedAssigments.js index 54bb069531b..a7b6226c339 100644 --- a/testdata/baselines/reference/submodule/compiler/expandoFunctionNestedAssigments.js +++ b/testdata/baselines/reference/submodule/compiler/expandoFunctionNestedAssigments.js @@ -99,31 +99,32 @@ for (let f in (Foo.forIn = [])) { //// [expandoFunctionNestedAssigments.d.ts] declare function Foo(): void; declare namespace Foo { - var inVariableInit: number; - var bla: { + export var inVariableInit: number; + export var bla: { foo: number; }; - var baz: number; - var bar: number; - var fromIf: number; - var inIf: number; - var fromWhileCondition: number; - var fromWhileBody: number; - var fromWhileBodyNested: number; - var fromDoBody: number; - var fromDoBodyNested: number; - var fromDoCondition: number; - var forInit: number; - var forCond: number; - var forIncr: number; - var fromForBody: number; - var fromForBodyNested: number; - var forOf: any[]; - var fromForOfBody: number; - var fromForOfBodyNested: number; - var forIn: any[]; - var fromForInBody: number; - var fromForInBodyNested: number; + export var baz: number; + var _a: number; + export { _a as bar }; + export var fromIf: number; + export var inIf: number; + export var fromWhileCondition: number; + export var fromWhileBody: number; + export var fromWhileBodyNested: number; + export var fromDoBody: number; + export var fromDoBodyNested: number; + export var fromDoCondition: number; + export var forInit: number; + export var forCond: number; + export var forIncr: number; + export var fromForBody: number; + export var fromForBodyNested: number; + export var forOf: any[]; + export var fromForOfBody: number; + export var fromForOfBodyNested: number; + export var forIn: any[]; + export var fromForInBody: number; + export var fromForInBodyNested: number; } declare let d: number; declare function bar(p?: number): void; diff --git a/testdata/baselines/reference/submodule/compiler/expandoFunctionNestedAssigments.js.diff b/testdata/baselines/reference/submodule/compiler/expandoFunctionNestedAssigments.js.diff new file mode 100644 index 00000000000..c857c558f55 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/expandoFunctionNestedAssigments.js.diff @@ -0,0 +1,58 @@ +--- old.expandoFunctionNestedAssigments.js ++++ new.expandoFunctionNestedAssigments.js +@@= skipped -98, +98 lines =@@ + //// [expandoFunctionNestedAssigments.d.ts] + declare function Foo(): void; + declare namespace Foo { +- var inVariableInit: number; +- var bla: { ++ export var inVariableInit: number; ++ export var bla: { + foo: number; + }; +- var baz: number; +- var bar: number; +- var fromIf: number; +- var inIf: number; +- var fromWhileCondition: number; +- var fromWhileBody: number; +- var fromWhileBodyNested: number; +- var fromDoBody: number; +- var fromDoBodyNested: number; +- var fromDoCondition: number; +- var forInit: number; +- var forCond: number; +- var forIncr: number; +- var fromForBody: number; +- var fromForBodyNested: number; +- var forOf: any[]; +- var fromForOfBody: number; +- var fromForOfBodyNested: number; +- var forIn: any[]; +- var fromForInBody: number; +- var fromForInBodyNested: number; ++ export var baz: number; ++ var _a: number; ++ export { _a as bar }; ++ export var fromIf: number; ++ export var inIf: number; ++ export var fromWhileCondition: number; ++ export var fromWhileBody: number; ++ export var fromWhileBodyNested: number; ++ export var fromDoBody: number; ++ export var fromDoBodyNested: number; ++ export var fromDoCondition: number; ++ export var forInit: number; ++ export var forCond: number; ++ export var forIncr: number; ++ export var fromForBody: number; ++ export var fromForBodyNested: number; ++ export var forOf: any[]; ++ export var fromForOfBody: number; ++ export var fromForOfBodyNested: number; ++ export var forIn: any[]; ++ export var fromForInBody: number; ++ export var fromForInBodyNested: number; + } + declare let d: number; + declare function bar(p?: number): void; \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/jsDeclarationEmitExportAssignedArray.js b/testdata/baselines/reference/submodule/compiler/jsDeclarationEmitExportAssignedArray.js index 97519198264..6f3f5b55a7d 100644 --- a/testdata/baselines/reference/submodule/compiler/jsDeclarationEmitExportAssignedArray.js +++ b/testdata/baselines/reference/submodule/compiler/jsDeclarationEmitExportAssignedArray.js @@ -6,9 +6,9 @@ module.exports = [{ name: 'other', displayName: 'Other', defaultEnabled: true }] //// [file.d.ts] -declare const _default: { +declare const _exports: { name: string; displayName: string; defaultEnabled: boolean; }[]; -export = _default; +export = _exports; diff --git a/testdata/baselines/reference/submodule/compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.js b/testdata/baselines/reference/submodule/compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.js index 04963921cf7..10157988ac1 100644 --- a/testdata/baselines/reference/submodule/compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.js +++ b/testdata/baselines/reference/submodule/compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.js @@ -29,11 +29,11 @@ module.exports = function loader(options) { }; * @typedef Options * @property {string} opt */ -export type Options = { - opt: string; -}; +export = loader; /** * @param {Options} options */ -declare const _default: (options: Options) => void; -export = _default; +declare function loader(options: Options): void; +export type Options = { + opt: string; +}; diff --git a/testdata/baselines/reference/submodule/compiler/jsExportAssignmentNonMutableLocation.js b/testdata/baselines/reference/submodule/compiler/jsExportAssignmentNonMutableLocation.js index 7b4d092edc7..4a7b434eaf8 100644 --- a/testdata/baselines/reference/submodule/compiler/jsExportAssignmentNonMutableLocation.js +++ b/testdata/baselines/reference/submodule/compiler/jsExportAssignmentNonMutableLocation.js @@ -13,8 +13,10 @@ exports.customSymbol2 = Symbol("custom"); //// [file.d.ts] -declare const _default: { +declare const _exports: { customSymbol: symbol; }; -export = _default; -export declare var customSymbol2: symbol; +export = _exports; +declare namespace _exports { + export var customSymbol2: symbol; +} diff --git a/testdata/baselines/reference/submodule/compiler/jsxDeclarationsWithEsModuleInteropNoCrash.js b/testdata/baselines/reference/submodule/compiler/jsxDeclarationsWithEsModuleInteropNoCrash.js index 0b018403f4a..aeaae0a2681 100644 --- a/testdata/baselines/reference/submodule/compiler/jsxDeclarationsWithEsModuleInteropNoCrash.js +++ b/testdata/baselines/reference/submodule/compiler/jsxDeclarationsWithEsModuleInteropNoCrash.js @@ -27,15 +27,17 @@ export default Foo; //// [jsxDeclarationsWithEsModuleInteropNoCrash.d.ts] /// import PropTypes from 'prop-types'; +declare const propTypes: { + bar: PropTypes.Requireable; +}; +declare const defaultProps: { + bar: boolean; +}; declare function Foo({ bar }: { bar: any; }): JSX.Element; declare namespace Foo { - var propTypes: { - bar: PropTypes.Requireable; - }; - var defaultProps: { - bar: boolean; - }; + export { propTypes }; + export { defaultProps }; } export default Foo; diff --git a/testdata/baselines/reference/submodule/compiler/modulePreserve4.js b/testdata/baselines/reference/submodule/compiler/modulePreserve4.js index cfd15fad02b..24126d6a002 100644 --- a/testdata/baselines/reference/submodule/compiler/modulePreserve4.js +++ b/testdata/baselines/reference/submodule/compiler/modulePreserve4.js @@ -205,8 +205,8 @@ declare const _default: { }; export = _default; //// [d.d.ts] -declare const _default: () => void; export = _default; +declare function _default(): void; //// [e.d.mts] declare const _default = 0; export = _default; diff --git a/testdata/baselines/reference/submodule/conformance/assignmentToVoidZero1.errors.txt b/testdata/baselines/reference/submodule/conformance/assignmentToVoidZero1.errors.txt deleted file mode 100644 index 79d12e13002..00000000000 --- a/testdata/baselines/reference/submodule/conformance/assignmentToVoidZero1.errors.txt +++ /dev/null @@ -1,11 +0,0 @@ -assignmentToVoidZero1.js(2,13): error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. - - -==== assignmentToVoidZero1.js (1 errors) ==== - // #38552 - exports.y = exports.x = void 0; - ~~~~~~~~~~~~~~~~~~ -!!! error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. - exports.x = 1; - exports.y = 2; - \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/assignmentToVoidZero1.js b/testdata/baselines/reference/submodule/conformance/assignmentToVoidZero1.js index 0ab31ab8e7c..b5d578a4d9a 100644 --- a/testdata/baselines/reference/submodule/conformance/assignmentToVoidZero1.js +++ b/testdata/baselines/reference/submodule/conformance/assignmentToVoidZero1.js @@ -13,3 +13,8 @@ exports.y = 2; exports.y = exports.x = void 0; exports.x = 1; exports.y = 2; + + +//// [assignmentToVoidZero1.d.ts] +export declare var y: 2; +export declare var x: 1; diff --git a/testdata/baselines/reference/submodule/conformance/commonJSAliasedExport.js b/testdata/baselines/reference/submodule/conformance/commonJSAliasedExport.js index 6b782b3aadd..375d422f990 100644 --- a/testdata/baselines/reference/submodule/conformance/commonJSAliasedExport.js +++ b/testdata/baselines/reference/submodule/conformance/commonJSAliasedExport.js @@ -34,9 +34,9 @@ var diddy = funky(1); //// [commonJSAliasedExport.d.ts] -declare const donkey: (ast: any) => any; -declare function funky(declaration: any): boolean; export = donkey; export { funky }; +declare const donkey: (ast: any) => any; +declare function funky(declaration: any): boolean; //// [bug43713.d.ts] export {}; diff --git a/testdata/baselines/reference/submodule/conformance/commonJSImportClassTypeReference.js b/testdata/baselines/reference/submodule/conformance/commonJSImportClassTypeReference.js index 41e2ff886b6..b2f5c65fb9b 100644 --- a/testdata/baselines/reference/submodule/conformance/commonJSImportClassTypeReference.js +++ b/testdata/baselines/reference/submodule/conformance/commonJSImportClassTypeReference.js @@ -34,9 +34,9 @@ function f(k) { //// [mod1.d.ts] +export { K }; declare class K { values(): K; } -export { K }; //// [main.d.ts] export {}; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsClassExtendsVisibility(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsClassExtendsVisibility(target=es2015).js index 2a33bb20873..7ac9e22282e 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsClassExtendsVisibility(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsClassExtendsVisibility(target=es2015).js @@ -32,10 +32,12 @@ module.exports.Strings = Strings; //// [bar.d.ts] +export = Bar; declare class Bar { } -export = Bar; //// [cls.d.ts] +export = Foo; +export { Strings }; import Bar = require("./bar"); declare const Strings: { a: string; @@ -43,5 +45,3 @@ declare const Strings: { }; declare class Foo extends Bar { } -export = Foo; -export { Strings }; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsClassStatic(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsClassStatic(target=es2015).js index 79ae706540a..f71f4d00399 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsClassStatic(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsClassStatic(target=es2015).js @@ -49,6 +49,8 @@ module.exports.Strings = Strings; //// [source.d.ts] +export = Handler; +export { Strings }; declare class Handler { static get OPTIONS(): number; process(): void; @@ -60,8 +62,6 @@ declare const Strings: { a: string; b: string; }; -export = Handler; -export { Strings }; export type HandlerOptions = { /** * Should be able to export a type alias at the same time. diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsCommonjsRelativePath.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsCommonjsRelativePath.js index 45f98704bad..0a509a77026 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsCommonjsRelativePath.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsCommonjsRelativePath.js @@ -14,16 +14,16 @@ module.exports = { Thing } //// [thing.d.ts] -declare class Thing { -} -declare const _default: { +declare const _exports: { Thing: typeof Thing; }; -export = _default; +export = _exports; +declare class Thing { +} //// [reexport.d.ts] -declare const _default: { +declare const _exports: { Thing: { new (): {}; }; }; -export = _default; +export = _exports; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsComputedNames(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsComputedNames(target=es2015).js index 2a3a73ef012..b5f1346e3fd 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsComputedNames(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsComputedNames(target=es2015).js @@ -62,9 +62,7 @@ MyClass[_a] = 12; //// [index.d.ts] -declare const TopLevelSym: unique symbol; -declare const InnerSym: unique symbol; -declare const _default: { +declare const _exports: { [TopLevelSym](x?: number): number; items: { [InnerSym]: (arg?: { @@ -72,7 +70,9 @@ declare const _default: { }) => number; }; }; -export = _default; +export = _exports; +declare const TopLevelSym: unique symbol; +declare const InnerSym: unique symbol; //// [index2.d.ts] declare const TopLevelSym: unique symbol; declare const InnerSym: unique symbol; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsCrossfileMerge(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsCrossfileMerge(target=es2015).js index 6568e366b52..4559276c345 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsCrossfileMerge(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsCrossfileMerge(target=es2015).js @@ -28,7 +28,9 @@ module.exports.memberName = "thing"; declare function validate(): void; export default validate; //// [index.d.ts] +declare const _exports: typeof m.default; +export = _exports; +declare namespace _exports { + export var memberName: "thing"; +} import m = require("./exporter"); -declare const _default: typeof m.default; -export = _default; -export declare var memberName: "thing"; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsDocCommentsOnConsts(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsDocCommentsOnConsts(target=es2015).js index b5fa95162aa..660cd7a7411 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsDocCommentsOnConsts(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsDocCommentsOnConsts(target=es2015).js @@ -35,12 +35,12 @@ module.exports = { x, b }; //// [index1.d.ts] +declare const _exports: { + x: (a: any) => string; + b: typeof b; +}; +export = _exports; /** * function doc comment */ declare function b(): number; -declare const _default: { - x: (a: any) => string; - b: typeof b; -}; -export = _default; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js index 3124bbc6df4..73f97db268c 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js @@ -23,8 +23,8 @@ module.exports = class { //// [index.d.ts] -export = _default; -declare class _default { +export = _exports; +declare class _exports { t: number; /** * @param {number} p diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js index 0eb912771b5..dff729e7f50 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js @@ -34,15 +34,17 @@ module.exports.Sub = class { //// [index.d.ts] -export = _default; -declare class _default { +export = _exports; +declare class _exports { t: number; /** * @param {number} p */ constructor(p: number); } -export declare class Sub { - instance: import("."); - constructor(); +declare namespace _exports { + export class Sub { + instance: import("."); + constructor(); + } } diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js index d4063a549b9..ca52f3c7807 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js @@ -36,12 +36,6 @@ module.exports.Another = Q; //// [index.d.ts] -declare class A { - member: Q; -} -declare class Q { - x: number; -} export = Q_1; declare class Q_1 { x: A; @@ -50,3 +44,9 @@ declare class Q_1 { declare namespace Q_1 { export { Q as Another }; } +declare class A { + member: Q; +} +declare class Q { + x: number; +} diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassInstance1(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassInstance1(target=es2015).js index 45f1a587352..f83e29edee2 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassInstance1(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassInstance1(target=es2015).js @@ -13,7 +13,7 @@ module.exports = new Foo(); //// [index.d.ts] +declare const _exports: Foo; +export = _exports; declare class Foo { } -declare const _default: Foo; -export = _default; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassInstance2(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassInstance2(target=es2015).js index 232d9f961bf..12211c2b157 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassInstance2(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassInstance2(target=es2015).js @@ -20,9 +20,9 @@ module.exports = new Foo(); //// [index.d.ts] +declare const _exports: Foo; +export = _exports; declare class Foo { static stat: number; member: number; } -declare const _default: Foo; -export = _default; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassInstance3(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassInstance3(target=es2015).js index 4fc53bd9403..0fb01fa3752 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassInstance3(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedClassInstance3(target=es2015).js @@ -23,10 +23,12 @@ module.exports.additional = 20; //// [index.d.ts] +declare const _exports: Foo; +export = _exports; +declare namespace _exports { + export var additional: 20; +} declare class Foo { static stat: number; member: number; } -declare const _default: Foo; -export = _default; -export declare var additional: 20; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedConstructorFunctionWithSub(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedConstructorFunctionWithSub(target=es2015).js index 00221b41040..7f43ee76163 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedConstructorFunctionWithSub(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedConstructorFunctionWithSub(target=es2015).js @@ -28,9 +28,11 @@ module.exports.Sub.prototype = {}; //// [jsDeclarationsExportAssignedConstructorFunctionWithSub.d.ts] +export = _exports; /** * @param {number} p */ -declare const _default: (p: number) => void; -export = _default; -export declare var Sub: () => void; +declare function _exports(p: number): void; +declare namespace _exports { + export var Sub: () => void; +} diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedConstructorFunctionWithSub(target=es2015).js.diff b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedConstructorFunctionWithSub(target=es2015).js.diff index 1787624254d..bd44e8a75e5 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedConstructorFunctionWithSub(target=es2015).js.diff +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedConstructorFunctionWithSub(target=es2015).js.diff @@ -7,9 +7,11 @@ + + +//// [jsDeclarationsExportAssignedConstructorFunctionWithSub.d.ts] ++export = _exports; +/** + * @param {number} p + */ -+declare const _default: (p: number) => void; -+export = _default; -+export declare var Sub: () => void; \ No newline at end of file ++declare function _exports(p: number): void; ++declare namespace _exports { ++ export var Sub: () => void; ++} \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js index 5cc1552e148..be3d718e4dc 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js @@ -42,9 +42,9 @@ declare class Obj { constructor(); } //// [index.d.ts] +export = Container; import Obj = require("./obj"); declare class Container { usage: Obj; constructor(); } -export = Container; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignmentExpressionPlusSecondary(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignmentExpressionPlusSecondary(target=es2015).js index 0a46aff695d..9c281733b68 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignmentExpressionPlusSecondary(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignmentExpressionPlusSecondary(target=es2015).js @@ -32,16 +32,18 @@ module.exports.Strings = Strings; //// [index.d.ts] -declare const Strings: { - a: string; - b: string; -}; -declare const _default: { +declare const _exports: { thing: string; also: string; desc: { item: string; }; }; -export = _default; -export { Strings }; +export = _exports; +declare namespace _exports { + export { Strings }; +} +declare const Strings: { + a: string; + b: string; +}; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignmentWithKeywordName(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignmentWithKeywordName(target=es2015).js index c7f7b7a2796..0ab98adb370 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignmentWithKeywordName(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportAssignmentWithKeywordName(target=es2015).js @@ -23,11 +23,11 @@ module.exports = { //// [index.d.ts] -declare const _default: { +declare const _exports: { extends: string; more: { others: string[]; }; x: number; }; -export = _default; +export = _exports; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDefinePropertyEmit.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDefinePropertyEmit.js index 42bb06820f4..3939665c742 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDefinePropertyEmit.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDefinePropertyEmit.js @@ -116,14 +116,25 @@ Object.defineProperty(module.exports, "j", { value: function j() { } }); //// [index.d.ts] export declare var a: () => void; export declare var b: () => void; +declare const _exported: typeof d; +export { _exported as d }; +declare const _exported_1: typeof e; +export { _exported_1 as e }; +declare const _exported_2: typeof f; +export { _exported_2 as f }; +declare const _exported_3: typeof g; +export { _exported_3 as g }; +export declare var h: typeof hh; +export declare var i: () => void; +export declare var ii: () => void; +export declare var jj: () => void; +export declare var j: () => void; /** * @param {number} a * @param {number} b * @return {string} */ declare function d(a: number, b: number): string; -declare const _exported: typeof d; -export { _exported as d }; /** * @template T,U * @param {T} a @@ -131,15 +142,11 @@ export { _exported as d }; * @return {T & U} */ declare function e(a: T, b: U): T & U; -declare const _exported_1: typeof e; -export { _exported_1 as e }; /** * @template T * @param {T} a */ declare function f(a: T): T; -declare const _exported_2: typeof f; -export { _exported_2 as f }; /** * @param {{x: string}} a * @param {{y: typeof module.exports.b}} b @@ -149,8 +156,6 @@ declare function g(a: { }, b: { y: () => void; }): void | ""; -declare const _exported_3: typeof g; -export { _exported_3 as g }; /** * @param {{x: string}} a * @param {{y: typeof module.exports.b}} b @@ -160,8 +165,3 @@ declare function hh(a: { }, b: { y: () => void; }): void | ""; -export declare var h: typeof hh; -export declare var i: () => void; -export declare var ii: () => void; -export declare var jj: () => void; -export declare var j: () => void; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.errors.txt b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.errors.txt deleted file mode 100644 index b752b3e407d..00000000000 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.errors.txt +++ /dev/null @@ -1,22 +0,0 @@ -index.js(3,5): error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. -index.js(9,5): error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. - - -==== index.js (2 errors) ==== - // @ts-nocheck - function foo() { - module.exports = exports = function (o) { - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - return (o == null) ? create(base) : defineProperties(Object(o), descriptors); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - }; - ~~~~~ -!!! error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. - const m = function () { - // I have no idea what to put here - } - exports.methods = m; - ~~~~~~~~~~~~~~~~~~~ -!!! error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. - } - \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.errors.txt.diff deleted file mode 100644 index e4579f13ce4..00000000000 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.errors.txt.diff +++ /dev/null @@ -1,26 +0,0 @@ ---- old.jsDeclarationsExportDoubleAssignmentInClosure.errors.txt -+++ new.jsDeclarationsExportDoubleAssignmentInClosure.errors.txt -@@= skipped -0, +0 lines =@@ -- -+index.js(3,5): error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. -+index.js(9,5): error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. -+ -+ -+==== index.js (2 errors) ==== -+ // @ts-nocheck -+ function foo() { -+ module.exports = exports = function (o) { -+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -+ return (o == null) ? create(base) : defineProperties(Object(o), descriptors); -+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -+ }; -+ ~~~~~ -+!!! error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. -+ const m = function () { -+ // I have no idea what to put here -+ } -+ exports.methods = m; -+ ~~~~~~~~~~~~~~~~~~~ -+!!! error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. -+ } -+ \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.js index b6404438a24..67d1264bc45 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.js @@ -25,3 +25,11 @@ function foo() { }; exports.methods = m; } + + +//// [index.d.ts] +export = _exports; +declare function _exports(o: any): any; +declare namespace _exports { + export var methods: () => void; +} diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.types b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.types index 0edcc0613a8..bffe6a94ce8 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.types +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.types @@ -16,19 +16,19 @@ function foo() { >o : any return (o == null) ? create(base) : defineProperties(Object(o), descriptors); ->(o == null) ? create(base) : defineProperties(Object(o), descriptors) : any +>(o == null) ? create(base) : defineProperties(Object(o), descriptors) : error >(o == null) : boolean >o == null : boolean >o : any ->create(base) : any ->create : any ->base : any ->defineProperties(Object(o), descriptors) : any ->defineProperties : any +>create(base) : error +>create : error +>base : error +>defineProperties(Object(o), descriptors) : error +>defineProperties : error >Object(o) : any >Object : ObjectConstructor >o : any ->descriptors : any +>descriptors : error }; const m = function () { @@ -39,7 +39,7 @@ function foo() { } exports.methods = m; >exports.methods = m : () => void ->exports.methods : any +>exports.methods : error >exports : (o: any) => any >methods : any >m : () => void diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.types.diff b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.types.diff index bff8a02152c..5f8e6db64cb 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.types.diff +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportDoubleAssignmentInClosure.types.diff @@ -18,35 +18,13 @@ >function (o) { return (o == null) ? create(base) : defineProperties(Object(o), descriptors); } : (o: any) => any >o : any - return (o == null) ? create(base) : defineProperties(Object(o), descriptors); -->(o == null) ? create(base) : defineProperties(Object(o), descriptors) : error -+>(o == null) ? create(base) : defineProperties(Object(o), descriptors) : any - >(o == null) : boolean - >o == null : boolean - >o : any -->create(base) : error -->create : error -->base : error -->defineProperties(Object(o), descriptors) : error -->defineProperties : error -+>create(base) : any -+>create : any -+>base : any -+>defineProperties(Object(o), descriptors) : any -+>defineProperties : any - >Object(o) : any - >Object : ObjectConstructor - >o : any -->descriptors : error -+>descriptors : any - - }; - const m = function () { -@@= skipped -34, +34 lines =@@ +@@= skipped -33, +33 lines =@@ + } exports.methods = m; >exports.methods = m : () => void - >exports.methods : any +->exports.methods : any ->exports : any ++>exports.methods : error +>exports : (o: any) => any >methods : any >m : () => void diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportForms(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportForms(target=es2015).js index b87ad507ffb..6767632cfbc 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportForms(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportForms(target=es2015).js @@ -283,20 +283,20 @@ export { ns }; import * as ns from "./cls"; export { ns as classContainer }; //// [cjs.d.ts] -import ns = require("./cls"); -declare const _default: { +declare const _exports: { ns: typeof ns; }; -export = _default; -//// [cjs2.d.ts] +export = _exports; import ns = require("./cls"); +//// [cjs2.d.ts] export = ns; -//// [cjs3.d.ts] import ns = require("./cls"); +//// [cjs3.d.ts] export { ns }; -//// [cjs4.d.ts] import ns = require("./cls"); +//// [cjs4.d.ts] export { ns as names }; +import ns = require("./cls"); //// [includeAll.d.ts] import "./cjs4"; import "./cjs3"; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportSubAssignments(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportSubAssignments(target=es2015).js index 22de5ceeda1..e7035cf595b 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportSubAssignments(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportSubAssignments(target=es2015).js @@ -22,11 +22,11 @@ module.exports.Strings = Strings; //// [cls.d.ts] +export = Foo; +export { Strings }; declare const Strings: { a: string; b: string; }; declare class Foo { } -export = Foo; -export { Strings }; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportedClassAliases.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportedClassAliases.js index a68376657ea..524cd12da14 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportedClassAliases.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsExportedClassAliases.js @@ -39,15 +39,15 @@ module.exports = { //// [errors.d.ts] +declare const _exports: { + FancyError: typeof FancyError; +}; +export = _exports; declare class FancyError extends Error { constructor(status: any); } -declare const _default: { - FancyError: typeof FancyError; -}; -export = _default; //// [index.d.ts] -declare const _default: { +declare const _exports: { errors: { FancyError: { new (status: any): { @@ -58,4 +58,4 @@ declare const _default: { }; }; }; -export = _default; +export = _exports; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionClassesCjsExportAssignment(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionClassesCjsExportAssignment(target=es2015).js index 6092af7095b..eaaac056b90 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionClassesCjsExportAssignment(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionClassesCjsExportAssignment(target=es2015).js @@ -141,11 +141,11 @@ module.exports = Hook; //// [timer.d.ts] +export = Timer; /** * @param {number} timeout */ declare function Timer(timeout: number): void; -export = Timer; //// [context.d.ts] /** * Imports @@ -154,6 +154,7 @@ export = Timer; * @typedef {import("./hook")} Hook * @typedef {import("./hook").HookHandler} HookHandler */ +export = Context; export type Timer = import("./timer"); export type Hook = import("./hook"); export type HookHandler = import("./hook").HookHandler; @@ -196,8 +197,8 @@ declare namespace Context { construct(input: Input, handle?: HookHandler | undefined): State; }; } -export = Context; //// [hook.d.ts] +export = Hook; export type HookHandler = (arg: import("./context")) => void; /** * @typedef {(arg: import("./context")) => void} HookHandler @@ -206,4 +207,3 @@ export type HookHandler = (arg: import("./context")) => void; * @param {HookHandler} handle */ declare function Hook(handle: HookHandler): void; -export = Hook; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionKeywordPropExhaustive(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionKeywordPropExhaustive(target=es2015).js index cecc90d6baa..26dfbafde27 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionKeywordPropExhaustive(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionKeywordPropExhaustive(target=es2015).js @@ -288,7 +288,8 @@ declare namespace foo { export var string: number; export var symbol: number; export var type: number; - export var undefined: number; + export var _21: number; + export { _21 as undefined }; export var unique: number; export var unknown: number; export var from: number; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionWithDefaultAssignedMember.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionWithDefaultAssignedMember.js index ddbcc324644..eaa44c00403 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionWithDefaultAssignedMember.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionWithDefaultAssignedMember.js @@ -16,10 +16,9 @@ module.exports = foo; //// [index.d.ts] +export = foo; declare function foo(): void; declare namespace foo { - export var foo: typeof import("."); - var _a: typeof import("."); - export { _a as default }; + export { foo }; + export { foo as default }; } -export = foo; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctions.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctions.js index 503e699412c..c61062a6efe 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctions.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctions.js @@ -151,7 +151,7 @@ export declare function e(a: T, b: U): T & U; */ export declare function f(a: T): T; export declare namespace f { - var self: typeof f; + export { f as self }; } /** * @param {{x: string}} a diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionsCjs.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionsCjs.js index 049f4415b53..d73ff821be8 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionsCjs.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsFunctionsCjs.js @@ -120,6 +120,12 @@ export declare var c: () => void; export declare var d: (a: number, b: number) => string; export declare var e: (a: T, b: U) => T & U; export declare var f: (a: T) => T; +export { g }; +export { hh as h }; +export declare var i: () => void; +export declare var ii: () => void; +export declare var jj: () => void; +export declare var j: () => void; /** * @param {{x: string}} a * @param {{y: typeof module.exports.b}} b @@ -129,7 +135,6 @@ declare function g(a: { }, b: { y: () => void; }): void | ""; -export { g }; /** * @param {{x: string}} a * @param {{y: typeof module.exports.b}} b @@ -139,8 +144,3 @@ declare function hh(a: { }, b: { y: () => void; }): void | ""; -export { hh as h }; -export declare var i: () => void; -export declare var ii: () => void; -export declare var jj: () => void; -export declare var j: () => void; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsImportAliasExposedWithinNamespaceCjs.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsImportAliasExposedWithinNamespaceCjs.js index dd9f943d9b4..1899566abe4 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsImportAliasExposedWithinNamespaceCjs.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsImportAliasExposedWithinNamespaceCjs.js @@ -54,6 +54,7 @@ module.exports = {testFn, testFnTypes}; //// [file.d.ts] +export { myTypes }; /** * @namespace myTypes * @global @@ -78,8 +79,12 @@ export declare namespace myTypes { export declare namespace myTypes { export type typeC = myTypes.typeB | Function; } -export { myTypes }; //// [file2.d.ts] +declare const _exports: { + testFn: typeof testFn; + testFnTypes: Record; +}; +export = _exports; import { myTypes } from './file.js'; /** * @namespace testFnTypes @@ -98,8 +103,3 @@ export declare namespace testFnTypes { * @returns {number|null} Result. */ declare function testFn(input: testFnTypes.input): number | null; -declare const _default: { - testFn: typeof testFn; - testFnTypes: Record; -}; -export = _default; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsJson(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsJson(target=es2015).js index 798cfe60e43..1a68812c886 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsJson(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsJson(target=es2015).js @@ -27,21 +27,21 @@ module.exports = j; //// [index.d.ts] -import j = require("./obj.json"); export = j; +import j = require("./obj.json"); //// [DtsFileErrors] -out/index.d.ts(1,20): error TS2307: Cannot find module './obj.json' or its corresponding type declarations. +out/index.d.ts(2,20): error TS2307: Cannot find module './obj.json' or its corresponding type declarations. ==== out/index.d.ts (1 errors) ==== + export = j; import j = require("./obj.json"); ~~~~~~~~~~~~ !!! error TS2307: Cannot find module './obj.json' or its corresponding type declarations. - export = j; ==== obj.json (0 errors) ==== { diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsPackageJson(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsPackageJson(target=es2015).js index 6e9a1a5bc61..109365e0663 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsPackageJson(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsPackageJson(target=es2015).js @@ -76,21 +76,21 @@ module.exports = j; //// [index.d.ts] -import j = require("./package.json"); export = j; +import j = require("./package.json"); //// [DtsFileErrors] -out/index.d.ts(1,20): error TS2307: Cannot find module './package.json' or its corresponding type declarations. +out/index.d.ts(2,20): error TS2307: Cannot find module './package.json' or its corresponding type declarations. ==== out/index.d.ts (1 errors) ==== + export = j; import j = require("./package.json"); ~~~~~~~~~~~~~~~~ !!! error TS2307: Cannot find module './package.json' or its corresponding type declarations. - export = j; ==== package.json (0 errors) ==== { diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js index aefaea945d6..61c74727f70 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js @@ -62,14 +62,14 @@ const test = (base) => { //// [base.d.ts] +export = BaseFactory; declare class Base { constructor(); } declare function BaseFactory(): Base; declare namespace BaseFactory { - var Base: typeof Base; + export { Base }; } -export = BaseFactory; //// [file.d.ts] type BaseFactory = import('./base'); type BaseFactoryFactory = (factory: import('./base')) => any; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit2.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit2.js index 5e5b0d4922f..670e7396adb 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit2.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit2.js @@ -50,14 +50,14 @@ const test = (base) => { //// [base.d.ts] +export = BaseFactory; declare class Base { constructor(); } declare function BaseFactory(): Base; declare namespace BaseFactory { - var Base: typeof Base; + export { Base }; } -export = BaseFactory; //// [file.d.ts] /** @typedef {typeof import('./base')} BaseFactory */ type BaseFactory = typeof import('./base'); @@ -67,33 +67,3 @@ type BaseFactory = typeof import('./base'); * @returns {InstanceType} */ declare const test: (base: InstanceType) => InstanceType; - - -//// [DtsFileErrors] - - -out/base.d.ts(6,9): error TS2502: 'Base' is referenced directly or indirectly in its own type annotation. - - -==== out/base.d.ts (1 errors) ==== - declare class Base { - constructor(); - } - declare function BaseFactory(): Base; - declare namespace BaseFactory { - var Base: typeof Base; - ~~~~ -!!! error TS2502: 'Base' is referenced directly or indirectly in its own type annotation. - } - export = BaseFactory; - -==== out/file.d.ts (0 errors) ==== - /** @typedef {typeof import('./base')} BaseFactory */ - type BaseFactory = typeof import('./base'); - /** - * - * @param {InstanceType} base - * @returns {InstanceType} - */ - declare const test: (base: InstanceType) => InstanceType; - \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsReexportedCjsAlias(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsReexportedCjsAlias(target=es2015).js index 25c818ddb87..347cd00d425 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsReexportedCjsAlias(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsReexportedCjsAlias(target=es2015).js @@ -53,6 +53,11 @@ module.exports = { //// [lib.d.ts] +declare const _exports: { + bar: typeof bar; + SomeClass: typeof SomeClass; +}; +export = _exports; /** * @param {string} a */ @@ -60,13 +65,8 @@ declare function bar(a: string): string; declare class SomeClass { a(): number; } -declare const _default: { - bar: typeof bar; - SomeClass: typeof SomeClass; -}; -export = _default; //// [main.d.ts] -declare const _default: { +declare const _exports: { SomeClass: { new (): { a(): number; @@ -78,4 +78,4 @@ declare const _default: { }; }; }; -export = _default; +export = _exports; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsReferenceToClassInstanceCrossFile.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsReferenceToClassInstanceCrossFile.js index 3b348e4dcd0..da1fb3e976c 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsReferenceToClassInstanceCrossFile.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsReferenceToClassInstanceCrossFile.js @@ -42,14 +42,18 @@ console.log("Objects", render.objects); //// [rectangle.d.ts] +declare const _exports: { + Rectangle: typeof Rectangle; +}; +export = _exports; declare class Rectangle { constructor(); } -declare const _default: { - Rectangle: typeof Rectangle; -}; -export = _default; //// [index.d.ts] +declare const _exports: { + Render: typeof Render; +}; +export = _exports; declare class Render { /** * Object list @@ -64,9 +68,5 @@ declare class Render { */ addRectangle(): Rectangle; } -declare const _default: { - Render: typeof Render; -}; -export = _default; //// [test.d.ts] export {}; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeAliases.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeAliases.js index ba2967e0945..174bb02fd7d 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeAliases.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeAliases.js @@ -135,6 +135,11 @@ export type Identity = (x: T) => T; * @returns {T} */ //// [mixed.d.ts] +declare const _exports: { + doTheThing: typeof doTheThing; + ExportedThing: typeof ExportedThing; +}; +export = _exports; export type SomeType = { x: string; } | number | LocalThing | ExportedThing; @@ -149,11 +154,6 @@ declare function doTheThing(x: number): SomeType; declare class ExportedThing { z: string; } -declare const _default: { - doTheThing: typeof doTheThing; - ExportedThing: typeof ExportedThing; -}; -export = _default; declare class LocalThing { y: string; } diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeReassignmentFromDeclaration.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeReassignmentFromDeclaration.js index ff05a8c9dce..1f7b1212d92 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeReassignmentFromDeclaration.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeReassignmentFromDeclaration.js @@ -19,6 +19,6 @@ module.exports = items; //// [index.d.ts] +export = items; /** @type {typeof import("/some-mod")} */ declare const items: typeof import("/some-mod"); -export = items; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeReferences(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeReferences(target=es2015).js index 189b0b17e9c..8d1708ca930 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeReferences(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeReferences(target=es2015).js @@ -27,7 +27,7 @@ module.exports = { //// [index.d.ts] -declare const _default: { +declare const _exports: { thing: import("fs").Something; }; -export = _default; +export = _exports; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeReferences2(target=es2015).js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeReferences2(target=es2015).js index fe4c142dcb0..84479f39acc 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeReferences2(target=es2015).js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypeReferences2(target=es2015).js @@ -39,7 +39,7 @@ export declare const o: { m: number; }; //// [index.d.ts] -declare const _default: { +declare const _exports: { thing: number; }; -export = _default; +export = _exports; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypedefAndImportTypes.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypedefAndImportTypes.js index 454ee00fe08..11faffb1309 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypedefAndImportTypes.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypedefAndImportTypes.js @@ -70,17 +70,21 @@ module.exports = { /** * @typedef {string | number} Whatever */ +export = Conn; export type Whatever = string | number; declare class Conn { constructor(); item: number; method(): void; } -export = Conn; //// [usage.d.ts] /** * @typedef {import("./conn")} Conn */ +declare const _exports: { + Wrap: typeof Wrap; +}; +export = _exports; export type Conn = import("./conn"); declare class Wrap { connItem: number; @@ -91,7 +95,3 @@ declare class Wrap { */ constructor(c: Conn); } -declare const _default: { - Wrap: typeof Wrap; -}; -export = _default; diff --git a/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypedefPropertyAndExportAssignment.js b/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypedefPropertyAndExportAssignment.js index 1a11320d0fd..89995573360 100644 --- a/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypedefPropertyAndExportAssignment.js +++ b/testdata/baselines/reference/submodule/conformance/jsDeclarationsTypedefPropertyAndExportAssignment.js @@ -105,13 +105,7 @@ module.exports = MainThreadTasks; //// [module.d.ts] /** @typedef {'parseHTML'|'styleLayout'} TaskGroupIds */ -export type TaskGroupIds = 'parseHTML' | 'styleLayout'; -export type TaskGroup = { - id: TaskGroupIds; - label: string; - traceEventNames: string[]; -}; -declare const _default: { +declare const _exports: { taskGroups: { parseHTML: { id: "parseHTML"; @@ -124,8 +118,15 @@ declare const _default: { }; taskNameToGroup: Record; }; -export = _default; +export = _exports; +export type TaskGroupIds = 'parseHTML' | 'styleLayout'; +export type TaskGroup = { + id: TaskGroupIds; + label: string; + traceEventNames: string[]; +}; //// [index.d.ts] +export = MainThreadTasks; export type TaskGroup = import('./module.js').TaskGroup; export type TaskNode = { children: TaskNode[]; @@ -150,4 +151,3 @@ declare class MainThreadTasks { */ constructor(x: TaskGroup, y: TaskNode); } -export = MainThreadTasks; diff --git a/testdata/baselines/reference/submodule/conformance/moduleExportAliasElementAccessExpression.js b/testdata/baselines/reference/submodule/conformance/moduleExportAliasElementAccessExpression.js index 2839ad52e66..ad04de9403a 100644 --- a/testdata/baselines/reference/submodule/conformance/moduleExportAliasElementAccessExpression.js +++ b/testdata/baselines/reference/submodule/conformance/moduleExportAliasElementAccessExpression.js @@ -16,6 +16,6 @@ exports["Does not work yet"] = D; //// [moduleExportAliasElementAccessExpression.d.ts] -declare function D(): void; export { D }; export { D as "Does not work yet" }; +declare function D(): void; diff --git a/testdata/baselines/reference/submodule/conformance/nestedDestructuringOfRequire.js b/testdata/baselines/reference/submodule/conformance/nestedDestructuringOfRequire.js index 8dfe5025bef..c1b0fdf014f 100644 --- a/testdata/baselines/reference/submodule/conformance/nestedDestructuringOfRequire.js +++ b/testdata/baselines/reference/submodule/conformance/nestedDestructuringOfRequire.js @@ -28,9 +28,9 @@ chalk; //// [mod1.d.ts] +export { chalk }; declare const chalk: { grey: {}; }; -export { chalk }; //// [main.d.ts] export {}; diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=node16).js b/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=node16).js index 2305e2ee032..474768908da 100644 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=node16).js +++ b/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=node16).js @@ -53,8 +53,8 @@ module.exports = a; declare const a: {}; export = a; //// [file.d.ts] -declare const a: {}; export = a; +declare const a: {}; //// [index.d.ts] declare const a: {}; export = a; diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=node18).js b/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=node18).js index 2305e2ee032..474768908da 100644 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=node18).js +++ b/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=node18).js @@ -53,8 +53,8 @@ module.exports = a; declare const a: {}; export = a; //// [file.d.ts] -declare const a: {}; export = a; +declare const a: {}; //// [index.d.ts] declare const a: {}; export = a; diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=node20).js b/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=node20).js index 2305e2ee032..474768908da 100644 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=node20).js +++ b/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=node20).js @@ -53,8 +53,8 @@ module.exports = a; declare const a: {}; export = a; //// [file.d.ts] -declare const a: {}; export = a; +declare const a: {}; //// [index.d.ts] declare const a: {}; export = a; diff --git a/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=nodenext).js b/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=nodenext).js index 2305e2ee032..474768908da 100644 --- a/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=nodenext).js +++ b/testdata/baselines/reference/submodule/conformance/nodeModulesAllowJsExportAssignment(module=nodenext).js @@ -53,8 +53,8 @@ module.exports = a; declare const a: {}; export = a; //// [file.d.ts] -declare const a: {}; export = a; +declare const a: {}; //// [index.d.ts] declare const a: {}; export = a; diff --git a/testdata/baselines/reference/submodule/conformance/nullPropertyName.js b/testdata/baselines/reference/submodule/conformance/nullPropertyName.js index 0708d76a769..2cf307d9ef8 100644 --- a/testdata/baselines/reference/submodule/conformance/nullPropertyName.js +++ b/testdata/baselines/reference/submodule/conformance/nullPropertyName.js @@ -289,7 +289,8 @@ declare namespace foo { export var string: number; export var symbol: number; export var type: number; - export var undefined: number; + export var _21: number; + export { _21 as undefined }; export var unique: number; export var unknown: number; export var from: number; diff --git a/testdata/baselines/reference/submoduleAccepted/compiler/amdLikeInputDeclarationEmit.errors.txt.diff b/testdata/baselines/reference/submoduleAccepted/compiler/amdLikeInputDeclarationEmit.errors.txt.diff deleted file mode 100644 index b4fe80c2893..00000000000 --- a/testdata/baselines/reference/submoduleAccepted/compiler/amdLikeInputDeclarationEmit.errors.txt.diff +++ /dev/null @@ -1,38 +0,0 @@ ---- old.amdLikeInputDeclarationEmit.errors.txt -+++ new.amdLikeInputDeclarationEmit.errors.txt -@@= skipped -0, +0 lines =@@ -- -+ExtendedClass.js(17,5): error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. -+ -+ -+==== typing.d.ts (0 errors) ==== -+ declare function define(name: string, modules: string[], ready: (...modules: unknown[]) => T); -+==== deps/BaseClass.d.ts (0 errors) ==== -+ declare module "deps/BaseClass" { -+ class BaseClass { -+ static extends(a: A): new () => A & BaseClass; -+ } -+ export = BaseClass; -+ } -+==== ExtendedClass.js (1 errors) ==== -+ define("lib/ExtendedClass", ["deps/BaseClass"], -+ /** -+ * {typeof import("deps/BaseClass")} -+ * @param {typeof import("deps/BaseClass")} BaseClass -+ * @returns -+ */ -+ (BaseClass) => { -+ -+ const ExtendedClass = BaseClass.extends({ -+ f: function() { -+ return "something"; -+ } -+ }); -+ -+ // Exports the module in a way tsc recognize class export -+ const module = {}; -+ module.exports = ExtendedClass -+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -+!!! error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. -+ return module.exports; -+ }); \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/compiler/amdLikeInputDeclarationEmit.js.diff b/testdata/baselines/reference/submoduleAccepted/compiler/amdLikeInputDeclarationEmit.js.diff index a81e1fd06e7..8f4f5a76012 100644 --- a/testdata/baselines/reference/submoduleAccepted/compiler/amdLikeInputDeclarationEmit.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/compiler/amdLikeInputDeclarationEmit.js.diff @@ -1,43 +1,11 @@ --- old.amdLikeInputDeclarationEmit.js +++ new.amdLikeInputDeclarationEmit.js -@@= skipped -0, +0 lines =@@ --//// [tests/cases/compiler/amdLikeInputDeclarationEmit.ts] //// -- --//// [typing.d.ts] --declare function define(name: string, modules: string[], ready: (...modules: unknown[]) => T); --//// [BaseClass.d.ts] --declare module "deps/BaseClass" { -- class BaseClass { -- static extends(a: A): new () => A & BaseClass; -- } -- export = BaseClass; --} --//// [ExtendedClass.js] --define("lib/ExtendedClass", ["deps/BaseClass"], --/** -- * {typeof import("deps/BaseClass")} -- * @param {typeof import("deps/BaseClass")} BaseClass -- * @returns -- */ --(BaseClass) => { -- -- const ExtendedClass = BaseClass.extends({ -- f: function() { -- return "something"; -- } -- }); -- -- // Exports the module in a way tsc recognize class export -- const module = {}; -- module.exports = ExtendedClass -- return module.exports; --}); -- -- -- --//// [ExtendedClass.d.ts] +@@= skipped -32, +32 lines =@@ + + + //// [ExtendedClass.d.ts] -export = ExtendedClass; --declare const ExtendedClass: new () => { -- f: () => "something"; --} & import("deps/BaseClass"); -+ \ No newline at end of file + declare const ExtendedClass: new () => { + f: () => "something"; + } & import("deps/BaseClass"); ++export = ExtendedClass; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/compiler/declarationEmitDefaultExportWithStaticAssignment.js.diff b/testdata/baselines/reference/submoduleAccepted/compiler/declarationEmitDefaultExportWithStaticAssignment.js.diff index ad314614786..73fd0ef4989 100644 --- a/testdata/baselines/reference/submoduleAccepted/compiler/declarationEmitDefaultExportWithStaticAssignment.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/compiler/declarationEmitDefaultExportWithStaticAssignment.js.diff @@ -1,12 +1,15 @@ --- old.declarationEmitDefaultExportWithStaticAssignment.js +++ new.declarationEmitDefaultExportWithStaticAssignment.js -@@= skipped -82, +82 lines =@@ +@@= skipped -81, +81 lines =@@ + export declare class Foo { } //// [index1.d.ts] ++import { Foo } from './foo'; declare function Example(): void; +export default Example; declare namespace Example { - var Foo: typeof import("./foo").Foo; +- var Foo: typeof import("./foo").Foo; ++ export { Foo }; } -export default Example; //// [index2.d.ts] @@ -15,7 +18,8 @@ declare function Example(): void; +export default Example; declare namespace Example { - var Foo: typeof import("./foo").Foo; +- var Foo: typeof import("./foo").Foo; ++ export { Foo }; } -export default Example; //// [index3.d.ts] @@ -24,9 +28,18 @@ declare function Example(): void; +export default Example; declare namespace Example { - var Bar: typeof import("./index3").Bar; +- var Bar: typeof import("./index3").Bar; ++ export { Bar }; } -export default Example; //// [index4.d.ts] ++declare function A(): void; ++declare function B(): void; export declare function C(): any; - export declare namespace C { \ No newline at end of file + export declare namespace C { +- var A: () => void; +- var B: () => void; ++ export { A }; ++ export { B }; + } ++export {}; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/compiler/jsDeclarationEmitExportAssignedArray.js.diff b/testdata/baselines/reference/submoduleAccepted/compiler/jsDeclarationEmitExportAssignedArray.js.diff deleted file mode 100644 index 44e36dcdbdd..00000000000 --- a/testdata/baselines/reference/submoduleAccepted/compiler/jsDeclarationEmitExportAssignedArray.js.diff +++ /dev/null @@ -1,14 +0,0 @@ ---- old.jsDeclarationEmitExportAssignedArray.js -+++ new.jsDeclarationEmitExportAssignedArray.js -@@= skipped -5, +5 lines =@@ - - - //// [file.d.ts] --declare const _exports: { -+declare const _default: { - name: string; - displayName: string; - defaultEnabled: boolean; - }[]; --export = _exports; -+export = _default; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.js.diff b/testdata/baselines/reference/submoduleAccepted/compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.js.diff index a798da462bd..fee2706376f 100644 --- a/testdata/baselines/reference/submoduleAccepted/compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.js.diff @@ -14,11 +14,11 @@ + * @typedef Options + * @property {string} opt + */ -+export type Options = { - opt: string; - }; ++export = loader; +/** + * @param {Options} options + */ -+declare const _default: (options: Options) => void; -+export = _default; \ No newline at end of file ++declare function loader(options: Options): void; ++export type Options = { + opt: string; + }; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/compiler/jsExportAssignmentNonMutableLocation.js.diff b/testdata/baselines/reference/submoduleAccepted/compiler/jsExportAssignmentNonMutableLocation.js.diff index 9d7a1df7129..8a1863685b8 100644 --- a/testdata/baselines/reference/submoduleAccepted/compiler/jsExportAssignmentNonMutableLocation.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/compiler/jsExportAssignmentNonMutableLocation.js.diff @@ -6,8 +6,10 @@ //// [file.d.ts] -export const customSymbol2: unique symbol; -export const customSymbol: unique symbol; -+declare const _default: { ++declare const _exports: { + customSymbol: symbol; +}; -+export = _default; -+export declare var customSymbol2: symbol; \ No newline at end of file ++export = _exports; ++declare namespace _exports { ++ export var customSymbol2: symbol; ++} \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/compiler/jsxDeclarationsWithEsModuleInteropNoCrash.js.diff b/testdata/baselines/reference/submoduleAccepted/compiler/jsxDeclarationsWithEsModuleInteropNoCrash.js.diff index 4efa57d164d..b85bc915540 100644 --- a/testdata/baselines/reference/submoduleAccepted/compiler/jsxDeclarationsWithEsModuleInteropNoCrash.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/compiler/jsxDeclarationsWithEsModuleInteropNoCrash.js.diff @@ -6,13 +6,19 @@ /// -export default Foo; +import PropTypes from 'prop-types'; ++declare const propTypes: { ++ bar: PropTypes.Requireable; ++}; ++declare const defaultProps: { ++ bar: boolean; ++}; declare function Foo({ bar }: { bar: any; }): JSX.Element; - declare namespace Foo { -- export { propTypes }; -- export { defaultProps }; --} +@@= skipped -8, +14 lines =@@ + export { propTypes }; + export { defaultProps }; + } -declare namespace propTypes { - let bar: PropTypes.Requireable; -} @@ -21,11 +27,4 @@ - export { bar_1 as bar }; -} -import PropTypes from 'prop-types'; -+ var propTypes: { -+ bar: PropTypes.Requireable; -+ }; -+ var defaultProps: { -+ bar: boolean; -+ }; -+} +export default Foo; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/compiler/modulePreserve4.js.diff b/testdata/baselines/reference/submoduleAccepted/compiler/modulePreserve4.js.diff index a03578236d4..5b31c0f3c9b 100644 --- a/testdata/baselines/reference/submoduleAccepted/compiler/modulePreserve4.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/compiler/modulePreserve4.js.diff @@ -12,9 +12,13 @@ export default _default; //// [c.d.ts] declare const _default: { -@@= skipped -13, +13 lines =@@ - declare const _default: () => void; +@@= skipped -10, +10 lines =@@ + }; export = _default; + //// [d.d.ts] +-declare const _default: () => void; + export = _default; ++declare function _default(): void; //// [e.d.mts] -declare const _default: 0; +declare const _default = 0; @@ -25,7 +29,7 @@ export default _default; //// [g.d.ts] declare const _default: 0; -@@= skipped -15, +15 lines =@@ +@@= skipped -18, +18 lines =@@ //// [main3.d.cts] export {}; //// [main4.d.cts] diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/assignmentToVoidZero1.errors.txt.diff b/testdata/baselines/reference/submoduleAccepted/conformance/assignmentToVoidZero1.errors.txt.diff index c39f58a997f..69b2d1c68ae 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/assignmentToVoidZero1.errors.txt.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/assignmentToVoidZero1.errors.txt.diff @@ -6,18 +6,13 @@ - - -==== assignmentToVoidZero1.js (2 errors) ==== -+assignmentToVoidZero1.js(2,13): error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. -+ -+ -+==== assignmentToVoidZero1.js (1 errors) ==== - // #38552 - exports.y = exports.x = void 0; +- // #38552 +- exports.y = exports.x = void 0; - ~~~~~~~~~ -!!! error TS2322: Type 'undefined' is not assignable to type '2'. - ~~~~~~~~~ -!!! error TS2322: Type 'undefined' is not assignable to type '1'. -+ ~~~~~~~~~~~~~~~~~~ -+!!! error TS6425: Nested CommonJS export constructs cannot be serialized for declaration emit. - exports.x = 1; - exports.y = 2; - \ No newline at end of file +- exports.x = 1; +- exports.y = 2; +- ++ \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/assignmentToVoidZero1.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/assignmentToVoidZero1.js.diff index baca5bd924a..7dc7af219f4 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/assignmentToVoidZero1.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/assignmentToVoidZero1.js.diff @@ -1,11 +1,10 @@ --- old.assignmentToVoidZero1.js +++ new.assignmentToVoidZero1.js -@@= skipped -12, +12 lines =@@ - exports.y = exports.x = void 0; - exports.x = 1; - exports.y = 2; -- -- --//// [assignmentToVoidZero1.d.ts] +@@= skipped -15, +15 lines =@@ + + + //// [assignmentToVoidZero1.d.ts] -export const x: 1; --export const y: 2; \ No newline at end of file +-export const y: 2; ++export declare var y: 2; ++export declare var x: 1; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/commonJSAliasedExport.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/commonJSAliasedExport.js.diff index ffec8c47604..3618d043bb2 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/commonJSAliasedExport.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/commonJSAliasedExport.js.diff @@ -1,17 +1,15 @@ --- old.commonJSAliasedExport.js +++ new.commonJSAliasedExport.js -@@= skipped -33, +33 lines =@@ - +@@= skipped -34, +34 lines =@@ //// [commonJSAliasedExport.d.ts] --export = donkey; + export = donkey; -declare function donkey(ast: any): any; -declare namespace donkey { - export { funky }; -} ++export { funky }; +declare const donkey: (ast: any) => any; declare function funky(declaration: any): boolean; -+export = donkey; -+export { funky }; //// [bug43713.d.ts] export {}; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/commonJSImportClassTypeReference.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/commonJSImportClassTypeReference.js.diff index b6d208dac7a..fe699a3a08e 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/commonJSImportClassTypeReference.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/commonJSImportClassTypeReference.js.diff @@ -5,9 +5,8 @@ //// [mod1.d.ts] -export class K { ++export { K }; +declare class K { values(): K; } -+export { K }; - //// [main.d.ts] - export {}; \ No newline at end of file + //// [main.d.ts] \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsClassExtendsVisibility(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsClassExtendsVisibility(target=es2015).js.diff index 7803a5e109c..47d97abe0fe 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsClassExtendsVisibility(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsClassExtendsVisibility(target=es2015).js.diff @@ -1,20 +1,15 @@ --- old.jsDeclarationsClassExtendsVisibility(target=es2015).js +++ new.jsDeclarationsClassExtendsVisibility(target=es2015).js -@@= skipped -31, +31 lines =@@ - - - //// [bar.d.ts] --export = Bar; - declare class Bar { +@@= skipped -36, +36 lines =@@ } -+export = Bar; //// [cls.d.ts] --export = Foo; + export = Foo; -declare class Foo extends Bar { -} -declare namespace Foo { - export { Strings }; -} ++export { Strings }; import Bar = require("./bar"); -declare namespace Strings { - let a: string; @@ -24,6 +19,4 @@ + b: string; +}; +declare class Foo extends Bar { - } -+export = Foo; -+export { Strings }; \ No newline at end of file + } \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsClassStatic(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsClassStatic(target=es2015).js.diff index dc85734a382..3be99878773 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsClassStatic(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsClassStatic(target=es2015).js.diff @@ -1,10 +1,10 @@ --- old.jsDeclarationsClassStatic(target=es2015).js +++ new.jsDeclarationsClassStatic(target=es2015).js -@@= skipped -48, +48 lines =@@ - +@@= skipped -49, +49 lines =@@ //// [source.d.ts] --export = Handler; + export = Handler; ++export { Strings }; declare class Handler { static get OPTIONS(): number; process(): void; @@ -24,8 +24,6 @@ + a: string; + b: string; +}; -+export = Handler; -+export { Strings }; +export type HandlerOptions = { /** * Should be able to export a type alias at the same time. diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsCommonjsRelativePath.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsCommonjsRelativePath.js.diff index e230b77856d..6cba8acd089 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsCommonjsRelativePath.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsCommonjsRelativePath.js.diff @@ -5,19 +5,19 @@ //// [thing.d.ts] -export class Thing { -+declare class Thing { - } -+declare const _default: { ++declare const _exports: { + Thing: typeof Thing; +}; -+export = _default; ++export = _exports; ++declare class Thing { + } //// [reexport.d.ts] -export { Thing }; -import Thing_1 = require("./thing"); -import Thing = Thing_1.Thing; -+declare const _default: { ++declare const _exports: { + Thing: { + new (): {}; + }; +}; -+export = _default; \ No newline at end of file ++export = _exports; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsComputedNames(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsComputedNames(target=es2015).js.diff index 11eef9f72c0..38ca97ed2b9 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsComputedNames(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsComputedNames(target=es2015).js.diff @@ -1,24 +1,8 @@ --- old.jsDeclarationsComputedNames(target=es2015).js +++ new.jsDeclarationsComputedNames(target=es2015).js -@@= skipped -61, +61 lines =@@ - - - //// [index.d.ts] --declare const _exports: { -+declare const TopLevelSym: unique symbol; -+declare const InnerSym: unique symbol; -+declare const _default: { - [TopLevelSym](x?: number): number; - items: { - [InnerSym]: (arg?: { -@@= skipped -8, +10 lines =@@ - }) => number; - }; - }; --export = _exports; --declare const TopLevelSym: unique symbol; --declare const InnerSym: unique symbol; -+export = _default; +@@= skipped -73, +73 lines =@@ + declare const TopLevelSym: unique symbol; + declare const InnerSym: unique symbol; //// [index2.d.ts] -export class MyClass { +declare const TopLevelSym: unique symbol; diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsCrossfileMerge(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsCrossfileMerge(target=es2015).js.diff index 11e2bfe7e65..1e46eaf6963 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsCrossfileMerge(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsCrossfileMerge(target=es2015).js.diff @@ -4,19 +4,19 @@ module.exports.memberName = "thing"; --//// [index.d.ts] --declare const _exports: typeof m.default; --export = _exports; --import m = require("./exporter"); ++//// [exporter.d.ts] ++declare function validate(): void; ++export default validate; + //// [index.d.ts] + declare const _exports: typeof m.default; + export = _exports; ++declare namespace _exports { ++ export var memberName: "thing"; ++} + import m = require("./exporter"); - - -!!!! File out/exporter.d.ts missing from original emit, but present in noCheck emit - //// [exporter.d.ts] +-//// [exporter.d.ts] -export default validate; - declare function validate(): void; -+export default validate; -+//// [index.d.ts] -+import m = require("./exporter"); -+declare const _default: typeof m.default; -+export = _default; -+export declare var memberName: "thing"; \ No newline at end of file +-declare function validate(): void; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsDocCommentsOnConsts(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsDocCommentsOnConsts(target=es2015).js.diff index 489e74d414c..e944ce29d4c 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsDocCommentsOnConsts(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsDocCommentsOnConsts(target=es2015).js.diff @@ -1,19 +1,20 @@ --- old.jsDeclarationsDocCommentsOnConsts(target=es2015).js +++ new.jsDeclarationsDocCommentsOnConsts(target=es2015).js -@@= skipped -35, +35 lines =@@ +@@= skipped -34, +34 lines =@@ + //// [index1.d.ts] - /** +-/** - * const doc comment - */ -export function x(a: any): string; --/** - * function doc comment - */ --export function b(): number; -+declare function b(): number; -+declare const _default: { ++declare const _exports: { + x: (a: any) => string; + b: typeof b; +}; -+export = _default; \ No newline at end of file ++export = _exports; + /** + * function doc comment + */ +-export function b(): number; ++declare function b(): number; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js.diff index 5d5ed381ec7..610bb92eca4 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionAnonymous(target=es2015).js.diff @@ -6,8 +6,8 @@ //// [index.d.ts] -export = exports; -declare class exports { -+export = _default; -+declare class _default { ++export = _exports; ++declare class _exports { + t: number; /** * @param {number} p diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js.diff index f320f390039..2c77d4b2fd6 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionAnonymousWithSub(target=es2015).js.diff @@ -6,8 +6,8 @@ //// [index.d.ts] -export = exports; -declare class exports { -+export = _default; -+declare class _default { ++export = _exports; ++declare class _exports { + t: number; /** * @param {number} p @@ -19,8 +19,11 @@ - export { Sub }; -} -declare class Sub { +- instance: import("."); +} -+export declare class Sub { - instance: import("."); -+ constructor(); ++declare namespace _exports { ++ export class Sub { ++ instance: import("."); ++ constructor(); ++ } } \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js.diff index 67e53eb2d5a..daada25fd09 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassExpressionShadowing(target=es2015).js.diff @@ -6,23 +6,20 @@ //// [index.d.ts] -export = Q; -declare class Q { -- x: A; --} ++export = Q_1; ++declare class Q_1 { + x: A; ++ constructor(); + } -declare namespace Q { - export { Q_1 as Another }; --} ++declare namespace Q_1 { ++ export { Q as Another }; + } declare class A { member: Q; } -declare class Q_1 { +declare class Q { x: number; -+} -+export = Q_1; -+declare class Q_1 { -+ x: A; -+ constructor(); -+} -+declare namespace Q_1 { -+ export { Q as Another }; } \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassInstance1(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassInstance1(target=es2015).js.diff deleted file mode 100644 index 035027565a3..00000000000 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassInstance1(target=es2015).js.diff +++ /dev/null @@ -1,12 +0,0 @@ ---- old.jsDeclarationsExportAssignedClassInstance1(target=es2015).js -+++ new.jsDeclarationsExportAssignedClassInstance1(target=es2015).js -@@= skipped -12, +12 lines =@@ - - - //// [index.d.ts] --declare const _exports: Foo; --export = _exports; - declare class Foo { - } -+declare const _default: Foo; -+export = _default; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassInstance2(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassInstance2(target=es2015).js.diff deleted file mode 100644 index e9419a589a5..00000000000 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassInstance2(target=es2015).js.diff +++ /dev/null @@ -1,14 +0,0 @@ ---- old.jsDeclarationsExportAssignedClassInstance2(target=es2015).js -+++ new.jsDeclarationsExportAssignedClassInstance2(target=es2015).js -@@= skipped -19, +19 lines =@@ - - - //// [index.d.ts] --declare const _exports: Foo; --export = _exports; - declare class Foo { - static stat: number; - member: number; - } -+declare const _default: Foo; -+export = _default; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassInstance3(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassInstance3(target=es2015).js.diff index 1bbbe195491..f127b1a16bc 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassInstance3(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedClassInstance3(target=es2015).js.diff @@ -6,10 +6,12 @@ //// [index.d.ts] -export let member: number; -export let additional: 20; ++declare const _exports: Foo; ++export = _exports; ++declare namespace _exports { ++ export var additional: 20; ++} +declare class Foo { + static stat: number; + member: number; -+} -+declare const _default: Foo; -+export = _default; -+export declare var additional: 20; \ No newline at end of file ++} \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js.diff index fc4ba1fffee..a6c3a5df604 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignedVisibility(target=es2015).js.diff @@ -7,11 +7,10 @@ + constructor(); } //// [index.d.ts] --export = Container; + export = Container; +import Obj = require("./obj"); declare class Container { usage: Obj; + constructor(); } --import Obj = require("./obj"); -+export = Container; \ No newline at end of file +-import Obj = require("./obj"); \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignmentExpressionPlusSecondary(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignmentExpressionPlusSecondary(target=es2015).js.diff index 252fb0716a5..57ea1ef4b6a 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignmentExpressionPlusSecondary(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignmentExpressionPlusSecondary(target=es2015).js.diff @@ -13,16 +13,18 @@ -export declare namespace desc { - let item: string; -} -+declare const Strings: { -+ a: string; -+ b: string; -+}; -+declare const _default: { ++declare const _exports: { + thing: string; + also: string; + desc: { + item: string; + }; +}; -+export = _default; -+export { Strings }; \ No newline at end of file ++export = _exports; ++declare namespace _exports { ++ export { Strings }; ++} ++declare const Strings: { ++ a: string; ++ b: string; ++}; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignmentWithKeywordName(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignmentWithKeywordName(target=es2015).js.diff index 9a59032426b..712d024d004 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignmentWithKeywordName(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportAssignmentWithKeywordName(target=es2015).js.diff @@ -10,11 +10,11 @@ - let others: string[]; -} -export { _extends as extends }; -+declare const _default: { ++declare const _exports: { + extends: string; + more: { + others: string[]; + }; + x: number; +}; -+export = _default; \ No newline at end of file ++export = _exports; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportDefinePropertyEmit.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportDefinePropertyEmit.js.diff index f34927faaed..3eb0a810114 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportDefinePropertyEmit.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportDefinePropertyEmit.js.diff @@ -11,6 +11,19 @@ -} +export declare var a: () => void; +export declare var b: () => void; ++declare const _exported: typeof d; ++export { _exported as d }; ++declare const _exported_1: typeof e; ++export { _exported_1 as e }; ++declare const _exported_2: typeof f; ++export { _exported_2 as f }; ++declare const _exported_3: typeof g; ++export { _exported_3 as g }; ++export declare var h: typeof hh; ++export declare var i: () => void; ++export declare var ii: () => void; ++export declare var jj: () => void; ++export declare var j: () => void; /** * @param {number} a * @param {number} b @@ -18,8 +31,6 @@ */ -export function d(a: number, b: number): string; +declare function d(a: number, b: number): string; -+declare const _exported: typeof d; -+export { _exported as d }; /** * @template T,U * @param {T} a @@ -28,8 +39,6 @@ */ -export function e(a: T, b: U): T & U; +declare function e(a: T, b: U): T & U; -+declare const _exported_1: typeof e; -+export { _exported_1 as e }; /** * @template T * @param {T} a @@ -65,8 +74,6 @@ -export function jj(): void; -export function j(): void; +declare function f(a: T): T; -+declare const _exported_2: typeof f; -+export { _exported_2 as f }; +/** + * @param {{x: string}} a + * @param {{y: typeof module.exports.b}} b @@ -76,8 +83,6 @@ +}, b: { + y: () => void; +}): void | ""; -+declare const _exported_3: typeof g; -+export { _exported_3 as g }; +/** + * @param {{x: string}} a + * @param {{y: typeof module.exports.b}} b @@ -86,9 +91,4 @@ + x: string; +}, b: { + y: () => void; -+}): void | ""; -+export declare var h: typeof hh; -+export declare var i: () => void; -+export declare var ii: () => void; -+export declare var jj: () => void; -+export declare var j: () => void; \ No newline at end of file ++}): void | ""; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportDoubleAssignmentInClosure.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportDoubleAssignmentInClosure.js.diff index 8338d8464eb..e3ceede5657 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportDoubleAssignmentInClosure.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportDoubleAssignmentInClosure.js.diff @@ -1,15 +1,14 @@ --- old.jsDeclarationsExportDoubleAssignmentInClosure.js +++ new.jsDeclarationsExportDoubleAssignmentInClosure.js -@@= skipped -24, +24 lines =@@ - }; - exports.methods = m; - } -- -- --//// [index.d.ts] --declare function _exports(o: any): any; --declare namespace _exports { +@@= skipped -27, +27 lines =@@ + + + //// [index.d.ts] ++export = _exports; + declare function _exports(o: any): any; + declare namespace _exports { - export { m as methods }; --} ++ export var methods: () => void; + } -export = _exports; -declare function m(): void; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportForms(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportForms(target=es2015).js.diff index 41c61509684..d6a8dd90a97 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportForms(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportForms(target=es2015).js.diff @@ -33,23 +33,16 @@ -import * as ns from "./cls"; //// [cjs.d.ts] -export { ns }; - import ns = require("./cls"); -+declare const _default: { ++declare const _exports: { + ns: typeof ns; +}; -+export = _default; ++export = _exports; + import ns = require("./cls"); //// [cjs2.d.ts] -+import ns = require("./cls"); export = ns; --import ns = require("./cls"); - //// [cjs3.d.ts] --export { ns }; - import ns = require("./cls"); -+export { ns }; - //// [cjs4.d.ts] -+import ns = require("./cls"); +@@= skipped -34, +37 lines =@@ export { ns as names }; --import ns = require("./cls"); + import ns = require("./cls"); //// [includeAll.d.ts] -export {}; +import "./cjs4"; diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportSubAssignments(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportSubAssignments(target=es2015).js.diff index 747dc7dd974..3928d1b3f66 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportSubAssignments(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportSubAssignments(target=es2015).js.diff @@ -1,22 +1,20 @@ --- old.jsDeclarationsExportSubAssignments(target=es2015).js +++ new.jsDeclarationsExportSubAssignments(target=es2015).js -@@= skipped -21, +21 lines =@@ - +@@= skipped -22, +22 lines =@@ //// [cls.d.ts] --export = Foo; + export = Foo; ++export { Strings }; +declare const Strings: { + a: string; + b: string; +}; declare class Foo { - } +-} -declare namespace Foo { - export { Strings }; -} -declare namespace Strings { - let a: string; - let b: string; --} -+export = Foo; -+export { Strings }; \ No newline at end of file + } \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportedClassAliases.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportedClassAliases.js.diff index 733e666d239..9a3bb80109b 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportedClassAliases.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsExportedClassAliases.js.diff @@ -5,17 +5,17 @@ //// [errors.d.ts] -export class FancyError extends Error { ++declare const _exports: { ++ FancyError: typeof FancyError; ++}; ++export = _exports; +declare class FancyError extends Error { constructor(status: any); } -+declare const _default: { -+ FancyError: typeof FancyError; -+}; -+export = _default; //// [index.d.ts] -export { errors }; -import errors = require("./errors"); -+declare const _default: { ++declare const _exports: { + errors: { + FancyError: { + new (status: any): { @@ -26,4 +26,4 @@ + }; + }; +}; -+export = _default; \ No newline at end of file ++export = _exports; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionClassesCjsExportAssignment(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionClassesCjsExportAssignment(target=es2015).js.diff index 63c8cac84a9..8c8226ac124 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionClassesCjsExportAssignment(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionClassesCjsExportAssignment(target=es2015).js.diff @@ -1,11 +1,6 @@ --- old.jsDeclarationsFunctionClassesCjsExportAssignment(target=es2015).js +++ new.jsDeclarationsFunctionClassesCjsExportAssignment(target=es2015).js -@@= skipped -140, +140 lines =@@ - - - //// [timer.d.ts] --export = Timer; - /** +@@= skipped -145, +145 lines =@@ * @param {number} timeout */ declare function Timer(timeout: number): void; @@ -16,16 +11,16 @@ - constructor(timeout: number); - timeout: number; -} -+export = Timer; //// [context.d.ts] -export = Context; /** * Imports * -@@= skipped -21, +13 lines =@@ +@@= skipped -16, +8 lines =@@ * @typedef {import("./hook")} Hook * @typedef {import("./hook").HookHandler} HookHandler */ ++export = Context; +export type Timer = import("./timer"); +export type Hook = import("./hook"); +export type HookHandler = import("./hook").HookHandler; @@ -40,7 +35,7 @@ /** * Input type definition * -@@= skipped -20, +31 lines =@@ +@@= skipped -20, +32 lines =@@ * @class * @param {Input} input */ @@ -120,9 +115,8 @@ - timer: Timer; - hook: Hook; -}; -+export = Context; //// [hook.d.ts] --export = Hook; + export = Hook; +export type HookHandler = (arg: import("./context")) => void; /** * @typedef {(arg: import("./context")) => void} HookHandler @@ -144,5 +138,4 @@ -declare namespace Hook { - export { HookHandler }; -} --type HookHandler = (arg: import("./context")) => void; -+export = Hook; \ No newline at end of file +-type HookHandler = (arg: import("./context")) => void; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionKeywordPropExhaustive(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionKeywordPropExhaustive(target=es2015).js.diff index 4df762f24e8..f73baba21e3 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionKeywordPropExhaustive(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionKeywordPropExhaustive(target=es2015).js.diff @@ -243,7 +243,8 @@ + export var string: number; + export var symbol: number; + export var type: number; -+ export var undefined: number; ++ export var _21: number; ++ export { _21 as undefined }; + export var unique: number; + export var unknown: number; + export var from: number; diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionWithDefaultAssignedMember.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionWithDefaultAssignedMember.js.diff deleted file mode 100644 index 06617636f6a..00000000000 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionWithDefaultAssignedMember.js.diff +++ /dev/null @@ -1,16 +0,0 @@ ---- old.jsDeclarationsFunctionWithDefaultAssignedMember.js -+++ new.jsDeclarationsFunctionWithDefaultAssignedMember.js -@@= skipped -15, +15 lines =@@ - - - //// [index.d.ts] --export = foo; - declare function foo(): void; - declare namespace foo { -- export { foo }; -- export { foo as default }; -+ export var foo: typeof import("."); -+ var _a: typeof import("."); -+ export { _a as default }; - } -+export = foo; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctions.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctions.js.diff index 134b02d0196..b1dcc9ec08d 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctions.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctions.js.diff @@ -43,15 +43,14 @@ */ -export function f(a: T): T; -export namespace f { -- export { f as self }; --} --export function i(): void; --export function j(): void; --declare class Cls { +export declare function f(a: T): T; +export declare namespace f { -+ var self: typeof f; + export { f as self }; } +-export function i(): void; +-export function j(): void; +-declare class Cls { +-} /** * @param {{x: string}} a * @param {{y: typeof b}} b diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionsCjs.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionsCjs.js.diff index a5d4596d66a..2330b548557 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionsCjs.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsFunctionsCjs.js.diff @@ -30,6 +30,12 @@ +export declare var d: (a: number, b: number) => string; +export declare var e: (a: T, b: U) => T & U; +export declare var f: (a: T) => T; ++export { g }; ++export { hh as h }; ++export declare var i: () => void; ++export declare var ii: () => void; ++export declare var jj: () => void; ++export declare var j: () => void; /** * @param {{x: string}} a * @param {{y: typeof module.exports.b}} b @@ -44,11 +50,9 @@ - }; + y: () => void; }): void | ""; -+export { g }; /** * @param {{x: string}} a - * @param {{y: typeof module.exports.b}} b -@@= skipped -39, +23 lines =@@ +@@= skipped -39, +28 lines =@@ declare function hh(a: { x: string; }, b: { @@ -58,9 +62,4 @@ - }; + y: () => void; }): void | ""; --export { hh as h, i as ii, j as jj }; -+export { hh as h }; -+export declare var i: () => void; -+export declare var ii: () => void; -+export declare var jj: () => void; -+export declare var j: () => void; \ No newline at end of file +-export { hh as h, i as ii, j as jj }; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsImportAliasExposedWithinNamespaceCjs.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsImportAliasExposedWithinNamespaceCjs.js.diff index 0dae91bcba7..d91f20e000b 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsImportAliasExposedWithinNamespaceCjs.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsImportAliasExposedWithinNamespaceCjs.js.diff @@ -1,6 +1,12 @@ --- old.jsDeclarationsImportAliasExposedWithinNamespaceCjs.js +++ new.jsDeclarationsImportAliasExposedWithinNamespaceCjs.js -@@= skipped -58, +58 lines =@@ +@@= skipped -53, +53 lines =@@ + + + //// [file.d.ts] ++export { myTypes }; + /** + * @namespace myTypes * @global * @type {Object} */ @@ -19,18 +25,21 @@ /** * - Prop 1. */ -@@= skipped -15, +15 lines =@@ +@@= skipped -20, +21 lines =@@ */ prop2: string; }; - type typeC = myTypes.typeB | Function; --} +} +export declare namespace myTypes { + export type typeC = myTypes.typeB | Function; -+} -+export { myTypes }; + } //// [file2.d.ts] ++declare const _exports: { ++ testFn: typeof testFn; ++ testFnTypes: Record; ++}; ++export = _exports; +import { myTypes } from './file.js'; +/** + * @namespace testFnTypes @@ -44,7 +53,7 @@ /** @typedef {boolean|myTypes.typeC} testFnTypes.input */ /** * @function testFn -@@= skipped -10, +23 lines =@@ +@@= skipped -10, +27 lines =@@ * @param {testFnTypes.input} input - Input. * @returns {number|null} Result. */ @@ -56,13 +65,9 @@ - */ -export const testFnTypes: { - [x: string]: any; -+declare function testFn(input: testFnTypes.input): number | null; -+declare const _default: { -+ testFn: typeof testFn; -+ testFnTypes: Record; - }; +-}; -export namespace testFnTypes { - type input = boolean | myTypes.typeC; -} -import { myTypes } from "./file.js"; -+export = _default; \ No newline at end of file ++declare function testFn(input: testFnTypes.input): number | null; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsJson(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsJson(target=es2015).js.diff index cb8a3a85cf0..38ab46df7e2 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsJson(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsJson(target=es2015).js.diff @@ -1,10 +1,8 @@ --- old.jsDeclarationsJson(target=es2015).js +++ new.jsDeclarationsJson(target=es2015).js -@@= skipped -26, +26 lines =@@ - +@@= skipped -27, +27 lines =@@ //// [index.d.ts] -+import j = require("./obj.json"); export = j; -declare const j: { - x: number; @@ -25,19 +23,20 @@ - })[]; - }; -}; ++import j = require("./obj.json"); + + +//// [DtsFileErrors] + + -+out/index.d.ts(1,20): error TS2307: Cannot find module './obj.json' or its corresponding type declarations. ++out/index.d.ts(2,20): error TS2307: Cannot find module './obj.json' or its corresponding type declarations. + + +==== out/index.d.ts (1 errors) ==== ++ export = j; + import j = require("./obj.json"); + ~~~~~~~~~~~~ +!!! error TS2307: Cannot find module './obj.json' or its corresponding type declarations. -+ export = j; + +==== obj.json (0 errors) ==== + { diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsPackageJson(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsPackageJson(target=es2015).js.diff index f9e0689b1c0..02dad036805 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsPackageJson(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsPackageJson(target=es2015).js.diff @@ -1,10 +1,8 @@ --- old.jsDeclarationsPackageJson(target=es2015).js +++ new.jsDeclarationsPackageJson(target=es2015).js -@@= skipped -75, +75 lines =@@ - +@@= skipped -76, +76 lines =@@ //// [index.d.ts] -+import j = require("./package.json"); export = j; -declare const j: { - name: string; @@ -35,19 +33,20 @@ - o: string[]; - }; -}; ++import j = require("./package.json"); + + +//// [DtsFileErrors] + + -+out/index.d.ts(1,20): error TS2307: Cannot find module './package.json' or its corresponding type declarations. ++out/index.d.ts(2,20): error TS2307: Cannot find module './package.json' or its corresponding type declarations. + + +==== out/index.d.ts (1 errors) ==== ++ export = j; + import j = require("./package.json"); + ~~~~~~~~~~~~~~~~ +!!! error TS2307: Cannot find module './package.json' or its corresponding type declarations. -+ export = j; + +==== package.json (0 errors) ==== + { diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js.diff index 408968833eb..f6649936e21 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit1.js.diff @@ -1,22 +1,18 @@ --- old.jsDeclarationsParameterTagReusesInputNodeInEmit1.js +++ new.jsDeclarationsParameterTagReusesInputNodeInEmit1.js -@@= skipped -61, +61 lines =@@ - +@@= skipped -62, +62 lines =@@ //// [base.d.ts] --export = BaseFactory; + export = BaseFactory; +declare class Base { + constructor(); +} declare function BaseFactory(): Base; declare namespace BaseFactory { -- export { Base }; --} + export { Base }; + } -declare class Base { -} -+ var Base: typeof Base; -+} -+export = BaseFactory; //// [file.d.ts] -type couldntThinkOfAny = { - (): {}; @@ -29,7 +25,7 @@ /** @typedef {import('./base')} BaseFactory */ /** * @callback BaseFactoryFactory -@@= skipped -26, +23 lines =@@ +@@= skipped -25, +22 lines =@@ * @param {InstanceType} base * @returns {InstanceType} */ diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit2.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit2.js.diff index f713f1521e3..15f95e2faec 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit2.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsParameterTagReusesInputNodeInEmit2.js.diff @@ -1,22 +1,18 @@ --- old.jsDeclarationsParameterTagReusesInputNodeInEmit2.js +++ new.jsDeclarationsParameterTagReusesInputNodeInEmit2.js -@@= skipped -49, +49 lines =@@ - +@@= skipped -50, +50 lines =@@ //// [base.d.ts] --export = BaseFactory; + export = BaseFactory; +declare class Base { + constructor(); +} declare function BaseFactory(): Base; declare namespace BaseFactory { -- export { Base }; --} + export { Base }; + } -declare class Base { -} -+ var Base: typeof Base; -+} -+export = BaseFactory; //// [file.d.ts] /** @typedef {typeof import('./base')} BaseFactory */ +type BaseFactory = typeof import('./base'); @@ -27,34 +23,4 @@ */ -declare function test(base: InstanceType): InstanceType; -type BaseFactory = typeof import("./base"); -+declare const test: (base: InstanceType) => InstanceType; -+ -+ -+//// [DtsFileErrors] -+ -+ -+out/base.d.ts(6,9): error TS2502: 'Base' is referenced directly or indirectly in its own type annotation. -+ -+ -+==== out/base.d.ts (1 errors) ==== -+ declare class Base { -+ constructor(); -+ } -+ declare function BaseFactory(): Base; -+ declare namespace BaseFactory { -+ var Base: typeof Base; -+ ~~~~ -+!!! error TS2502: 'Base' is referenced directly or indirectly in its own type annotation. -+ } -+ export = BaseFactory; -+ -+==== out/file.d.ts (0 errors) ==== -+ /** @typedef {typeof import('./base')} BaseFactory */ -+ type BaseFactory = typeof import('./base'); -+ /** -+ * -+ * @param {InstanceType} base -+ * @returns {InstanceType} -+ */ -+ declare const test: (base: InstanceType) => InstanceType; -+ \ No newline at end of file ++declare const test: (base: InstanceType) => InstanceType; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsReexportedCjsAlias(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsReexportedCjsAlias(target=es2015).js.diff index b51af10c426..98770290f9c 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsReexportedCjsAlias(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsReexportedCjsAlias(target=es2015).js.diff @@ -1,6 +1,14 @@ --- old.jsDeclarationsReexportedCjsAlias(target=es2015).js +++ new.jsDeclarationsReexportedCjsAlias(target=es2015).js -@@= skipped -55, +55 lines =@@ +@@= skipped -52, +52 lines =@@ + + + //// [lib.d.ts] ++declare const _exports: { ++ bar: typeof bar; ++ SomeClass: typeof SomeClass; ++}; ++export = _exports; /** * @param {string} a */ @@ -10,16 +18,11 @@ +declare class SomeClass { a(): number; } -+declare const _default: { -+ bar: typeof bar; -+ SomeClass: typeof SomeClass; -+}; -+export = _default; //// [main.d.ts] -import { SomeClass } from "./lib"; -import { SomeClass as Another } from "./lib"; -export { SomeClass, Another }; -+declare const _default: { ++declare const _exports: { + SomeClass: { + new (): { + a(): number; @@ -31,4 +34,4 @@ + }; + }; +}; -+export = _default; \ No newline at end of file ++export = _exports; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsReferenceToClassInstanceCrossFile.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsReferenceToClassInstanceCrossFile.js.diff index a1fc4253ec6..edc21a6c3e4 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsReferenceToClassInstanceCrossFile.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsReferenceToClassInstanceCrossFile.js.diff @@ -5,15 +5,19 @@ //// [rectangle.d.ts] -export class Rectangle { ++declare const _exports: { ++ Rectangle: typeof Rectangle; ++}; ++export = _exports; +declare class Rectangle { + constructor(); } -+declare const _default: { -+ Rectangle: typeof Rectangle; -+}; -+export = _default; //// [index.d.ts] -export class Render { ++declare const _exports: { ++ Render: typeof Render; ++}; ++export = _exports; +declare class Render { /** * Object list @@ -24,14 +28,10 @@ /** * Adds a rectangle * -@@= skipped -16, +22 lines =@@ +@@= skipped -16, +26 lines =@@ */ addRectangle(): Rectangle; } -import { Rectangle } from "./rectangle"; -+declare const _default: { -+ Render: typeof Render; -+}; -+export = _default; //// [test.d.ts] export {}; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeAliases.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeAliases.js.diff index abba5cd4960..bfcfda4d679 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeAliases.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeAliases.js.diff @@ -40,9 +40,15 @@ + * @returns {T} + */ //// [mixed.d.ts] ++declare const _exports: { ++ doTheThing: typeof doTheThing; ++ ExportedThing: typeof ExportedThing; ++}; ++export = _exports; export type SomeType = { x: string; -@@= skipped -23, +40 lines =@@ + } | number | LocalThing | ExportedThing; +@@= skipped -23, +45 lines =@@ * @param {number} x * @returns {SomeType} */ @@ -52,11 +58,6 @@ +declare class ExportedThing { z: string; } -+declare const _default: { -+ doTheThing: typeof doTheThing; -+ ExportedThing: typeof ExportedThing; -+}; -+export = _default; declare class LocalThing { y: string; } diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeReassignmentFromDeclaration.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeReassignmentFromDeclaration.js.diff deleted file mode 100644 index 89fcb400e1f..00000000000 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeReassignmentFromDeclaration.js.diff +++ /dev/null @@ -1,10 +0,0 @@ ---- old.jsDeclarationsTypeReassignmentFromDeclaration.js -+++ new.jsDeclarationsTypeReassignmentFromDeclaration.js -@@= skipped -18, +18 lines =@@ - - - //// [index.d.ts] --export = items; - /** @type {typeof import("/some-mod")} */ - declare const items: typeof import("/some-mod"); -+export = items; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeReferences(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeReferences(target=es2015).js.diff index 71a5206de52..f623399b54f 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeReferences(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeReferences(target=es2015).js.diff @@ -7,7 +7,7 @@ -export const thing: Something; -import Something_1 = require("fs"); -import Something = Something_1.Something; -+declare const _default: { ++declare const _exports: { + thing: import("fs").Something; +}; -+export = _default; \ No newline at end of file ++export = _exports; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeReferences2(target=es2015).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeReferences2(target=es2015).js.diff index 4b5424a955d..11fcf7e912c 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeReferences2(target=es2015).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypeReferences2(target=es2015).js.diff @@ -5,7 +5,7 @@ }; //// [index.d.ts] -export const thing: number; -+declare const _default: { ++declare const _exports: { + thing: number; +}; -+export = _default; \ No newline at end of file ++export = _exports; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypedefAndImportTypes.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypedefAndImportTypes.js.diff index ccfe330fdc6..b3252bea214 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypedefAndImportTypes.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypedefAndImportTypes.js.diff @@ -8,6 +8,7 @@ /** * @typedef {string | number} Whatever */ ++export = Conn; +export type Whatever = string | number; declare class Conn { + constructor(); @@ -18,13 +19,16 @@ - export { Whatever }; -} -type Whatever = string | number; -+export = Conn; //// [usage.d.ts] -export type Conn = import("./conn"); /** * @typedef {import("./conn")} Conn */ -export class Wrap { ++declare const _exports: { ++ Wrap: typeof Wrap; ++}; ++export = _exports; +export type Conn = import("./conn"); +declare class Wrap { + connItem: number; @@ -37,8 +41,4 @@ - connItem: number; - /** @type {import("./conn").Whatever} */ - another: import("./conn").Whatever; - } -+declare const _default: { -+ Wrap: typeof Wrap; -+}; -+export = _default; \ No newline at end of file + } \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypedefPropertyAndExportAssignment.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypedefPropertyAndExportAssignment.js.diff index 6b37beef66c..b19eaa5a97c 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypedefPropertyAndExportAssignment.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/jsDeclarationsTypedefPropertyAndExportAssignment.js.diff @@ -6,6 +6,20 @@ //// [module.d.ts] -export type TaskGroupIds = "parseHTML" | "styleLayout"; +/** @typedef {'parseHTML'|'styleLayout'} TaskGroupIds */ ++declare const _exports: { ++ taskGroups: { ++ parseHTML: { ++ id: "parseHTML"; ++ label: string; ++ }; ++ styleLayout: { ++ id: "styleLayout"; ++ label: string; ++ }; ++ }; ++ taskNameToGroup: Record; ++}; ++export = _exports; +export type TaskGroupIds = 'parseHTML' | 'styleLayout'; export type TaskGroup = { id: TaskGroupIds; @@ -29,22 +43,9 @@ -/** @type {Object} */ -export const taskNameToGroup: { - [x: string]: TaskGroup; -+declare const _default: { -+ taskGroups: { -+ parseHTML: { -+ id: "parseHTML"; -+ label: string; -+ }; -+ styleLayout: { -+ id: "styleLayout"; -+ label: string; -+ }; -+ }; -+ taskNameToGroup: Record; - }; -+export = _default; +-}; //// [index.d.ts] --export = MainThreadTasks; + export = MainThreadTasks; +export type TaskGroup = import('./module.js').TaskGroup; +export type TaskNode = { + children: TaskNode[]; @@ -57,7 +58,7 @@ /** @typedef {import('./module.js').TaskGroup} TaskGroup */ /** * @typedef TaskNode -@@= skipped -41, +46 lines =@@ +@@= skipped -41, +47 lines =@@ */ constructor(x: TaskGroup, y: TaskNode); } @@ -72,5 +73,4 @@ -}; -type PriorTaskData = { - timers: Map; --}; -+export = MainThreadTasks; \ No newline at end of file +-}; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportAliasElementAccessExpression.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportAliasElementAccessExpression.js.diff index dd1e3f8f3c2..7825246ffad 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportAliasElementAccessExpression.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/moduleExportAliasElementAccessExpression.js.diff @@ -6,6 +6,6 @@ //// [moduleExportAliasElementAccessExpression.d.ts] -export function D(): void; -export { D as _Does_not_work_yet }; -+declare function D(): void; +export { D }; -+export { D as "Does not work yet" }; \ No newline at end of file ++export { D as "Does not work yet" }; ++declare function D(): void; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/nestedDestructuringOfRequire.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/nestedDestructuringOfRequire.js.diff index 0a43c5adc3a..245c3023b5f 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/nestedDestructuringOfRequire.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/nestedDestructuringOfRequire.js.diff @@ -7,9 +7,9 @@ -export namespace chalk { - let grey: {}; -} ++export { chalk }; +declare const chalk: { + grey: {}; +}; -+export { chalk }; //// [main.d.ts] export {}; \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=node16).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=node16).js.diff index dcf0155ae01..c3e189bdeec 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=node16).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=node16).js.diff @@ -8,9 +8,8 @@ declare const a: {}; +export = a; //// [file.d.ts] --export = a; + export = a; declare const a: {}; -+export = a; //// [index.d.ts] -export = a; declare const a: {}; diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=node18).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=node18).js.diff index 6ea9c065ca2..36535d8e3c0 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=node18).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=node18).js.diff @@ -8,9 +8,8 @@ declare const a: {}; +export = a; //// [file.d.ts] --export = a; + export = a; declare const a: {}; -+export = a; //// [index.d.ts] -export = a; declare const a: {}; diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=node20).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=node20).js.diff index 10b6a06b1e5..e7067752375 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=node20).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=node20).js.diff @@ -8,9 +8,8 @@ declare const a: {}; +export = a; //// [file.d.ts] --export = a; + export = a; declare const a: {}; -+export = a; //// [index.d.ts] -export = a; declare const a: {}; diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=nodenext).js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=nodenext).js.diff index 6128e581d4e..362b3b1562d 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=nodenext).js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/nodeModulesAllowJsExportAssignment(module=nodenext).js.diff @@ -8,9 +8,8 @@ declare const a: {}; +export = a; //// [file.d.ts] --export = a; + export = a; declare const a: {}; -+export = a; //// [index.d.ts] -export = a; declare const a: {}; diff --git a/testdata/baselines/reference/submoduleAccepted/conformance/nullPropertyName.js.diff b/testdata/baselines/reference/submoduleAccepted/conformance/nullPropertyName.js.diff index 0c8c34e14b7..40d612d097c 100644 --- a/testdata/baselines/reference/submoduleAccepted/conformance/nullPropertyName.js.diff +++ b/testdata/baselines/reference/submoduleAccepted/conformance/nullPropertyName.js.diff @@ -140,7 +140,16 @@ export var abstract: number; export var as: number; export var asserts: number; -@@= skipped -75, +120 lines =@@ +@@= skipped -68, +113 lines =@@ + export var string: number; + export var symbol: number; + export var type: number; +- export var undefined: number; ++ export var _21: number; ++ export { _21 as undefined }; + export var unique: number; + export var unknown: number; + export var from: number; export var global: number; export var bigint: number; export var of: number; diff --git a/testdata/baselines/reference/tsbuild/javascriptProjectEmit/loads-js-based-projects-and-emits-them-correctly.js b/testdata/baselines/reference/tsbuild/javascriptProjectEmit/loads-js-based-projects-and-emits-them-correctly.js index 09dab50a6d0..fec31535583 100644 --- a/testdata/baselines/reference/tsbuild/javascriptProjectEmit/loads-js-based-projects-and-emits-them-correctly.js +++ b/testdata/baselines/reference/tsbuild/javascriptProjectEmit/loads-js-based-projects-and-emits-them-correctly.js @@ -153,13 +153,13 @@ interface Symbol { } declare const console: { log(msg: any): void; }; //// [/home/src/workspaces/lib/common/nominal.d.ts] *new* -export type Nominal = T & {}; /** * @template T, Name * @typedef {T & {[Symbol.species]: Name}} Nominal */ -declare const _default: {}; -export = _default; +declare const _exports: {}; +export = _exports; +export type Nominal = T & {}; //// [/home/src/workspaces/lib/common/nominal.js] *new* "use strict"; @@ -170,7 +170,7 @@ export = _default; module.exports = {}; //// [/home/src/workspaces/lib/common/tsconfig.tsbuildinfo] *new* -{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2025.full.d.ts","../../solution/common/nominal.js"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"a19075dfba5b2d593b761ed8d8cd526f-/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\nmodule.exports = {};","signature":"78da1fe1eee174b6f46c66b629c86122-export type Nominal = T & {};\n/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _default: {};\nexport = _default;\n","impliedNodeFormat":1}],"options":{"allowJs":true,"checkJs":true,"composite":true,"declaration":true,"outDir":"..","rootDir":"../../solution","skipLibCheck":true},"semanticDiagnosticsPerFile":[[2,[{"pos":51,"end":58,"code":2339,"category":1,"messageKey":"Property_0_does_not_exist_on_type_1_2339","messageArgs":["species","SymbolConstructor"]}]]],"latestChangedDtsFile":"./nominal.d.ts"} +{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2025.full.d.ts","../../solution/common/nominal.js"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"a19075dfba5b2d593b761ed8d8cd526f-/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\nmodule.exports = {};","signature":"81ac3a6e47fc4be420cd544e175d838d-/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _exports: {};\nexport = _exports;\nexport type Nominal = T & {};\n","impliedNodeFormat":1}],"options":{"allowJs":true,"checkJs":true,"composite":true,"declaration":true,"outDir":"..","rootDir":"../../solution","skipLibCheck":true},"semanticDiagnosticsPerFile":[[2,[{"pos":51,"end":58,"code":2339,"category":1,"messageKey":"Property_0_does_not_exist_on_type_1_2339","messageArgs":["species","SymbolConstructor"]}]]],"latestChangedDtsFile":"./nominal.d.ts"} //// [/home/src/workspaces/lib/common/tsconfig.tsbuildinfo.readable.baseline.txt] *new* { "version": "FakeTSVersion", @@ -202,11 +202,11 @@ module.exports = {}; { "fileName": "../../solution/common/nominal.js", "version": "a19075dfba5b2d593b761ed8d8cd526f-/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\nmodule.exports = {};", - "signature": "78da1fe1eee174b6f46c66b629c86122-export type Nominal = T & {};\n/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _default: {};\nexport = _default;\n", + "signature": "81ac3a6e47fc4be420cd544e175d838d-/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _exports: {};\nexport = _exports;\nexport type Nominal = T & {};\n", "impliedNodeFormat": "CommonJS", "original": { "version": "a19075dfba5b2d593b761ed8d8cd526f-/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\nmodule.exports = {};", - "signature": "78da1fe1eee174b6f46c66b629c86122-export type Nominal = T & {};\n/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _default: {};\nexport = _default;\n", + "signature": "81ac3a6e47fc4be420cd544e175d838d-/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _exports: {};\nexport = _exports;\nexport type Nominal = T & {};\n", "impliedNodeFormat": 1 } } @@ -265,7 +265,7 @@ export function getVar() { } //// [/home/src/workspaces/lib/sub-project-2/tsconfig.tsbuildinfo] *new* -{"version":"FakeTSVersion","root":[4],"fileNames":["lib.es2025.full.d.ts","../common/nominal.d.ts","../sub-project/index.d.ts","../../solution/sub-project-2/index.js"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},"78da1fe1eee174b6f46c66b629c86122-export type Nominal = T & {};\n/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _default: {};\nexport = _default;\n","225285a996cc5c4120877a377890d79e-import { Nominal } from '../common/nominal';\nexport type MyNominal = Nominal;\n/**\n * @typedef {Nominal} MyNominal\n */ \n",{"version":"db2a90e082fd17d65127bda69975a727-import { MyNominal } from '../sub-project/index';\n\nconst variable = {\n key: /** @type {MyNominal} */('value'),\n};\n\n/**\n * @return {keyof typeof variable}\n */\nexport function getVar() {\n return 'key';\n}","signature":"0620ca4a9fe7036c93bd1594ffccf8eb-import { MyNominal } from '../sub-project/index';\ndeclare const variable: {\n key: MyNominal;\n};\n/**\n * @return {keyof typeof variable}\n */\nexport declare function getVar(): keyof typeof variable;\nexport {};\n","impliedNodeFormat":1}],"fileIdsList":[[2],[3]],"options":{"allowJs":true,"checkJs":true,"composite":true,"declaration":true,"outDir":"..","rootDir":"../../solution","skipLibCheck":true},"referencedMap":[[3,1],[4,2]],"semanticDiagnosticsPerFile":[[4,[{"pos":9,"end":18,"code":18042,"category":1,"messageKey":"_0_is_a_type_and_cannot_be_imported_in_JavaScript_files_Use_1_in_a_JSDoc_type_annotation_18042","messageArgs":["MyNominal","import(\"../sub-project/index\").MyNominal"]}]]],"latestChangedDtsFile":"./index.d.ts"} +{"version":"FakeTSVersion","root":[4],"fileNames":["lib.es2025.full.d.ts","../common/nominal.d.ts","../sub-project/index.d.ts","../../solution/sub-project-2/index.js"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},"81ac3a6e47fc4be420cd544e175d838d-/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _exports: {};\nexport = _exports;\nexport type Nominal = T & {};\n","225285a996cc5c4120877a377890d79e-import { Nominal } from '../common/nominal';\nexport type MyNominal = Nominal;\n/**\n * @typedef {Nominal} MyNominal\n */ \n",{"version":"db2a90e082fd17d65127bda69975a727-import { MyNominal } from '../sub-project/index';\n\nconst variable = {\n key: /** @type {MyNominal} */('value'),\n};\n\n/**\n * @return {keyof typeof variable}\n */\nexport function getVar() {\n return 'key';\n}","signature":"0620ca4a9fe7036c93bd1594ffccf8eb-import { MyNominal } from '../sub-project/index';\ndeclare const variable: {\n key: MyNominal;\n};\n/**\n * @return {keyof typeof variable}\n */\nexport declare function getVar(): keyof typeof variable;\nexport {};\n","impliedNodeFormat":1}],"fileIdsList":[[2],[3]],"options":{"allowJs":true,"checkJs":true,"composite":true,"declaration":true,"outDir":"..","rootDir":"../../solution","skipLibCheck":true},"referencedMap":[[3,1],[4,2]],"semanticDiagnosticsPerFile":[[4,[{"pos":9,"end":18,"code":18042,"category":1,"messageKey":"_0_is_a_type_and_cannot_be_imported_in_JavaScript_files_Use_1_in_a_JSDoc_type_annotation_18042","messageArgs":["MyNominal","import(\"../sub-project/index\").MyNominal"]}]]],"latestChangedDtsFile":"./index.d.ts"} //// [/home/src/workspaces/lib/sub-project-2/tsconfig.tsbuildinfo.readable.baseline.txt] *new* { "version": "FakeTSVersion", @@ -298,8 +298,8 @@ export function getVar() { }, { "fileName": "../common/nominal.d.ts", - "version": "78da1fe1eee174b6f46c66b629c86122-export type Nominal = T & {};\n/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _default: {};\nexport = _default;\n", - "signature": "78da1fe1eee174b6f46c66b629c86122-export type Nominal = T & {};\n/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _default: {};\nexport = _default;\n", + "version": "81ac3a6e47fc4be420cd544e175d838d-/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _exports: {};\nexport = _exports;\nexport type Nominal = T & {};\n", + "signature": "81ac3a6e47fc4be420cd544e175d838d-/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _exports: {};\nexport = _exports;\nexport type Nominal = T & {};\n", "impliedNodeFormat": "CommonJS" }, { @@ -380,7 +380,7 @@ import { Nominal } from '../common/nominal'; */ //// [/home/src/workspaces/lib/sub-project/tsconfig.tsbuildinfo] *new* -{"version":"FakeTSVersion","root":[3],"fileNames":["lib.es2025.full.d.ts","../common/nominal.d.ts","../../solution/sub-project/index.js"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},"78da1fe1eee174b6f46c66b629c86122-export type Nominal = T & {};\n/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _default: {};\nexport = _default;\n",{"version":"00b7836eaf1e026f7764b7be6efcc8f5-import { Nominal } from '../common/nominal';\n\n/**\n * @typedef {Nominal} MyNominal\n */","signature":"225285a996cc5c4120877a377890d79e-import { Nominal } from '../common/nominal';\nexport type MyNominal = Nominal;\n/**\n * @typedef {Nominal} MyNominal\n */ \n","impliedNodeFormat":1}],"fileIdsList":[[2]],"options":{"allowJs":true,"checkJs":true,"composite":true,"declaration":true,"outDir":"..","rootDir":"../../solution","skipLibCheck":true},"referencedMap":[[3,1]],"semanticDiagnosticsPerFile":[[3,[{"pos":9,"end":16,"code":18042,"category":1,"messageKey":"_0_is_a_type_and_cannot_be_imported_in_JavaScript_files_Use_1_in_a_JSDoc_type_annotation_18042","messageArgs":["Nominal","import(\"../common/nominal\").Nominal"]}]]],"latestChangedDtsFile":"./index.d.ts"} +{"version":"FakeTSVersion","root":[3],"fileNames":["lib.es2025.full.d.ts","../common/nominal.d.ts","../../solution/sub-project/index.js"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// \ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array { length: number; [n: number]: T; }\ninterface ReadonlyArray {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},"81ac3a6e47fc4be420cd544e175d838d-/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _exports: {};\nexport = _exports;\nexport type Nominal = T & {};\n",{"version":"00b7836eaf1e026f7764b7be6efcc8f5-import { Nominal } from '../common/nominal';\n\n/**\n * @typedef {Nominal} MyNominal\n */","signature":"225285a996cc5c4120877a377890d79e-import { Nominal } from '../common/nominal';\nexport type MyNominal = Nominal;\n/**\n * @typedef {Nominal} MyNominal\n */ \n","impliedNodeFormat":1}],"fileIdsList":[[2]],"options":{"allowJs":true,"checkJs":true,"composite":true,"declaration":true,"outDir":"..","rootDir":"../../solution","skipLibCheck":true},"referencedMap":[[3,1]],"semanticDiagnosticsPerFile":[[3,[{"pos":9,"end":16,"code":18042,"category":1,"messageKey":"_0_is_a_type_and_cannot_be_imported_in_JavaScript_files_Use_1_in_a_JSDoc_type_annotation_18042","messageArgs":["Nominal","import(\"../common/nominal\").Nominal"]}]]],"latestChangedDtsFile":"./index.d.ts"} //// [/home/src/workspaces/lib/sub-project/tsconfig.tsbuildinfo.readable.baseline.txt] *new* { "version": "FakeTSVersion", @@ -412,8 +412,8 @@ import { Nominal } from '../common/nominal'; }, { "fileName": "../common/nominal.d.ts", - "version": "78da1fe1eee174b6f46c66b629c86122-export type Nominal = T & {};\n/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _default: {};\nexport = _default;\n", - "signature": "78da1fe1eee174b6f46c66b629c86122-export type Nominal = T & {};\n/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _default: {};\nexport = _default;\n", + "version": "81ac3a6e47fc4be420cd544e175d838d-/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _exports: {};\nexport = _exports;\nexport type Nominal = T & {};\n", + "signature": "81ac3a6e47fc4be420cd544e175d838d-/**\n * @template T, Name\n * @typedef {T & {[Symbol.species]: Name}} Nominal\n */\ndeclare const _exports: {};\nexport = _exports;\nexport type Nominal = T & {};\n", "impliedNodeFormat": "CommonJS" }, { diff --git a/testdata/submoduleAccepted.txt b/testdata/submoduleAccepted.txt index b9e3d2c233a..b979f793aaa 100644 --- a/testdata/submoduleAccepted.txt +++ b/testdata/submoduleAccepted.txt @@ -220,7 +220,6 @@ conformance/tsxReactEmit8(jsx=react-jsxdev).js.diff ## deprecated features ## compiler/allowJsClassThisTypeCrash.symbols.diff -compiler/amdLikeInputDeclarationEmit.errors.txt.diff compiler/amdLikeInputDeclarationEmit.js.diff compiler/amdLikeInputDeclarationEmit.symbols.diff compiler/amdLikeInputDeclarationEmit.types.diff @@ -974,12 +973,9 @@ compiler/nodeNextPackageSelfNameWithOutDirDeclDirNestedDirs.js.diff conformance/exportDefaultNamespace.js.diff conformance/jsDeclarationsClassStatic2.js.diff conformance/jsDeclarationsComputedNames(target=es2015).js.diff -conformance/jsDeclarationsExportAssignedClassInstance1(target=es2015).js.diff -conformance/jsDeclarationsExportAssignedClassInstance2(target=es2015).js.diff conformance/jsDeclarationsJson(target=es2015).js.diff conformance/jsDeclarationsPrivateFields01.js.diff conformance/jsDeclarationsReexportAliases(target=es2015).js.diff -conformance/jsDeclarationsTypeReassignmentFromDeclaration.js.diff conformance/typedefOnStatements.js.diff conformance/typeTagNoErasure.js.diff conformance/recursiveTypeReferences2.js.diff @@ -1258,7 +1254,6 @@ conformance/jsDeclarationsClassStaticMethodAugmentation(target=es2015).js.diff conformance/jsDeclarationsCrossfileMerge(target=es2015).js.diff conformance/jsDeclarationsDefaultsErr(target=es2015).js.diff conformance/jsDeclarationsFunctions.js.diff -conformance/jsDeclarationsFunctionWithDefaultAssignedMember.js.diff conformance/jsDeclarationsFunctionJSDoc(target=es2015).js.diff conformance/jsDeclarationsClassAccessor.js.diff conformance/jsDeclarationsExportAssignedClassInstance3(target=es2015).js.diff @@ -1367,7 +1362,6 @@ conformance/moduleExportsElementAccessAssignment.js.diff # CJS `module.exports = {}` now emits `export = _default` with inlined object type instead of named exports ## https://github.com/microsoft/typescript-go/issues/3543 -compiler/jsDeclarationEmitExportAssignedArray.js.diff compiler/jsDeclarationEmitExportAssignedFunctionWithExtraTypedefsMembers.js.diff conformance/jsDeclarationsCommonjsRelativePath.js.diff conformance/jsDeclarationsDocCommentsOnConsts(target=es2015).js.diff From dc37b5249ab60e2bbce936f71b883e6c8136167e Mon Sep 17 00:00:00 2001 From: Matheus Mol <80729568+oMatheusmol@users.noreply.github.com> Date: Fri, 19 Jun 2026 16:40:43 -0300 Subject: [PATCH 39/49] Fix nil pointer panic when extending an empty config file (#4377) --- internal/tsoptions/tsconfigparsing.go | 3 +- internal/tsoptions/tsconfigparsing_test.go | 34 ++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/internal/tsoptions/tsconfigparsing.go b/internal/tsoptions/tsconfigparsing.go index 2bd2482a2b4..8a189239062 100644 --- a/internal/tsoptions/tsconfigparsing.go +++ b/internal/tsoptions/tsconfigparsing.go @@ -960,8 +960,9 @@ func readJsonConfigFile(fileName string, path tspath.Path, readFile func(fileNam }, text, core.ScriptKindJSON), }, diagnostic } else { + factory := &ast.NodeFactory{} file := &TsConfigSourceFile{ - SourceFile: (&ast.NodeFactory{}).NewSourceFile(ast.SourceFileParseOptions{FileName: fileName, Path: path}, "", nil, (&ast.NodeFactory{}).NewToken(ast.KindEndOfFile)).AsSourceFile(), + SourceFile: factory.NewSourceFile(ast.SourceFileParseOptions{FileName: fileName, Path: path}, "", factory.NewNodeList([]*ast.Node{}), factory.NewToken(ast.KindEndOfFile)).AsSourceFile(), } file.SourceFile.SetDiagnostics(diagnostic) return file, diagnostic diff --git a/internal/tsoptions/tsconfigparsing_test.go b/internal/tsoptions/tsconfigparsing_test.go index 961f9c1fe6c..72c2667a7ad 100644 --- a/internal/tsoptions/tsconfigparsing_test.go +++ b/internal/tsoptions/tsconfigparsing_test.go @@ -887,6 +887,40 @@ func TestParseJsonSourceFileConfigFileContentReportsInvalidExtendedConfig(t *tes } } +// Extending an empty config file used to panic on nil Statements (#4265). +func TestParseJsonSourceFileConfigFileContentWithEmptyExtendedConfig(t *testing.T) { + t.Parallel() + files := map[string]string{ + "/project/tsconfig.json": `{ + "extends": "./base.json" +}`, + "/project/base.json": "", + "/project/main.ts": "export const x = 1;", + } + host := tsoptionstest.NewVFSParseConfigHost(files, "/project", true /*useCaseSensitiveFileNames*/) + configFileName := "/project/tsconfig.json" + configFile := tsoptions.NewTsconfigSourceFileFromFilePath( + configFileName, + tspath.ToPath(configFileName, host.GetCurrentDirectory(), host.FS().UseCaseSensitiveFileNames()), + files[configFileName], + ) + + parsed := tsoptions.ParseJsonSourceFileConfigFileContent( + configFile, + host, + host.GetCurrentDirectory(), + nil, + nil, + configFileName, + nil, + nil, + nil, + ) + + assert.Assert(t, parsed != nil) + assert.DeepEqual(t, parsed.FileNames(), []string{"/project/main.ts"}) +} + func TestParseJsonSourceFileConfigFileContentDoesNotDuplicateUnquotedKeyDiagnostics(t *testing.T) { t.Parallel() parsed := tsoptionstest.GetParsedCommandLine(t, `{ From 97195f8bf39073d75358543d6159ba0d24d63e11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 08:23:05 -0700 Subject: [PATCH 40/49] Bump actions/checkout from 6.0.3 to 7.0.0 in the github-actions group across 1 directory (#4390) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 20 ++++++++++---------- .github/workflows/codeql.yml | 2 +- .github/workflows/copilot-setup-steps.yml | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed8ffbc41ab..c05b5049b63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: docker-images: false swap-storage: false dotnet: false - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - uses: ./.github/actions/setup-go @@ -50,7 +50,7 @@ jobs: if: ${{ github.repository == 'microsoft/typescript-go' && github.event_name != 'merge_group' }} runs-on: TypeScript-1ES-GitHub-XL-2 steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 'lts/*' @@ -72,7 +72,7 @@ jobs: extension: runs-on: ubuntu-latest steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - run: npm ci @@ -137,7 +137,7 @@ jobs: - run: git config --system core.longpaths true if: ${{ matrix.config.os == 'windows-latest' }} - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: submodules: ${{ !matrix.config.no-submodules }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 @@ -231,7 +231,7 @@ jobs: docker-images: false swap-storage: false dotnet: false - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - uses: ./.github/actions/setup-go @@ -251,7 +251,7 @@ jobs: format: runs-on: ubuntu-slim steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - uses: ./.github/actions/setup-go @@ -268,7 +268,7 @@ jobs: docker-images: false swap-storage: false dotnet: false - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: submodules: true - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 @@ -307,7 +307,7 @@ jobs: tidy: runs-on: ubuntu-slim steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: ./.github/actions/setup-go - run: go mod tidy -diff @@ -322,7 +322,7 @@ jobs: docker-images: false swap-storage: false dotnet: false - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: submodules: true - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 @@ -349,7 +349,7 @@ jobs: misc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - uses: ./.github/actions/setup-go diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4bc0c6355a3..dc33a1cc50d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -44,7 +44,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 14efb450b8d..fccdfcfe9ac 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -26,7 +26,7 @@ jobs: docker-images: false swap-storage: false dotnet: false - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: submodules: true - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 From 997b92acc7440efa411a72d9b5b5f500148dacb2 Mon Sep 17 00:00:00 2001 From: Oleksandr Tarasiuk Date: Mon, 22 Jun 2026 18:52:25 +0300 Subject: [PATCH 41/49] fix(4354): add tsconfig option errors from project diagnostics (#4355) --- internal/project/project.go | 4 +- internal/project/project_test.go | 29 ++++++++ internal/testutil/harnessutil/harnessutil.go | 1 + .../tsconfigRootdirInclude.errors.txt | 5 +- .../deprecatedCompilerOptions1.errors.txt | 43 ++++++++++++ .../deprecatedCompilerOptions3.errors.txt | 43 ++++++++++++ .../deprecatedCompilerOptions4.errors.txt | 44 ++++++++++++ .../deprecatedCompilerOptions5.errors.txt | 44 ++++++++++++ .../conformance/typingsLookup1.errors.txt | 15 ++++ .../typingsLookup1.errors.txt.diff | 19 +++++ .../conformance/typingsLookup3.errors.txt | 5 +- .../typingsLookup3.errors.txt.diff | 17 +++++ ...deprecatedCompilerOptions1.errors.txt.diff | 68 ++++++++++-------- ...deprecatedCompilerOptions3.errors.txt.diff | 68 ++++++++++-------- ...deprecatedCompilerOptions4.errors.txt.diff | 69 +++++++++++-------- ...deprecatedCompilerOptions5.errors.txt.diff | 69 +++++++++++-------- 16 files changed, 426 insertions(+), 117 deletions(-) create mode 100644 testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions1.errors.txt create mode 100644 testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions3.errors.txt create mode 100644 testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions4.errors.txt create mode 100644 testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions5.errors.txt create mode 100644 testdata/baselines/reference/submodule/conformance/typingsLookup1.errors.txt create mode 100644 testdata/baselines/reference/submodule/conformance/typingsLookup1.errors.txt.diff create mode 100644 testdata/baselines/reference/submodule/conformance/typingsLookup3.errors.txt.diff diff --git a/internal/project/project.go b/internal/project/project.go index 0fb1c6f2a2b..39c77aa78df 100644 --- a/internal/project/project.go +++ b/internal/project/project.go @@ -3,6 +3,7 @@ package project import ( "context" "fmt" + "slices" "strings" "sync" @@ -219,7 +220,8 @@ func (p *Project) GetProjectDiagnostics(ctx context.Context) []*ast.Diagnostic { if p.checkerPool != nil { globalDiags = p.checkerPool.GetGlobalDiagnostics() } - return compiler.SortAndDeduplicateDiagnostics(core.Concatenate( + return compiler.SortAndDeduplicateDiagnostics(slices.Concat( + p.Program.GetConfigFileParsingDiagnostics(), p.Program.GetProgramDiagnostics(), globalDiags, )) diff --git a/internal/project/project_test.go b/internal/project/project_test.go index b819f860d7c..f4f10edfae1 100644 --- a/internal/project/project_test.go +++ b/internal/project/project_test.go @@ -2,6 +2,7 @@ package project_test import ( "context" + "slices" "strings" "testing" @@ -300,6 +301,34 @@ func TestPushDiagnostics(t *testing.T) { assert.Assert(t, len(tsconfigCall.Params.Diagnostics) > 0, "expected at least one diagnostic") }) + t.Run("publishes config parsing diagnostics on initial program creation", func(t *testing.T) { + t.Parallel() + files := map[string]any{ + "/src/tsconfig.json": `{ + "compilerOptions": { + "target": "nope" + } + }`, + "/src/index.ts": "export const x = 1;", + } + session, utils := projecttestutil.Setup(files) + session.DidOpenFile(context.Background(), "file:///src/index.ts", 1, files["/src/index.ts"].(string), lsproto.LanguageKindTypeScript) + _, err := session.GetLanguageService(context.Background(), lsproto.DocumentUri("file:///src/index.ts")) + assert.NilError(t, err) + + session.WaitForBackgroundTasks() + + calls := utils.Client().PublishDiagnosticsCalls() + tsconfigCalls := filterDiagnosticsByURI(calls, "file:///src/tsconfig.json", 0) + assert.Assert(t, len(tsconfigCalls) > 0, "expected PublishDiagnostics call for tsconfig.json") + lastTsconfigCall := tsconfigCalls[len(tsconfigCalls)-1] + + expectedMessage := "Argument for '--target' option must be:" + assert.Assert(t, slices.ContainsFunc(lastTsconfigCall.Params.Diagnostics, func(diag *lsproto.Diagnostic) bool { + return strings.Contains(diag.Message.AsString(), expectedMessage) + }), "expected invalid target diagnostic on tsconfig.json, got: %v", lastTsconfigCall.Params.Diagnostics) + }) + t.Run("clears diagnostics when project is removed", func(t *testing.T) { t.Parallel() files := map[string]any{ diff --git a/internal/testutil/harnessutil/harnessutil.go b/internal/testutil/harnessutil/harnessutil.go index a2e6ac4881e..efa6ee7c93f 100644 --- a/internal/testutil/harnessutil/harnessutil.go +++ b/internal/testutil/harnessutil/harnessutil.go @@ -661,6 +661,7 @@ func compileFilesWithHost( ctx := context.Background() program := createProgram(host, config) var diagnostics []*ast.Diagnostic + diagnostics = append(diagnostics, program.GetConfigFileParsingDiagnostics()...) diagnostics = append(diagnostics, program.GetProgramDiagnostics()...) diagnostics = append(diagnostics, program.GetSyntacticDiagnostics(ctx, nil)...) diagnostics = append(diagnostics, program.GetSemanticDiagnostics(ctx, nil)...) diff --git a/testdata/baselines/reference/compiler/tsconfigRootdirInclude.errors.txt b/testdata/baselines/reference/compiler/tsconfigRootdirInclude.errors.txt index 5632c36242d..5d7877c1065 100644 --- a/testdata/baselines/reference/compiler/tsconfigRootdirInclude.errors.txt +++ b/testdata/baselines/reference/compiler/tsconfigRootdirInclude.errors.txt @@ -1,12 +1,13 @@ error TS6059: File 'src1/main.ts' is not under 'rootDir' 'src2'. 'rootDir' is expected to contain all source files. The file is in the program because: Root file specified for compilation +tsconfig.json(8,5): error TS1005: ',' expected. !!! error TS6059: File 'src1/main.ts' is not under 'rootDir' 'src2'. 'rootDir' is expected to contain all source files. !!! error TS6059: The file is in the program because: !!! error TS6059: Root file specified for compilation -==== tsconfig.json (0 errors) ==== +==== tsconfig.json (1 errors) ==== { "compilerOptions": { "target": "es2020", @@ -15,6 +16,8 @@ error TS6059: File 'src1/main.ts' is not under 'rootDir' 'src2'. 'rootDir' is ex "rootDir": "./src2", } "include": ["src1/*"] + ~~~~~~~~~ +!!! error TS1005: ',' expected. } ==== src1/main.ts (0 errors) ==== \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions1.errors.txt b/testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions1.errors.txt new file mode 100644 index 00000000000..cd633d485c4 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions1.errors.txt @@ -0,0 +1,43 @@ +/foo/tsconfig.json(3,19): error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. +/foo/tsconfig.json(4,9): error TS5023: Unknown compiler option 'noImplicitUseStrict'. +/foo/tsconfig.json(5,9): error TS5023: Unknown compiler option 'keyofStringsOnly'. +/foo/tsconfig.json(6,9): error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. +/foo/tsconfig.json(7,9): error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. +/foo/tsconfig.json(8,9): error TS5023: Unknown compiler option 'noStrictGenericChecks'. +/foo/tsconfig.json(9,9): error TS5023: Unknown compiler option 'charset'. +/foo/tsconfig.json(10,9): error TS5023: Unknown compiler option 'out'. + + +==== /foo/tsconfig.json (8 errors) ==== + { + "compilerOptions": { + "target": "ES3", + ~~~~~ +!!! error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. + "noImplicitUseStrict": true, + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'noImplicitUseStrict'. + "keyofStringsOnly": true, + ~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'keyofStringsOnly'. + "suppressExcessPropertyErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. + "suppressImplicitAnyIndexErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. + "noStrictGenericChecks": true, + ~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'noStrictGenericChecks'. + "charset": "utf8", + ~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'charset'. + "out": "dist.js" + ~~~~~ +!!! error TS5023: Unknown compiler option 'out'. + } + } + +==== /foo/a.ts (0 errors) ==== + const a = 1; + \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions3.errors.txt b/testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions3.errors.txt new file mode 100644 index 00000000000..532ed1f0947 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions3.errors.txt @@ -0,0 +1,43 @@ +/foo/tsconfig.json(3,19): error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. +/foo/tsconfig.json(4,9): error TS5023: Unknown compiler option 'noImplicitUseStrict'. +/foo/tsconfig.json(5,9): error TS5023: Unknown compiler option 'keyofStringsOnly'. +/foo/tsconfig.json(6,9): error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. +/foo/tsconfig.json(7,9): error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. +/foo/tsconfig.json(8,9): error TS5023: Unknown compiler option 'noStrictGenericChecks'. +/foo/tsconfig.json(9,9): error TS5023: Unknown compiler option 'charset'. +/foo/tsconfig.json(10,9): error TS5023: Unknown compiler option 'out'. + + +==== /foo/tsconfig.json (8 errors) ==== + { + "compilerOptions": { + "target": "ES3", + ~~~~~ +!!! error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. + "noImplicitUseStrict": true, + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'noImplicitUseStrict'. + "keyofStringsOnly": true, + ~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'keyofStringsOnly'. + "suppressExcessPropertyErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. + "suppressImplicitAnyIndexErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. + "noStrictGenericChecks": true, + ~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'noStrictGenericChecks'. + "charset": "utf8", + ~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'charset'. + "out": "dist.js", + ~~~~~ +!!! error TS5023: Unknown compiler option 'out'. + } + } + +==== /foo/a.ts (0 errors) ==== + const a = 1; + \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions4.errors.txt b/testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions4.errors.txt new file mode 100644 index 00000000000..f85e7fec3e4 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions4.errors.txt @@ -0,0 +1,44 @@ +/foo/tsconfig.json(3,19): error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. +/foo/tsconfig.json(4,9): error TS5023: Unknown compiler option 'noImplicitUseStrict'. +/foo/tsconfig.json(5,9): error TS5023: Unknown compiler option 'keyofStringsOnly'. +/foo/tsconfig.json(6,9): error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. +/foo/tsconfig.json(7,9): error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. +/foo/tsconfig.json(8,9): error TS5023: Unknown compiler option 'noStrictGenericChecks'. +/foo/tsconfig.json(9,9): error TS5023: Unknown compiler option 'charset'. +/foo/tsconfig.json(10,9): error TS5023: Unknown compiler option 'out'. + + +==== /foo/tsconfig.json (8 errors) ==== + { + "compilerOptions": { + "target": "ES3", + ~~~~~ +!!! error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. + "noImplicitUseStrict": true, + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'noImplicitUseStrict'. + "keyofStringsOnly": true, + ~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'keyofStringsOnly'. + "suppressExcessPropertyErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. + "suppressImplicitAnyIndexErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. + "noStrictGenericChecks": true, + ~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'noStrictGenericChecks'. + "charset": "utf8", + ~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'charset'. + "out": "dist.js", + ~~~~~ +!!! error TS5023: Unknown compiler option 'out'. + "ignoreDeprecations": "5.0" + } + } + +==== /foo/a.ts (0 errors) ==== + const a = 1; + \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions5.errors.txt b/testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions5.errors.txt new file mode 100644 index 00000000000..f85e7fec3e4 --- /dev/null +++ b/testdata/baselines/reference/submodule/compiler/deprecatedCompilerOptions5.errors.txt @@ -0,0 +1,44 @@ +/foo/tsconfig.json(3,19): error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. +/foo/tsconfig.json(4,9): error TS5023: Unknown compiler option 'noImplicitUseStrict'. +/foo/tsconfig.json(5,9): error TS5023: Unknown compiler option 'keyofStringsOnly'. +/foo/tsconfig.json(6,9): error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. +/foo/tsconfig.json(7,9): error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. +/foo/tsconfig.json(8,9): error TS5023: Unknown compiler option 'noStrictGenericChecks'. +/foo/tsconfig.json(9,9): error TS5023: Unknown compiler option 'charset'. +/foo/tsconfig.json(10,9): error TS5023: Unknown compiler option 'out'. + + +==== /foo/tsconfig.json (8 errors) ==== + { + "compilerOptions": { + "target": "ES3", + ~~~~~ +!!! error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. + "noImplicitUseStrict": true, + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'noImplicitUseStrict'. + "keyofStringsOnly": true, + ~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'keyofStringsOnly'. + "suppressExcessPropertyErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. + "suppressImplicitAnyIndexErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. + "noStrictGenericChecks": true, + ~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'noStrictGenericChecks'. + "charset": "utf8", + ~~~~~~~~~ +!!! error TS5023: Unknown compiler option 'charset'. + "out": "dist.js", + ~~~~~ +!!! error TS5023: Unknown compiler option 'out'. + "ignoreDeprecations": "5.0" + } + } + +==== /foo/a.ts (0 errors) ==== + const a = 1; + \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/typingsLookup1.errors.txt b/testdata/baselines/reference/submodule/conformance/typingsLookup1.errors.txt new file mode 100644 index 00000000000..16696ad90ea --- /dev/null +++ b/testdata/baselines/reference/submodule/conformance/typingsLookup1.errors.txt @@ -0,0 +1,15 @@ +/tsconfig.json(1,12): error TS5024: Compiler option 'files' requires a value of type Array. + + +==== /tsconfig.json (1 errors) ==== + { "files": "a.ts" } + ~~~~~~ +!!! error TS5024: Compiler option 'files' requires a value of type Array. + +==== /a.ts (0 errors) ==== + /// + $.x; + +==== /node_modules/@types/jquery/index.d.ts (0 errors) ==== + declare var $: { x: any }; + \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/typingsLookup1.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/typingsLookup1.errors.txt.diff new file mode 100644 index 00000000000..2d3273f9a9d --- /dev/null +++ b/testdata/baselines/reference/submodule/conformance/typingsLookup1.errors.txt.diff @@ -0,0 +1,19 @@ +--- old.typingsLookup1.errors.txt ++++ new.typingsLookup1.errors.txt +@@= skipped -0, +0 lines =@@ +- ++/tsconfig.json(1,12): error TS5024: Compiler option 'files' requires a value of type Array. ++ ++ ++==== /tsconfig.json (1 errors) ==== ++ { "files": "a.ts" } ++ ~~~~~~ ++!!! error TS5024: Compiler option 'files' requires a value of type Array. ++ ++==== /a.ts (0 errors) ==== ++ /// ++ $.x; ++ ++==== /node_modules/@types/jquery/index.d.ts (0 errors) ==== ++ declare var $: { x: any }; ++ \ No newline at end of file diff --git a/testdata/baselines/reference/submodule/conformance/typingsLookup3.errors.txt b/testdata/baselines/reference/submodule/conformance/typingsLookup3.errors.txt index 5073cb54939..ba55afd72b7 100644 --- a/testdata/baselines/reference/submodule/conformance/typingsLookup3.errors.txt +++ b/testdata/baselines/reference/submodule/conformance/typingsLookup3.errors.txt @@ -1,8 +1,11 @@ /a.ts(1,23): error TS2688: Cannot find type definition file for 'JqUeRy'. +/tsconfig.json(1,12): error TS5024: Compiler option 'files' requires a value of type Array. -==== /tsconfig.json (0 errors) ==== +==== /tsconfig.json (1 errors) ==== { "files": "a.ts" } + ~~~~~~ +!!! error TS5024: Compiler option 'files' requires a value of type Array. ==== /a.ts (1 errors) ==== /// diff --git a/testdata/baselines/reference/submodule/conformance/typingsLookup3.errors.txt.diff b/testdata/baselines/reference/submodule/conformance/typingsLookup3.errors.txt.diff new file mode 100644 index 00000000000..b03a83b38e7 --- /dev/null +++ b/testdata/baselines/reference/submodule/conformance/typingsLookup3.errors.txt.diff @@ -0,0 +1,17 @@ +--- old.typingsLookup3.errors.txt ++++ new.typingsLookup3.errors.txt +@@= skipped -0, +0 lines =@@ + /a.ts(1,23): error TS2688: Cannot find type definition file for 'JqUeRy'. +- +- +-==== /tsconfig.json (0 errors) ==== ++/tsconfig.json(1,12): error TS5024: Compiler option 'files' requires a value of type Array. ++ ++ ++==== /tsconfig.json (1 errors) ==== + { "files": "a.ts" } ++ ~~~~~~ ++!!! error TS5024: Compiler option 'files' requires a value of type Array. + + ==== /a.ts (1 errors) ==== + /// \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions1.errors.txt.diff b/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions1.errors.txt.diff index cbcceeff13d..211e4ab699a 100644 --- a/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions1.errors.txt.diff +++ b/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions1.errors.txt.diff @@ -9,39 +9,51 @@ -/foo/tsconfig.json(8,9): error TS5101: Option 'noStrictGenericChecks' is deprecated and will stop functioning in TypeScript 5.5. Specify compilerOption '"ignoreDeprecations": "5.0"' to silence this error. -/foo/tsconfig.json(9,9): error TS5101: Option 'charset' is deprecated and will stop functioning in TypeScript 5.5. Specify compilerOption '"ignoreDeprecations": "5.0"' to silence this error. -/foo/tsconfig.json(10,9): error TS5101: Option 'out' is deprecated and will stop functioning in TypeScript 5.5. Specify compilerOption '"ignoreDeprecations": "5.0"' to silence this error. -- -- --==== /foo/tsconfig.json (8 errors) ==== -- { -- "compilerOptions": { -- "target": "ES3", -- ~~~~~ ++/foo/tsconfig.json(3,19): error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. ++/foo/tsconfig.json(4,9): error TS5023: Unknown compiler option 'noImplicitUseStrict'. ++/foo/tsconfig.json(5,9): error TS5023: Unknown compiler option 'keyofStringsOnly'. ++/foo/tsconfig.json(6,9): error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. ++/foo/tsconfig.json(7,9): error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. ++/foo/tsconfig.json(8,9): error TS5023: Unknown compiler option 'noStrictGenericChecks'. ++/foo/tsconfig.json(9,9): error TS5023: Unknown compiler option 'charset'. ++/foo/tsconfig.json(10,9): error TS5023: Unknown compiler option 'out'. + + + ==== /foo/tsconfig.json (8 errors) ==== +@@= skipped -12, +12 lines =@@ + "compilerOptions": { + "target": "ES3", + ~~~~~ -!!! error TS5107: Option 'target=ES3' is deprecated and will stop functioning in TypeScript 5.5. Specify compilerOption '"ignoreDeprecations": "5.0"' to silence this error. -- "noImplicitUseStrict": true, -- ~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. + "noImplicitUseStrict": true, + ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5101: Option 'noImplicitUseStrict' is deprecated and will stop functioning in TypeScript 5.5. Specify compilerOption '"ignoreDeprecations": "5.0"' to silence this error. -- "keyofStringsOnly": true, -- ~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'noImplicitUseStrict'. + "keyofStringsOnly": true, + ~~~~~~~~~~~~~~~~~~ -!!! error TS5101: Option 'keyofStringsOnly' is deprecated and will stop functioning in TypeScript 5.5. Specify compilerOption '"ignoreDeprecations": "5.0"' to silence this error. -- "suppressExcessPropertyErrors": true, -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'keyofStringsOnly'. + "suppressExcessPropertyErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5101: Option 'suppressExcessPropertyErrors' is deprecated and will stop functioning in TypeScript 5.5. Specify compilerOption '"ignoreDeprecations": "5.0"' to silence this error. -- "suppressImplicitAnyIndexErrors": true, -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. + "suppressImplicitAnyIndexErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5101: Option 'suppressImplicitAnyIndexErrors' is deprecated and will stop functioning in TypeScript 5.5. Specify compilerOption '"ignoreDeprecations": "5.0"' to silence this error. -- "noStrictGenericChecks": true, -- ~~~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. + "noStrictGenericChecks": true, + ~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5101: Option 'noStrictGenericChecks' is deprecated and will stop functioning in TypeScript 5.5. Specify compilerOption '"ignoreDeprecations": "5.0"' to silence this error. -- "charset": "utf8", -- ~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'noStrictGenericChecks'. + "charset": "utf8", + ~~~~~~~~~ -!!! error TS5101: Option 'charset' is deprecated and will stop functioning in TypeScript 5.5. Specify compilerOption '"ignoreDeprecations": "5.0"' to silence this error. -- "out": "dist.js" -- ~~~~~ ++!!! error TS5023: Unknown compiler option 'charset'. + "out": "dist.js" + ~~~~~ -!!! error TS5101: Option 'out' is deprecated and will stop functioning in TypeScript 5.5. Specify compilerOption '"ignoreDeprecations": "5.0"' to silence this error. -- } -- } -- --==== /foo/a.ts (0 errors) ==== -- const a = 1; -- -+ \ No newline at end of file ++!!! error TS5023: Unknown compiler option 'out'. + } + } + \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions3.errors.txt.diff b/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions3.errors.txt.diff index bdede95c32d..5bb82a1411e 100644 --- a/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions3.errors.txt.diff +++ b/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions3.errors.txt.diff @@ -9,39 +9,51 @@ -/foo/tsconfig.json(8,9): error TS5102: Option 'noStrictGenericChecks' has been removed. Please remove it from your configuration. -/foo/tsconfig.json(9,9): error TS5102: Option 'charset' has been removed. Please remove it from your configuration. -/foo/tsconfig.json(10,9): error TS5102: Option 'out' has been removed. Please remove it from your configuration. -- -- --==== /foo/tsconfig.json (8 errors) ==== -- { -- "compilerOptions": { -- "target": "ES3", -- ~~~~~ ++/foo/tsconfig.json(3,19): error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. ++/foo/tsconfig.json(4,9): error TS5023: Unknown compiler option 'noImplicitUseStrict'. ++/foo/tsconfig.json(5,9): error TS5023: Unknown compiler option 'keyofStringsOnly'. ++/foo/tsconfig.json(6,9): error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. ++/foo/tsconfig.json(7,9): error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. ++/foo/tsconfig.json(8,9): error TS5023: Unknown compiler option 'noStrictGenericChecks'. ++/foo/tsconfig.json(9,9): error TS5023: Unknown compiler option 'charset'. ++/foo/tsconfig.json(10,9): error TS5023: Unknown compiler option 'out'. + + + ==== /foo/tsconfig.json (8 errors) ==== +@@= skipped -12, +12 lines =@@ + "compilerOptions": { + "target": "ES3", + ~~~~~ -!!! error TS5108: Option 'target=ES3' has been removed. Please remove it from your configuration. -- "noImplicitUseStrict": true, -- ~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. + "noImplicitUseStrict": true, + ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5102: Option 'noImplicitUseStrict' has been removed. Please remove it from your configuration. -- "keyofStringsOnly": true, -- ~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'noImplicitUseStrict'. + "keyofStringsOnly": true, + ~~~~~~~~~~~~~~~~~~ -!!! error TS5102: Option 'keyofStringsOnly' has been removed. Please remove it from your configuration. -- "suppressExcessPropertyErrors": true, -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'keyofStringsOnly'. + "suppressExcessPropertyErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5102: Option 'suppressExcessPropertyErrors' has been removed. Please remove it from your configuration. -- "suppressImplicitAnyIndexErrors": true, -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. + "suppressImplicitAnyIndexErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5102: Option 'suppressImplicitAnyIndexErrors' has been removed. Please remove it from your configuration. -- "noStrictGenericChecks": true, -- ~~~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. + "noStrictGenericChecks": true, + ~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5102: Option 'noStrictGenericChecks' has been removed. Please remove it from your configuration. -- "charset": "utf8", -- ~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'noStrictGenericChecks'. + "charset": "utf8", + ~~~~~~~~~ -!!! error TS5102: Option 'charset' has been removed. Please remove it from your configuration. -- "out": "dist.js", -- ~~~~~ ++!!! error TS5023: Unknown compiler option 'charset'. + "out": "dist.js", + ~~~~~ -!!! error TS5102: Option 'out' has been removed. Please remove it from your configuration. -- } -- } -- --==== /foo/a.ts (0 errors) ==== -- const a = 1; -- -+ \ No newline at end of file ++!!! error TS5023: Unknown compiler option 'out'. + } + } + \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions4.errors.txt.diff b/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions4.errors.txt.diff index e37051bd470..1603c3cbad7 100644 --- a/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions4.errors.txt.diff +++ b/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions4.errors.txt.diff @@ -9,40 +9,51 @@ -/foo/tsconfig.json(8,9): error TS5102: Option 'noStrictGenericChecks' has been removed. Please remove it from your configuration. -/foo/tsconfig.json(9,9): error TS5102: Option 'charset' has been removed. Please remove it from your configuration. -/foo/tsconfig.json(10,9): error TS5102: Option 'out' has been removed. Please remove it from your configuration. -- -- --==== /foo/tsconfig.json (8 errors) ==== -- { -- "compilerOptions": { -- "target": "ES3", -- ~~~~~ ++/foo/tsconfig.json(3,19): error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. ++/foo/tsconfig.json(4,9): error TS5023: Unknown compiler option 'noImplicitUseStrict'. ++/foo/tsconfig.json(5,9): error TS5023: Unknown compiler option 'keyofStringsOnly'. ++/foo/tsconfig.json(6,9): error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. ++/foo/tsconfig.json(7,9): error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. ++/foo/tsconfig.json(8,9): error TS5023: Unknown compiler option 'noStrictGenericChecks'. ++/foo/tsconfig.json(9,9): error TS5023: Unknown compiler option 'charset'. ++/foo/tsconfig.json(10,9): error TS5023: Unknown compiler option 'out'. + + + ==== /foo/tsconfig.json (8 errors) ==== +@@= skipped -12, +12 lines =@@ + "compilerOptions": { + "target": "ES3", + ~~~~~ -!!! error TS5108: Option 'target=ES3' has been removed. Please remove it from your configuration. -- "noImplicitUseStrict": true, -- ~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. + "noImplicitUseStrict": true, + ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5102: Option 'noImplicitUseStrict' has been removed. Please remove it from your configuration. -- "keyofStringsOnly": true, -- ~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'noImplicitUseStrict'. + "keyofStringsOnly": true, + ~~~~~~~~~~~~~~~~~~ -!!! error TS5102: Option 'keyofStringsOnly' has been removed. Please remove it from your configuration. -- "suppressExcessPropertyErrors": true, -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'keyofStringsOnly'. + "suppressExcessPropertyErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5102: Option 'suppressExcessPropertyErrors' has been removed. Please remove it from your configuration. -- "suppressImplicitAnyIndexErrors": true, -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. + "suppressImplicitAnyIndexErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5102: Option 'suppressImplicitAnyIndexErrors' has been removed. Please remove it from your configuration. -- "noStrictGenericChecks": true, -- ~~~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. + "noStrictGenericChecks": true, + ~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5102: Option 'noStrictGenericChecks' has been removed. Please remove it from your configuration. -- "charset": "utf8", -- ~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'noStrictGenericChecks'. + "charset": "utf8", + ~~~~~~~~~ -!!! error TS5102: Option 'charset' has been removed. Please remove it from your configuration. -- "out": "dist.js", -- ~~~~~ ++!!! error TS5023: Unknown compiler option 'charset'. + "out": "dist.js", + ~~~~~ -!!! error TS5102: Option 'out' has been removed. Please remove it from your configuration. -- "ignoreDeprecations": "5.0" -- } -- } -- --==== /foo/a.ts (0 errors) ==== -- const a = 1; -- -+ \ No newline at end of file ++!!! error TS5023: Unknown compiler option 'out'. + "ignoreDeprecations": "5.0" + } + } \ No newline at end of file diff --git a/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions5.errors.txt.diff b/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions5.errors.txt.diff index 2afd3aa63f1..de026fcba73 100644 --- a/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions5.errors.txt.diff +++ b/testdata/baselines/reference/submoduleTriaged/compiler/deprecatedCompilerOptions5.errors.txt.diff @@ -9,40 +9,51 @@ -/foo/tsconfig.json(8,9): error TS5102: Option 'noStrictGenericChecks' has been removed. Please remove it from your configuration. -/foo/tsconfig.json(9,9): error TS5102: Option 'charset' has been removed. Please remove it from your configuration. -/foo/tsconfig.json(10,9): error TS5102: Option 'out' has been removed. Please remove it from your configuration. -- -- --==== /foo/tsconfig.json (8 errors) ==== -- { -- "compilerOptions": { -- "target": "ES3", -- ~~~~~ ++/foo/tsconfig.json(3,19): error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. ++/foo/tsconfig.json(4,9): error TS5023: Unknown compiler option 'noImplicitUseStrict'. ++/foo/tsconfig.json(5,9): error TS5023: Unknown compiler option 'keyofStringsOnly'. ++/foo/tsconfig.json(6,9): error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. ++/foo/tsconfig.json(7,9): error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. ++/foo/tsconfig.json(8,9): error TS5023: Unknown compiler option 'noStrictGenericChecks'. ++/foo/tsconfig.json(9,9): error TS5023: Unknown compiler option 'charset'. ++/foo/tsconfig.json(10,9): error TS5023: Unknown compiler option 'out'. + + + ==== /foo/tsconfig.json (8 errors) ==== +@@= skipped -12, +12 lines =@@ + "compilerOptions": { + "target": "ES3", + ~~~~~ -!!! error TS5108: Option 'target=ES3' has been removed. Please remove it from your configuration. -- "noImplicitUseStrict": true, -- ~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS6046: Argument for '--target' option must be: 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'es2023', 'es2024', 'es2025', 'esnext'. + "noImplicitUseStrict": true, + ~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5102: Option 'noImplicitUseStrict' has been removed. Please remove it from your configuration. -- "keyofStringsOnly": true, -- ~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'noImplicitUseStrict'. + "keyofStringsOnly": true, + ~~~~~~~~~~~~~~~~~~ -!!! error TS5102: Option 'keyofStringsOnly' has been removed. Please remove it from your configuration. -- "suppressExcessPropertyErrors": true, -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'keyofStringsOnly'. + "suppressExcessPropertyErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5102: Option 'suppressExcessPropertyErrors' has been removed. Please remove it from your configuration. -- "suppressImplicitAnyIndexErrors": true, -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'suppressExcessPropertyErrors'. + "suppressImplicitAnyIndexErrors": true, + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5102: Option 'suppressImplicitAnyIndexErrors' has been removed. Please remove it from your configuration. -- "noStrictGenericChecks": true, -- ~~~~~~~~~~~~~~~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'suppressImplicitAnyIndexErrors'. + "noStrictGenericChecks": true, + ~~~~~~~~~~~~~~~~~~~~~~~ -!!! error TS5102: Option 'noStrictGenericChecks' has been removed. Please remove it from your configuration. -- "charset": "utf8", -- ~~~~~~~~~ ++!!! error TS5023: Unknown compiler option 'noStrictGenericChecks'. + "charset": "utf8", + ~~~~~~~~~ -!!! error TS5102: Option 'charset' has been removed. Please remove it from your configuration. -- "out": "dist.js", -- ~~~~~ ++!!! error TS5023: Unknown compiler option 'charset'. + "out": "dist.js", + ~~~~~ -!!! error TS5102: Option 'out' has been removed. Please remove it from your configuration. -- "ignoreDeprecations": "5.0" -- } -- } -- --==== /foo/a.ts (0 errors) ==== -- const a = 1; -- -+ \ No newline at end of file ++!!! error TS5023: Unknown compiler option 'out'. + "ignoreDeprecations": "5.0" + } + } \ No newline at end of file From 52ef010827df5cf4ed5480b6e6d58e678df6fdcc Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Mon, 22 Jun 2026 09:12:54 -0700 Subject: [PATCH 42/49] Fix generated Is* guards for type-parameter-kinded nodes (#4394) --- _scripts/generate-go-ast.ts | 7 +- internal/ast/ast_generated.go | 188 +--------------------------------- 2 files changed, 4 insertions(+), 191 deletions(-) diff --git a/_scripts/generate-go-ast.ts b/_scripts/generate-go-ast.ts index 0be1fb223bb..17075abf067 100644 --- a/_scripts/generate-go-ast.ts +++ b/_scripts/generate-go-ast.ts @@ -561,7 +561,6 @@ function generateClone(w: CodeWriter, node: NodeType) { return "node.Kind"; } const type = m.declaredType; - const isPrivate = m.inherited ? (m.inheritedField?.private || false) : (m.private || false); if (m.inherited && type?.kind === "alias" && type.name === "ModifierLike") { return "node.Modifiers()"; @@ -607,14 +606,10 @@ function generateIsFunction(w: CodeWriter, node: NodeType) { w.write(`func Is${node.name}(node *Node) bool {`); w.push(); w.write("switch node.Kind {"); - w.push(); - for (const kind of kindTypes) { - w.write(`case ${kind.formatGoConstant()}:`); - } + w.write(`case ${kindTypes.map(kind => kind.formatGoConstant()).join(", ")}:`); w.push(); w.write("return true"); w.pop(); - w.pop(); w.write("}"); w.write("return false"); w.pop(); diff --git a/internal/ast/ast_generated.go b/internal/ast/ast_generated.go index 7567b2e7833..1b314eb4d2b 100644 --- a/internal/ast/ast_generated.go +++ b/internal/ast/ast_generated.go @@ -607,173 +607,7 @@ func (node *Token) Clone(f NodeFactoryCoercible) *Node { func IsToken(node *Node) bool { switch node.Kind { - case KindUnknown: - case KindEndOfFile: - case KindSingleLineCommentTrivia: - case KindMultiLineCommentTrivia: - case KindNewLineTrivia: - case KindWhitespaceTrivia: - case KindConflictMarkerTrivia: - case KindNonTextFileMarkerTrivia: - case KindNumericLiteral: - case KindBigIntLiteral: - case KindStringLiteral: - case KindJsxText: - case KindJsxTextAllWhiteSpaces: - case KindRegularExpressionLiteral: - case KindNoSubstitutionTemplateLiteral: - case KindTemplateHead: - case KindTemplateMiddle: - case KindTemplateTail: - case KindOpenBraceToken: - case KindCloseBraceToken: - case KindOpenParenToken: - case KindCloseParenToken: - case KindOpenBracketToken: - case KindCloseBracketToken: - case KindDotToken: - case KindDotDotDotToken: - case KindSemicolonToken: - case KindCommaToken: - case KindQuestionDotToken: - case KindLessThanToken: - case KindLessThanSlashToken: - case KindGreaterThanToken: - case KindLessThanEqualsToken: - case KindGreaterThanEqualsToken: - case KindEqualsEqualsToken: - case KindExclamationEqualsToken: - case KindEqualsEqualsEqualsToken: - case KindExclamationEqualsEqualsToken: - case KindEqualsGreaterThanToken: - case KindPlusToken: - case KindMinusToken: - case KindAsteriskToken: - case KindAsteriskAsteriskToken: - case KindSlashToken: - case KindPercentToken: - case KindPlusPlusToken: - case KindMinusMinusToken: - case KindLessThanLessThanToken: - case KindGreaterThanGreaterThanToken: - case KindGreaterThanGreaterThanGreaterThanToken: - case KindAmpersandToken: - case KindBarToken: - case KindCaretToken: - case KindExclamationToken: - case KindTildeToken: - case KindAmpersandAmpersandToken: - case KindBarBarToken: - case KindQuestionToken: - case KindColonToken: - case KindAtToken: - case KindQuestionQuestionToken: - case KindBacktickToken: - case KindHashToken: - case KindEqualsToken: - case KindPlusEqualsToken: - case KindMinusEqualsToken: - case KindAsteriskEqualsToken: - case KindAsteriskAsteriskEqualsToken: - case KindSlashEqualsToken: - case KindPercentEqualsToken: - case KindLessThanLessThanEqualsToken: - case KindGreaterThanGreaterThanEqualsToken: - case KindGreaterThanGreaterThanGreaterThanEqualsToken: - case KindAmpersandEqualsToken: - case KindBarEqualsToken: - case KindBarBarEqualsToken: - case KindAmpersandAmpersandEqualsToken: - case KindQuestionQuestionEqualsToken: - case KindCaretEqualsToken: - case KindIdentifier: - case KindPrivateIdentifier: - case KindJSDocCommentTextToken: - case KindBreakKeyword: - case KindCaseKeyword: - case KindCatchKeyword: - case KindClassKeyword: - case KindConstKeyword: - case KindContinueKeyword: - case KindDebuggerKeyword: - case KindDefaultKeyword: - case KindDeleteKeyword: - case KindDoKeyword: - case KindElseKeyword: - case KindEnumKeyword: - case KindExportKeyword: - case KindExtendsKeyword: - case KindFalseKeyword: - case KindFinallyKeyword: - case KindForKeyword: - case KindFunctionKeyword: - case KindIfKeyword: - case KindImportKeyword: - case KindInKeyword: - case KindInstanceOfKeyword: - case KindNewKeyword: - case KindNullKeyword: - case KindReturnKeyword: - case KindSuperKeyword: - case KindSwitchKeyword: - case KindThisKeyword: - case KindThrowKeyword: - case KindTrueKeyword: - case KindTryKeyword: - case KindTypeOfKeyword: - case KindVarKeyword: - case KindVoidKeyword: - case KindWhileKeyword: - case KindWithKeyword: - case KindImplementsKeyword: - case KindInterfaceKeyword: - case KindLetKeyword: - case KindPackageKeyword: - case KindPrivateKeyword: - case KindProtectedKeyword: - case KindPublicKeyword: - case KindStaticKeyword: - case KindYieldKeyword: - case KindAbstractKeyword: - case KindAccessorKeyword: - case KindAsKeyword: - case KindAssertsKeyword: - case KindAssertKeyword: - case KindAnyKeyword: - case KindAsyncKeyword: - case KindAwaitKeyword: - case KindBooleanKeyword: - case KindConstructorKeyword: - case KindDeclareKeyword: - case KindGetKeyword: - case KindImmediateKeyword: - case KindInferKeyword: - case KindIntrinsicKeyword: - case KindIsKeyword: - case KindKeyOfKeyword: - case KindModuleKeyword: - case KindNamespaceKeyword: - case KindNeverKeyword: - case KindOutKeyword: - case KindReadonlyKeyword: - case KindRequireKeyword: - case KindNumberKeyword: - case KindObjectKeyword: - case KindSatisfiesKeyword: - case KindSetKeyword: - case KindStringKeyword: - case KindSymbolKeyword: - case KindTypeKeyword: - case KindUndefinedKeyword: - case KindUniqueKeyword: - case KindUnknownKeyword: - case KindUsingKeyword: - case KindFromKeyword: - case KindGlobalKeyword: - case KindBigIntKeyword: - case KindOverrideKeyword: - case KindOfKeyword: - case KindDeferKeyword: + case KindUnknown, KindEndOfFile, KindSingleLineCommentTrivia, KindMultiLineCommentTrivia, KindNewLineTrivia, KindWhitespaceTrivia, KindConflictMarkerTrivia, KindNonTextFileMarkerTrivia, KindNumericLiteral, KindBigIntLiteral, KindStringLiteral, KindJsxText, KindJsxTextAllWhiteSpaces, KindRegularExpressionLiteral, KindNoSubstitutionTemplateLiteral, KindTemplateHead, KindTemplateMiddle, KindTemplateTail, KindOpenBraceToken, KindCloseBraceToken, KindOpenParenToken, KindCloseParenToken, KindOpenBracketToken, KindCloseBracketToken, KindDotToken, KindDotDotDotToken, KindSemicolonToken, KindCommaToken, KindQuestionDotToken, KindLessThanToken, KindLessThanSlashToken, KindGreaterThanToken, KindLessThanEqualsToken, KindGreaterThanEqualsToken, KindEqualsEqualsToken, KindExclamationEqualsToken, KindEqualsEqualsEqualsToken, KindExclamationEqualsEqualsToken, KindEqualsGreaterThanToken, KindPlusToken, KindMinusToken, KindAsteriskToken, KindAsteriskAsteriskToken, KindSlashToken, KindPercentToken, KindPlusPlusToken, KindMinusMinusToken, KindLessThanLessThanToken, KindGreaterThanGreaterThanToken, KindGreaterThanGreaterThanGreaterThanToken, KindAmpersandToken, KindBarToken, KindCaretToken, KindExclamationToken, KindTildeToken, KindAmpersandAmpersandToken, KindBarBarToken, KindQuestionToken, KindColonToken, KindAtToken, KindQuestionQuestionToken, KindBacktickToken, KindHashToken, KindEqualsToken, KindPlusEqualsToken, KindMinusEqualsToken, KindAsteriskEqualsToken, KindAsteriskAsteriskEqualsToken, KindSlashEqualsToken, KindPercentEqualsToken, KindLessThanLessThanEqualsToken, KindGreaterThanGreaterThanEqualsToken, KindGreaterThanGreaterThanGreaterThanEqualsToken, KindAmpersandEqualsToken, KindBarEqualsToken, KindBarBarEqualsToken, KindAmpersandAmpersandEqualsToken, KindQuestionQuestionEqualsToken, KindCaretEqualsToken, KindIdentifier, KindPrivateIdentifier, KindJSDocCommentTextToken, KindBreakKeyword, KindCaseKeyword, KindCatchKeyword, KindClassKeyword, KindConstKeyword, KindContinueKeyword, KindDebuggerKeyword, KindDefaultKeyword, KindDeleteKeyword, KindDoKeyword, KindElseKeyword, KindEnumKeyword, KindExportKeyword, KindExtendsKeyword, KindFalseKeyword, KindFinallyKeyword, KindForKeyword, KindFunctionKeyword, KindIfKeyword, KindImportKeyword, KindInKeyword, KindInstanceOfKeyword, KindNewKeyword, KindNullKeyword, KindReturnKeyword, KindSuperKeyword, KindSwitchKeyword, KindThisKeyword, KindThrowKeyword, KindTrueKeyword, KindTryKeyword, KindTypeOfKeyword, KindVarKeyword, KindVoidKeyword, KindWhileKeyword, KindWithKeyword, KindImplementsKeyword, KindInterfaceKeyword, KindLetKeyword, KindPackageKeyword, KindPrivateKeyword, KindProtectedKeyword, KindPublicKeyword, KindStaticKeyword, KindYieldKeyword, KindAbstractKeyword, KindAccessorKeyword, KindAsKeyword, KindAssertsKeyword, KindAssertKeyword, KindAnyKeyword, KindAsyncKeyword, KindAwaitKeyword, KindBooleanKeyword, KindConstructorKeyword, KindDeclareKeyword, KindGetKeyword, KindImmediateKeyword, KindInferKeyword, KindIntrinsicKeyword, KindIsKeyword, KindKeyOfKeyword, KindModuleKeyword, KindNamespaceKeyword, KindNeverKeyword, KindOutKeyword, KindReadonlyKeyword, KindRequireKeyword, KindNumberKeyword, KindObjectKeyword, KindSatisfiesKeyword, KindSetKeyword, KindStringKeyword, KindSymbolKeyword, KindTypeKeyword, KindUndefinedKeyword, KindUniqueKeyword, KindUnknownKeyword, KindUsingKeyword, KindFromKeyword, KindGlobalKeyword, KindBigIntKeyword, KindOverrideKeyword, KindOfKeyword, KindDeferKeyword: return true } return false @@ -3742,12 +3576,7 @@ func (node *KeywordExpression) Clone(f NodeFactoryCoercible) *Node { func IsKeywordExpression(node *Node) bool { switch node.Kind { - case KindNullKeyword: - case KindTrueKeyword: - case KindFalseKeyword: - case KindThisKeyword: - case KindSuperKeyword: - case KindImportKeyword: + case KindNullKeyword, KindTrueKeyword, KindFalseKeyword, KindThisKeyword, KindSuperKeyword, KindImportKeyword: return true } return false @@ -5279,18 +5108,7 @@ func (node *KeywordTypeNode) Clone(f NodeFactoryCoercible) *Node { func IsKeywordTypeNode(node *Node) bool { switch node.Kind { - case KindAnyKeyword: - case KindBigIntKeyword: - case KindBooleanKeyword: - case KindIntrinsicKeyword: - case KindNeverKeyword: - case KindNumberKeyword: - case KindObjectKeyword: - case KindStringKeyword: - case KindSymbolKeyword: - case KindUndefinedKeyword: - case KindUnknownKeyword: - case KindVoidKeyword: + case KindAnyKeyword, KindBigIntKeyword, KindBooleanKeyword, KindIntrinsicKeyword, KindNeverKeyword, KindNumberKeyword, KindObjectKeyword, KindStringKeyword, KindSymbolKeyword, KindUndefinedKeyword, KindUnknownKeyword, KindVoidKeyword: return true } return false From 6a0ce6312566b0799c4bf99b0f711ecea686cdde Mon Sep 17 00:00:00 2001 From: Marius Schulz Date: Mon, 22 Jun 2026 17:24:13 +0100 Subject: [PATCH 43/49] Skip mark/rewind in `tryParseTypeArgumentsInExpression` when token isn't `<` (#4234) --- internal/parser/parser.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 42fdddafa1c..2dfc63e03f0 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -5247,8 +5247,14 @@ func (p *Parser) isTemplateStartOfTaggedTemplate() bool { func (p *Parser) tryParseTypeArgumentsInExpression() *ast.NodeList { // TypeArguments must not be parsed in JavaScript files to avoid ambiguity with binary operators. + // Check the cheap preconditions before saving the parser state: unless the current token is `<` + // (or `<<`, which reScanLessThanToken would split), there is nothing to speculatively parse and + // the mark/rewind would be a no-op. + if p.contextFlags&ast.NodeFlagsJavaScriptFile != 0 || (p.token != ast.KindLessThanToken && p.token != ast.KindLessThanLessThanToken) { + return nil + } state := p.mark() - if p.contextFlags&ast.NodeFlagsJavaScriptFile == 0 && p.reScanLessThanToken() == ast.KindLessThanToken { + if p.reScanLessThanToken() == ast.KindLessThanToken { p.nextToken() typeArguments := p.parseDelimitedList(PCTypeArguments, (*Parser).parseType) // If it doesn't have the closing `>` then it's definitely not an type argument list. From 133d33ade8d5898cbdd980227ad8df46959da29f Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:05:00 -0700 Subject: [PATCH 44/49] Fix watches on symlinked directories (#4376) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- .golangci.yml | 2 +- internal/fswatch/CHANGES.md | 10 ++ internal/fswatch/fanotify_linux.go | 42 +++--- internal/fswatch/fsevents_darwin.go | 9 +- internal/fswatch/inotify_linux.go | 26 ++-- internal/fswatch/kqueue.go | 100 ++++++++----- internal/fswatch/walkdir_test.go | 23 ++- internal/fswatch/walkdir_windows.go | 3 +- internal/fswatch/watcher.go | 97 +++++++++++-- internal/fswatch/watcher_test.go | 137 +++++++++++++++++- internal/fswatch/windows.go | 7 +- internal/lsp/lspwatcher/lspwatcher.go | 53 ++----- internal/lsp/lspwatcher/lspwatcher_test.go | 90 ++++++------ .../{vfs/osvfs => nativepath}/eintr_unix.go | 2 +- .../osvfs => nativepath}/realpath_darwin.go | 4 +- .../osvfs => nativepath}/realpath_linux.go | 4 +- internal/nativepath/realpath_other.go | 9 ++ .../osvfs => nativepath}/realpath_windows.go | 4 +- internal/nativepath/symlink_other.go | 10 ++ .../symlink_windows.go} | 4 +- .../symlink_windows_test.go} | 56 ++++--- internal/vfs/osvfs/os.go | 7 +- internal/vfs/osvfs/realpath_other.go | 11 -- internal/vfs/osvfs/reparsepoint_other.go | 6 - 24 files changed, 491 insertions(+), 225 deletions(-) rename internal/{vfs/osvfs => nativepath}/eintr_unix.go (93%) rename internal/{vfs/osvfs => nativepath}/realpath_darwin.go (96%) rename internal/{vfs/osvfs => nativepath}/realpath_linux.go (96%) create mode 100644 internal/nativepath/realpath_other.go rename internal/{vfs/osvfs => nativepath}/realpath_windows.go (97%) create mode 100644 internal/nativepath/symlink_other.go rename internal/{vfs/osvfs/reparsepoint_windows.go => nativepath/symlink_windows.go} (86%) rename internal/{vfs/osvfs/reparsepoint_windows_test.go => nativepath/symlink_windows_test.go} (69%) delete mode 100644 internal/vfs/osvfs/realpath_other.go delete mode 100644 internal/vfs/osvfs/reparsepoint_other.go diff --git a/.golangci.yml b/.golangci.yml index 28a868275c8..07389a449f2 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -96,7 +96,7 @@ linters: - path: internal/fourslash/tests/gen/ linters: - misspell - - path: 'internal/(repo|testutil|testrunner|vfs|pprof|execute/tsctests|bundled|fswatch)|cmd/tsgo' + - path: 'internal/(repo|testutil|testrunner|vfs|pprof|execute/tsctests|bundled|fswatch|nativepath)|cmd/tsgo' text: should likely be used instead - path: '(.+)_test\.go$' text: should likely be used instead diff --git a/internal/fswatch/CHANGES.md b/internal/fswatch/CHANGES.md index 347d9005ade..01ecfd975d5 100644 --- a/internal/fswatch/CHANGES.md +++ b/internal/fswatch/CHANGES.md @@ -22,6 +22,16 @@ default**, watching only direct children. Pass `WithRecursive()` to watch the entire tree. This matches TypeScript's `watchDirectory(path, cb, recursive?)` where recursive is opt-in. +### Symlinked watch roots + +When `WatchDirectory` is called with a symlink or reparse point to a directory, +Go follows the link for the OS subscription but reports events under the +caller-provided path. This matches TypeScript/Node's behavior for watch roots +while keeping the logical paths stable for callers. + +Userspace recursive traversal still does not follow symlinked descendant +directories. + ### Event kinds C++ has three event kinds: create, update, delete. Go has two: **`EventUpdate`** diff --git a/internal/fswatch/fanotify_linux.go b/internal/fswatch/fanotify_linux.go index ef9d00f3a06..82fe3eb8062 100644 --- a/internal/fswatch/fanotify_linux.go +++ b/internal/fswatch/fanotify_linux.go @@ -147,9 +147,10 @@ func makeFanotifyHandleKey(fsid [2]int32, handleType int32, handleBytes []byte) // fanotifySubscription mirrors inotifySubscription for the fanotify backend. type fanotifySubscription struct { - path string - dirWatch *dirWatch - key fanotifyHandleKey + path string + watchPath string + dirWatch *dirWatch + key fanotifyHandleKey } // fanotifyDfidName holds parsed directory FID + name from an info record. @@ -294,7 +295,7 @@ func (b *fanotifyBackend) subscribe(w *dirWatch) error { b.markMask = fanotifyMarkMaskMovedFromTo } else { b.markMask = fanotifyMarkMaskRename - err := unix.FanotifyMark(b.fanotifyFD, fanotifyMarkAddFlags, fanotifyMarkMaskRename, unix.AT_FDCWD, w.dir) + err := unix.FanotifyMark(b.fanotifyFD, fanotifyMarkAddFlags, fanotifyMarkMaskRename, unix.AT_FDCWD, w.physicalDir) switch { case err == nil: // B5: pair the probe Add with a matching Remove. If @@ -305,7 +306,7 @@ func (b *fanotifyBackend) subscribe(w *dirWatch) error { // flags the kernel just merges them. The probe is the // only failure path we explicitly retry. for { - rmErr := unix.FanotifyMark(b.fanotifyFD, unix.FAN_MARK_REMOVE|unix.FAN_MARK_ONLYDIR, fanotifyMarkMaskRename, unix.AT_FDCWD, w.dir) + rmErr := unix.FanotifyMark(b.fanotifyFD, unix.FAN_MARK_REMOVE|unix.FAN_MARK_ONLYDIR, fanotifyMarkMaskRename, unix.AT_FDCWD, w.physicalDir) if rmErr == nil || !errors.Is(rmErr, unix.EINTR) { break } @@ -316,7 +317,7 @@ func (b *fanotifyBackend) subscribe(w *dirWatch) error { } } if !w.recursive { - if err := b.markDir(w, w.dir); err != nil { + if err := b.markDir(w, w.dir, w.physicalDir); err != nil { return &dirWatchError{ err: fmt.Errorf("fanotify_mark on '%s' failed: %w", w.dir, err), dirWatch: w, @@ -324,11 +325,12 @@ func (b *fanotifyBackend) subscribe(w *dirWatch) error { } return nil } - if err := walkDir(w.dir, true, func(path string, isDir bool) error { + if err := walkDir(w.physicalDir, true, func(watchPath string, isDir bool) error { if !isDir { return nil } - if err := b.markDir(w, path); err != nil { + path := w.displayPath(watchPath) + if err := b.markDir(w, path, watchPath); err != nil { return &dirWatchError{ err: fmt.Errorf("fanotify_mark on '%s' failed: %w", path, err), dirWatch: w, @@ -342,23 +344,23 @@ func (b *fanotifyBackend) subscribe(w *dirWatch) error { return nil } -func (b *fanotifyBackend) markDir(w *dirWatch, path string) error { - if err := unix.FanotifyMark(b.fanotifyFD, fanotifyMarkAddFlags, b.markMask, unix.AT_FDCWD, path); err != nil { +func (b *fanotifyBackend) markDir(w *dirWatch, path string, markPath string) error { + if err := unix.FanotifyMark(b.fanotifyFD, fanotifyMarkAddFlags, b.markMask, unix.AT_FDCWD, markPath); err != nil { return err } - handle, _, err := unix.NameToHandleAt(unix.AT_FDCWD, path, 0) + handle, _, err := unix.NameToHandleAt(unix.AT_FDCWD, markPath, 0) if err != nil { // Unmark since we can't track this directory without a handle. - _ = unix.FanotifyMark(b.fanotifyFD, unix.FAN_MARK_REMOVE|unix.FAN_MARK_ONLYDIR, b.markMask, unix.AT_FDCWD, path) + _ = unix.FanotifyMark(b.fanotifyFD, unix.FAN_MARK_REMOVE|unix.FAN_MARK_ONLYDIR, b.markMask, unix.AT_FDCWD, markPath) return fmt.Errorf("name_to_handle_at: %w", err) } var st unix.Statfs_t - if err := unix.Statfs(path, &st); err != nil { - _ = unix.FanotifyMark(b.fanotifyFD, unix.FAN_MARK_REMOVE|unix.FAN_MARK_ONLYDIR, b.markMask, unix.AT_FDCWD, path) + if err := unix.Statfs(markPath, &st); err != nil { + _ = unix.FanotifyMark(b.fanotifyFD, unix.FAN_MARK_REMOVE|unix.FAN_MARK_ONLYDIR, b.markMask, unix.AT_FDCWD, markPath) return fmt.Errorf("statfs: %w", err) } key := makeFanotifyHandleKey(st.Fsid.Val, handle.Type(), handle.Bytes()) - sub := &fanotifySubscription{path: path, dirWatch: w, key: key} + sub := &fanotifySubscription{path: path, watchPath: markPath, dirWatch: w, key: key} b.subscriptions[key] = append(b.subscriptions[key], sub) return nil } @@ -468,11 +470,11 @@ func (b *fanotifyBackend) handleRenameEvent(mask uint64, dfidOld *fanotifyDfidNa newPath := s.path + "/" + dfidNew.name s.dirWatch.events.create(newPath) if isDir && s.dirWatch.recursive { - _ = walkDir(newPath, true, func(p string, pIsDir bool) error { + _ = walkDir(s.dirWatch.physicalPath(newPath), true, func(p string, pIsDir bool) error { if !pIsDir { return nil } - _ = b.markDir(s.dirWatch, p) + _ = b.markDir(s.dirWatch, s.dirWatch.displayPath(p), p) return nil }) } @@ -564,11 +566,11 @@ func (b *fanotifyBackend) handleSubscription(mask uint64, dfid *fanotifyDfidName if hasCreate { w.events.create(path) if isDir && w.recursive { - _ = walkDir(path, true, func(p string, pIsDir bool) error { + _ = walkDir(w.physicalPath(path), true, func(p string, pIsDir bool) error { if !pIsDir { return nil } - _ = b.markDir(w, p) + _ = b.markDir(w, w.displayPath(p), p) return nil }) } @@ -728,7 +730,7 @@ func (b *fanotifyBackend) closeWatch(w *dirWatch) error { for _, s := range list { if s.dirWatch == w { removedAny = true - removedPath = s.path + removedPath = s.watchPath continue } kept = append(kept, s) diff --git a/internal/fswatch/fsevents_darwin.go b/internal/fswatch/fsevents_darwin.go index a0bf2bea9fb..fe99bd1f36b 100644 --- a/internal/fswatch/fsevents_darwin.go +++ b/internal/fswatch/fsevents_darwin.go @@ -189,7 +189,7 @@ func (b *fsEventsBackend) start() error { // checkWatcher mirrors the helper of the same name. func checkWatcher(w *dirWatch) error { - info, err := os.Stat(w.dir) + info, err := os.Stat(w.physicalDir) if err != nil { return &dirWatchError{err: err, dirWatch: w} } @@ -223,7 +223,7 @@ func (b *fsEventsBackend) startStream(w *dirWatch, since uint64) error { return errMissingFSEventsState } - dirCStr := append([]byte(w.dir), 0) + dirCStr := append([]byte(w.physicalDir), 0) cfDir := cfStringCreate(0, unsafe.Pointer(&dirCStr[0]), cfStringEncodingUTF8) defer cfRelease(cfDir) @@ -386,6 +386,9 @@ func fsEventsCallback(cb *streamCallback, payload *fsEventsCallbackPayload) { // the directory, not the dir's own metadata churn. // (A removal of the dir is still propagated because Watcher // relies on it to tear down the stream.) + rawPath := path + path = w.displayPath(path) + if path == w.dir && !isRemoved && !isRenamed { continue } @@ -402,7 +405,7 @@ func fsEventsCallback(cb *streamCallback, payload *fsEventsCallbackPayload) { // moved in (update); remove+create could mean replaced. // Stat to check existence. var st unix.Stat_t - if unix.Lstat(path, &st) != nil { + if unix.Lstat(rawPath, &st) != nil { w.events.remove(path) if path == w.dir { deletedRoot = true diff --git a/internal/fswatch/inotify_linux.go b/internal/fswatch/inotify_linux.go index 29e8053038a..87619ad5056 100644 --- a/internal/fswatch/inotify_linux.go +++ b/internal/fswatch/inotify_linux.go @@ -89,9 +89,10 @@ const ( // inotifySubscription. type inotifySubscription struct { - path string - dirWatch *dirWatch - wd int + path string + watchPath string + dirWatch *dirWatch + wd int } // inotifyBackend. @@ -213,7 +214,7 @@ func (b *inotifyBackend) shutdown() { // virtual dispatch under b.mu (so it's serialized against handleEvent). func (b *inotifyBackend) subscribe(w *dirWatch) error { if !w.recursive { - if _, err := b.watchDir(w, w.dir); err != nil { + if _, err := b.watchDir(w, w.dir, w.physicalDir); err != nil { return &dirWatchError{ err: fmt.Errorf("inotify_add_watch on '%s' failed: %w", w.dir, err), dirWatch: w, @@ -221,11 +222,12 @@ func (b *inotifyBackend) subscribe(w *dirWatch) error { } return nil } - if err := walkDir(w.dir, true, func(path string, isDir bool) error { + if err := walkDir(w.physicalDir, true, func(watchPath string, isDir bool) error { if !isDir { return nil } - if _, err := b.watchDir(w, path); err != nil { + path := w.displayPath(watchPath) + if _, err := b.watchDir(w, path, watchPath); err != nil { return &dirWatchError{ err: fmt.Errorf("inotify_add_watch on '%s' failed: %w", path, err), dirWatch: w, @@ -241,12 +243,12 @@ func (b *inotifyBackend) subscribe(w *dirWatch) error { // watchDir registers an inotify watch on path and records the resulting // subscription. Returns the kernel watch descriptor on success. -func (b *inotifyBackend) watchDir(w *dirWatch, path string) (int, error) { - wd, err := unix.InotifyAddWatch(b.inotify, path, inotifyMask) +func (b *inotifyBackend) watchDir(w *dirWatch, path string, watchPath string) (int, error) { + wd, err := unix.InotifyAddWatch(b.inotify, watchPath, inotifyMask) if err != nil { return 0, err } - sub := &inotifySubscription{path: path, dirWatch: w, wd: wd} + sub := &inotifySubscription{path: path, watchPath: watchPath, dirWatch: w, wd: wd} b.subscriptions[wd] = append(b.subscriptions[wd], sub) return wd, nil } @@ -328,20 +330,22 @@ func (b *inotifyBackend) handleEvent(ev *unix.InotifyEvent, name string, touched func (b *inotifyBackend) handleSubscription(ev *unix.InotifyEvent, name string, sub *inotifySubscription) bool { w := sub.dirWatch path := sub.path + watchPath := sub.watchPath isDir := ev.Mask&unix.IN_ISDIR != 0 if name != "" { path = path + "/" + name + watchPath = watchPath + "/" + name } switch { case ev.Mask&(unix.IN_CREATE|unix.IN_MOVED_TO) != 0: w.events.create(path) if isDir && w.recursive { - _ = walkDir(path, true, func(p string, pIsDir bool) error { + _ = walkDir(watchPath, true, func(p string, pIsDir bool) error { if !pIsDir { return nil } - _, _ = b.watchDir(w, p) + _, _ = b.watchDir(w, w.displayPath(p), p) return nil }) } diff --git a/internal/fswatch/kqueue.go b/internal/fswatch/kqueue.go index 1f445d24863..ae3807c1791 100644 --- a/internal/fswatch/kqueue.go +++ b/internal/fswatch/kqueue.go @@ -95,9 +95,10 @@ func openForEvents(path string) (int, error) { // dirEntry tracks a watched path for kqueue's fd↔path mapping. type dirEntry struct { - path string - isDir bool - state any // stores the open fd + path string + watchPath string + isDir bool + state any // stores the open fd } // kqueueSubscription. @@ -373,7 +374,7 @@ func (b *kqueueBackend) closeDescendantFDsLocked(w *dirWatch, entries map[string // and returns true. The caller should emit update instead of delete. func (b *kqueueBackend) tryRewatchLocked(entry *dirEntry) bool { var st unix.Stat_t - if unix.Lstat(entry.path, &st) != nil { + if unix.Lstat(entry.watchPath, &st) != nil { return false } @@ -384,7 +385,7 @@ func (b *kqueueBackend) tryRewatchLocked(entry *dirEntry) bool { return false } - fd, err := openForEvents(entry.path) + fd, err := openForEvents(entry.watchPath) if err != nil { return false } @@ -459,8 +460,9 @@ func (b *kqueueBackend) subscribe(w *dirWatch) error { // publishes the entries map to the event loop (via subsByPath), // which could read it via compareDir while we're still populating it. entries := map[string]*dirEntry{} - if err := walkDir(w.dir, w.recursive, func(path string, isDir bool) error { - entries[path] = &dirEntry{path: path, isDir: isDir} + if err := walkDir(w.physicalDir, w.recursive, func(watchPath string, isDir bool) error { + path := w.displayPath(watchPath) + entries[path] = &dirEntry{path: path, watchPath: watchPath, isDir: isDir} return nil }); err != nil { return err @@ -474,7 +476,7 @@ func (b *kqueueBackend) subscribe(w *dirWatch) error { defer b.mu.Unlock() for path, entry := range entries { - fd, err := openForEvents(path) + fd, err := openForEvents(entry.watchPath) if err != nil { if path == w.dir { b.cleanupEntriesLocked(entries) @@ -537,7 +539,7 @@ func (b *kqueueBackend) watchPath(w *dirWatch, path string, entries map[string]* sub := &kqueueSubscription{dirWatch: w, path: path, entries: entries} if entry.state == nil { - fd, err := openForEvents(path) + fd, err := openForEvents(entry.watchPath) if err != nil { return false } @@ -584,24 +586,43 @@ func (b *kqueueBackend) compareDir(_ int, path string, touched map[*dirWatch]str subs = filteredSubs dirStart := path + string(filepath.Separator) - - // Read the current dir contents from disk. - diskEntries, err := readEntries(path) - if err != nil { - return false + type diskSnapshot struct { + entries []os.DirEntry + currentDisplayPaths map[string]struct{} } + snapshots := map[string]diskSnapshot{} // Each subscription has its own entries map (built in subscribe). // Multiple subs at the same path arise from multiple dirWatches // covering overlapping subtrees; their maps are always distinct, so // we iterate subs directly rather than trying to dedup by map identity. - currentSet := map[string]struct{}{} - for _, ent := range diskEntries { - fullPath := dirStart + ent.Name() - currentSet[fullPath] = struct{}{} + for _, sub := range subs { + baseEntry := sub.entries[path] + if baseEntry == nil { + continue + } + watchPath := baseEntry.watchPath + watchDirStart := watchPath + string(filepath.Separator) + + snapshot, ok := snapshots[watchPath] + if !ok { + diskEntries, err := readEntries(watchPath) + if err != nil { + continue + } + snapshot.entries = diskEntries + snapshot.currentDisplayPaths = make(map[string]struct{}, len(diskEntries)) + for _, ent := range diskEntries { + snapshot.currentDisplayPaths[dirStart+ent.Name()] = struct{}{} + } + snapshots[watchPath] = snapshot + } + + entries := sub.entries + for _, ent := range snapshot.entries { + fullPath := dirStart + ent.Name() + fullWatchPath := watchDirStart + ent.Name() - for _, sub := range subs { - entries := sub.entries existing := entries[fullPath] if existing != nil { if existing.state != nil { @@ -612,7 +633,7 @@ func (b *kqueueBackend) compareDir(_ int, path string, touched map[*dirWatch]str // (now unlinked) inode. if fd, ok := existing.state.(int); ok { var fdSt, pathSt unix.Stat_t - if unix.Fstat(fd, &fdSt) == nil && unix.Lstat(fullPath, &pathSt) == nil { + if unix.Fstat(fd, &fdSt) == nil && unix.Lstat(fullWatchPath, &pathSt) == nil { if fdSt.Dev != pathSt.Dev || fdSt.Ino != pathSt.Ino { // Inode changed: path was replaced. b.mu.Lock() @@ -638,20 +659,21 @@ func (b *kqueueBackend) compareDir(_ int, path string, touched map[*dirWatch]str sub.dirWatch.events.update(fullPath) touched[sub.dirWatch] = struct{}{} if ent.IsDir() && sub.dirWatch.recursive { - _ = walkDir(fullPath, true, func(p string, pIsDir bool) error { - if p == fullPath { + _ = walkDir(fullWatchPath, true, func(p string, pIsDir bool) error { + if p == fullWatchPath { return nil } - e := &dirEntry{path: p, isDir: pIsDir} - entries[p] = e - sub.dirWatch.events.create(p) - b.watchPath(sub.dirWatch, p, entries) + displayPath := sub.dirWatch.displayPath(p) + e := &dirEntry{path: displayPath, watchPath: p, isDir: pIsDir} + entries[displayPath] = e + sub.dirWatch.events.create(displayPath) + b.watchPath(sub.dirWatch, displayPath, entries) return nil }) } continue } - e := &dirEntry{path: fullPath, isDir: ent.IsDir()} + e := &dirEntry{path: fullPath, watchPath: fullWatchPath, isDir: ent.IsDir()} entries[fullPath] = e if !b.watchPath(sub.dirWatch, fullPath, entries) { delete(entries, fullPath) @@ -664,24 +686,22 @@ func (b *kqueueBackend) compareDir(_ int, path string, touched map[*dirWatch]str // to catch pre-populated subdirectories (e.g. a directory // tree moved into the watched area). if ent.IsDir() && sub.dirWatch.recursive { - _ = walkDir(fullPath, true, func(p string, pIsDir bool) error { - if p == fullPath { + _ = walkDir(fullWatchPath, true, func(p string, pIsDir bool) error { + if p == fullWatchPath { return nil // already handled above } - entry := &dirEntry{path: p, isDir: pIsDir} - entries[p] = entry - sub.dirWatch.events.create(p) - b.watchPath(sub.dirWatch, p, entries) + displayPath := sub.dirWatch.displayPath(p) + entry := &dirEntry{path: displayPath, watchPath: p, isDir: pIsDir} + entries[displayPath] = entry + sub.dirWatch.events.create(displayPath) + b.watchPath(sub.dirWatch, displayPath, entries) return nil }) } } - } - // Detect removals: entries directly under dirStart that no longer - // exist on disk. - for _, sub := range subs { - entries := sub.entries + // Detect removals: entries directly under dirStart that no longer + // exist on disk. var toRemove []string for p := range entries { if !strings.HasPrefix(p, dirStart) { @@ -691,7 +711,7 @@ func (b *kqueueBackend) compareDir(_ int, path string, touched map[*dirWatch]str if strings.Contains(rest, string(filepath.Separator)) { continue } - if _, ok := currentSet[p]; ok { + if _, ok := snapshot.currentDisplayPaths[p]; ok { continue } toRemove = append(toRemove, p) diff --git a/internal/fswatch/walkdir_test.go b/internal/fswatch/walkdir_test.go index 81ae452207f..acdf5de29d9 100644 --- a/internal/fswatch/walkdir_test.go +++ b/internal/fswatch/walkdir_test.go @@ -31,6 +31,25 @@ func TestWalkDirDoesNotFollowSymlinkedDir(t *testing.T) { //nolint:paralleltest runWalkDirTest(t, testWalkDirDoesNotFollowSymlinkedDir) } +func TestWalkDirDoesNotFollowRootSymlinkedDir(t *testing.T) { //nolint:paralleltest // runWalkDirTest calls t.Parallel. + runWalkDirTest(t, testWalkDirDoesNotFollowRootSymlinkedDir) +} + +func testWalkDirDoesNotFollowRootSymlinkedDir(t *testing.T, walk walkDirFunc) { + root := newTmpDir(t) + target := filepath.Join(t.TempDir(), "target") + if err := os.Mkdir(target, 0o755); err != nil { + t.Fatal(err) + } + + link := filepath.Join(root, "link") + makeDirSymlink(t, target, link) + + if err := walk(link, true, nil); err == nil { + t.Fatal("expected error for root symlinked directory") + } +} + func testWalkDirDoesNotFollowSymlinkedDir(t *testing.T, walk walkDirFunc) { root := newTmpDir(t) target := filepath.Join(t.TempDir(), "target") @@ -42,9 +61,7 @@ func testWalkDirDoesNotFollowSymlinkedDir(t *testing.T, walk walkDirFunc) { } link := filepath.Join(root, "link") - if err := os.Symlink(target, link); err != nil { - t.Fatal(err) - } + makeDirSymlink(t, target, link) found := map[string]bool{} if err := walk(root, true, func(path string, isDir bool) error { diff --git a/internal/fswatch/walkdir_windows.go b/internal/fswatch/walkdir_windows.go index 3e8d0d9a798..a73bf47a2bf 100644 --- a/internal/fswatch/walkdir_windows.go +++ b/internal/fswatch/walkdir_windows.go @@ -20,7 +20,8 @@ func walkDir(dir string, recursive bool, fn func(path string, isDir bool) error) if err := windows.GetFileAttributesEx(rootPtr, windows.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&rootData))); err != nil { return fmt.Errorf("error opening directory: %w", err) } - if rootData.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY == 0 { + if rootData.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY == 0 || + rootData.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 { return syscall.ENOTDIR } if fn != nil { diff --git a/internal/fswatch/watcher.go b/internal/fswatch/watcher.go index e17d5012904..1500417c3e4 100644 --- a/internal/fswatch/watcher.go +++ b/internal/fswatch/watcher.go @@ -3,11 +3,14 @@ package fswatch import ( "errors" "fmt" + "os" "path/filepath" "runtime" "slices" "strings" "sync" + + "github.com/microsoft/typescript-go/internal/nativepath" ) var errNilCallback = errors.New("fswatch: callback must not be nil") @@ -56,7 +59,11 @@ type Watcher interface { // WatchDirectory watches dir for changes, calling fn with batched // events. By default, only direct children are watched. Use // [WithRecursive] to watch the entire directory tree. - // dir must be an absolute path to an existing directory. + // dir must be an absolute path to an existing directory. If dir is a + // symlink or reparse point to a directory, the OS subscription follows + // the target directory but delivered event paths remain rooted at dir. + // Userspace recursive traversal does not follow symlinked descendant + // directories. // Returns [ErrUnavailable] if the watcher is not supported on // the current platform. WatchDirectory(dir string, fn WatchCallback, opts ...WatchOption) (Watch, error) @@ -259,7 +266,7 @@ func (w *watcher) getImpl() (watcherImpl, error) { return impl, nil } -func (w *watcher) getOrCreateDirWatch(dir string, recursive bool) *dirWatch { +func (w *watcher) getOrCreateDirWatch(dir string, physicalDir string, recursive bool) *dirWatch { w.mu.Lock() defer w.mu.Unlock() if w.dirWatches == nil { @@ -275,7 +282,7 @@ func (w *watcher) getOrCreateDirWatch(dir string, recursive bool) *dirWatch { if dw, ok := w.dirWatches[key]; ok { return dw } - dw := newDirWatch(dir, w.debounce) + dw := newDirWatch(dir, physicalDir, w.debounce) dw.recursive = recursive w.dirWatches[key] = dw return dw @@ -306,13 +313,14 @@ func (w *watcher) WatchDirectory(dir string, fn WatchCallback, opts ...WatchOpti return nil, errNotAbsolute } dir = canonicalizePath(dir) + physicalDir := physicalDirFor(dir) var sopts watchOptions for _, o := range opts { o.applyWatchOption(&sopts) } - dw := w.getOrCreateDirWatch(dir, sopts.recursive) + dw := w.getOrCreateDirWatch(dir, physicalDir, sopts.recursive) id, _ := dw.watch(fn, sopts.ignore) impl, err := w.getImpl() @@ -521,9 +529,13 @@ func (e *dirWatchError) Unwrap() error { return e.err } // dirWatch holds per-directory state: pending events, registered callbacks, // and a reference to the shared debouncer. Each watched directory has one. type dirWatch struct { - dir string - recursive bool - events eventList + // dir is the caller-visible watch root used in delivered event paths. + dir string + // physicalDir is the path passed to OS watcher APIs. It differs from dir + // only when dir is a symlink or reparse point to a directory. + physicalDir string + recursive bool + events eventList // state stores per-directory platform-specific bookkeeping (fsevents, windows). state any @@ -534,13 +546,80 @@ type dirWatch struct { nextCBID uint64 } -func newDirWatch(dir string, db *debounce) *dirWatch { - dw := &dirWatch{dir: dir} +func newDirWatch(dir string, physicalDir string, db *debounce) *dirWatch { + dw := &dirWatch{dir: dir, physicalDir: physicalDir} dw.debounce = db dw.debounce.add(dw, func() { dw.triggerCallbacks() }) return dw } +// physicalDirFor returns the physical path to watch for dir. If dir is a +// symlink or reparse point, events are subscribed on its realpath while +// callbacks still use dir. +func physicalDirFor(dir string) string { + if !nativepath.IsSymlinkOrReparsePoint(dir) { + return dir + } + realpath, err := nativepath.Realpath(dir) + if err != nil { + return dir + } + if realpath == dir { + return dir + } + return canonicalizePath(filepath.Clean(realpath)) +} + +// displayPath maps a physical event path back under the caller-visible +// watch root. +func (dw *dirWatch) displayPath(watchPath string) string { + return rebasePath(watchPath, dw.physicalDir, dw.dir) +} + +// physicalPath maps a caller-visible path to the physical watched root. +func (dw *dirWatch) physicalPath(displayPath string) string { + return rebasePath(displayPath, dw.dir, dw.physicalDir) +} + +// rebasePath replaces the from root in path with to, preserving any child +// suffix. Prefix matches must end at a path separator so sibling paths like +// "/foo2" are not rebased from "/foo". +func rebasePath(path string, from string, to string) string { + if from == to { + return path + } + if path == from { + return to + } + if !strings.HasPrefix(path, from) { + return path + } + suffix := path[len(from):] + if len(from) > 0 && os.IsPathSeparator(from[len(from)-1]) { + return joinPathSuffix(to, suffix) + } + if len(suffix) == 0 || !os.IsPathSeparator(suffix[0]) { + return path + } + return joinPathSuffix(to, suffix) +} + +func joinPathSuffix(root string, suffix string) string { + if suffix == "" { + return root + } + if os.IsPathSeparator(suffix[0]) { + if len(root) > 0 && os.IsPathSeparator(root[len(root)-1]) { + return root + suffix[1:] + } + return root + suffix + } + if len(root) > 0 && os.IsPathSeparator(root[len(root)-1]) { + return root + suffix + } + return root + string(filepath.Separator) + suffix +} + func (dw *dirWatch) destroyDebounce() { dw.mu.Lock() db := dw.debounce diff --git a/internal/fswatch/watcher_test.go b/internal/fswatch/watcher_test.go index 7fe53c65e74..a051ff7c197 100644 --- a/internal/fswatch/watcher_test.go +++ b/internal/fswatch/watcher_test.go @@ -12,6 +12,7 @@ import ( "fmt" "math/rand" "os" + "os/exec" "path/filepath" "runtime" "slices" @@ -117,6 +118,18 @@ func newTmpDir(t testingT) string { return resolved } +func makeDirSymlink(t testingT, target string, link string) { + t.Helper() + if runtime.GOOS == "windows" { + if err := exec.Command("cmd", "/c", "mklink", "/J", link, target).Run(); err == nil { + return + } + } + if err := os.Symlink(target, link); err != nil { + t.Skipf("directory symlink support is not available: %v", err) + } +} + // nameCounter generates unique file names per test to avoid collisions. var nameCounter atomic.Uint64 @@ -136,7 +149,7 @@ func subPath(dir string) string { // test gets its own debouncer so tests don't share goroutine state. func newDirectWatcher(t testingT, dir string) *dirWatch { t.Helper() - w := newDirWatch(dir, newDebounce()) + w := newDirWatch(dir, dir, newDebounce()) w.recursive = true t.Cleanup(func() { w.destroyDebounce() }) return w @@ -562,6 +575,68 @@ func containsEvent(got []Event, typ EventKind, path string) bool { return false } +func TestRebasePath(t *testing.T) { + t.Parallel() + + volume := filepath.VolumeName(os.TempDir()) + root := volume + string(filepath.Separator) + from := filepath.Join(root, "from") + to := filepath.Join(root, "to") + + tests := []struct { + name string + path string + from string + to string + want string + }{ + { + name: "exact root", + path: from, + from: from, + to: to, + want: to, + }, + { + name: "child", + path: filepath.Join(from, "child"), + from: from, + to: to, + want: filepath.Join(to, "child"), + }, + { + name: "sibling", + path: filepath.Join(root, "from-sibling", "child"), + from: from, + to: to, + want: filepath.Join(root, "from-sibling", "child"), + }, + { + name: "from root", + path: filepath.Join(root, "child"), + from: root, + to: to, + want: filepath.Join(to, "child"), + }, + { + name: "to root", + path: filepath.Join(from, "child"), + from: from, + to: root, + want: filepath.Join(root, "child"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := rebasePath(tt.path, tt.from, tt.to); got != tt.want { + t.Fatalf("rebasePath(%q, %q, %q) = %q, want %q", tt.path, tt.from, tt.to, got, tt.want) + } + }) + } +} + // filterEventsForPaths returns only the events whose Path is in the // allowed set. Used to discard incidental dir-update events that some // backends emit for the parent dir of a touched file. @@ -997,6 +1072,66 @@ func TestSubscribeSymlinkDelete(t *testing.T) { }) } +func TestSubscribeSymlinkedDirectoryRebasesTargetEvents(t *testing.T) { + t.Parallel() + runForEachWatcher(t, func(t testingT, watcherImpl Watcher) { + dir := newTmpDir(t) + target := filepath.Join(dir, "target") + if err := os.Mkdir(target, 0o755); err != nil { + t.Fatal(err) + } + link := filepath.Join(dir, "link") + makeDirSymlink(t, target, link) + + r, _ := subscribeFor(t, link, watcherImpl) + targetChild := filepath.Join(target, "child") + if err := os.WriteFile(targetChild, []byte("x"), 0o644); err != nil { + t.Fatal(err) + } + expectContains(t, r, EventUpdate, filepath.Join(link, "child")) + }) +} + +func TestRecursiveSubscribeSymlinkedDirectoryDoesNotFollowDescendantSymlink(t *testing.T) { + t.Parallel() + runForEachWatcher(t, func(t testingT, watcherImpl Watcher) { + if watcherImpl.HasFastRecursiveBackend() { + t.Skip("fast recursive backends do not use the userspace recursive walk") + } + dir := newTmpDir(t) + target := filepath.Join(dir, "target") + if err := os.Mkdir(target, 0o755); err != nil { + t.Fatal(err) + } + link := filepath.Join(dir, "link") + makeDirSymlink(t, target, link) + + descendantTarget := filepath.Join(dir, "descendant-target") + if err := os.Mkdir(descendantTarget, 0o755); err != nil { + t.Fatal(err) + } + descendantLink := filepath.Join(target, "descendant-link") + makeDirSymlink(t, descendantTarget, descendantLink) + + r, _ := subscribeFor(t, link, watcherImpl) + logicalGrandchild := filepath.Join(link, "descendant-link", "grandchild") + physicalGrandchild := filepath.Join(descendantTarget, "grandchild") + if err := os.WriteFile(logicalGrandchild, []byte("x"), 0o644); err != nil { + t.Fatal(err) + } + + marker := filepath.Join(target, "marker") + if err := os.WriteFile(marker, []byte("flush"), 0o644); err != nil { + t.Fatal(err) + } + + got := expectContains(t, r, EventUpdate, filepath.Join(link, "marker")) + got = append(got, r.drainQuiet(2*maxWaitTime)...) + assertNoEventsForPath(t, got, logicalGrandchild, "expected no events through descendant symlink") + assertNoEventsForPath(t, got, physicalGrandchild, "expected no events for descendant symlink target") + }) +} + // ----- event coalescing -------------------------------------------------- func TestSubscribeCoalesceCreateUpdate(t *testing.T) { diff --git a/internal/fswatch/windows.go b/internal/fswatch/windows.go index fff9b10d4ef..8935cef56ad 100644 --- a/internal/fswatch/windows.go +++ b/internal/fswatch/windows.go @@ -161,7 +161,7 @@ type windowsRead struct { } func newWindowsSubscription(watcherImpl *windowsBackend, w *dirWatch) (*windowsSubscription, error) { - pathPtr, err := windows.UTF16PtrFromString(w.dir) + pathPtr, err := windows.UTF16PtrFromString(w.physicalDir) if err != nil { return nil, &dirWatchError{err: err, dirWatch: w} } @@ -333,7 +333,7 @@ func (s *windowsSubscription) processCompletion(callErr error, buf []byte, bytes return false case errors.Is(callErr, windows.ERROR_ACCESS_DENIED): // Possibly the watched dir was deleted; check and handle. - pathPtr, _ := windows.UTF16PtrFromString(s.dirWatch.dir) + pathPtr, _ := windows.UTF16PtrFromString(s.dirWatch.physicalDir) attrs, err := windows.GetFileAttributes(pathPtr) if err != nil || attrs == windows.INVALID_FILE_ATTRIBUTES || attrs&windows.FILE_ATTRIBUTE_DIRECTORY == 0 { s.dirWatch.events.remove(s.dirWatch.dir) @@ -375,6 +375,7 @@ func (s *windowsSubscription) processCompletion(callErr error, buf []byte, bytes func (s *windowsSubscription) processOne(action uint32, name string) { path := s.dirWatch.dir + "\\" + name + watchPath := s.dirWatch.physicalDir + "\\" + name switch action { case windows.FILE_ACTION_ADDED, windows.FILE_ACTION_RENAMED_NEW_NAME: // Always emit the event, even if the file is already gone by the @@ -383,7 +384,7 @@ func (s *windowsSubscription) processOne(action uint32, name string) { // the create+delete pair coalesces away. s.dirWatch.events.create(path) case windows.FILE_ACTION_MODIFIED: - if pathPtr, err := windows.UTF16PtrFromString(path); err == nil { + if pathPtr, err := windows.UTF16PtrFromString(watchPath); err == nil { var data windows.Win32FileAttributeData if err := windows.GetFileAttributesEx(pathPtr, windows.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&data))); err == nil { if data.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY == 0 { diff --git a/internal/lsp/lspwatcher/lspwatcher.go b/internal/lsp/lspwatcher/lspwatcher.go index 24366a3f747..6b5fbc6544f 100644 --- a/internal/lsp/lspwatcher/lspwatcher.go +++ b/internal/lsp/lspwatcher/lspwatcher.go @@ -248,7 +248,7 @@ func (w *watch) reconcile(emitSyntheticCreates bool) error { if w.watchingTarget && w.subscription != nil { return nil // already watching the target } - targetDirectory := watcher.fs.Realpath(w.requestedDirectory) + targetDirectory := w.requestedDirectory var options []fswatch.WatchOption if w.recursive { options = append(options, fswatch.WithRecursive()) @@ -265,7 +265,7 @@ func (w *watch) reconcile(emitSyntheticCreates bool) error { _ = previous.Close() } if emitSyntheticCreates { - watcher.emitSyntheticCreates(targetDirectory, w.requestedDirectory, w.kind, w.recursive) + watcher.emitSyntheticCreates(targetDirectory, w.kind, w.recursive) } return nil } @@ -282,7 +282,7 @@ func (w *watch) reconcile(emitSyntheticCreates bool) error { } return nil } - ancestorDirectory := watcher.fs.Realpath(ancestor) + ancestorDirectory := ancestor if !w.watchingTarget && w.subscription != nil && w.watchedDirectory == ancestorDirectory { return nil // already watching the correct ancestor } @@ -326,7 +326,7 @@ func (w *watch) targetCallback(watchedDirectory string) fswatch.WatchCallback { } } if len(events) > 0 { - watcher.forwardEvents(watchedDirectory, w.requestedDirectory, w.kind, events) + watcher.forwardEvents(w.kind, events) } if terminated { // The delete event for the directory was forwarded above; now @@ -386,10 +386,9 @@ func nearestExistingAncestor(fs vfs.FS, dir string) (string, bool) { } } -// forwardEvents translates fswatch events rooted at watchedDirectory into LSP -// file events (remapping paths back into the requestedDirectory namespace) and -// enqueues them for the next debounced flush. -func (w *Watcher) forwardEvents(watchedDirectory, requestedDirectory string, kind lsproto.WatchKind, events []fswatch.Event) { +// forwardEvents translates fswatch events into LSP file events and enqueues +// them for the next debounced flush. +func (w *Watcher) forwardEvents(kind lsproto.WatchKind, events []fswatch.Event) { w.mu.Lock() if w.closed { w.mu.Unlock() @@ -398,7 +397,6 @@ func (w *Watcher) forwardEvents(watchedDirectory, requestedDirectory string, kin if w.pending == nil { w.pending = make(map[string]*lsproto.FileEvent, len(events)) } - comparePathsOptions := tspath.ComparePathsOptions{UseCaseSensitiveFileNames: w.fs.UseCaseSensitiveFileNames()} for _, event := range events { var changeType lsproto.FileChangeType switch event.Kind { @@ -419,7 +417,7 @@ func (w *Watcher) forwardEvents(watchedDirectory, requestedDirectory string, kin continue } - path := remapEventPath(watchedDirectory, requestedDirectory, tspath.NormalizeSlashes(event.Path), comparePathsOptions) + path := tspath.NormalizeSlashes(event.Path) uri := lsconv.FileNameToDocumentURI(path) w.pending[string(uri)] = &lsproto.FileEvent{ Uri: uri, @@ -436,31 +434,30 @@ func (w *Watcher) forwardEvents(watchedDirectory, requestedDirectory string, kin // directory itself is always included; for a non-recursive watch its immediate // children are added, and for a recursive watch its whole subtree is walked. // Nothing is emitted if the watch doesn't request create notifications. -func (w *Watcher) emitSyntheticCreates(watchedDirectory, requestedDirectory string, kind lsproto.WatchKind, recursive bool) { +func (w *Watcher) emitSyntheticCreates(directory string, kind lsproto.WatchKind, recursive bool) { if kind&lsproto.WatchKindCreate == 0 { return } - comparePathsOptions := tspath.ComparePathsOptions{UseCaseSensitiveFileNames: w.fs.UseCaseSensitiveFileNames()} - paths := []string{requestedDirectory} + paths := []string{directory} if recursive { - _ = w.fs.WalkDir(watchedDirectory, func(path string, entry vfs.DirEntry, err error) error { + _ = w.fs.WalkDir(directory, func(path string, entry vfs.DirEntry, err error) error { if err != nil { return nil } normalizedPath := tspath.NormalizeSlashes(path) - if normalizedPath == watchedDirectory { + if normalizedPath == directory { return nil } - paths = append(paths, remapEventPath(watchedDirectory, requestedDirectory, normalizedPath, comparePathsOptions)) + paths = append(paths, normalizedPath) return nil }) } else { - entries := w.fs.GetAccessibleEntries(watchedDirectory) + entries := w.fs.GetAccessibleEntries(directory) for _, name := range entries.Files { - paths = append(paths, tspath.CombinePaths(requestedDirectory, name)) + paths = append(paths, tspath.CombinePaths(directory, name)) } for _, name := range entries.Directories { - paths = append(paths, tspath.CombinePaths(requestedDirectory, name)) + paths = append(paths, tspath.CombinePaths(directory, name)) } } w.enqueueSyntheticCreates(paths) @@ -500,24 +497,6 @@ func (w *Watcher) scheduleFlushLocked() { } } -// remapEventPath translates an absolute path observed under watchedDirectory -// (the canonicalized, symlink-resolved directory actually watched) back into -// the requestedDirectory namespace the session requested. When the two -// directories are identical, the path is returned unchanged. -func remapEventPath(watchedDirectory, requestedDirectory, path string, comparePathsOptions tspath.ComparePathsOptions) string { - if watchedDirectory == requestedDirectory { - return path - } - if tspath.ContainsPath(watchedDirectory, path, comparePathsOptions) { - relative := tspath.GetRelativePathFromDirectory(watchedDirectory, path, comparePathsOptions) - if relative == "" || relative == "." { - return requestedDirectory - } - return tspath.CombinePaths(requestedDirectory, relative) - } - return path -} - func (w *Watcher) flush() { w.mu.Lock() if w.closed { diff --git a/internal/lsp/lspwatcher/lspwatcher_test.go b/internal/lsp/lspwatcher/lspwatcher_test.go index d514ba89792..90ad3a010cc 100644 --- a/internal/lsp/lspwatcher/lspwatcher_test.go +++ b/internal/lsp/lspwatcher/lspwatcher_test.go @@ -320,15 +320,12 @@ func TestWatcher_NonRecursiveGlobIsNotRecursive(t *testing.T) { t.Fatal(err) } - realDir := tspath.NormalizeSlashes(fs.Realpath(dirNorm)) - realSub := tspath.NormalizeSlashes(fs.Realpath(subNorm)) - backend.mu.Lock() defer backend.mu.Unlock() - if got := backend.optCount[realDir]; got != 1 { + if got := backend.optCount[dirNorm]; got != 1 { t.Errorf("recursive glob %q: expected 1 watch option (WithRecursive), got %d", recursive, got) } - if got := backend.optCount[realSub]; got != 0 { + if got := backend.optCount[subNorm]; got != 0 { t.Errorf("non-recursive glob %q: expected 0 watch options, got %d", nonRecursive, got) } } @@ -402,7 +399,7 @@ func TestWatcher_MissingDirectoryTracksAncestor(t *testing.T) { t.Cleanup(w.Close) base := t.TempDir() - baseReal := tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(base))) + baseNorm := tspath.NormalizeSlashes(base) target := tspath.NormalizeSlashes(filepath.Join(base, "pkg")) pattern := target + "/*" @@ -414,8 +411,8 @@ func TestWatcher_MissingDirectoryTracksAncestor(t *testing.T) { // A missing target directory installs an ancestor watch on the nearest // existing ancestor (the base dir), not on the target. - if !backend.isWatching(baseReal) { - t.Fatalf("expected ancestor watch on ancestor %q, watched: %v", baseReal, backend.watchedDirs()) + if !backend.isWatching(baseNorm) { + t.Fatalf("expected ancestor watch on ancestor %q, watched: %v", baseNorm, backend.watchedDirs()) } if dirs := backend.watchedDirs(); len(dirs) != 1 { t.Fatalf("expected exactly one (ancestor) watch, got %v", dirs) @@ -446,7 +443,7 @@ func TestWatcher_MissingDirectoryPromotesOnCreate(t *testing.T) { t.Cleanup(w.Close) base := t.TempDir() - baseReal := tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(base))) + baseNorm := tspath.NormalizeSlashes(base) target := tspath.NormalizeSlashes(filepath.Join(base, "pkg")) pattern := target + "/*" kind := lsproto.WatchKindCreate | lsproto.WatchKindChange | lsproto.WatchKindDelete @@ -465,12 +462,11 @@ func TestWatcher_MissingDirectoryPromotesOnCreate(t *testing.T) { if err := os.WriteFile(filepath.Join(base, "pkg", "index.ts"), []byte("export {}"), 0o644); err != nil { t.Fatal(err) } - backend.emit(baseReal, []fswatch.Event{ + backend.emit(baseNorm, []fswatch.Event{ {Kind: fswatch.EventUpdate, Path: filepath.Join(base, "pkg")}, }, nil) - targetReal := tspath.NormalizeSlashes(fs.Realpath(target)) - waitFor(t, func() bool { return backend.isWatching(targetReal) }, "promotion to target watch") + waitFor(t, func() bool { return backend.isWatching(target) }, "promotion to target watch") // Synthetic creates must cover the target dir and its immediate child so // the session re-resolves files created before the watch was installed. @@ -503,7 +499,7 @@ func TestWatcher_MultiLevelDescend(t *testing.T) { t.Cleanup(w.Close) base := t.TempDir() - baseReal := tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(base))) + baseNorm := tspath.NormalizeSlashes(base) target := tspath.NormalizeSlashes(filepath.Join(base, "a", "b", "c")) pattern := target + "/*" @@ -512,30 +508,30 @@ func TestWatcher_MultiLevelDescend(t *testing.T) { }}); err != nil { t.Fatal(err) } - if !backend.isWatching(baseReal) { - t.Fatalf("expected initial ancestor watch on %q, got %v", baseReal, backend.watchedDirs()) + if !backend.isWatching(baseNorm) { + t.Fatalf("expected initial ancestor watch on %q, got %v", baseNorm, backend.watchedDirs()) } // Reveal one path component at a time; the ancestor watch should descend. - mkdirAndRealpath := func(rel string) string { + mkdirAndPath := func(rel string) string { p := filepath.Join(base, rel) if err := os.MkdirAll(p, 0o755); err != nil { t.Fatal(err) } - return tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(p))) + return tspath.NormalizeSlashes(p) } - aReal := mkdirAndRealpath("a") - backend.emit(baseReal, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "a")}}, nil) - waitFor(t, func() bool { return backend.isWatching(aReal) }, "descend to a") + aDir := mkdirAndPath("a") + backend.emit(baseNorm, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "a")}}, nil) + waitFor(t, func() bool { return backend.isWatching(aDir) }, "descend to a") - abReal := mkdirAndRealpath(filepath.Join("a", "b")) - backend.emit(aReal, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "a", "b")}}, nil) - waitFor(t, func() bool { return backend.isWatching(abReal) }, "descend to a/b") + abDir := mkdirAndPath(filepath.Join("a", "b")) + backend.emit(aDir, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "a", "b")}}, nil) + waitFor(t, func() bool { return backend.isWatching(abDir) }, "descend to a/b") - abcReal := mkdirAndRealpath(filepath.Join("a", "b", "c")) - backend.emit(abReal, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "a", "b", "c")}}, nil) - waitFor(t, func() bool { return backend.isWatching(abcReal) }, "promote to target a/b/c") + abcDir := mkdirAndPath(filepath.Join("a", "b", "c")) + backend.emit(abDir, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "a", "b", "c")}}, nil) + waitFor(t, func() bool { return backend.isWatching(abcDir) }, "promote to target a/b/c") } func TestWatcher_AtomicTreeCreateRace(t *testing.T) { @@ -547,7 +543,7 @@ func TestWatcher_AtomicTreeCreateRace(t *testing.T) { t.Cleanup(w.Close) base := t.TempDir() - baseReal := tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(base))) + baseNorm := tspath.NormalizeSlashes(base) target := tspath.NormalizeSlashes(filepath.Join(base, "a", "b", "c")) pattern := target + "/*" @@ -563,10 +559,9 @@ func TestWatcher_AtomicTreeCreateRace(t *testing.T) { if err := os.MkdirAll(filepath.Join(base, "a", "b", "c"), 0o755); err != nil { t.Fatal(err) } - backend.emit(baseReal, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "a")}}, nil) + backend.emit(baseNorm, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "a")}}, nil) - targetReal := tspath.NormalizeSlashes(fs.Realpath(target)) - waitFor(t, func() bool { return backend.isWatching(targetReal) }, "promote to target in one pass") + waitFor(t, func() bool { return backend.isWatching(target) }, "promote to target in one pass") } func TestWatcher_SyntheticCreateDepth(t *testing.T) { @@ -589,7 +584,7 @@ func TestWatcher_SyntheticCreateDepth(t *testing.T) { t.Cleanup(w.Close) base := t.TempDir() - baseReal := tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(base))) + baseNorm := tspath.NormalizeSlashes(base) target := tspath.NormalizeSlashes(filepath.Join(base, "pkg")) kind := lsproto.WatchKindCreate | lsproto.WatchKindChange | lsproto.WatchKindDelete var pattern string @@ -616,10 +611,9 @@ func TestWatcher_SyntheticCreateDepth(t *testing.T) { if err := os.WriteFile(filepath.Join(base, "pkg", "sub", "deep.ts"), []byte("export {}"), 0o644); err != nil { t.Fatal(err) } - backend.emit(baseReal, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "pkg")}}, nil) + backend.emit(baseNorm, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "pkg")}}, nil) - targetReal := tspath.NormalizeSlashes(fs.Realpath(target)) - waitFor(t, func() bool { return backend.isWatching(targetReal) }, "promotion") + waitFor(t, func() bool { return backend.isWatching(target) }, "promotion") created := func() map[string]bool { mu.Lock() @@ -687,12 +681,11 @@ func TestWatcher_TerminatedFallsBackAndRecovers(t *testing.T) { t.Cleanup(w.Close) base := t.TempDir() - baseReal := tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(base))) + baseNorm := tspath.NormalizeSlashes(base) target := tspath.NormalizeSlashes(filepath.Join(base, "pkg")) if err := os.MkdirAll(filepath.Join(base, "pkg"), 0o755); err != nil { t.Fatal(err) } - targetReal := tspath.NormalizeSlashes(fs.Realpath(target)) pattern := target + "/*" kind := lsproto.WatchKindCreate | lsproto.WatchKindChange | lsproto.WatchKindDelete @@ -702,8 +695,8 @@ func TestWatcher_TerminatedFallsBackAndRecovers(t *testing.T) { }}); err != nil { t.Fatal(err) } - if !backend.isWatching(targetReal) { - t.Fatalf("expected target watch on %q, got %v", targetReal, backend.watchedDirs()) + if !backend.isWatching(target) { + t.Fatalf("expected target watch on %q, got %v", target, backend.watchedDirs()) } // Delete the directory and deliver ErrWatchTerminated together with the @@ -711,7 +704,7 @@ func TestWatcher_TerminatedFallsBackAndRecovers(t *testing.T) { if err := os.RemoveAll(filepath.Join(base, "pkg")); err != nil { t.Fatal(err) } - backend.emit(targetReal, []fswatch.Event{ + backend.emit(target, []fswatch.Event{ {Kind: fswatch.EventDelete, Path: filepath.Join(base, "pkg")}, }, errors.Join(fswatch.ErrWatchTerminated, errors.New("removed"))) @@ -727,15 +720,14 @@ func TestWatcher_TerminatedFallsBackAndRecovers(t *testing.T) { } return false }, "forwarded delete of terminated dir") - waitFor(t, func() bool { return backend.isWatching(baseReal) && !backend.isWatching(targetReal) }, "fallback to ancestor watch") + waitFor(t, func() bool { return backend.isWatching(baseNorm) && !backend.isWatching(target) }, "fallback to ancestor watch") // Recreate the directory; the ancestor watch must promote back to target. if err := os.MkdirAll(filepath.Join(base, "pkg"), 0o755); err != nil { t.Fatal(err) } - recreatedReal := tspath.NormalizeSlashes(fs.Realpath(target)) - backend.emit(baseReal, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "pkg")}}, nil) - waitFor(t, func() bool { return backend.isWatching(recreatedReal) }, "recovery to target watch after recreation") + backend.emit(baseNorm, []fswatch.Event{{Kind: fswatch.EventUpdate, Path: filepath.Join(base, "pkg")}}, nil) + waitFor(t, func() bool { return backend.isWatching(target) }, "recovery to target watch after recreation") } func TestWatcher_GenuineFailureRollsBackForRetry(t *testing.T) { @@ -747,12 +739,12 @@ func TestWatcher_GenuineFailureRollsBackForRetry(t *testing.T) { t.Cleanup(w.Close) dir := t.TempDir() - dirReal := tspath.NormalizeSlashes(fs.Realpath(tspath.NormalizeSlashes(dir))) - pattern := tspath.NormalizeSlashes(dir) + "/*" + dirNorm := tspath.NormalizeSlashes(dir) + pattern := dirNorm + "/*" // Inject a genuine backend failure for the existing directory. backend.mu.Lock() - backend.failDirs[dirReal] = errors.New("too many open files") + backend.failDirs[dirNorm] = errors.New("too many open files") backend.mu.Unlock() err := w.WatchFiles("id", []*lsproto.FileSystemWatcher{{ @@ -766,7 +758,7 @@ func TestWatcher_GenuineFailureRollsBackForRetry(t *testing.T) { // (rather than hitting the duplicate-id error). Clear the injected failure // to simulate the resource pressure easing on retry. backend.mu.Lock() - delete(backend.failDirs, dirReal) + delete(backend.failDirs, dirNorm) backend.mu.Unlock() if err := w.WatchFiles("id", []*lsproto.FileSystemWatcher{{ @@ -774,8 +766,8 @@ func TestWatcher_GenuineFailureRollsBackForRetry(t *testing.T) { }}); err != nil { t.Fatalf("retry after rollback should succeed, got %v", err) } - if !backend.isWatching(dirReal) { - t.Fatalf("expected watch on %q after successful retry, got %v", dirReal, backend.watchedDirs()) + if !backend.isWatching(dirNorm) { + t.Fatalf("expected watch on %q after successful retry, got %v", dirNorm, backend.watchedDirs()) } } diff --git a/internal/vfs/osvfs/eintr_unix.go b/internal/nativepath/eintr_unix.go similarity index 93% rename from internal/vfs/osvfs/eintr_unix.go rename to internal/nativepath/eintr_unix.go index 02a1fc9bb31..9e2df3108e0 100644 --- a/internal/vfs/osvfs/eintr_unix.go +++ b/internal/nativepath/eintr_unix.go @@ -1,6 +1,6 @@ //go:build linux || darwin -package osvfs +package nativepath import "syscall" diff --git a/internal/vfs/osvfs/realpath_darwin.go b/internal/nativepath/realpath_darwin.go similarity index 96% rename from internal/vfs/osvfs/realpath_darwin.go rename to internal/nativepath/realpath_darwin.go index fadc8f8ef62..afc9384cc6a 100644 --- a/internal/vfs/osvfs/realpath_darwin.go +++ b/internal/nativepath/realpath_darwin.go @@ -1,4 +1,4 @@ -package osvfs +package nativepath import ( "path/filepath" @@ -46,7 +46,7 @@ func fcntlGetPathPtr(fd uintptr, buf uintptr) (int, error) { return unix.FcntlInt(fd, unix.F_GETPATH, int(buf)) } -func realpath(path string) (string, error) { +func Realpath(path string) (string, error) { if !hasFGetPath() { return filepath.EvalSymlinks(path) } diff --git a/internal/vfs/osvfs/realpath_linux.go b/internal/nativepath/realpath_linux.go similarity index 96% rename from internal/vfs/osvfs/realpath_linux.go rename to internal/nativepath/realpath_linux.go index 467d7ee5da2..e41ad6d34f9 100644 --- a/internal/vfs/osvfs/realpath_linux.go +++ b/internal/nativepath/realpath_linux.go @@ -1,4 +1,4 @@ -package osvfs +package nativepath import ( "os" @@ -36,7 +36,7 @@ var hasProcSelfFD = sync.OnceValue(func() bool { return unix.Stat(_procSelfFD, &stat) == nil }) -func realpath(path string) (string, error) { +func Realpath(path string) (string, error) { if !hasProcSelfFD() { return filepath.EvalSymlinks(path) } diff --git a/internal/nativepath/realpath_other.go b/internal/nativepath/realpath_other.go new file mode 100644 index 00000000000..946239b51ca --- /dev/null +++ b/internal/nativepath/realpath_other.go @@ -0,0 +1,9 @@ +//go:build !windows && !linux && !darwin + +package nativepath + +import "path/filepath" + +func Realpath(path string) (string, error) { + return filepath.EvalSymlinks(path) +} diff --git a/internal/vfs/osvfs/realpath_windows.go b/internal/nativepath/realpath_windows.go similarity index 97% rename from internal/vfs/osvfs/realpath_windows.go rename to internal/nativepath/realpath_windows.go index 77b2d5a5c48..6ef410b9f2e 100644 --- a/internal/vfs/osvfs/realpath_windows.go +++ b/internal/nativepath/realpath_windows.go @@ -1,4 +1,4 @@ -package osvfs +package nativepath import ( "errors" @@ -10,7 +10,7 @@ import ( // This implementation is based on what Node's fs.realpath.native does, via libuv: https://github.com/libuv/libuv/blob/ec5a4b54f7da7eeb01679005c615fee9633cdb3b/src/win/fs.c#L2937 -func realpath(path string) (string, error) { +func Realpath(path string) (string, error) { var h windows.Handle if len(path) < 248 { var err error diff --git a/internal/nativepath/symlink_other.go b/internal/nativepath/symlink_other.go new file mode 100644 index 00000000000..3286ccd6df0 --- /dev/null +++ b/internal/nativepath/symlink_other.go @@ -0,0 +1,10 @@ +//go:build !windows + +package nativepath + +import "os" + +func IsSymlinkOrReparsePoint(path string) bool { + info, err := os.Lstat(path) + return err == nil && info.Mode()&os.ModeSymlink != 0 +} diff --git a/internal/vfs/osvfs/reparsepoint_windows.go b/internal/nativepath/symlink_windows.go similarity index 86% rename from internal/vfs/osvfs/reparsepoint_windows.go rename to internal/nativepath/symlink_windows.go index 12a1e59cb04..20b3563b804 100644 --- a/internal/vfs/osvfs/reparsepoint_windows.go +++ b/internal/nativepath/symlink_windows.go @@ -1,11 +1,11 @@ -package osvfs +package nativepath import ( "syscall" "unsafe" ) -func isReparsePoint(path string) bool { +func IsSymlinkOrReparsePoint(path string) bool { if len(path) >= 248 { path = `\\?\` + path } diff --git a/internal/vfs/osvfs/reparsepoint_windows_test.go b/internal/nativepath/symlink_windows_test.go similarity index 69% rename from internal/vfs/osvfs/reparsepoint_windows_test.go rename to internal/nativepath/symlink_windows_test.go index c1dba9ecd8c..ff843c592c6 100644 --- a/internal/vfs/osvfs/reparsepoint_windows_test.go +++ b/internal/nativepath/symlink_windows_test.go @@ -1,15 +1,16 @@ -package osvfs +package nativepath import ( "os" "os/exec" "path/filepath" + "strings" "testing" "gotest.tools/v3/assert" ) -func TestIsReparsePoint(t *testing.T) { +func TestIsSymlinkOrReparsePoint(t *testing.T) { t.Parallel() tmp := t.TempDir() @@ -18,14 +19,14 @@ func TestIsReparsePoint(t *testing.T) { t.Parallel() file := filepath.Join(tmp, "regular.txt") assert.NilError(t, os.WriteFile(file, []byte("hello"), 0o666)) - assert.Equal(t, isReparsePoint(file), false) + assert.Equal(t, IsSymlinkOrReparsePoint(file), false) }) t.Run("regular directory", func(t *testing.T) { t.Parallel() dir := filepath.Join(tmp, "regular-dir") assert.NilError(t, os.MkdirAll(dir, 0o777)) - assert.Equal(t, isReparsePoint(dir), false) + assert.Equal(t, IsSymlinkOrReparsePoint(dir), false) }) t.Run("junction point", func(t *testing.T) { @@ -34,7 +35,7 @@ func TestIsReparsePoint(t *testing.T) { link := filepath.Join(tmp, "junction-link") assert.NilError(t, os.MkdirAll(target, 0o777)) mklink(t, target, link, true) - assert.Equal(t, isReparsePoint(link), true) + assert.Equal(t, IsSymlinkOrReparsePoint(link), true) }) t.Run("file symlink", func(t *testing.T) { @@ -43,7 +44,7 @@ func TestIsReparsePoint(t *testing.T) { link := filepath.Join(tmp, "symlink-link.txt") assert.NilError(t, os.WriteFile(target, []byte("hello"), 0o666)) mklink(t, target, link, false) - assert.Equal(t, isReparsePoint(link), true) + assert.Equal(t, IsSymlinkOrReparsePoint(link), true) }) t.Run("directory symlink", func(t *testing.T) { @@ -52,27 +53,27 @@ func TestIsReparsePoint(t *testing.T) { link := filepath.Join(tmp, "dir-symlink-link") assert.NilError(t, os.MkdirAll(target, 0o777)) mklink(t, target, link, false) - assert.Equal(t, isReparsePoint(link), true) + assert.Equal(t, IsSymlinkOrReparsePoint(link), true) }) t.Run("nonexistent path", func(t *testing.T) { t.Parallel() nonexistent := filepath.Join(tmp, "does-not-exist") - assert.Equal(t, isReparsePoint(nonexistent), false) + assert.Equal(t, IsSymlinkOrReparsePoint(nonexistent), false) }) t.Run("empty path", func(t *testing.T) { t.Parallel() - assert.Equal(t, isReparsePoint(""), false) + assert.Equal(t, IsSymlinkOrReparsePoint(""), false) }) t.Run("invalid path with null byte", func(t *testing.T) { t.Parallel() - assert.Equal(t, isReparsePoint("invalid\x00path"), false) + assert.Equal(t, IsSymlinkOrReparsePoint("invalid\x00path"), false) }) } -func TestIsReparsePointLongPath(t *testing.T) { +func TestIsSymlinkOrReparsePointLongPath(t *testing.T) { t.Parallel() tmp := t.TempDir() @@ -96,10 +97,10 @@ func TestIsReparsePointLongPath(t *testing.T) { assert.NilError(t, exec.Command("cmd", "/c", "mklink", "/J", longLink, longTarget).Run()) // With long path support enabled, this should work even for paths >= 248 chars - assert.Equal(t, isReparsePoint(link), true) + assert.Equal(t, IsSymlinkOrReparsePoint(link), true) } -func TestIsReparsePointNestedInSymlink(t *testing.T) { +func TestIsSymlinkOrReparsePointNestedInSymlink(t *testing.T) { t.Parallel() tmp := t.TempDir() @@ -118,10 +119,10 @@ func TestIsReparsePointNestedInSymlink(t *testing.T) { // Check the junction through the symlink path nestedPath := filepath.Join(link, "inner-link") - assert.Equal(t, isReparsePoint(nestedPath), true) + assert.Equal(t, IsSymlinkOrReparsePoint(nestedPath), true) } -func TestIsReparsePointRelativePath(t *testing.T) { //nolint:paralleltest // Cannot use t.Parallel() with t.Chdir() +func TestIsSymlinkOrReparsePointRelativePath(t *testing.T) { //nolint:paralleltest // Cannot use t.Parallel() with t.Chdir() tmp := t.TempDir() t.Chdir(tmp) @@ -130,8 +131,8 @@ func TestIsReparsePointRelativePath(t *testing.T) { //nolint:paralleltest // Can assert.NilError(t, os.MkdirAll(target, 0o777)) mklink(t, target, link, true) - assert.Equal(t, isReparsePoint(link), true) - assert.Equal(t, isReparsePoint(target), false) + assert.Equal(t, IsSymlinkOrReparsePoint(link), true) + assert.Equal(t, IsSymlinkOrReparsePoint(target), false) } func BenchmarkIsSymlinkOrJunction(b *testing.B) { @@ -148,14 +149,14 @@ func BenchmarkIsSymlinkOrJunction(b *testing.B) { b.Run("regular file", func(b *testing.B) { b.ReportAllocs() for b.Loop() { - isReparsePoint(regularFile) + IsSymlinkOrReparsePoint(regularFile) } }) b.Run("junction", func(b *testing.B) { b.ReportAllocs() for b.Loop() { - isReparsePoint(link) + IsSymlinkOrReparsePoint(link) } }) @@ -163,7 +164,22 @@ func BenchmarkIsSymlinkOrJunction(b *testing.B) { b.ReportAllocs() nonexistent := filepath.Join(tmp, "does-not-exist") for b.Loop() { - isReparsePoint(nonexistent) + IsSymlinkOrReparsePoint(nonexistent) } }) } + +func mklink(tb testing.TB, target, link string, isDir bool) { + tb.Helper() + + if isDir { + assert.NilError(tb, exec.Command("cmd", "/c", "mklink", "/J", link, target).Run()) + } else { + err := os.Symlink(target, link) + if err != nil && strings.Contains(err.Error(), "A required privilege is not held by the client") { + tb.Log(err) + tb.Skip("file symlink support is not enabled without elevation or developer mode") + } + assert.NilError(tb, err) + } +} diff --git a/internal/vfs/osvfs/os.go b/internal/vfs/osvfs/os.go index 308c0039a82..7db6f338b82 100644 --- a/internal/vfs/osvfs/os.go +++ b/internal/vfs/osvfs/os.go @@ -12,6 +12,7 @@ import ( "unicode" "github.com/microsoft/typescript-go/internal/core" + "github.com/microsoft/typescript-go/internal/nativepath" "github.com/microsoft/typescript-go/internal/tspath" "github.com/microsoft/typescript-go/internal/vfs" "github.com/microsoft/typescript-go/internal/vfs/internal" @@ -159,7 +160,7 @@ func osFSRealpath(path string) string { orig := path path = filepath.FromSlash(path) - path, err := realpath(path) + path, err := nativepath.Realpath(path) if err != nil { return orig } @@ -170,6 +171,10 @@ func osFSRealpath(path string) string { return tspath.NormalizeSlashes(path) } +func isReparsePoint(path string) bool { + return nativepath.IsSymlinkOrReparsePoint(filepath.FromSlash(path)) +} + func (vfs *osFS) writeFileWithFlag(path string, content string, flag int) error { defer writeSema.Acquire()() diff --git a/internal/vfs/osvfs/realpath_other.go b/internal/vfs/osvfs/realpath_other.go deleted file mode 100644 index dcbb2a1ad37..00000000000 --- a/internal/vfs/osvfs/realpath_other.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !windows && !linux && !darwin - -package osvfs - -import ( - "path/filepath" -) - -func realpath(path string) (string, error) { - return filepath.EvalSymlinks(path) -} diff --git a/internal/vfs/osvfs/reparsepoint_other.go b/internal/vfs/osvfs/reparsepoint_other.go deleted file mode 100644 index 1206b6bc363..00000000000 --- a/internal/vfs/osvfs/reparsepoint_other.go +++ /dev/null @@ -1,6 +0,0 @@ -//go:build !windows - -package osvfs - -// Only Windows has reparse points; leave this nil for other OSes. -var isReparsePoint func(path string) bool From 481a4aa627f158166f11bc9eea8a98f02a40405e Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 22 Jun 2026 16:06:26 -0400 Subject: [PATCH 45/49] Warn on TSServer Plugin Contributions from other extensions (#4063) --- _extension/l10n/bundle.l10n.json | 4 ++ _extension/src/extension.ts | 105 ++++++++++++++++++++++++++++++- _extension/src/util.ts | 25 ++++++-- 3 files changed, 126 insertions(+), 8 deletions(-) diff --git a/_extension/l10n/bundle.l10n.json b/_extension/l10n/bundle.l10n.json index 99d88aea82c..f3bf601b98e 100644 --- a/_extension/l10n/bundle.l10n.json +++ b/_extension/l10n/bundle.l10n.json @@ -59,6 +59,10 @@ "Ignore": "Ignore", "TypeScript Native Preview is disabled. Select 'Enable TypeScript Native Preview (Experimental)' in the command palette to enable it.": "TypeScript Native Preview is disabled. Select 'Enable TypeScript Native Preview (Experimental)' in the command palette to enable it.", "Error prompting to use workspace version: {0}": "Error prompting to use workspace version: {0}", + "TypeScript server plugins from the \"{0}\" extension will not be loaded because TypeScript Native Preview is enabled globally.": "TypeScript server plugins from the \"{0}\" extension will not be loaded because TypeScript Native Preview is enabled globally.", + "{0} extensions contribute TypeScript server plugins that will not be loaded because TypeScript Native Preview is enabled globally: {1}": "{0} extensions contribute TypeScript server plugins that will not be loaded because TypeScript Native Preview is enabled globally: {1}", + "Disable Native Preview in Workspace": "Disable Native Preview in Workspace", + "Don't Show Again": "Don't Show Again", "Unexpected number of arguments.": "Unexpected number of arguments.", "Starting language server...": "Starting language server...", "Language client is not initialized": "Language client is not initialized", diff --git a/_extension/src/extension.ts b/_extension/src/extension.ts index 42064bf3e2e..68bf3ded40f 100644 --- a/_extension/src/extension.ts +++ b/_extension/src/extension.ts @@ -6,8 +6,9 @@ import { } from "./commands"; import { aiConnectionString, + getExplicitConfigTarget, getUseTsgo, - getUseTsgoFalseSetting, + getWinningTsgoConfigKey, needsExtHostRestartOnChange, } from "./util"; @@ -98,7 +99,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { + warnAboutTsServerPlugins(context, output); + }); + context.subscriptions.push(onDidChangeExtensions); + warnAboutTsServerPlugins(context, output); + await sessionManager.start(context); // Prompt user to use workspace version if one is detected and they haven't opted in yet. @@ -146,4 +154,97 @@ export async function activate(context: vscode.ExtensionContext): Promise { + // Never show more than once. + if (pluginWarningShown) { + return; + } + + if (getUseTsgo() !== true) { + return; + } + + const settingName = getWinningTsgoConfigKey(); + assert(settingName !== undefined, "Expected some useTsgo configuration to be explicitly set."); + + const target = getExplicitConfigTarget(vscode.workspace.getConfiguration(), settingName); + assert(target !== undefined, "Expected an explicit configuration target."); + + // We only want to warn about plugins when Corsa is enabled in a user's global configuration. + // Setting in the workspace is an indication that the user is aware of the potential issues. + if (target !== vscode.ConfigurationTarget.Global) { + return; + } + + const hasWorkspaceFolder = !!vscode.workspace.workspaceFolders?.length; + + if (context.globalState.get(pluginWarningDismissedKey)) { + return; + } + + const pluginExtensions = getExtensionsWithTsServerPlugins(); + if (pluginExtensions.length === 0) { + return; + } + + const uniqueExtensionNames = [...new Set(pluginExtensions.map(p => p.extensionId))]; + const extensionNames = uniqueExtensionNames.join(", "); + output.appendLine(`Extensions contributing tsserver plugins that will not apply with TypeScript Native Preview: ${extensionNames}`); + + const message = uniqueExtensionNames.length === 1 + // Pick the first extension & plugin, even though extensions can have multiple plugins + ? vscode.l10n.t(`TypeScript server plugins from the "{0}" extension will not be loaded because TypeScript Native Preview is enabled globally.`, pluginExtensions[0].extensionId) + : vscode.l10n.t(`{0} extensions contribute TypeScript server plugins that will not be loaded because TypeScript Native Preview is enabled globally: {1}`, uniqueExtensionNames.length, extensionNames); + + const ok = vscode.l10n.t("OK"); + const disableInWorkspace = vscode.l10n.t("Disable Native Preview in Workspace"); + const dontShowAgain = vscode.l10n.t("Don't Show Again"); + + const options = [ok]; + if (hasWorkspaceFolder) { + options.push(disableInWorkspace); + } + options.push(dontShowAgain); + + // Make sure we never show this message again in this session. + pluginWarningShown = true; + + const selected = await vscode.window.showWarningMessage(message, ...options); + if (selected === disableInWorkspace) { + await vscode.workspace.getConfiguration("js/ts").update("experimental.useTsgo", false, vscode.ConfigurationTarget.Workspace); + } + else if (selected === dontShowAgain) { + await context.globalState.update(pluginWarningDismissedKey, true); + } + } +} + +const suppressedPluginExtensionIds: Set = new Set([ + "github.copilot", + "github.copilot-chat", +]); + +const pluginWarningDismissedKey = "tsServerPluginWarningDismissed"; + +function getExtensionsWithTsServerPlugins(): { extensionId: string; pluginName: string; }[] { + const results: { extensionId: string; pluginName: string; }[] = []; + // Despite its name, 'all' actually only seems to contain active extensions. + for (const extension of vscode.extensions.all) { + // Ignore built-in extensions that always try to contribute plugins. + const id = extension.id.toLowerCase(); + if (suppressedPluginExtensionIds.has(id)) { + continue; + } + + const contributes = extension.packageJSON?.contributes; + if (contributes && Array.isArray(contributes.typescriptServerPlugins)) { + for (const plugin of contributes.typescriptServerPlugins) { + if (typeof plugin.name === "string" && plugin.name) { + results.push({ extensionId: extension.id, pluginName: plugin.name }); + } + } + } + } + return results; } diff --git a/_extension/src/util.ts b/_extension/src/util.ts index 63f3b2516db..f9237658f76 100644 --- a/_extension/src/util.ts +++ b/_extension/src/util.ts @@ -227,14 +227,27 @@ export function getExplicitConfigTarget( return undefined; } +export enum ConfigName { + JsTsConfigName = "js/ts.experimental.useTsgo", + DeprecatedTypeScriptConfigName = "typescript.experimental.useTsgo", +} + /** - * Returns the name of the setting that is explicitly set to `false`, - * preferring `js/ts` over `typescript`. Returns `undefined` if neither - * is explicitly `false`. + * Returns the name of the setting with the appropriate precedence that is explicitly set, + * preferring `js/ts` over `typescript`. + * Returns `undefined` if neither is explicitly specified. */ -export function getUseTsgoFalseSetting(): string | undefined { - if (getExplicitUseTsgo("js/ts") === false) return "js/ts.experimental.useTsgo"; - if (getExplicitUseTsgo("typescript") === false) return "typescript.experimental.useTsgo"; +export function getWinningTsgoConfigKey(): ConfigName | undefined { + const jsTsValue = getExplicitUseTsgo("js/ts"); + const tsValue = getExplicitUseTsgo("typescript"); + + if (jsTsValue !== undefined || tsValue !== undefined) { + const jsTsTarget = getExplicitConfigTarget(vscode.workspace.getConfiguration("js/ts"), "experimental.useTsgo"); + const tsTarget = getExplicitConfigTarget(vscode.workspace.getConfiguration("typescript"), "experimental.useTsgo"); + const mostSpecific = Math.max(jsTsTarget ?? vscode.ConfigurationTarget.Global, tsTarget ?? vscode.ConfigurationTarget.Global); + return jsTsTarget === mostSpecific ? ConfigName.JsTsConfigName : ConfigName.DeprecatedTypeScriptConfigName; + } + return undefined; } From a8447526c6e6f9f94706bd05fd5b99bf108b1ccd Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Jun 2026 13:37:11 -0700 Subject: [PATCH 46/49] Add per-kind literal type interfaces and decode BigInt literal values to `bigint` in the API (#4344) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: andrewbranch <3277153+andrewbranch@users.noreply.github.com> --- _packages/native-preview/src/api/async/api.ts | 14 ++++++-- .../native-preview/src/api/async/types.ts | 30 ++++++++++++++-- _packages/native-preview/src/api/proto.ts | 3 +- _packages/native-preview/src/api/sync/api.ts | 14 ++++++-- .../native-preview/src/api/sync/types.ts | 30 ++++++++++++++-- .../native-preview/test/async/api.test.ts | 34 +++++++++++++++++++ .../native-preview/test/sync/api.test.ts | 34 +++++++++++++++++++ internal/api/proto.go | 2 ++ 8 files changed, 148 insertions(+), 13 deletions(-) diff --git a/_packages/native-preview/src/api/async/api.ts b/_packages/native-preview/src/api/async/api.ts index 711a191553e..eb58109a9e9 100644 --- a/_packages/native-preview/src/api/async/api.ts +++ b/_packages/native-preview/src/api/async/api.ts @@ -70,6 +70,8 @@ import { import type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, + BigIntLiteralType, + BooleanLiteralType, CompletionEntry, CompletionInfo, CompletionOptions, @@ -84,7 +86,9 @@ import type { IntersectionType, IntrinsicType, LiteralType, + NumberLiteralType, ObjectType, + StringLiteralType, StringMappingType, SubstitutionType, TemplateLiteralType, @@ -101,7 +105,7 @@ import type { export { CompletionItemKind, DiagnosticCategory, ElementFlags, ModifierFlags, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind }; export type { APIOptions, ClientSocketOptions, ClientSpawnOptions, DocumentIdentifier, DocumentPosition, LSPConnectionOptions }; -export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; +export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, BigIntLiteralType, BooleanLiteralType, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, NumberLiteralType, ObjectType, StringLiteralType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts"; export class API { @@ -1168,7 +1172,7 @@ class TypeObject implements Type { readonly flags: TypeFlags; readonly objectFlags!: ObjectFlags; readonly symbol!: number; - readonly value!: string | number | boolean; + readonly value!: string | number | boolean | bigint; readonly intrinsicName!: string; readonly isThisType!: boolean; readonly freshType!: number; @@ -1197,7 +1201,11 @@ class TypeObject implements Type { this.flags = data.flags; if (data.objectFlags !== undefined) this.objectFlags = data.objectFlags; if (data.symbol !== undefined) this.symbol = data.symbol; - if (data.value !== undefined) this.value = data.value; + if (data.value != null) { + // BigInt literal values are serialized as decimal strings (e.g. "-123") because + // JSON cannot represent bigint. Decode them back into a real bigint here. + this.value = (data.flags & TypeFlags.BigIntLiteral) ? BigInt(data.value) : data.value; + } if (data.intrinsicName !== undefined) this.intrinsicName = data.intrinsicName; if (data.isThisType !== undefined) this.isThisType = data.isThisType; if (data.freshType !== undefined) this.freshType = data.freshType; diff --git a/_packages/native-preview/src/api/async/types.ts b/_packages/native-preview/src/api/async/types.ts index d5c2f97ac65..955d36178d2 100644 --- a/_packages/native-preview/src/api/async/types.ts +++ b/_packages/native-preview/src/api/async/types.ts @@ -17,7 +17,7 @@ import type { * * ```ts * if (type.flags & TypeFlags.StringLiteral) { - * console.log((type as LiteralType).value); // string + * console.log((type as StringLiteralType).value); // string * } * ``` */ @@ -49,8 +49,32 @@ export interface FreshableType extends Type { /** Literal types: StringLiteral, NumberLiteral, BigIntLiteral, BooleanLiteral */ export interface LiteralType extends FreshableType { - /** The literal value */ - readonly value: string | number | boolean; + /** The literal value. Use TypeFlags to narrow to a specific literal subtype with a concrete value type. */ + readonly value: string | number | boolean | bigint; +} + +/** String literal types (TypeFlags.StringLiteral) */ +export interface StringLiteralType extends LiteralType { + /** The string value of the literal */ + readonly value: string; +} + +/** Numeric literal types (TypeFlags.NumberLiteral) */ +export interface NumberLiteralType extends LiteralType { + /** The numeric value of the literal */ + readonly value: number; +} + +/** BigInt literal types (TypeFlags.BigIntLiteral) */ +export interface BigIntLiteralType extends LiteralType { + /** The bigint value of the literal */ + readonly value: bigint; +} + +/** Boolean literal types (TypeFlags.BooleanLiteral) */ +export interface BooleanLiteralType extends LiteralType { + /** The boolean value of the literal */ + readonly value: boolean; } /** Object types (TypeFlags.Object) */ diff --git a/_packages/native-preview/src/api/proto.ts b/_packages/native-preview/src/api/proto.ts index a54e3185834..0edeb9b5274 100644 --- a/_packages/native-preview/src/api/proto.ts +++ b/_packages/native-preview/src/api/proto.ts @@ -142,7 +142,8 @@ export interface TypeResponse { id: number; flags: number; objectFlags?: number; - value?: string | number | boolean; + /** Literal value. BigInt literals are encoded as a decimal string (e.g. "-123") since JSON cannot represent bigint. Absent values are serialized as null. */ + value?: string | number | boolean | null; freshType?: number; regularType?: number; target?: number; diff --git a/_packages/native-preview/src/api/sync/api.ts b/_packages/native-preview/src/api/sync/api.ts index 730f538b08f..d4d1c59894c 100644 --- a/_packages/native-preview/src/api/sync/api.ts +++ b/_packages/native-preview/src/api/sync/api.ts @@ -78,6 +78,8 @@ import { import type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, + BigIntLiteralType, + BooleanLiteralType, CompletionEntry, CompletionInfo, CompletionOptions, @@ -92,7 +94,9 @@ import type { IntersectionType, IntrinsicType, LiteralType, + NumberLiteralType, ObjectType, + StringLiteralType, StringMappingType, SubstitutionType, TemplateLiteralType, @@ -109,7 +113,7 @@ import type { export { CompletionItemKind, DiagnosticCategory, ElementFlags, ModifierFlags, NodeBuilderFlags, ObjectFlags, SignatureFlags, SignatureKind, SymbolFlags, TypeFlags, TypePredicateKind }; export type { APIOptions, ClientSocketOptions, ClientSpawnOptions, DocumentIdentifier, DocumentPosition, LSPConnectionOptions }; -export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, ObjectType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; +export type { AssertsIdentifierTypePredicate, AssertsThisTypePredicate, BigIntLiteralType, BooleanLiteralType, CompletionEntry, CompletionInfo, CompletionOptions, ConditionalType, Diagnostic, FreshableType, IdentifierTypePredicate, IndexedAccessType, IndexInfo, IndexType, InterfaceType, IntersectionType, IntrinsicType, LiteralType, NumberLiteralType, ObjectType, StringLiteralType, StringMappingType, SubstitutionType, TemplateLiteralType, ThisTypePredicate, TupleType, Type, TypeParameter, TypePredicate, TypePredicateBase, TypeReference, UnionOrIntersectionType, UnionType }; export { documentURIToFileName, fileNameToDocumentURI } from "../path.ts"; export class API { @@ -1176,7 +1180,7 @@ class TypeObject implements Type { readonly flags: TypeFlags; readonly objectFlags!: ObjectFlags; readonly symbol!: number; - readonly value!: string | number | boolean; + readonly value!: string | number | boolean | bigint; readonly intrinsicName!: string; readonly isThisType!: boolean; readonly freshType!: number; @@ -1205,7 +1209,11 @@ class TypeObject implements Type { this.flags = data.flags; if (data.objectFlags !== undefined) this.objectFlags = data.objectFlags; if (data.symbol !== undefined) this.symbol = data.symbol; - if (data.value !== undefined) this.value = data.value; + if (data.value != null) { + // BigInt literal values are serialized as decimal strings (e.g. "-123") because + // JSON cannot represent bigint. Decode them back into a real bigint here. + this.value = (data.flags & TypeFlags.BigIntLiteral) ? BigInt(data.value) : data.value; + } if (data.intrinsicName !== undefined) this.intrinsicName = data.intrinsicName; if (data.isThisType !== undefined) this.isThisType = data.isThisType; if (data.freshType !== undefined) this.freshType = data.freshType; diff --git a/_packages/native-preview/src/api/sync/types.ts b/_packages/native-preview/src/api/sync/types.ts index 90226d1bb7b..86d31f003a0 100644 --- a/_packages/native-preview/src/api/sync/types.ts +++ b/_packages/native-preview/src/api/sync/types.ts @@ -25,7 +25,7 @@ import type { * * ```ts * if (type.flags & TypeFlags.StringLiteral) { - * console.log((type as LiteralType).value); // string + * console.log((type as StringLiteralType).value); // string * } * ``` */ @@ -57,8 +57,32 @@ export interface FreshableType extends Type { /** Literal types: StringLiteral, NumberLiteral, BigIntLiteral, BooleanLiteral */ export interface LiteralType extends FreshableType { - /** The literal value */ - readonly value: string | number | boolean; + /** The literal value. Use TypeFlags to narrow to a specific literal subtype with a concrete value type. */ + readonly value: string | number | boolean | bigint; +} + +/** String literal types (TypeFlags.StringLiteral) */ +export interface StringLiteralType extends LiteralType { + /** The string value of the literal */ + readonly value: string; +} + +/** Numeric literal types (TypeFlags.NumberLiteral) */ +export interface NumberLiteralType extends LiteralType { + /** The numeric value of the literal */ + readonly value: number; +} + +/** BigInt literal types (TypeFlags.BigIntLiteral) */ +export interface BigIntLiteralType extends LiteralType { + /** The bigint value of the literal */ + readonly value: bigint; +} + +/** Boolean literal types (TypeFlags.BooleanLiteral) */ +export interface BooleanLiteralType extends LiteralType { + /** The boolean value of the literal */ + readonly value: boolean; } /** Object types (TypeFlags.Object) */ diff --git a/_packages/native-preview/test/async/api.test.ts b/_packages/native-preview/test/async/api.test.ts index c2e2e847510..637d1069cdc 100644 --- a/_packages/native-preview/test/async/api.test.ts +++ b/_packages/native-preview/test/async/api.test.ts @@ -33,6 +33,7 @@ import { import { visitEachChild } from "@typescript/native-preview/unstable/ast/visitor"; import { API, + type BigIntLiteralType, type ConditionalType, DiagnosticCategory, type FreshableType, @@ -2447,6 +2448,39 @@ describe("FreshableType - getFreshType and getRegularType", () => { } }); + test("BigIntLiteralType.value is a bigint (positive and negative)", async () => { + const src = `\nexport const pos = 123n;\nexport const neg = -123n;\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = await api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + + const posSymbol = await project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("pos =")); + assert.ok(posSymbol); + const posType = await project.checker.getTypeOfSymbol(posSymbol); + assert.ok(posType); + assert.ok(posType.flags & TypeFlags.BigIntLiteral, "Expected BigIntLiteral"); + const posLiteral = posType as BigIntLiteralType; + assert.equal(typeof posLiteral.value, "bigint"); + assert.equal(posLiteral.value, 123n); + + const negSymbol = await project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("neg =")); + assert.ok(negSymbol); + const negType = await project.checker.getTypeOfSymbol(negSymbol); + assert.ok(negType); + assert.ok(negType.flags & TypeFlags.BigIntLiteral, "Expected BigIntLiteral"); + const negLiteral = negType as BigIntLiteralType; + assert.equal(typeof negLiteral.value, "bigint"); + assert.equal(negLiteral.value, -123n); + } + finally { + await api.close(); + } + }); + test("getFreshType() returns a fresh twin with matching value", async () => { const src = `\nexport const greeting: "hello" = "hello";\n`; const api = spawnAPI({ diff --git a/_packages/native-preview/test/sync/api.test.ts b/_packages/native-preview/test/sync/api.test.ts index 113538c1df6..f56913c3492 100644 --- a/_packages/native-preview/test/sync/api.test.ts +++ b/_packages/native-preview/test/sync/api.test.ts @@ -43,6 +43,7 @@ import { createVirtualFileSystem } from "@typescript/native-preview/unstable/fs" import type { FileSystem } from "@typescript/native-preview/unstable/fs"; import { API, + type BigIntLiteralType, type ConditionalType, DiagnosticCategory, type FreshableType, @@ -2455,6 +2456,39 @@ describe("FreshableType - getFreshType and getRegularType", () => { } }); + test("BigIntLiteralType.value is a bigint (positive and negative)", () => { + const src = `\nexport const pos = 123n;\nexport const neg = -123n;\n`; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/main.ts": src, + }); + try { + const snapshot = api.updateSnapshot({ openProject: "/tsconfig.json" }); + const project = snapshot.getProject("/tsconfig.json")!; + + const posSymbol = project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("pos =")); + assert.ok(posSymbol); + const posType = project.checker.getTypeOfSymbol(posSymbol); + assert.ok(posType); + assert.ok(posType.flags & TypeFlags.BigIntLiteral, "Expected BigIntLiteral"); + const posLiteral = posType as BigIntLiteralType; + assert.equal(typeof posLiteral.value, "bigint"); + assert.equal(posLiteral.value, 123n); + + const negSymbol = project.checker.getSymbolAtPosition("/src/main.ts", src.indexOf("neg =")); + assert.ok(negSymbol); + const negType = project.checker.getTypeOfSymbol(negSymbol); + assert.ok(negType); + assert.ok(negType.flags & TypeFlags.BigIntLiteral, "Expected BigIntLiteral"); + const negLiteral = negType as BigIntLiteralType; + assert.equal(typeof negLiteral.value, "bigint"); + assert.equal(negLiteral.value, -123n); + } + finally { + api.close(); + } + }); + test("getFreshType() returns a fresh twin with matching value", () => { const src = `\nexport const greeting: "hello" = "hello";\n`; const api = spawnAPI({ diff --git a/internal/api/proto.go b/internal/api/proto.go index 42573a235c3..31cd3df96f3 100644 --- a/internal/api/proto.go +++ b/internal/api/proto.go @@ -665,6 +665,8 @@ func literalValueToJSON(value any) any { case bool: return v case jsnum.PseudoBigInt: + // Encode bigint literals as a signed decimal string (e.g. "-123"); the + // API client decodes this back into a real bigint. JSON has no bigint. return v.String() default: return nil From 9415bebdeb9e69997846d2a2844c78f439ff22f1 Mon Sep 17 00:00:00 2001 From: Matheus Mol <80729568+oMatheusmol@users.noreply.github.com> Date: Mon, 22 Jun 2026 19:20:31 -0300 Subject: [PATCH 47/49] Fix panic on non-string include/exclude/files in extended configs (#4384) --- internal/execute/tsctests/tsc_test.go | 40 +++++++++++++++++ internal/tsoptions/tsconfigparsing.go | 10 +++-- ...with-mixed-valid-and-non-string-include.js | 45 +++++++++++++++++++ .../extends-config-with-non-string-exclude.js | 45 +++++++++++++++++++ .../extends-config-with-non-string-files.js | 19 ++++++++ .../extends-config-with-non-string-include.js | 20 +++++++++ 6 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 testdata/baselines/reference/tsc/extends/extends-config-with-mixed-valid-and-non-string-include.js create mode 100644 testdata/baselines/reference/tsc/extends/extends-config-with-non-string-exclude.js create mode 100644 testdata/baselines/reference/tsc/extends/extends-config-with-non-string-files.js create mode 100644 testdata/baselines/reference/tsc/extends/extends-config-with-non-string-include.js diff --git a/internal/execute/tsctests/tsc_test.go b/internal/execute/tsctests/tsc_test.go index 1acebdcb1a5..46128bbb759 100644 --- a/internal/execute/tsctests/tsc_test.go +++ b/internal/execute/tsctests/tsc_test.go @@ -1071,6 +1071,34 @@ func TestTscExtends(t *testing.T) { edits: edits, } } + getTscExtendsNonStringPathTestCase := func(propertyName string) *tscInput { + return &tscInput{ + subScenario: "extends config with non-string " + propertyName, + files: FileMap{ + "/home/src/projects/project/tsconfig.json": stringtestutil.Dedent(` + { + "extends": "./base.json", + }`), + "/home/src/projects/project/base.json": stringtestutil.Dedent(` + { + "` + propertyName + `": [1], + }`), + "/home/src/projects/project/main.ts": `export const x = 1;`, + }, + cwd: "/home/src/projects/project", + commandLineArgs: []string{"-p", "tsconfig.json", "--pretty", "false"}, + } + } + getTscExtendsBase := func(baseContents string) FileMap { + return FileMap{ + "/home/src/projects/project/tsconfig.json": stringtestutil.Dedent(` + { + "extends": "./base.json", + }`), + "/home/src/projects/project/base.json": stringtestutil.Dedent(baseContents), + "/home/src/projects/project/main.ts": `export const x = 1;`, + } + } testCases := []*tscInput{ { subScenario: "when building solution with projects extends config with include", @@ -1078,6 +1106,18 @@ func TestTscExtends(t *testing.T) { cwd: "/home/src/workspaces/solution", commandLineArgs: []string{"--b", "--v", "--listFiles"}, }, + getTscExtendsNonStringPathTestCase("include"), + getTscExtendsNonStringPathTestCase("exclude"), + getTscExtendsNonStringPathTestCase("files"), + { + subScenario: "extends config with mixed valid and non-string include", + files: getTscExtendsBase(` + { + "include": ["main.ts", 1], + }`), + cwd: "/home/src/projects/project", + commandLineArgs: []string{"-p", "tsconfig.json", "--pretty", "false"}, + }, { subScenario: "when building project uses reference and both extend config with include", files: getBuildConfigFileExtendsFileMap(), diff --git a/internal/tsoptions/tsconfigparsing.go b/internal/tsoptions/tsconfigparsing.go index 8a189239062..65b7f8495e8 100644 --- a/internal/tsoptions/tsconfigparsing.go +++ b/internal/tsoptions/tsconfigparsing.go @@ -1091,8 +1091,12 @@ func parseConfig( if rawMap, ok := extendsRaw.(*collections.OrderedMap[string, any]); ok && rawMap.Has(propertyName) { if slice, _ := rawMap.GetOrZero(propertyName).([]any); slice != nil { value := core.Map(slice, func(path any) any { - if startsWithConfigDirTemplate(path) || tspath.IsRootedDiskPath(path.(string)) { - return path.(string) + pathStr, isString := path.(string) + if !isString { + return path + } + if startsWithConfigDirTemplate(path) || tspath.IsRootedDiskPath(pathStr) { + return pathStr } else { if relativeDifference == "" { t := tspath.ComparePathsOptions{ @@ -1101,7 +1105,7 @@ func parseConfig( } relativeDifference = tspath.ConvertToRelativePath(tspath.GetDirectoryPath(extendedConfigPath), t) } - return tspath.CombinePaths(relativeDifference, path.(string)) + return tspath.CombinePaths(relativeDifference, pathStr) } }) if propertyName == "include" { diff --git a/testdata/baselines/reference/tsc/extends/extends-config-with-mixed-valid-and-non-string-include.js b/testdata/baselines/reference/tsc/extends/extends-config-with-mixed-valid-and-non-string-include.js new file mode 100644 index 00000000000..8b3991de62d --- /dev/null +++ b/testdata/baselines/reference/tsc/extends/extends-config-with-mixed-valid-and-non-string-include.js @@ -0,0 +1,45 @@ +currentDirectory::/home/src/projects/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/projects/project/base.json] *new* +{ + "include": ["main.ts", 1], +} +//// [/home/src/projects/project/main.ts] *new* +export const x = 1; +//// [/home/src/projects/project/tsconfig.json] *new* +{ + "extends": "./base.json", +} + +tsgo -p tsconfig.json --pretty false +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +base.json(2,28): error TS5024: Compiler option 'include' requires a value of type string. +//// [/home/src/projects/project/main.js] *new* +export const x = 1; + +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/testdata/baselines/reference/tsc/extends/extends-config-with-non-string-exclude.js b/testdata/baselines/reference/tsc/extends/extends-config-with-non-string-exclude.js new file mode 100644 index 00000000000..5f93b995a4f --- /dev/null +++ b/testdata/baselines/reference/tsc/extends/extends-config-with-non-string-exclude.js @@ -0,0 +1,45 @@ +currentDirectory::/home/src/projects/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/projects/project/base.json] *new* +{ + "exclude": [1], +} +//// [/home/src/projects/project/main.ts] *new* +export const x = 1; +//// [/home/src/projects/project/tsconfig.json] *new* +{ + "extends": "./base.json", +} + +tsgo -p tsconfig.json --pretty false +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +base.json(2,17): error TS5024: Compiler option 'exclude' requires a value of type string. +//// [/home/src/projects/project/main.js] *new* +export const x = 1; + +//// [/home/src/tslibs/TS/Lib/lib.es2025.full.d.ts] *Lib* +/// +interface Boolean {} +interface Function {} +interface CallableFunction {} +interface NewableFunction {} +interface IArguments {} +interface Number { toExponential: any; } +interface Object {} +interface RegExp {} +interface String { charAt: any; } +interface Array { length: number; [n: number]: T; } +interface ReadonlyArray {} +interface SymbolConstructor { + (desc?: string | number): symbol; + for(name: string): symbol; + readonly toStringTag: symbol; +} +declare var Symbol: SymbolConstructor; +interface Symbol { + readonly [Symbol.toStringTag]: string; +} +declare const console: { log(msg: any): void; }; + diff --git a/testdata/baselines/reference/tsc/extends/extends-config-with-non-string-files.js b/testdata/baselines/reference/tsc/extends/extends-config-with-non-string-files.js new file mode 100644 index 00000000000..284b33d4b5f --- /dev/null +++ b/testdata/baselines/reference/tsc/extends/extends-config-with-non-string-files.js @@ -0,0 +1,19 @@ +currentDirectory::/home/src/projects/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/projects/project/base.json] *new* +{ + "files": [1], +} +//// [/home/src/projects/project/main.ts] *new* +export const x = 1; +//// [/home/src/projects/project/tsconfig.json] *new* +{ + "extends": "./base.json", +} + +tsgo -p tsconfig.json --pretty false +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +base.json(2,15): error TS5024: Compiler option 'files' requires a value of type string. + diff --git a/testdata/baselines/reference/tsc/extends/extends-config-with-non-string-include.js b/testdata/baselines/reference/tsc/extends/extends-config-with-non-string-include.js new file mode 100644 index 00000000000..90c121a51d5 --- /dev/null +++ b/testdata/baselines/reference/tsc/extends/extends-config-with-non-string-include.js @@ -0,0 +1,20 @@ +currentDirectory::/home/src/projects/project +useCaseSensitiveFileNames::true +Input:: +//// [/home/src/projects/project/base.json] *new* +{ + "include": [1], +} +//// [/home/src/projects/project/main.ts] *new* +export const x = 1; +//// [/home/src/projects/project/tsconfig.json] *new* +{ + "extends": "./base.json", +} + +tsgo -p tsconfig.json --pretty false +ExitStatus:: DiagnosticsPresent_OutputsGenerated +Output:: +error TS18003: No inputs were found in config file '/home/src/projects/project/tsconfig.json'. Specified 'include' paths were '[1]' and 'exclude' paths were '[]'. +base.json(2,17): error TS5024: Compiler option 'include' requires a value of type string. + From f6e1c6d58c5e155727a9d3f02f17aa1a5b8b6535 Mon Sep 17 00:00:00 2001 From: Matheus Mol <80729568+oMatheusmol@users.noreply.github.com> Date: Tue, 23 Jun 2026 14:36:09 -0300 Subject: [PATCH 48/49] API: add the missing position and text getters to Node (#4308) --- .../src/api/node/node.generated.ts | 34 ++++ _packages/native-preview/src/ast/ast.ts | 8 + .../src/ast/factory.generated.ts | 34 ++++ .../native-preview/test/sync/ast.test.ts | 186 ++++++++++++++++++ _scripts/generate-encoder.ts | 34 ++++ _scripts/generate-ts-ast.ts | 34 ++++ 6 files changed, 330 insertions(+) diff --git a/_packages/native-preview/src/api/node/node.generated.ts b/_packages/native-preview/src/api/node/node.generated.ts index 47a367e4563..ff74677b3fa 100644 --- a/_packages/native-preview/src/api/node/node.generated.ts +++ b/_packages/native-preview/src/api/node/node.generated.ts @@ -1,6 +1,7 @@ // Code generated by _scripts/generate-encoder.ts. DO NOT EDIT. import { + getTokenPosOfNode, ModifierFlags, type Node, type NodeArray, @@ -254,6 +255,39 @@ export class RemoteNode extends RemoteNodeBase implements Node { return this.sourceFile as unknown as SourceFile; } + getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number { + return getTokenPosOfNode(this as unknown as Node, sourceFile ?? this.getSourceFile(), includeJsDocComment); + } + + getFullStart(): number { + return this.pos; + } + + getEnd(): number { + return this.end; + } + + getWidth(sourceFile?: SourceFile): number { + return this.getEnd() - this.getStart(sourceFile); + } + + getFullWidth(): number { + return this.end - this.pos; + } + + getLeadingTriviaWidth(sourceFile?: SourceFile): number { + return this.getStart(sourceFile) - this.pos; + } + + getFullText(sourceFile?: SourceFile): string { + return (sourceFile ?? this.getSourceFile()).text.substring(this.pos, this.end); + } + + getText(sourceFile?: SourceFile): string { + sourceFile ??= this.getSourceFile(); + return sourceFile.text.substring(this.getStart(sourceFile), this.end); + } + protected getString(index: number): string { const offsetStringTableOffsets = this.sourceFile._offsetStringTableOffsets; const start = this.view.getUint32(offsetStringTableOffsets + index * 4, true); diff --git a/_packages/native-preview/src/ast/ast.ts b/_packages/native-preview/src/ast/ast.ts index ae0231ae44b..c12a7953e4e 100644 --- a/_packages/native-preview/src/ast/ast.ts +++ b/_packages/native-preview/src/ast/ast.ts @@ -48,6 +48,14 @@ export interface Node extends ReadonlyTextRange { readonly jsDoc?: readonly Node[]; forEachChild(visitor: (node: Node) => T, visitArray?: (nodes: NodeArray) => T): T | undefined; getSourceFile(): SourceFile; + getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number; + getFullStart(): number; + getEnd(): number; + getWidth(sourceFile?: SourceFile): number; + getFullWidth(): number; + getLeadingTriviaWidth(sourceFile?: SourceFile): number; + getFullText(sourceFile?: SourceFile): string; + getText(sourceFile?: SourceFile): string; } export interface FileReference extends TextRange { diff --git a/_packages/native-preview/src/ast/factory.generated.ts b/_packages/native-preview/src/ast/factory.generated.ts index afb2859f8e2..3181d175715 100644 --- a/_packages/native-preview/src/ast/factory.generated.ts +++ b/_packages/native-preview/src/ast/factory.generated.ts @@ -257,6 +257,7 @@ import type { WithStatement, YieldExpression, } from "./ast.ts"; +import { getTokenPosOfNode } from "./astnav.ts"; import { forEachChildOfJSDocParameterTag, forEachChildOfJSDocPropertyTag, @@ -667,6 +668,39 @@ export class NodeObject { while (node.parent) node = node.parent; return node as unknown as SourceFile; } + + getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number { + return getTokenPosOfNode(this as unknown as Node, sourceFile ?? this.getSourceFile(), includeJsDocComment); + } + + getFullStart(): number { + return this.pos; + } + + getEnd(): number { + return this.end; + } + + getWidth(sourceFile?: SourceFile): number { + return this.getEnd() - this.getStart(sourceFile); + } + + getFullWidth(): number { + return this.end - this.pos; + } + + getLeadingTriviaWidth(sourceFile?: SourceFile): number { + return this.getStart(sourceFile) - this.pos; + } + + getFullText(sourceFile?: SourceFile): string { + return (sourceFile ?? this.getSourceFile()).text.substring(this.pos, this.end); + } + + getText(sourceFile?: SourceFile): string { + sourceFile ??= this.getSourceFile(); + return sourceFile.text.substring(this.getStart(sourceFile), this.end); + } } function isNodeArray(array: readonly T[]): array is NodeArray { diff --git a/_packages/native-preview/test/sync/ast.test.ts b/_packages/native-preview/test/sync/ast.test.ts index 5ce07c3de19..076345e3c44 100644 --- a/_packages/native-preview/test/sync/ast.test.ts +++ b/_packages/native-preview/test/sync/ast.test.ts @@ -3,7 +3,9 @@ import type { Identifier, Node, NodeArray, + SourceFile, StringLiteralLikeNode, + VariableStatement, } from "@typescript/native-preview/unstable/ast"; import { isImportDeclaration, @@ -662,3 +664,187 @@ describe("RemoteNode + getSynthesizedDeepClone", () => { } }); }); + +// --------------------------------------------------------------------------- +// RemoteNode: position and text getters +// --------------------------------------------------------------------------- + +// Relationships that must hold between the position/text getters on any node. +function assertGetterInvariants(node: Node, sf: SourceFile) { + const fullStart = node.getFullStart(); + const start = node.getStart(sf); + const end = node.getEnd(); + + assert.strictEqual(fullStart, node.pos); + assert.strictEqual(end, node.end); + assert.ok(start >= fullStart, `getStart (${start}) must be >= getFullStart (${fullStart})`); + assert.ok(end >= start, `getEnd (${end}) must be >= getStart (${start})`); + + assert.strictEqual(node.getFullWidth(), end - fullStart); + assert.strictEqual(node.getWidth(sf), end - start); + assert.strictEqual(node.getLeadingTriviaWidth(sf), start - fullStart); + + const fullText = node.getFullText(sf); + const text = node.getText(sf); + assert.strictEqual(fullText.length, node.getFullWidth()); + assert.strictEqual(text, fullText.slice(node.getLeadingTriviaWidth(sf))); + + // No-argument variants resolve the source file themselves and must agree. + assert.strictEqual(node.getStart(), start); + assert.strictEqual(node.getFullText(), fullText); + assert.strictEqual(node.getText(), text); + + node.forEachChild(child => { + assertGetterInvariants(child, sf); + return undefined; + }); +} + +describe("RemoteNode + position/text getters", () => { + const source = "/* lead */ const value = 123;"; + const files = { + "/tsconfig.json": "{}", + "/src/getters.ts": source, + }; + + test("position and text getters on a parsed statement", () => { + const api = spawnAPI(files); + try { + const sf = getRemoteSourceFile(api, "/tsconfig.json", "/src/getters.ts"); + const stmt = sf.statements[0]; + assert.ok(stmt); + + const tokenStart = source.indexOf("const"); + + // Full start includes leading trivia; start skips it. + assert.strictEqual(stmt.getFullStart(), 0); + assert.strictEqual(stmt.getStart(), tokenStart); + assert.strictEqual(stmt.getStart(sf), tokenStart); + assert.strictEqual(stmt.getLeadingTriviaWidth(), tokenStart); + + // End and widths. + assert.strictEqual(stmt.getEnd(), source.length); + assert.strictEqual(stmt.getFullWidth(), source.length); + assert.strictEqual(stmt.getWidth(), source.length - tokenStart); + + // Text slices, with and without leading trivia. + assert.strictEqual(stmt.getFullText(), source); + assert.strictEqual(stmt.getText(), source.slice(tokenStart)); + } + finally { + api.close(); + } + }); + + test("getText/getFullText on a nested node", () => { + const api = spawnAPI(files); + try { + const sf = getRemoteSourceFile(api, "/tsconfig.json", "/src/getters.ts"); + const stmt = sf.statements[0] as VariableStatement; + const name = stmt.declarationList.declarations[0].name; + + assert.strictEqual(name.getText(), "value"); + // getFullText keeps the leading whitespace trivia before the identifier. + assert.strictEqual(name.getFullText().trimStart(), "value"); + assert.ok(name.getFullText().endsWith("value")); + } + finally { + api.close(); + } + }); + + test("getStart can include leading JSDoc comments", () => { + const docSource = "/** doc */\nfunction f() {}\n"; + const api = spawnAPI({ + "/tsconfig.json": "{}", + "/src/doc.ts": docSource, + }); + try { + const sf = getRemoteSourceFile(api, "/tsconfig.json", "/src/doc.ts"); + const fn = sf.statements[0]; + assert.ok(fn); + + // By default the JSDoc comment is treated as leading trivia and skipped. + assert.strictEqual(fn.getStart(), docSource.indexOf("function")); + + // With includeJsDocComment, the start moves back to the JSDoc comment. + assert.ok(fn.jsDoc && fn.jsDoc.length > 0, "function declaration should have attached JSDoc"); + assert.strictEqual(fn.getStart(sf, /*includeJsDocComment*/ true), 0); + } + finally { + api.close(); + } + }); + + test("a node without leading trivia has zero leading trivia width", () => { + const api = spawnAPI({ "/tsconfig.json": "{}", "/src/plain.ts": "const x = 1;\n" }); + try { + const sf = getRemoteSourceFile(api, "/tsconfig.json", "/src/plain.ts"); + const stmt = sf.statements[0]; + assert.ok(stmt); + + assert.strictEqual(stmt.getFullStart(), 0); + assert.strictEqual(stmt.getStart(), 0); + assert.strictEqual(stmt.getLeadingTriviaWidth(), 0); + assert.strictEqual(stmt.getText(), "const x = 1;"); + assert.strictEqual(stmt.getText(), stmt.getFullText()); + } + finally { + api.close(); + } + }); + + test("the SourceFile node spans the whole file text", () => { + const text = "/* head */ const y = 2;\n"; + const api = spawnAPI({ "/tsconfig.json": "{}", "/src/whole.ts": text }); + try { + const sf = getRemoteSourceFile(api, "/tsconfig.json", "/src/whole.ts"); + + assert.strictEqual(sf.getFullStart(), 0); + assert.strictEqual(sf.getFullText(), sf.text); + assert.strictEqual(sf.getEnd(), sf.text.length); + // getStart skips the file's leading comment trivia. + assert.strictEqual(sf.getStart(), text.indexOf("const")); + } + finally { + api.close(); + } + }); + + test("getter invariants hold for every node in a representative tree", () => { + const treeSource = [ + `import { foo } from "./foo";`, + ``, + `/** docs */`, + `export function add(a: number, b: number): number {`, + ` // body comment`, + ` return a + b;`, + `}`, + ``, + `const obj = { x: 1, y: "two" };`, + ``, + ].join("\n"); + const api = spawnAPI({ "/tsconfig.json": "{}", "/src/tree.ts": treeSource }); + try { + const sf = getRemoteSourceFile(api, "/tsconfig.json", "/src/tree.ts"); + assertGetterInvariants(sf, sf); + } + finally { + api.close(); + } + }); + + test("getter invariants hold even for malformed source with missing nodes", () => { + // Error recovery produces zero-width / missing nodes; the getters must + // still satisfy their invariants and must not throw. + const malformed = "const a = b +;\nfunction ("; + const api = spawnAPI({ "/tsconfig.json": "{}", "/src/broken.ts": malformed }); + try { + const sf = getRemoteSourceFile(api, "/tsconfig.json", "/src/broken.ts"); + assertGetterInvariants(sf, sf); + } + finally { + api.close(); + } + }); +}); diff --git a/_scripts/generate-encoder.ts b/_scripts/generate-encoder.ts index 64fd89f2ea8..caddf3356ae 100644 --- a/_scripts/generate-encoder.ts +++ b/_scripts/generate-encoder.ts @@ -1474,6 +1474,7 @@ function generateTSNodeGenerated(): string { function emitNodeGeneratedImports(w: CodeWriter) { w.write(`import {`); + w.write(` getTokenPosOfNode,`); w.write(` ModifierFlags,`); w.write(` type Node,`); w.write(` type NodeArray,`); @@ -1691,6 +1692,39 @@ function emitRemoteNodeClassOpen(w: CodeWriter) { w.write(` return this.sourceFile as unknown as SourceFile;`); w.write(` }`); w.write(``); + w.write(` getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number {`); + w.write(` return getTokenPosOfNode(this as unknown as Node, sourceFile ?? this.getSourceFile(), includeJsDocComment);`); + w.write(` }`); + w.write(``); + w.write(` getFullStart(): number {`); + w.write(` return this.pos;`); + w.write(` }`); + w.write(``); + w.write(` getEnd(): number {`); + w.write(` return this.end;`); + w.write(` }`); + w.write(``); + w.write(` getWidth(sourceFile?: SourceFile): number {`); + w.write(` return this.getEnd() - this.getStart(sourceFile);`); + w.write(` }`); + w.write(``); + w.write(` getFullWidth(): number {`); + w.write(` return this.end - this.pos;`); + w.write(` }`); + w.write(``); + w.write(` getLeadingTriviaWidth(sourceFile?: SourceFile): number {`); + w.write(` return this.getStart(sourceFile) - this.pos;`); + w.write(` }`); + w.write(``); + w.write(` getFullText(sourceFile?: SourceFile): string {`); + w.write(` return (sourceFile ?? this.getSourceFile()).text.substring(this.pos, this.end);`); + w.write(` }`); + w.write(``); + w.write(` getText(sourceFile?: SourceFile): string {`); + w.write(` sourceFile ??= this.getSourceFile();`); + w.write(` return sourceFile.text.substring(this.getStart(sourceFile), this.end);`); + w.write(` }`); + w.write(``); w.write(` protected getString(index: number): string {`); w.write(` const offsetStringTableOffsets = this.sourceFile._offsetStringTableOffsets;`); w.write(` const start = this.view.getUint32(offsetStringTableOffsets + index * 4, true);`); diff --git a/_scripts/generate-ts-ast.ts b/_scripts/generate-ts-ast.ts index 00c8a3e80b6..73e621b07fe 100644 --- a/_scripts/generate-ts-ast.ts +++ b/_scripts/generate-ts-ast.ts @@ -517,6 +517,7 @@ function generateFactory(): string { out.push(` ${t},`); } out.push(`} from "./ast.ts";`); + out.push(`import { getTokenPosOfNode } from "./astnav.ts";`); // Import hand-written forEachChild functions const handWrittenForEachChildImports: string[] = []; @@ -573,6 +574,39 @@ function generateFactory(): string { out.push(` while (node.parent) node = node.parent;`); out.push(` return node as unknown as SourceFile;`); out.push(` }`); + out.push(``); + out.push(` getStart(sourceFile?: SourceFile, includeJsDocComment?: boolean): number {`); + out.push(` return getTokenPosOfNode(this as unknown as Node, sourceFile ?? this.getSourceFile(), includeJsDocComment);`); + out.push(` }`); + out.push(``); + out.push(` getFullStart(): number {`); + out.push(` return this.pos;`); + out.push(` }`); + out.push(``); + out.push(` getEnd(): number {`); + out.push(` return this.end;`); + out.push(` }`); + out.push(``); + out.push(` getWidth(sourceFile?: SourceFile): number {`); + out.push(` return this.getEnd() - this.getStart(sourceFile);`); + out.push(` }`); + out.push(``); + out.push(` getFullWidth(): number {`); + out.push(` return this.end - this.pos;`); + out.push(` }`); + out.push(``); + out.push(` getLeadingTriviaWidth(sourceFile?: SourceFile): number {`); + out.push(` return this.getStart(sourceFile) - this.pos;`); + out.push(` }`); + out.push(``); + out.push(` getFullText(sourceFile?: SourceFile): string {`); + out.push(` return (sourceFile ?? this.getSourceFile()).text.substring(this.pos, this.end);`); + out.push(` }`); + out.push(``); + out.push(` getText(sourceFile?: SourceFile): string {`); + out.push(` sourceFile ??= this.getSourceFile();`); + out.push(` return sourceFile.text.substring(this.getStart(sourceFile), this.end);`); + out.push(` }`); out.push(`}`); out.push(``); From 03924874d4a15c20d5e67c63fac044b53d3defc0 Mon Sep 17 00:00:00 2001 From: Piotr Tomiak Date: Tue, 23 Jun 2026 20:36:52 +0200 Subject: [PATCH 49/49] API: make object registries project-specific to avoid id collisions on types and signatures (#4341) --- _packages/native-preview/src/api/async/api.ts | 199 ++++++++---- .../native-preview/src/api/objectRegistry.ts | 97 ------ _packages/native-preview/src/api/sync/api.ts | 199 ++++++++---- .../native-preview/test/async/api.test.ts | 152 +++++++++ .../native-preview/test/sync/api.test.ts | 152 +++++++++ internal/api/proto.go | 2 + internal/api/session.go | 302 +++++++++++------- 7 files changed, 783 insertions(+), 320 deletions(-) delete mode 100644 _packages/native-preview/src/api/objectRegistry.ts diff --git a/_packages/native-preview/src/api/async/api.ts b/_packages/native-preview/src/api/async/api.ts index eb58109a9e9..ec37e1a61a1 100644 --- a/_packages/native-preview/src/api/async/api.ts +++ b/_packages/native-preview/src/api/async/api.ts @@ -31,10 +31,6 @@ import { readSourceFileHash, RemoteSourceFile, } from "../node/node.ts"; -import { - type ObjectFactories, - ObjectRegistry, -} from "../objectRegistry.ts"; import type { APIOptions, LSPConnectionOptions, @@ -236,9 +232,9 @@ export class Snapshot { private projectMap: Map; private toPath: (fileName: string) => Path; private client: Client; - private objectRegistry: SnapshotObjectRegistry; private disposed: boolean = false; private onDispose: () => void; + private snapshotRegistry: SnapshotObjectRegistry; constructor( data: UpdateSnapshotResponse, @@ -251,21 +247,11 @@ export class Snapshot { this.client = client; this.toPath = toPath; this.onDispose = onDispose; + this.snapshotRegistry = new SnapshotObjectRegistry(client, this.id); - this.objectRegistry = new SnapshotObjectRegistry( - { - createSymbol: symbolData => new Symbol(symbolData, this.objectRegistry), - createType: typeData => new TypeObject(typeData, this.objectRegistry), - createSignature: sigData => new Signature(sigData, this.objectRegistry), - }, - client, - this.id, - ); - - // Create projects this.projectMap = new Map(); for (const projData of data.projects) { - const project = new Project(projData, this.id, client, this.objectRegistry, sourceFileCache, toPath); + const project = new Project(projData, this.id, client, sourceFileCache, toPath, this.snapshotRegistry); this.projectMap.set(toPath(projData.configFileName), project); } } @@ -297,7 +283,11 @@ export class Snapshot { async dispose(): Promise { if (this.disposed) return; this.disposed = true; - this.objectRegistry.clear(); + for (const project of this.projectMap.values()) { + project.dispose(); + } + this.projectMap.clear(); + this.snapshotRegistry.clear(); this.onDispose(); await this.client.apiRequest("release", { snapshot: this.id }); } @@ -313,18 +303,128 @@ export class Snapshot { } } -class SnapshotObjectRegistry extends ObjectRegistry { +class SnapshotObjectRegistry { + private readonly symbols: Map = new Map(); + private readonly client: Client; + private readonly snapshotId: number; + + constructor(client: Client, snapshotId: number) { + this.client = client; + this.snapshotId = snapshotId; + } + + getOrCreateSymbol(data: SymbolResponse): Symbol { + let symbol = this.symbols.get(data.id); + if (!symbol) { + symbol = new Symbol(data, this); + this.symbols.set(data.id, symbol); + } + return symbol; + } + + getSymbol(id: number): Symbol | undefined { + return this.symbols.get(id); + } + + clear(): void { + this.symbols.clear(); + } + + async fetchSymbol(source: Symbol | Signature | Type, method: string, handle: number | undefined, projectId?: string): Promise { + if (!handle) return undefined as unknown as Symbol; + const cached = this.getSymbol(handle); + if (cached) return cached; + + const data = await this.client.apiRequest(method, { + snapshot: this.snapshotId, + project: projectId, + objectId: source.id, + }); + if (!data) throw new Error(`${method} returned null symbol for ${source.constructor.name} ${source.id}`); + return this.getOrCreateSymbol(data); + } + + async fetchSymbols(source: Symbol | Signature | Type, method: string, handles?: readonly number[], projectId?: string): Promise { + if (handles) { + const result = new Array(handles.length); + let allCached = true; + for (let i = 0; i < handles.length; i++) { + const cached = this.getSymbol(handles[i]); + if (!cached) { + allCached = false; + break; + } + result[i] = cached; + } + if (allCached) return result; + } + const symbolData = await this.client.apiRequest(method, { + snapshot: this.snapshotId, + project: projectId, + objectId: source.id, + }); + if (symbolData == null) return []; + else return symbolData.map(data => this.getOrCreateSymbol(data)); + } +} + +class ProjectObjectRegistry { private client: Client; private snapshotId: number; + private projectId: string; + private snapshotRegistry: SnapshotObjectRegistry; + private types: Map = new Map(); + private signatures: Map = new Map(); constructor( - factories: ObjectFactories, client: Client, snapshotId: number, + projectId: string, + snapshotRegistry: SnapshotObjectRegistry, ) { - super(factories); this.client = client; this.snapshotId = snapshotId; + this.projectId = projectId; + this.snapshotRegistry = snapshotRegistry; + } + + getOrCreateSymbol(data: SymbolResponse): Symbol { + return this.snapshotRegistry.getOrCreateSymbol(data); + } + + getSymbol(id: number): Symbol | undefined { + return this.snapshotRegistry.getSymbol(id); + } + + getOrCreateType(data: TypeResponse): TypeObject { + let type = this.types.get(data.id); + if (!type) { + type = new TypeObject(data, this); + this.types.set(data.id, type); + } + return type; + } + + getType(id: number): TypeObject | undefined { + return this.types.get(id); + } + + getOrCreateSignature(data: SignatureResponse): Signature { + let sig = this.signatures.get(data.id); + if (!sig) { + sig = new Signature(data, this); + this.signatures.set(data.id, sig); + } + return sig; + } + + getSignature(id: number): Signature | undefined { + return this.signatures.get(id); + } + + clear(): void { + this.types.clear(); + this.signatures.clear(); } async fetchType(source: Symbol | Signature | Type, method: string, handle: number | undefined): Promise { @@ -334,6 +434,7 @@ class SnapshotObjectRegistry extends ObjectRegistry(method, { snapshot: this.snapshotId, + project: this.projectId, objectId: source.id, }); if (!data) throw new Error(`${method} returned null type for ${source.constructor.name} ${source.id}`); @@ -341,16 +442,7 @@ class SnapshotObjectRegistry extends ObjectRegistry { - if (!handle) return undefined as unknown as Symbol; - const cached = this.getSymbol(handle); - if (cached) return cached; - - const data = await this.client.apiRequest(method, { - snapshot: this.snapshotId, - objectId: source.id, - }); - if (!data) throw new Error(`${method} returned null symbol for ${source.constructor.name} ${source.id}`); - return this.getOrCreateSymbol(data); + return this.snapshotRegistry.fetchSymbol(source, method, handle, this.projectId); } async fetchSignature(source: Symbol | Signature | Type, method: string, handle: number | undefined): Promise { @@ -360,6 +452,7 @@ class SnapshotObjectRegistry extends ObjectRegistry(method, { snapshot: this.snapshotId, + project: this.projectId, objectId: source.id, }); if (!data) throw new Error(`${method} returned null signature for ${source.constructor.name} ${source.id}`); @@ -382,6 +475,7 @@ class SnapshotObjectRegistry extends ObjectRegistry(method, { snapshot: this.snapshotId, + project: this.projectId, objectId: source.id, }); if (typesData == null) return []; @@ -389,25 +483,7 @@ class SnapshotObjectRegistry extends ObjectRegistry { - if (handles) { - const result = new Array(handles.length); - let allCached = true; - for (let i = 0; i < handles.length; i++) { - const cached = this.getSymbol(handles[i]); - if (!cached) { - allCached = false; - break; - } - result[i] = cached; - } - if (allCached) return result; - } - const symbolData = await this.client.apiRequest(method, { - snapshot: this.snapshotId, - objectId: source.id, - }); - if (symbolData == null) return []; - else return symbolData.map(data => this.getOrCreateSymbol(data)); + return this.snapshotRegistry.fetchSymbols(source, method, handles, this.projectId); } } @@ -426,9 +502,9 @@ export class Project { data: ProjectResponse, snapshotId: number, client: Client, - objectRegistry: SnapshotObjectRegistry, sourceFileCache: SourceFileCache, toPath: (fileName: string) => Path, + snapshotRegistry: SnapshotObjectRegistry, ) { this.id = data.id; this.configFileName = data.configFileName; @@ -442,6 +518,7 @@ export class Project { sourceFileCache, toPath, ); + const objectRegistry = new ProjectObjectRegistry(client, snapshotId, this.id, snapshotRegistry); this.checker = new Checker( snapshotId, this.id, @@ -450,6 +527,10 @@ export class Project { ); this.emitter = new Emitter(client); } + + dispose(): void { + this.checker.dispose(); + } } export class Program { @@ -571,13 +652,13 @@ export class Checker { private snapshotId: number; private projectId: string; private client: Client; - private objectRegistry: SnapshotObjectRegistry; + private objectRegistry: ProjectObjectRegistry; constructor( snapshotId: number, projectId: string, client: Client, - objectRegistry: SnapshotObjectRegistry, + objectRegistry: ProjectObjectRegistry, ) { this.snapshotId = snapshotId; this.projectId = projectId; @@ -585,6 +666,10 @@ export class Checker { this.objectRegistry = objectRegistry; } + dispose(): void { + this.objectRegistry.clear(); + } + getSymbolAtLocation(node: Node): Promise; getSymbolAtLocation(nodes: readonly Node[]): Promise<(Symbol | undefined)[]>; async getSymbolAtLocation(nodeOrNodes: Node | readonly Node[]): Promise { @@ -1166,7 +1251,7 @@ export class Symbol { } class TypeObject implements Type { - private objectRegistry: SnapshotObjectRegistry; + private objectRegistry: ProjectObjectRegistry; readonly id: number; readonly flags: TypeFlags; @@ -1194,7 +1279,7 @@ class TypeObject implements Type { readonly baseType!: number; readonly substConstraint!: number; - constructor(data: TypeResponse, objectRegistry: SnapshotObjectRegistry) { + constructor(data: TypeResponse, objectRegistry: ProjectObjectRegistry) { this.objectRegistry = objectRegistry; this.id = data.id; @@ -1295,7 +1380,7 @@ class TypeObject implements Type { export class Signature { private flags: number; - private objectRegistry: SnapshotObjectRegistry; + private objectRegistry: ProjectObjectRegistry; readonly id: number; readonly declaration?: NodeHandle | undefined; @@ -1304,7 +1389,7 @@ export class Signature { readonly thisParameter?: number | undefined; readonly target?: number | undefined; - constructor(data: SignatureResponse, objectRegistry: SnapshotObjectRegistry) { + constructor(data: SignatureResponse, objectRegistry: ProjectObjectRegistry) { this.id = data.id; this.flags = data.flags; this.objectRegistry = objectRegistry; diff --git a/_packages/native-preview/src/api/objectRegistry.ts b/_packages/native-preview/src/api/objectRegistry.ts deleted file mode 100644 index 06355b74cdc..00000000000 --- a/_packages/native-preview/src/api/objectRegistry.ts +++ /dev/null @@ -1,97 +0,0 @@ -import type { - SignatureResponse, - SymbolResponse, - TypeResponse, -} from "./proto.ts"; - -/** - * Interface for objects with an ID that can be tracked by the registry. - */ -export interface Identifiable { - readonly id: number; -} - -/** - * Factory functions for creating API objects. - */ -export interface ObjectFactories { - createSymbol(data: SymbolResponse): TSymbol; - createType(data: TypeResponse): TType; - createSignature(data: SignatureResponse): TSignature; -} - -/** - * Object registry scoped to a single snapshot. - * - * This registry ensures that the same server-side object ID always maps to - * the same client-side object instance within a snapshot, enabling proper - * object identity semantics across API calls against the same snapshot. - * - * Symbol and type lifetimes are tied to the snapshot - when the snapshot - * is disposed, all its objects are implicitly released. - */ -export class ObjectRegistry< - TSymbol extends Identifiable, - TType extends Identifiable, - TSignature extends Identifiable = Identifiable, -> { - private symbols: Map = new Map(); - private types: Map = new Map(); - private signatures: Map = new Map(); - private factories: ObjectFactories; - - constructor(factories: ObjectFactories) { - this.factories = factories; - } - - getOrCreateSymbol(data: SymbolResponse): TSymbol { - let symbol = this.symbols.get(data.id); - if (symbol) { - return symbol; - } - - symbol = this.factories.createSymbol(data); - this.symbols.set(data.id, symbol); - return symbol; - } - - getOrCreateType(data: TypeResponse): TType { - let type = this.types.get(data.id); - if (type) { - return type; - } - - type = this.factories.createType(data); - this.types.set(data.id, type); - return type; - } - - getOrCreateSignature(data: SignatureResponse): TSignature { - let signature = this.signatures.get(data.id); - if (signature) { - return signature; - } - - signature = this.factories.createSignature(data); - this.signatures.set(data.id, signature); - return signature; - } - - getType(id: number): TType | undefined { - return this.types.get(id); - } - - getSymbol(id: number): TSymbol | undefined { - return this.symbols.get(id); - } - - getSignature(id: number): TSignature | undefined { - return this.signatures.get(id); - } - - clear(): void { - this.symbols.clear(); - this.types.clear(); - this.signatures.clear(); - } -} diff --git a/_packages/native-preview/src/api/sync/api.ts b/_packages/native-preview/src/api/sync/api.ts index d4d1c59894c..40224ef06e8 100644 --- a/_packages/native-preview/src/api/sync/api.ts +++ b/_packages/native-preview/src/api/sync/api.ts @@ -39,10 +39,6 @@ import { readSourceFileHash, RemoteSourceFile, } from "../node/node.ts"; -import { - type ObjectFactories, - ObjectRegistry, -} from "../objectRegistry.ts"; import type { APIOptions, LSPConnectionOptions, @@ -244,9 +240,9 @@ export class Snapshot { private projectMap: Map; private toPath: (fileName: string) => Path; private client: Client; - private objectRegistry: SnapshotObjectRegistry; private disposed: boolean = false; private onDispose: () => void; + private snapshotRegistry: SnapshotObjectRegistry; constructor( data: UpdateSnapshotResponse, @@ -259,21 +255,11 @@ export class Snapshot { this.client = client; this.toPath = toPath; this.onDispose = onDispose; + this.snapshotRegistry = new SnapshotObjectRegistry(client, this.id); - this.objectRegistry = new SnapshotObjectRegistry( - { - createSymbol: symbolData => new Symbol(symbolData, this.objectRegistry), - createType: typeData => new TypeObject(typeData, this.objectRegistry), - createSignature: sigData => new Signature(sigData, this.objectRegistry), - }, - client, - this.id, - ); - - // Create projects this.projectMap = new Map(); for (const projData of data.projects) { - const project = new Project(projData, this.id, client, this.objectRegistry, sourceFileCache, toPath); + const project = new Project(projData, this.id, client, sourceFileCache, toPath, this.snapshotRegistry); this.projectMap.set(toPath(projData.configFileName), project); } } @@ -305,7 +291,11 @@ export class Snapshot { dispose(): void { if (this.disposed) return; this.disposed = true; - this.objectRegistry.clear(); + for (const project of this.projectMap.values()) { + project.dispose(); + } + this.projectMap.clear(); + this.snapshotRegistry.clear(); this.onDispose(); this.client.apiRequest("release", { snapshot: this.id }); } @@ -321,18 +311,128 @@ export class Snapshot { } } -class SnapshotObjectRegistry extends ObjectRegistry { +class SnapshotObjectRegistry { + private readonly symbols: Map = new Map(); + private readonly client: Client; + private readonly snapshotId: number; + + constructor(client: Client, snapshotId: number) { + this.client = client; + this.snapshotId = snapshotId; + } + + getOrCreateSymbol(data: SymbolResponse): Symbol { + let symbol = this.symbols.get(data.id); + if (!symbol) { + symbol = new Symbol(data, this); + this.symbols.set(data.id, symbol); + } + return symbol; + } + + getSymbol(id: number): Symbol | undefined { + return this.symbols.get(id); + } + + clear(): void { + this.symbols.clear(); + } + + fetchSymbol(source: Symbol | Signature | Type, method: string, handle: number | undefined, projectId?: string): Symbol { + if (!handle) return undefined as unknown as Symbol; + const cached = this.getSymbol(handle); + if (cached) return cached; + + const data = this.client.apiRequest(method, { + snapshot: this.snapshotId, + project: projectId, + objectId: source.id, + }); + if (!data) throw new Error(`${method} returned null symbol for ${source.constructor.name} ${source.id}`); + return this.getOrCreateSymbol(data); + } + + fetchSymbols(source: Symbol | Signature | Type, method: string, handles?: readonly number[], projectId?: string): readonly Symbol[] { + if (handles) { + const result = new Array(handles.length); + let allCached = true; + for (let i = 0; i < handles.length; i++) { + const cached = this.getSymbol(handles[i]); + if (!cached) { + allCached = false; + break; + } + result[i] = cached; + } + if (allCached) return result; + } + const symbolData = this.client.apiRequest(method, { + snapshot: this.snapshotId, + project: projectId, + objectId: source.id, + }); + if (symbolData == null) return []; + else return symbolData.map(data => this.getOrCreateSymbol(data)); + } +} + +class ProjectObjectRegistry { private client: Client; private snapshotId: number; + private projectId: string; + private snapshotRegistry: SnapshotObjectRegistry; + private types: Map = new Map(); + private signatures: Map = new Map(); constructor( - factories: ObjectFactories, client: Client, snapshotId: number, + projectId: string, + snapshotRegistry: SnapshotObjectRegistry, ) { - super(factories); this.client = client; this.snapshotId = snapshotId; + this.projectId = projectId; + this.snapshotRegistry = snapshotRegistry; + } + + getOrCreateSymbol(data: SymbolResponse): Symbol { + return this.snapshotRegistry.getOrCreateSymbol(data); + } + + getSymbol(id: number): Symbol | undefined { + return this.snapshotRegistry.getSymbol(id); + } + + getOrCreateType(data: TypeResponse): TypeObject { + let type = this.types.get(data.id); + if (!type) { + type = new TypeObject(data, this); + this.types.set(data.id, type); + } + return type; + } + + getType(id: number): TypeObject | undefined { + return this.types.get(id); + } + + getOrCreateSignature(data: SignatureResponse): Signature { + let sig = this.signatures.get(data.id); + if (!sig) { + sig = new Signature(data, this); + this.signatures.set(data.id, sig); + } + return sig; + } + + getSignature(id: number): Signature | undefined { + return this.signatures.get(id); + } + + clear(): void { + this.types.clear(); + this.signatures.clear(); } fetchType(source: Symbol | Signature | Type, method: string, handle: number | undefined): T { @@ -342,6 +442,7 @@ class SnapshotObjectRegistry extends ObjectRegistry(method, { snapshot: this.snapshotId, + project: this.projectId, objectId: source.id, }); if (!data) throw new Error(`${method} returned null type for ${source.constructor.name} ${source.id}`); @@ -349,16 +450,7 @@ class SnapshotObjectRegistry extends ObjectRegistry(method, { - snapshot: this.snapshotId, - objectId: source.id, - }); - if (!data) throw new Error(`${method} returned null symbol for ${source.constructor.name} ${source.id}`); - return this.getOrCreateSymbol(data); + return this.snapshotRegistry.fetchSymbol(source, method, handle, this.projectId); } fetchSignature(source: Symbol | Signature | Type, method: string, handle: number | undefined): Signature { @@ -368,6 +460,7 @@ class SnapshotObjectRegistry extends ObjectRegistry(method, { snapshot: this.snapshotId, + project: this.projectId, objectId: source.id, }); if (!data) throw new Error(`${method} returned null signature for ${source.constructor.name} ${source.id}`); @@ -390,6 +483,7 @@ class SnapshotObjectRegistry extends ObjectRegistry(method, { snapshot: this.snapshotId, + project: this.projectId, objectId: source.id, }); if (typesData == null) return []; @@ -397,25 +491,7 @@ class SnapshotObjectRegistry extends ObjectRegistry(handles.length); - let allCached = true; - for (let i = 0; i < handles.length; i++) { - const cached = this.getSymbol(handles[i]); - if (!cached) { - allCached = false; - break; - } - result[i] = cached; - } - if (allCached) return result; - } - const symbolData = this.client.apiRequest(method, { - snapshot: this.snapshotId, - objectId: source.id, - }); - if (symbolData == null) return []; - else return symbolData.map(data => this.getOrCreateSymbol(data)); + return this.snapshotRegistry.fetchSymbols(source, method, handles, this.projectId); } } @@ -434,9 +510,9 @@ export class Project { data: ProjectResponse, snapshotId: number, client: Client, - objectRegistry: SnapshotObjectRegistry, sourceFileCache: SourceFileCache, toPath: (fileName: string) => Path, + snapshotRegistry: SnapshotObjectRegistry, ) { this.id = data.id; this.configFileName = data.configFileName; @@ -450,6 +526,7 @@ export class Project { sourceFileCache, toPath, ); + const objectRegistry = new ProjectObjectRegistry(client, snapshotId, this.id, snapshotRegistry); this.checker = new Checker( snapshotId, this.id, @@ -458,6 +535,10 @@ export class Project { ); this.emitter = new Emitter(client); } + + dispose(): void { + this.checker.dispose(); + } } export class Program { @@ -579,13 +660,13 @@ export class Checker { private snapshotId: number; private projectId: string; private client: Client; - private objectRegistry: SnapshotObjectRegistry; + private objectRegistry: ProjectObjectRegistry; constructor( snapshotId: number, projectId: string, client: Client, - objectRegistry: SnapshotObjectRegistry, + objectRegistry: ProjectObjectRegistry, ) { this.snapshotId = snapshotId; this.projectId = projectId; @@ -593,6 +674,10 @@ export class Checker { this.objectRegistry = objectRegistry; } + dispose(): void { + this.objectRegistry.clear(); + } + getSymbolAtLocation(node: Node): Symbol | undefined; getSymbolAtLocation(nodes: readonly Node[]): (Symbol | undefined)[]; getSymbolAtLocation(nodeOrNodes: Node | readonly Node[]): Symbol | (Symbol | undefined)[] | undefined { @@ -1174,7 +1259,7 @@ export class Symbol { } class TypeObject implements Type { - private objectRegistry: SnapshotObjectRegistry; + private objectRegistry: ProjectObjectRegistry; readonly id: number; readonly flags: TypeFlags; @@ -1202,7 +1287,7 @@ class TypeObject implements Type { readonly baseType!: number; readonly substConstraint!: number; - constructor(data: TypeResponse, objectRegistry: SnapshotObjectRegistry) { + constructor(data: TypeResponse, objectRegistry: ProjectObjectRegistry) { this.objectRegistry = objectRegistry; this.id = data.id; @@ -1303,7 +1388,7 @@ class TypeObject implements Type { export class Signature { private flags: number; - private objectRegistry: SnapshotObjectRegistry; + private objectRegistry: ProjectObjectRegistry; readonly id: number; readonly declaration?: NodeHandle | undefined; @@ -1312,7 +1397,7 @@ export class Signature { readonly thisParameter?: number | undefined; readonly target?: number | undefined; - constructor(data: SignatureResponse, objectRegistry: SnapshotObjectRegistry) { + constructor(data: SignatureResponse, objectRegistry: ProjectObjectRegistry) { this.id = data.id; this.flags = data.flags; this.objectRegistry = objectRegistry; diff --git a/_packages/native-preview/test/async/api.test.ts b/_packages/native-preview/test/async/api.test.ts index 637d1069cdc..015cd521e36 100644 --- a/_packages/native-preview/test/async/api.test.ts +++ b/_packages/native-preview/test/async/api.test.ts @@ -701,6 +701,43 @@ describe("Source file cache keying across projects", () => { }); }); +describe("Checker - symbol identity across projects", () => { + const sharedSymbolFiles = { + "/projectA/tsconfig.json": JSON.stringify({ files: ["../src/shared.ts"] }), + "/projectB/tsconfig.json": JSON.stringify({ files: ["../src/shared.ts"] }), + "/src/shared.ts": `export const sharedVar = 42;`, + }; + + test("getSymbolAtPosition returns same Symbol instance across projects", async () => { + const api = spawnAPI(sharedSymbolFiles); + try { + await api.updateSnapshot({ openProject: "/projectA/tsconfig.json" }); + const snapshot = await api.updateSnapshot({ openProject: "/projectB/tsconfig.json" }); + + const projectA = snapshot.getProject("/projectA/tsconfig.json")!; + const projectB = snapshot.getProject("/projectB/tsconfig.json")!; + assert.ok(projectA, "projectA should exist"); + assert.ok(projectB, "projectB should exist"); + + const src = sharedSymbolFiles["/src/shared.ts"]; + const varPos = src.indexOf("sharedVar"); + + const symbolA = await projectA.checker.getSymbolAtPosition("/src/shared.ts", varPos); + const symbolB = await projectB.checker.getSymbolAtPosition("/src/shared.ts", varPos); + + assert.ok(symbolA, "symbolA should exist"); + assert.ok(symbolB, "symbolB should exist"); + assert.equal(symbolA.name, "sharedVar"); + assert.equal(symbolB.name, "sharedVar"); + + assert.strictEqual(symbolA, symbolB, "Same source symbol queried from two projects should be the same object"); + } + finally { + await api.close(); + } + }); +}); + describe("Checker - types and signatures", () => { const checkerFiles = { "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), @@ -1555,6 +1592,121 @@ describe("Checker - intrinsic type getters", () => { }); }); +describe("Checker - multi-project type ID uniqueness", () => { + test("intrinsic types from 3 projects in the same snapshot have non-colliding IDs", async () => { + const api = spawnAPI({ + "/proj1/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/proj1/src/index.ts": `export const x = 1;`, + "/proj2/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/proj2/src/index.ts": `export const y = "hello";`, + "/proj3/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/proj3/src/index.ts": `export const z = true;`, + }); + try { + // Open all 3 projects — each updateSnapshot accumulates open projects + await api.updateSnapshot({ openProject: "/proj1/tsconfig.json" }); + await api.updateSnapshot({ openProject: "/proj2/tsconfig.json" }); + const snapshot = await api.updateSnapshot({ openProject: "/proj3/tsconfig.json" }); + + const proj1 = snapshot.getProject("/proj1/tsconfig.json")!; + const proj2 = snapshot.getProject("/proj2/tsconfig.json")!; + const proj3 = snapshot.getProject("/proj3/tsconfig.json")!; + assert.ok(proj1, "proj1 should be in final snapshot"); + assert.ok(proj2, "proj2 should be in final snapshot"); + assert.ok(proj3, "proj3 should be in final snapshot"); + + // Fetch several intrinsic types from each checker. + // If type IDs collide across checkers, registerType panics → API error. + const num1 = await proj1.checker.getNumberType(); + const str1 = await proj1.checker.getStringType(); + const bool1 = await proj1.checker.getBooleanType(); + const any1 = await proj1.checker.getAnyType(); + const num2 = await proj2.checker.getNumberType(); + const str2 = await proj2.checker.getStringType(); + const bool2 = await proj2.checker.getBooleanType(); + const any2 = await proj2.checker.getAnyType(); + const num3 = await proj3.checker.getNumberType(); + const str3 = await proj3.checker.getStringType(); + const bool3 = await proj3.checker.getBooleanType(); + const any3 = await proj3.checker.getAnyType(); + + assert.ok(num1.flags & TypeFlags.Number, "proj1 number type"); + assert.ok(str1.flags & TypeFlags.String, "proj1 string type"); + assert.ok(bool1.flags & TypeFlags.Boolean, "proj1 boolean type"); + assert.ok(any1.flags & TypeFlags.Any, "proj1 any type"); + + assert.ok(num2.flags & TypeFlags.Number, "proj2 number type"); + assert.ok(str2.flags & TypeFlags.String, "proj2 string type"); + assert.ok(bool2.flags & TypeFlags.Boolean, "proj2 boolean type"); + assert.ok(any2.flags & TypeFlags.Any, "proj2 any type"); + + assert.ok(num3.flags & TypeFlags.Number, "proj3 number type"); + assert.ok(str3.flags & TypeFlags.String, "proj3 string type"); + assert.ok(bool3.flags & TypeFlags.Boolean, "proj3 boolean type"); + assert.ok(any3.flags & TypeFlags.Any, "proj3 any type"); + } + finally { + await api.close(); + } + }); + + test("symbol and signature handles from 3 projects in the same snapshot have non-colliding IDs", async () => { + const api = spawnAPI({ + "/proj1/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/proj1/src/index.ts": `export function add(a: number, b: number): number { return a + b; }`, + "/proj2/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/proj2/src/index.ts": `export function greet(name: string): string { return "hello " + name; }`, + "/proj3/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/proj3/src/index.ts": `export function toggle(b: boolean): boolean { return !b; }`, + }); + try { + await api.updateSnapshot({ openProject: "/proj1/tsconfig.json" }); + await api.updateSnapshot({ openProject: "/proj2/tsconfig.json" }); + const snapshot = await api.updateSnapshot({ openProject: "/proj3/tsconfig.json" }); + + const proj1 = snapshot.getProject("/proj1/tsconfig.json")!; + const proj2 = snapshot.getProject("/proj2/tsconfig.json")!; + const proj3 = snapshot.getProject("/proj3/tsconfig.json")!; + + // Get a symbol from each project (exercises symbol registry) + const src1 = `export function add(a: number, b: number): number { return a + b; }`; + const src2 = `export function greet(name: string): string { return "hello " + name; }`; + const src3 = `export function toggle(b: boolean): boolean { return !b; }`; + + const sym1 = await proj1.checker.getSymbolAtPosition("/proj1/src/index.ts", src1.indexOf("add")); + const sym2 = await proj2.checker.getSymbolAtPosition("/proj2/src/index.ts", src2.indexOf("greet")); + const sym3 = await proj3.checker.getSymbolAtPosition("/proj3/src/index.ts", src3.indexOf("toggle")); + assert.ok(sym1, "proj1 symbol"); + assert.ok(sym2, "proj2 symbol"); + assert.ok(sym3, "proj3 symbol"); + assert.equal(sym1.name, "add", "proj1 symbol name"); + assert.equal(sym2.name, "greet", "proj2 symbol name"); + assert.equal(sym3.name, "toggle", "proj3 symbol name"); + + // Get type of each symbol, then signatures (exercises type + signature registries) + const type1 = await proj1.checker.getTypeOfSymbol(sym1); + const type2 = await proj2.checker.getTypeOfSymbol(sym2); + const type3 = await proj3.checker.getTypeOfSymbol(sym3); + assert.ok(type1, "proj1 function type"); + assert.ok(type2, "proj2 function type"); + assert.ok(type3, "proj3 function type"); + + const sigs1 = await proj1.checker.getSignaturesOfType(type1, SignatureKind.Call); + const sigs2 = await proj2.checker.getSignaturesOfType(type2, SignatureKind.Call); + const sigs3 = await proj3.checker.getSignaturesOfType(type3, SignatureKind.Call); + assert.equal(sigs1.length, 1, "proj1 has 1 call signature"); + assert.equal(sigs2.length, 1, "proj2 has 1 call signature"); + assert.equal(sigs3.length, 1, "proj3 has 1 call signature"); + assert.equal(sigs1[0].parameters.length, 2, "proj1 add() has 2 params"); + assert.equal(sigs2[0].parameters.length, 1, "proj2 greet() has 1 param"); + assert.equal(sigs3[0].parameters.length, 1, "proj3 toggle() has 1 param"); + } + finally { + await api.close(); + } + }); +}); + describe("Checker - getBaseTypeOfLiteralType", () => { test("number literal widens to number", async () => { const api = spawnAPI({ diff --git a/_packages/native-preview/test/sync/api.test.ts b/_packages/native-preview/test/sync/api.test.ts index f56913c3492..74041ca1e85 100644 --- a/_packages/native-preview/test/sync/api.test.ts +++ b/_packages/native-preview/test/sync/api.test.ts @@ -709,6 +709,43 @@ describe("Source file cache keying across projects", () => { }); }); +describe("Checker - symbol identity across projects", () => { + const sharedSymbolFiles = { + "/projectA/tsconfig.json": JSON.stringify({ files: ["../src/shared.ts"] }), + "/projectB/tsconfig.json": JSON.stringify({ files: ["../src/shared.ts"] }), + "/src/shared.ts": `export const sharedVar = 42;`, + }; + + test("getSymbolAtPosition returns same Symbol instance across projects", () => { + const api = spawnAPI(sharedSymbolFiles); + try { + api.updateSnapshot({ openProject: "/projectA/tsconfig.json" }); + const snapshot = api.updateSnapshot({ openProject: "/projectB/tsconfig.json" }); + + const projectA = snapshot.getProject("/projectA/tsconfig.json")!; + const projectB = snapshot.getProject("/projectB/tsconfig.json")!; + assert.ok(projectA, "projectA should exist"); + assert.ok(projectB, "projectB should exist"); + + const src = sharedSymbolFiles["/src/shared.ts"]; + const varPos = src.indexOf("sharedVar"); + + const symbolA = projectA.checker.getSymbolAtPosition("/src/shared.ts", varPos); + const symbolB = projectB.checker.getSymbolAtPosition("/src/shared.ts", varPos); + + assert.ok(symbolA, "symbolA should exist"); + assert.ok(symbolB, "symbolB should exist"); + assert.equal(symbolA.name, "sharedVar"); + assert.equal(symbolB.name, "sharedVar"); + + assert.strictEqual(symbolA, symbolB, "Same source symbol queried from two projects should be the same object"); + } + finally { + api.close(); + } + }); +}); + describe("Checker - types and signatures", () => { const checkerFiles = { "/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), @@ -1563,6 +1600,121 @@ describe("Checker - intrinsic type getters", () => { }); }); +describe("Checker - multi-project type ID uniqueness", () => { + test("intrinsic types from 3 projects in the same snapshot have non-colliding IDs", () => { + const api = spawnAPI({ + "/proj1/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/proj1/src/index.ts": `export const x = 1;`, + "/proj2/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/proj2/src/index.ts": `export const y = "hello";`, + "/proj3/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/proj3/src/index.ts": `export const z = true;`, + }); + try { + // Open all 3 projects — each updateSnapshot accumulates open projects + api.updateSnapshot({ openProject: "/proj1/tsconfig.json" }); + api.updateSnapshot({ openProject: "/proj2/tsconfig.json" }); + const snapshot = api.updateSnapshot({ openProject: "/proj3/tsconfig.json" }); + + const proj1 = snapshot.getProject("/proj1/tsconfig.json")!; + const proj2 = snapshot.getProject("/proj2/tsconfig.json")!; + const proj3 = snapshot.getProject("/proj3/tsconfig.json")!; + assert.ok(proj1, "proj1 should be in final snapshot"); + assert.ok(proj2, "proj2 should be in final snapshot"); + assert.ok(proj3, "proj3 should be in final snapshot"); + + // Fetch several intrinsic types from each checker. + // If type IDs collide across checkers, registerType panics → API error. + const num1 = proj1.checker.getNumberType(); + const str1 = proj1.checker.getStringType(); + const bool1 = proj1.checker.getBooleanType(); + const any1 = proj1.checker.getAnyType(); + const num2 = proj2.checker.getNumberType(); + const str2 = proj2.checker.getStringType(); + const bool2 = proj2.checker.getBooleanType(); + const any2 = proj2.checker.getAnyType(); + const num3 = proj3.checker.getNumberType(); + const str3 = proj3.checker.getStringType(); + const bool3 = proj3.checker.getBooleanType(); + const any3 = proj3.checker.getAnyType(); + + assert.ok(num1.flags & TypeFlags.Number, "proj1 number type"); + assert.ok(str1.flags & TypeFlags.String, "proj1 string type"); + assert.ok(bool1.flags & TypeFlags.Boolean, "proj1 boolean type"); + assert.ok(any1.flags & TypeFlags.Any, "proj1 any type"); + + assert.ok(num2.flags & TypeFlags.Number, "proj2 number type"); + assert.ok(str2.flags & TypeFlags.String, "proj2 string type"); + assert.ok(bool2.flags & TypeFlags.Boolean, "proj2 boolean type"); + assert.ok(any2.flags & TypeFlags.Any, "proj2 any type"); + + assert.ok(num3.flags & TypeFlags.Number, "proj3 number type"); + assert.ok(str3.flags & TypeFlags.String, "proj3 string type"); + assert.ok(bool3.flags & TypeFlags.Boolean, "proj3 boolean type"); + assert.ok(any3.flags & TypeFlags.Any, "proj3 any type"); + } + finally { + api.close(); + } + }); + + test("symbol and signature handles from 3 projects in the same snapshot have non-colliding IDs", () => { + const api = spawnAPI({ + "/proj1/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/proj1/src/index.ts": `export function add(a: number, b: number): number { return a + b; }`, + "/proj2/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/proj2/src/index.ts": `export function greet(name: string): string { return "hello " + name; }`, + "/proj3/tsconfig.json": JSON.stringify({ compilerOptions: { strict: true } }), + "/proj3/src/index.ts": `export function toggle(b: boolean): boolean { return !b; }`, + }); + try { + api.updateSnapshot({ openProject: "/proj1/tsconfig.json" }); + api.updateSnapshot({ openProject: "/proj2/tsconfig.json" }); + const snapshot = api.updateSnapshot({ openProject: "/proj3/tsconfig.json" }); + + const proj1 = snapshot.getProject("/proj1/tsconfig.json")!; + const proj2 = snapshot.getProject("/proj2/tsconfig.json")!; + const proj3 = snapshot.getProject("/proj3/tsconfig.json")!; + + // Get a symbol from each project (exercises symbol registry) + const src1 = `export function add(a: number, b: number): number { return a + b; }`; + const src2 = `export function greet(name: string): string { return "hello " + name; }`; + const src3 = `export function toggle(b: boolean): boolean { return !b; }`; + + const sym1 = proj1.checker.getSymbolAtPosition("/proj1/src/index.ts", src1.indexOf("add")); + const sym2 = proj2.checker.getSymbolAtPosition("/proj2/src/index.ts", src2.indexOf("greet")); + const sym3 = proj3.checker.getSymbolAtPosition("/proj3/src/index.ts", src3.indexOf("toggle")); + assert.ok(sym1, "proj1 symbol"); + assert.ok(sym2, "proj2 symbol"); + assert.ok(sym3, "proj3 symbol"); + assert.equal(sym1.name, "add", "proj1 symbol name"); + assert.equal(sym2.name, "greet", "proj2 symbol name"); + assert.equal(sym3.name, "toggle", "proj3 symbol name"); + + // Get type of each symbol, then signatures (exercises type + signature registries) + const type1 = proj1.checker.getTypeOfSymbol(sym1); + const type2 = proj2.checker.getTypeOfSymbol(sym2); + const type3 = proj3.checker.getTypeOfSymbol(sym3); + assert.ok(type1, "proj1 function type"); + assert.ok(type2, "proj2 function type"); + assert.ok(type3, "proj3 function type"); + + const sigs1 = proj1.checker.getSignaturesOfType(type1, SignatureKind.Call); + const sigs2 = proj2.checker.getSignaturesOfType(type2, SignatureKind.Call); + const sigs3 = proj3.checker.getSignaturesOfType(type3, SignatureKind.Call); + assert.equal(sigs1.length, 1, "proj1 has 1 call signature"); + assert.equal(sigs2.length, 1, "proj2 has 1 call signature"); + assert.equal(sigs3.length, 1, "proj3 has 1 call signature"); + assert.equal(sigs1[0].parameters.length, 2, "proj1 add() has 2 params"); + assert.equal(sigs2[0].parameters.length, 1, "proj2 greet() has 1 param"); + assert.equal(sigs3[0].parameters.length, 1, "proj3 toggle() has 1 param"); + } + finally { + api.close(); + } + }); +}); + describe("Checker - getBaseTypeOfLiteralType", () => { test("number literal widens to number", () => { const api = spawnAPI({ diff --git a/internal/api/proto.go b/internal/api/proto.go index 31cd3df96f3..8128018f3e0 100644 --- a/internal/api/proto.go +++ b/internal/api/proto.go @@ -703,6 +703,7 @@ type ResolveNameParams struct { // GetTypePropertyParams is used for all type sub-property endpoints. type GetTypePropertyParams struct { Snapshot SnapshotID `json:"snapshot"` + Project ProjectID `json:"project"` Type TypeID `json:"objectId"` } @@ -715,6 +716,7 @@ type GetSymbolPropertyParams struct { // GetSignaturePropertyParams is used for all signature sub-property endpoints. type GetSignaturePropertyParams struct { Snapshot SnapshotID `json:"snapshot"` + Project ProjectID `json:"project"` Signature SignatureID `json:"objectId"` } diff --git a/internal/api/session.go b/internal/api/session.go index 6ebed37a9ba..1a8fd17bb3c 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -36,14 +36,26 @@ type snapshotData struct { snapshot *project.Snapshot refCount int + // Symbol IDs come from ast.GetSymbolId, a global atomic counter, so the same + // *ast.Symbol pointer always has the same unique ID across all projects in the + // snapshot. Symbols are registered snapshot-wide to ensure identity semantics: + // querying the same symbol from two different projects returns the same handle. symbolRegistry map[SymbolID]*ast.Symbol symbolRegistryMu sync.RWMutex + projectRegistries map[ProjectID]*projectRegistryData + projectRegistriesMu sync.RWMutex +} + +// projectRegistryData holds per-project type and signature registries. +// Types and signatures use per-checker sequential IDs, so the same local ID +// can appear in multiple projects. Separate maps per project prevent collisions +// and allow clean teardown when a project is removed. +type projectRegistryData struct { typeRegistry map[TypeID]*checker.Type typeRegistryMu sync.RWMutex signatureRegistry map[SignatureID]*checker.Signature - signatureNextID uint64 signatureRegistryMu sync.RWMutex } @@ -73,7 +85,31 @@ func (sd *snapshotData) nodeHandleFrom(node *ast.Node) NodeHandle { return NodeHandle(fmt.Sprintf("%d.%d.%s", idx, node.Kind, path)) } -// newSymbolResponse registers a symbol in this snapshot's registry and returns the response. +// getOrCreateProjectRegistry returns the registry for the given project, creating it if needed. +func (sd *snapshotData) getOrCreateProjectRegistry(projectID ProjectID) *projectRegistryData { + if projectID == "" { + panic("getOrCreateProjectRegistry: empty project ID") + } + // Fast path: registry already exists — read lock only. + sd.projectRegistriesMu.RLock() + reg := sd.projectRegistries[projectID] + sd.projectRegistriesMu.RUnlock() + if reg != nil { + return reg + } + // Slow path: create under write lock. + sd.projectRegistriesMu.Lock() + defer sd.projectRegistriesMu.Unlock() + if sd.projectRegistries[projectID] == nil { + sd.projectRegistries[projectID] = &projectRegistryData{ + typeRegistry: make(map[TypeID]*checker.Type), + signatureRegistry: make(map[SignatureID]*checker.Signature), + } + } + return sd.projectRegistries[projectID] +} + +// newSymbolResponse registers a symbol in the snapshot's registry and returns the response. func (sd *snapshotData) newSymbolResponse(symbol *ast.Symbol) *SymbolResponse { if symbol == nil { return nil @@ -126,22 +162,23 @@ func (sd *snapshotData) registerSymbol(symbol *ast.Symbol) SymbolID { return id } -// newTypeResponse registers a type in this snapshot's registry and returns the response. -func (sd *snapshotData) newTypeResponse(t *checker.Type) *TypeResponse { +// newTypeResponse registers a type in the project's registry and returns the response. +func (sd *snapshotData) newTypeResponse(projectID ProjectID, t *checker.Type) *TypeResponse { if t == nil { return nil } - return newTypeResponse(t, sd.registerType(t)) + return newTypeResponse(t, sd.registerType(projectID, t)) } -func (sd *snapshotData) registerType(t *checker.Type) TypeID { +func (sd *snapshotData) registerType(projectID ProjectID, t *checker.Type) TypeID { if t == nil { return 0 } id := TypeHandle(t) - sd.typeRegistryMu.Lock() - defer sd.typeRegistryMu.Unlock() - existing := sd.typeRegistry[id] + reg := sd.getOrCreateProjectRegistry(projectID) + reg.typeRegistryMu.Lock() + defer reg.typeRegistryMu.Unlock() + existing := reg.typeRegistry[id] if existing != nil { if existing != t { @@ -149,11 +186,11 @@ func (sd *snapshotData) registerType(t *checker.Type) TypeID { } return id } - sd.typeRegistry[id] = t + reg.typeRegistry[id] = t return id } -// resolveSymbolHandle resolves a symbol handle to a symbol within this snapshot. +// resolveSymbolHandle resolves a symbol handle within the snapshot's registry. func (sd *snapshotData) resolveSymbolHandle(handle SymbolID) (*ast.Symbol, error) { if handle == 0 { return nil, fmt.Errorf("%w: empty symbol handle", ErrClientError) @@ -170,47 +207,69 @@ func (sd *snapshotData) resolveSymbolHandle(handle SymbolID) (*ast.Symbol, error return symbol, nil } -// resolveTypeHandle resolves a type handle to a type within this snapshot. -func (sd *snapshotData) resolveTypeHandle(handle TypeID) (*checker.Type, error) { +// resolveTypeHandle resolves a type handle within the project's registry. +func (sd *snapshotData) resolveTypeHandle(projectID ProjectID, handle TypeID) (*checker.Type, error) { if handle == 0 { return nil, fmt.Errorf("%w: empty type handle", ErrClientError) } + if projectID == "" { + return nil, fmt.Errorf("%w: empty project ID for type handle %d", ErrClientError, handle) + } + + sd.projectRegistriesMu.RLock() + reg := sd.projectRegistries[projectID] + sd.projectRegistriesMu.RUnlock() + + if reg == nil { + return nil, fmt.Errorf("%w: type handle %d not found (no registry for project %s)", ErrClientError, handle, projectID) + } - sd.typeRegistryMu.RLock() - t, ok := sd.typeRegistry[handle] - sd.typeRegistryMu.RUnlock() + reg.typeRegistryMu.RLock() + t, ok := reg.typeRegistry[handle] + reg.typeRegistryMu.RUnlock() if !ok { - return nil, fmt.Errorf("%w: type handle %d not found in snapshot registry", ErrClientError, handle) + return nil, fmt.Errorf("%w: type handle %d not found in project registry", ErrClientError, handle) } return t, nil } -// resolveSignatureHandle resolves a signature handle to a signature within this snapshot. -func (sd *snapshotData) resolveSignatureHandle(handle SignatureID) (*checker.Signature, error) { +// resolveSignatureHandle resolves a signature handle within the project's registry. +func (sd *snapshotData) resolveSignatureHandle(projectID ProjectID, handle SignatureID) (*checker.Signature, error) { if handle == 0 { return nil, fmt.Errorf("%w: empty signature handle", ErrClientError) } + if projectID == "" { + return nil, fmt.Errorf("%w: empty project ID for signature handle %d", ErrClientError, handle) + } - sd.signatureRegistryMu.RLock() - sig, ok := sd.signatureRegistry[handle] - sd.signatureRegistryMu.RUnlock() + sd.projectRegistriesMu.RLock() + reg := sd.projectRegistries[projectID] + sd.projectRegistriesMu.RUnlock() + + if reg == nil { + return nil, fmt.Errorf("%w: signature handle %d not found (no registry for project %s)", ErrClientError, handle, projectID) + } + + reg.signatureRegistryMu.RLock() + sig, ok := reg.signatureRegistry[handle] + reg.signatureRegistryMu.RUnlock() if !ok { - return nil, fmt.Errorf("%w: signature handle %d not found in snapshot registry", ErrClientError, handle) + return nil, fmt.Errorf("%w: signature handle %d not found in project registry", ErrClientError, handle) } return sig, nil } -// newSignatureResponse registers a signature in this snapshot's registry and returns the response. -func (sd *snapshotData) newSignatureResponse(sig *checker.Signature) *SignatureResponse { +// newSignatureResponse registers a signature in the project's registry and returns the response. +func (sd *snapshotData) newSignatureResponse(projectID ProjectID, sig *checker.Signature) *SignatureResponse { if sig == nil { return nil } resp := &SignatureResponse{ - Id: sd.registerSignature(sig), + Id: sd.registerSignature(projectID, sig), Flags: uint32(sig.Flags()), } @@ -237,15 +296,15 @@ func (sd *snapshotData) newSignatureResponse(sig *checker.Signature) *SignatureR return resp } -func (sd *snapshotData) registerSignature(sig *checker.Signature) SignatureID { +func (sd *snapshotData) registerSignature(projectID ProjectID, sig *checker.Signature) SignatureID { if sig == nil { return 0 } id := SignatureHandle(sig) - - sd.signatureRegistryMu.Lock() - defer sd.signatureRegistryMu.Unlock() - existing := sd.signatureRegistry[id] + reg := sd.getOrCreateProjectRegistry(projectID) + reg.signatureRegistryMu.Lock() + defer reg.signatureRegistryMu.Unlock() + existing := reg.signatureRegistry[id] if existing != nil { if existing != sig { @@ -253,7 +312,7 @@ func (sd *snapshotData) registerSignature(sig *checker.Signature) SignatureID { } return id } - sd.signatureRegistry[id] = sig + reg.signatureRegistry[id] = sig return id } @@ -331,10 +390,35 @@ func (s *Session) getSnapshotData(handle SnapshotID) (*snapshotData, error) { // checkerSetup holds the common context needed by handlers that require a type checker. type checkerSetup struct { - sd *snapshotData - program *compiler.Program - checker *checker.Checker - done func() + sd *snapshotData + program *compiler.Program + checker *checker.Checker + done func() + projectID ProjectID +} + +func (setup checkerSetup) newTypeResponse(t *checker.Type) *TypeResponse { + return setup.sd.newTypeResponse(setup.projectID, t) +} + +func (setup checkerSetup) newSymbolResponse(sym *ast.Symbol) *SymbolResponse { + return setup.sd.newSymbolResponse(sym) +} + +func (setup checkerSetup) newSignatureResponse(sig *checker.Signature) *SignatureResponse { + return setup.sd.newSignatureResponse(setup.projectID, sig) +} + +func (setup checkerSetup) resolveTypeHandle(id TypeID) (*checker.Type, error) { + return setup.sd.resolveTypeHandle(setup.projectID, id) +} + +func (setup checkerSetup) resolveSymbolHandle(id SymbolID) (*ast.Symbol, error) { + return setup.sd.resolveSymbolHandle(id) +} + +func (setup checkerSetup) resolveSignatureHandle(id SignatureID) (*checker.Signature, error) { + return setup.sd.resolveSignatureHandle(setup.projectID, id) } // setupChecker resolves snapshot, program, and type checker for a project. @@ -352,10 +436,11 @@ func (s *Session) setupChecker(ctx context.Context, snapshot SnapshotID, project c, done := program.GetTypeChecker(core.WithCheckerLifetime(ctx, core.CheckerLifetimeAPI)) return checkerSetup{ - sd: sd, - program: program, - checker: c, - done: done, + sd: sd, + program: program, + checker: c, + done: done, + projectID: projectHandle, }, nil } @@ -659,8 +744,7 @@ func (s *Session) handleUpdateSnapshot(ctx context.Context, params *UpdateSnapsh snapshot: snapshot, refCount: 1, symbolRegistry: make(map[SymbolID]*ast.Symbol), - typeRegistry: make(map[TypeID]*checker.Type), - signatureRegistry: make(map[SignatureID]*checker.Signature), + projectRegistries: make(map[ProjectID]*projectRegistryData), } s.snapshots[handle] = sd } @@ -824,7 +908,7 @@ func (s *Session) handleGetSymbolAtPosition(ctx context.Context, params *GetSymb return nil, nil } - return setup.sd.newSymbolResponse(symbol), nil + return setup.newSymbolResponse(symbol), nil } // handleGetSymbolsAtPositions returns symbols at multiple positions in a file. @@ -849,7 +933,7 @@ func (s *Session) handleGetSymbolsAtPositions(ctx context.Context, params *GetSy } symbol := setup.checker.GetSymbolAtLocation(node) if symbol != nil { - results[i] = setup.sd.newSymbolResponse(symbol) + results[i] = setup.newSymbolResponse(symbol) } } @@ -877,7 +961,7 @@ func (s *Session) handleGetSymbolAtLocation(ctx context.Context, params *GetSymb return nil, nil } - return setup.sd.newSymbolResponse(symbol), nil + return setup.newSymbolResponse(symbol), nil } // handleGetSymbolsAtLocations returns symbols at multiple node locations. @@ -899,7 +983,7 @@ func (s *Session) handleGetSymbolsAtLocations(ctx context.Context, params *GetSy } symbol := setup.checker.GetSymbolAtLocation(node) if symbol != nil { - results[i] = setup.sd.newSymbolResponse(symbol) + results[i] = setup.newSymbolResponse(symbol) } } @@ -914,7 +998,7 @@ func (s *Session) handleGetTypeOfSymbol(ctx context.Context, params *GetTypeOfSy } defer setup.done() - symbol, err := setup.sd.resolveSymbolHandle(params.Symbol) + symbol, err := setup.resolveSymbolHandle(params.Symbol) if err != nil { return nil, err } @@ -927,7 +1011,7 @@ func (s *Session) handleGetTypeOfSymbol(ctx context.Context, params *GetTypeOfSy return nil, nil } - return setup.sd.newTypeResponse(t), nil + return setup.newTypeResponse(t), nil } // handleGetTypesOfSymbols returns the types of multiple symbols. @@ -940,7 +1024,7 @@ func (s *Session) handleGetTypesOfSymbols(ctx context.Context, params *GetTypesO results := make([]*TypeResponse, len(params.Symbols)) for i, symHandle := range params.Symbols { - symbol, err := setup.sd.resolveSymbolHandle(symHandle) + symbol, err := setup.resolveSymbolHandle(symHandle) if err != nil { return nil, err } @@ -949,7 +1033,7 @@ func (s *Session) handleGetTypesOfSymbols(ctx context.Context, params *GetTypesO } t := setup.checker.GetTypeOfSymbol(symbol) if t != nil { - results[i] = setup.sd.newTypeResponse(t) + results[i] = setup.newTypeResponse(t) } } @@ -964,7 +1048,7 @@ func (s *Session) handleGetDeclaredTypeOfSymbol(ctx context.Context, params *Get } defer setup.done() - symbol, err := setup.sd.resolveSymbolHandle(params.Symbol) + symbol, err := setup.resolveSymbolHandle(params.Symbol) if err != nil { return nil, err } @@ -977,7 +1061,7 @@ func (s *Session) handleGetDeclaredTypeOfSymbol(ctx context.Context, params *Get return nil, nil } - return setup.sd.newTypeResponse(t), nil + return setup.newTypeResponse(t), nil } // handleResolveName resolves a name to a symbol at a given location. @@ -1008,7 +1092,7 @@ func (s *Session) handleResolveName(ctx context.Context, params *ResolveNamePara return nil, nil } - return setup.sd.newSymbolResponse(symbol), nil + return setup.newSymbolResponse(symbol), nil } // handleGetSignaturesOfType returns the call or construct signatures of a type. @@ -1019,7 +1103,7 @@ func (s *Session) handleGetSignaturesOfType(ctx context.Context, params *GetSign } defer setup.done() - t, err := setup.sd.resolveTypeHandle(params.Type) + t, err := setup.resolveTypeHandle(params.Type) if err != nil { return nil, err } @@ -1027,7 +1111,7 @@ func (s *Session) handleGetSignaturesOfType(ctx context.Context, params *GetSign sigs := setup.checker.GetSignaturesOfType(t, checker.SignatureKind(params.Kind)) results := make([]*SignatureResponse, len(sigs)) for i, sig := range sigs { - results[i] = setup.sd.newSignatureResponse(sig) + results[i] = setup.newSignatureResponse(sig) } return results, nil @@ -1050,7 +1134,7 @@ func (s *Session) handleGetResolvedSignature(ctx context.Context, params *GetRes } sig := setup.checker.GetResolvedSignature(node) - return setup.sd.newSignatureResponse(sig), nil + return setup.newSignatureResponse(sig), nil } // handleGetTypeAtLocation returns the type at a node location. @@ -1074,7 +1158,7 @@ func (s *Session) handleGetTypeAtLocation(ctx context.Context, params *GetTypeAt return nil, nil } - return setup.sd.newTypeResponse(t), nil + return setup.newTypeResponse(t), nil } // handleGetTypeAtLocations returns types at multiple node locations. @@ -1096,7 +1180,7 @@ func (s *Session) handleGetTypeAtLocations(ctx context.Context, params *GetTypeA } t := setup.checker.GetTypeAtLocation(node) if t != nil { - results[i] = setup.sd.newTypeResponse(t) + results[i] = setup.newTypeResponse(t) } } @@ -1127,7 +1211,7 @@ func (s *Session) handleGetTypeAtPosition(ctx context.Context, params *GetTypeAt return nil, nil } - return setup.sd.newTypeResponse(t), nil + return setup.newTypeResponse(t), nil } // handleGetTypesAtPositions returns types at multiple positions in a file. @@ -1152,7 +1236,7 @@ func (s *Session) handleGetTypesAtPositions(ctx context.Context, params *GetType } t := setup.checker.GetTypeAtLocation(node) if t != nil { - results[i] = setup.sd.newTypeResponse(t) + results[i] = setup.newTypeResponse(t) } } @@ -1276,7 +1360,7 @@ func (s *Session) resolveTypePropertyOfType(params *GetTypePropertyParams, gette return nil, err } - t, err := sd.resolveTypeHandle(params.Type) + t, err := sd.resolveTypeHandle(params.Project, params.Type) if err != nil { return nil, err } @@ -1286,7 +1370,7 @@ func (s *Session) resolveTypePropertyOfType(params *GetTypePropertyParams, gette return nil, nil } - return sd.newTypeResponse(result), nil + return sd.newTypeResponse(params.Project, result), nil } // resolveTypeArrayPropertyOfType resolves a type property of an array of types and returns an array of type responses. @@ -1296,7 +1380,7 @@ func (s *Session) resolveTypeArrayPropertyOfType(params *GetTypePropertyParams, return nil, err } - t, err := sd.resolveTypeHandle(params.Type) + t, err := sd.resolveTypeHandle(params.Project, params.Type) if err != nil { return nil, err } @@ -1308,7 +1392,7 @@ func (s *Session) resolveTypeArrayPropertyOfType(params *GetTypePropertyParams, results := make([]*TypeResponse, len(types)) for i, sub := range types { - results[i] = sd.newTypeResponse(sub) + results[i] = sd.newTypeResponse(params.Project, sub) } return results, nil } @@ -1320,7 +1404,7 @@ func (s *Session) resolveSymbolPropertyOfType(params *GetTypePropertyParams, get return nil, err } - t, err := sd.resolveTypeHandle(params.Type) + t, err := sd.resolveTypeHandle(params.Project, params.Type) if err != nil { return nil, err } @@ -1382,7 +1466,7 @@ func (s *Session) resolveSymbolArrayPropertyOfSignature(params *GetSignatureProp return nil, err } - sig, err := sd.resolveSignatureHandle(params.Signature) + sig, err := sd.resolveSignatureHandle(params.Project, params.Signature) if err != nil { return nil, err } @@ -1406,7 +1490,7 @@ func (s *Session) resolveSymbolPropertyOfSignature(params *GetSignaturePropertyP return nil, err } - sig, err := sd.resolveSignatureHandle(params.Signature) + sig, err := sd.resolveSignatureHandle(params.Project, params.Signature) if err != nil { return nil, err } @@ -1424,7 +1508,7 @@ func (s *Session) resolveTypeArrayPropertyOfSignature(params *GetSignatureProper return nil, err } - sig, err := sd.resolveSignatureHandle(params.Signature) + sig, err := sd.resolveSignatureHandle(params.Project, params.Signature) if err != nil { return nil, err } @@ -1436,7 +1520,7 @@ func (s *Session) resolveTypeArrayPropertyOfSignature(params *GetSignatureProper results := make([]*TypeResponse, len(types)) for i, sub := range types { - results[i] = sd.newTypeResponse(sub) + results[i] = sd.newTypeResponse(params.Project, sub) } return results, nil } @@ -1447,7 +1531,7 @@ func (s *Session) resolveSignaturePropertyOfSignature(params *GetSignatureProper return nil, err } - sig, err := sd.resolveSignatureHandle(params.Signature) + sig, err := sd.resolveSignatureHandle(params.Project, params.Signature) if err != nil { return nil, err } @@ -1456,7 +1540,7 @@ func (s *Session) resolveSignaturePropertyOfSignature(params *GetSignatureProper if result == nil { return nil, nil } - return sd.newSignatureResponse(result), nil + return sd.newSignatureResponse(params.Project, result), nil } // handleGetContextualType returns the contextual type for a node. @@ -1480,7 +1564,7 @@ func (s *Session) handleGetContextualType(ctx context.Context, params *GetContex return nil, nil } - return setup.sd.newTypeResponse(t), nil + return setup.newTypeResponse(t), nil } // handleGetBaseTypeOfLiteralType returns the base type of a literal type (e.g. number for 42). @@ -1491,7 +1575,7 @@ func (s *Session) handleGetBaseTypeOfLiteralType(ctx context.Context, params *Ge } defer setup.done() - t, err := setup.sd.resolveTypeHandle(params.Type) + t, err := setup.resolveTypeHandle(params.Type) if err != nil { return nil, err } @@ -1501,7 +1585,7 @@ func (s *Session) handleGetBaseTypeOfLiteralType(ctx context.Context, params *Ge return nil, nil } - return setup.sd.newTypeResponse(result), nil + return setup.newTypeResponse(result), nil } // handleGetNonNullableType returns the type with null and undefined removed. @@ -1512,7 +1596,7 @@ func (s *Session) handleGetNonNullableType(ctx context.Context, params *GetNonNu } defer setup.done() - t, err := setup.sd.resolveTypeHandle(params.Type) + t, err := setup.resolveTypeHandle(params.Type) if err != nil { return nil, err } @@ -1522,7 +1606,7 @@ func (s *Session) handleGetNonNullableType(ctx context.Context, params *GetNonNu return nil, nil } - return setup.sd.newTypeResponse(result), nil + return setup.newTypeResponse(result), nil } // handleGetTypeFromTypeNode returns the type for a type node. @@ -1546,7 +1630,7 @@ func (s *Session) handleGetTypeFromTypeNode(ctx context.Context, params *GetType return nil, nil } - return setup.sd.newTypeResponse(t), nil + return setup.newTypeResponse(t), nil } // handleGetWidenedType returns the widened type. @@ -1557,7 +1641,7 @@ func (s *Session) handleGetWidenedType(ctx context.Context, params *GetWidenedTy } defer setup.done() - t, err := setup.sd.resolveTypeHandle(params.Type) + t, err := setup.resolveTypeHandle(params.Type) if err != nil { return nil, err } @@ -1567,7 +1651,7 @@ func (s *Session) handleGetWidenedType(ctx context.Context, params *GetWidenedTy return nil, nil } - return setup.sd.newTypeResponse(result), nil + return setup.newTypeResponse(result), nil } // handleGetParameterType returns the type of a parameter at a given index in a signature. @@ -1578,7 +1662,7 @@ func (s *Session) handleGetParameterType(ctx context.Context, params *GetParamet } defer setup.done() - sig, err := setup.sd.resolveSignatureHandle(params.Signature) + sig, err := setup.resolveSignatureHandle(params.Signature) if err != nil { return nil, err } @@ -1592,7 +1676,7 @@ func (s *Session) handleGetParameterType(ctx context.Context, params *GetParamet return nil, nil } - return setup.sd.newTypeResponse(t), nil + return setup.newTypeResponse(t), nil } // handleIsArrayLikeType returns whether a type is array-like. @@ -1603,7 +1687,7 @@ func (s *Session) handleIsArrayLikeType(ctx context.Context, params *IsArrayLike } defer setup.done() - t, err := setup.sd.resolveTypeHandle(params.Type) + t, err := setup.resolveTypeHandle(params.Type) if err != nil { return false, err } @@ -1619,11 +1703,11 @@ func (s *Session) handleIsTypeAssignableTo(ctx context.Context, params *IsTypeAs } defer setup.done() - source, err := setup.sd.resolveTypeHandle(params.Source) + source, err := setup.resolveTypeHandle(params.Source) if err != nil { return false, err } - target, err := setup.sd.resolveTypeHandle(params.Target) + target, err := setup.resolveTypeHandle(params.Target) if err != nil { return false, err } @@ -1652,7 +1736,7 @@ func (s *Session) handleGetShorthandAssignmentValueSymbol(ctx context.Context, p return nil, nil } - return setup.sd.newSymbolResponse(symbol), nil + return setup.newSymbolResponse(symbol), nil } // handleGetTypeOfSymbolAtLocation returns the narrowed type of a symbol at a specific location. @@ -1663,7 +1747,7 @@ func (s *Session) handleGetTypeOfSymbolAtLocation(ctx context.Context, params *G } defer setup.done() - symbol, err := setup.sd.resolveSymbolHandle(params.Symbol) + symbol, err := setup.resolveSymbolHandle(params.Symbol) if err != nil { return nil, err } @@ -1684,7 +1768,7 @@ func (s *Session) handleGetTypeOfSymbolAtLocation(ctx context.Context, params *G return nil, nil } - return setup.sd.newTypeResponse(t), nil + return setup.newTypeResponse(t), nil } // handleTypeToTypeNode converts a Type to a TypeNode AST and returns it as binary-encoded data. @@ -1695,7 +1779,7 @@ func (s *Session) handleTypeToTypeNode(ctx context.Context, params *TypeToTypeNo } defer setup.done() - t, err := setup.sd.resolveTypeHandle(params.Type) + t, err := setup.resolveTypeHandle(params.Type) if err != nil { return nil, err } @@ -1733,7 +1817,7 @@ func (s *Session) handleSignatureToSignatureDeclaration(ctx context.Context, par } defer setup.done() - sig, err := setup.sd.resolveSignatureHandle(params.Signature) + sig, err := setup.resolveSignatureHandle(params.Signature) if err != nil { return nil, err } @@ -1772,7 +1856,7 @@ func (s *Session) handleTypeToString(ctx context.Context, params *TypeToTypeNode } defer setup.done() - t, err := setup.sd.resolveTypeHandle(params.Type) + t, err := setup.resolveTypeHandle(params.Type) if err != nil { return nil, err } @@ -1824,7 +1908,7 @@ func (s *Session) handleGetIntrinsicType(ctx context.Context, params *GetIntrins return nil, nil } - return setup.sd.newTypeResponse(t), nil + return setup.newTypeResponse(t), nil } // handleIsContextSensitive returns whether a node is context-sensitive. @@ -1854,7 +1938,7 @@ func (s *Session) handleGetReturnTypeOfSignature(ctx context.Context, params *Ch } defer setup.done() - sig, err := setup.sd.resolveSignatureHandle(params.Signature) + sig, err := setup.resolveSignatureHandle(params.Signature) if err != nil { return nil, err } @@ -1864,7 +1948,7 @@ func (s *Session) handleGetReturnTypeOfSignature(ctx context.Context, params *Ch return nil, nil } - return setup.sd.newTypeResponse(t), nil + return setup.newTypeResponse(t), nil } // handleGetRestTypeOfSignature returns the rest type of a signature. @@ -1875,7 +1959,7 @@ func (s *Session) handleGetRestTypeOfSignature(ctx context.Context, params *Chec } defer setup.done() - sig, err := setup.sd.resolveSignatureHandle(params.Signature) + sig, err := setup.resolveSignatureHandle(params.Signature) if err != nil { return nil, err } @@ -1885,7 +1969,7 @@ func (s *Session) handleGetRestTypeOfSignature(ctx context.Context, params *Chec return nil, nil } - return setup.sd.newTypeResponse(t), nil + return setup.newTypeResponse(t), nil } // handleGetTypePredicateOfSignature returns the type predicate of a signature. @@ -1896,7 +1980,7 @@ func (s *Session) handleGetTypePredicateOfSignature(ctx context.Context, params } defer setup.done() - sig, err := setup.sd.resolveSignatureHandle(params.Signature) + sig, err := setup.resolveSignatureHandle(params.Signature) if err != nil { return nil, err } @@ -1912,7 +1996,7 @@ func (s *Session) handleGetTypePredicateOfSignature(ctx context.Context, params ParameterName: pred.ParameterName(), } if pred.Type() != nil { - resp.Type = setup.sd.newTypeResponse(pred.Type()) + resp.Type = setup.newTypeResponse(pred.Type()) } return resp, nil @@ -1926,7 +2010,7 @@ func (s *Session) handleGetBaseTypes(ctx context.Context, params *CheckerTypePar } defer setup.done() - t, err := setup.sd.resolveTypeHandle(params.Type) + t, err := setup.resolveTypeHandle(params.Type) if err != nil { return nil, err } @@ -1938,7 +2022,7 @@ func (s *Session) handleGetBaseTypes(ctx context.Context, params *CheckerTypePar results := make([]*TypeResponse, len(baseTypes)) for i, bt := range baseTypes { - results[i] = setup.sd.newTypeResponse(bt) + results[i] = setup.newTypeResponse(bt) } return results, nil @@ -1952,7 +2036,7 @@ func (s *Session) handleGetPropertiesOfType(ctx context.Context, params *Checker } defer setup.done() - t, err := setup.sd.resolveTypeHandle(params.Type) + t, err := setup.resolveTypeHandle(params.Type) if err != nil { return nil, err } @@ -1964,7 +2048,7 @@ func (s *Session) handleGetPropertiesOfType(ctx context.Context, params *Checker results := make([]*SymbolResponse, len(props)) for i, prop := range props { - results[i] = setup.sd.newSymbolResponse(prop) + results[i] = setup.newSymbolResponse(prop) } return results, nil @@ -1978,7 +2062,7 @@ func (s *Session) handleGetIndexInfosOfType(ctx context.Context, params *Checker } defer setup.done() - t, err := setup.sd.resolveTypeHandle(params.Type) + t, err := setup.resolveTypeHandle(params.Type) if err != nil { return nil, err } @@ -1991,8 +2075,8 @@ func (s *Session) handleGetIndexInfosOfType(ctx context.Context, params *Checker results := make([]*IndexInfoResponse, len(infos)) for i, info := range infos { results[i] = &IndexInfoResponse{ - KeyType: *setup.sd.newTypeResponse(info.KeyType()), - ValueType: *setup.sd.newTypeResponse(info.ValueType()), + KeyType: *setup.newTypeResponse(info.KeyType()), + ValueType: *setup.newTypeResponse(info.ValueType()), IsReadonly: info.IsReadonly(), } if info.Declaration() != nil { @@ -2011,7 +2095,7 @@ func (s *Session) handleGetConstraintOfTypeParameter(ctx context.Context, params } defer setup.done() - t, err := setup.sd.resolveTypeHandle(params.Type) + t, err := setup.resolveTypeHandle(params.Type) if err != nil { return nil, err } @@ -2021,7 +2105,7 @@ func (s *Session) handleGetConstraintOfTypeParameter(ctx context.Context, params return nil, nil } - return setup.sd.newTypeResponse(constraint), nil + return setup.newTypeResponse(constraint), nil } // handleGetTypeArguments returns the type arguments of a type reference. @@ -2032,7 +2116,7 @@ func (s *Session) handleGetTypeArguments(ctx context.Context, params *CheckerTyp } defer setup.done() - t, err := setup.sd.resolveTypeHandle(params.Type) + t, err := setup.resolveTypeHandle(params.Type) if err != nil { return nil, err } @@ -2044,7 +2128,7 @@ func (s *Session) handleGetTypeArguments(ctx context.Context, params *CheckerTyp results := make([]*TypeResponse, len(typeArgs)) for i, ta := range typeArgs { - results[i] = setup.sd.newTypeResponse(ta) + results[i] = setup.newTypeResponse(ta) } return results, nil @@ -2314,7 +2398,7 @@ func (s *Session) handleGetReferencesToSymbolInFile(ctx context.Context, params } defer setup.done() - symbol, err := setup.sd.resolveSymbolHandle(params.Symbol) + symbol, err := setup.resolveSymbolHandle(params.Symbol) if err != nil { return nil, err }