From 6e41510d082148d448e779c003cc44967bc5ca71 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Jul 2026 18:17:20 +0000 Subject: [PATCH 1/3] Initial plan From 6463d29f689db6b3eaae58a4ccf1671d9a065d57 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Jul 2026 18:58:06 +0000 Subject: [PATCH 2/3] Fix multiline JSX ts-ignore diagnostics Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/compiler/program.go | 46 +++++++-- ...xternalLibraryMultilineJsxTsIgnore.symbols | 96 +++++++++++++++++++ .../externalLibraryMultilineJsxTsIgnore.types | 92 ++++++++++++++++++ .../externalLibraryMultilineJsxTsIgnore.ts | 58 +++++++++++ 4 files changed, 282 insertions(+), 10 deletions(-) create mode 100644 testdata/baselines/reference/compiler/externalLibraryMultilineJsxTsIgnore.symbols create mode 100644 testdata/baselines/reference/compiler/externalLibraryMultilineJsxTsIgnore.types create mode 100644 testdata/tests/cases/compiler/externalLibraryMultilineJsxTsIgnore.ts diff --git a/internal/compiler/program.go b/internal/compiler/program.go index 74b2504d9ec..a4d9ee15e4e 100644 --- a/internal/compiler/program.go +++ b/internal/compiler/program.go @@ -1375,17 +1375,29 @@ func (p *Program) getDiagnosticsWithPrecedingDirectives(sourceFile *ast.SourceFi filtered := make([]*ast.Diagnostic, 0, len(diags)) for _, diagnostic := range diags { ignoreDiagnostic := false - for line := scanner.ComputeLineOfPosition(lineStarts, diagnostic.Pos()) - 1; line >= 0; line-- { - // If line contains a @ts-ignore or @ts-expect-error directive, ignore this diagnostic and change - // the directive kind to @ts-ignore to indicate it was used. - if directive, ok := directivesByLine[line]; ok { - ignoreDiagnostic = true - directive.Kind = ast.CommentDirectiveKindIgnore - directivesByLine[line] = directive - break + startLines := []int{scanner.ComputeLineOfPosition(lineStarts, diagnostic.Pos()) - 1} + if openingElement := getJsxOpeningElementForDiagnostic(sourceFile, diagnostic.Pos()); openingElement != nil { + openingElementLine := scanner.ComputeLineOfPosition(lineStarts, openingElement.TagName().Pos()) - 1 + if openingElementLine != startLines[0] { + startLines = append(startLines, openingElementLine) + } + } + for _, startLine := range startLines { + for line := startLine; line >= 0; line-- { + // If line contains a @ts-ignore or @ts-expect-error directive, ignore this diagnostic and change + // the directive kind to @ts-ignore to indicate it was used. + if directive, ok := directivesByLine[line]; ok { + ignoreDiagnostic = true + directive.Kind = ast.CommentDirectiveKindIgnore + directivesByLine[line] = directive + break + } + // Stop searching backwards when we encounter a line that isn't blank or a comment. + if !isCommentOrBlankLine(sourceFile.Text(), int(lineStarts[line])) { + break + } } - // Stop searching backwards when we encounter a line that isn't blank or a comment. - if !isCommentOrBlankLine(sourceFile.Text(), int(lineStarts[line])) { + if ignoreDiagnostic { break } } @@ -1396,6 +1408,20 @@ func (p *Program) getDiagnosticsWithPrecedingDirectives(sourceFile *ast.SourceFi return filtered, directivesByLine } +func getJsxOpeningElementForDiagnostic(sourceFile *ast.SourceFile, pos int) *ast.Node { + node := ast.GetNodeAtPosition(sourceFile, pos, false) + attribute := ast.FindAncestor(node, ast.IsJsxAttribute) + if attribute == nil || attribute.Parent == nil || !ast.IsJsxOpeningElement(attribute.Parent.Parent) { + return nil + } + openingElement := attribute.Parent.Parent + parent := openingElement.Parent + if parent != nil && ast.IsJsxElement(parent) && parent.AsJsxElement().OpeningElement == openingElement && len(ast.GetSemanticJsxChildren(parent.Children().Nodes)) != 0 { + return openingElement + } + return nil +} + func (p *Program) getDeclarationDiagnosticsForFile(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { if sourceFile.IsDeclarationFile { return []*ast.Diagnostic{} diff --git a/testdata/baselines/reference/compiler/externalLibraryMultilineJsxTsIgnore.symbols b/testdata/baselines/reference/compiler/externalLibraryMultilineJsxTsIgnore.symbols new file mode 100644 index 00000000000..c8570b582fa --- /dev/null +++ b/testdata/baselines/reference/compiler/externalLibraryMultilineJsxTsIgnore.symbols @@ -0,0 +1,96 @@ +//// [tests/cases/compiler/externalLibraryMultilineJsxTsIgnore.ts] //// + +=== /node_modules/pkg/exports.d.ts === +export { x, y } from "./src/ui"; +>x : Symbol(x, Decl(exports.d.ts, 0, 8)) +>y : Symbol(y, Decl(exports.d.ts, 0, 11)) + +=== /node_modules/pkg/src/ui.tsx === +declare global { +>global : Symbol(global, Decl(ui.tsx, 0, 0)) + + namespace JSX { +>JSX : Symbol(JSX, Decl(ui.tsx, 0, 16)) + + interface Element {} +>Element : Symbol(Element, Decl(ui.tsx, 1, 19)) + + interface IntrinsicAttributes {} +>IntrinsicAttributes : Symbol(IntrinsicAttributes, Decl(ui.tsx, 2, 28)) + + interface ElementChildrenAttribute { +>ElementChildrenAttribute : Symbol(ElementChildrenAttribute, Decl(ui.tsx, 3, 40)) + + children: {}; +>children : Symbol(ElementChildrenAttribute.children, Decl(ui.tsx, 4, 44)) + } + interface IntrinsicElements { +>IntrinsicElements : Symbol(IntrinsicElements, Decl(ui.tsx, 6, 9)) + + div: {}; +>div : Symbol(IntrinsicElements.div, Decl(ui.tsx, 7, 37)) + } + } +} + +declare const Component: (props: {}) => JSX.Element; +>Component : Symbol(Component, Decl(ui.tsx, 13, 13)) +>props : Symbol(props, Decl(ui.tsx, 13, 26)) +>JSX : Symbol(JSX, Decl(ui.tsx, 0, 16)) +>Element : Symbol(JSX.Element, Decl(ui.tsx, 1, 19)) + +export const x = ( +>x : Symbol(x, Decl(ui.tsx, 15, 12)) + + // @ts-ignore + Component : Symbol(Component, Decl(ui.tsx, 13, 13)) + + invalid="" +>invalid : Symbol(invalid, Decl(ui.tsx, 17, 14)) + + > +
+>div : Symbol(JSX.IntrinsicElements.div, Decl(ui.tsx, 7, 37)) + + +>Component : Symbol(Component, Decl(ui.tsx, 13, 13)) + +); + +export const y = ( +>y : Symbol(y, Decl(ui.tsx, 24, 12)) + +
+>div : Symbol(JSX.IntrinsicElements.div, Decl(ui.tsx, 7, 37)) + + {/* @ts-ignore */} + Component : Symbol(Component, Decl(ui.tsx, 13, 13)) + + invalid="" +>invalid : Symbol(invalid, Decl(ui.tsx, 27, 18)) + + > +
+>div : Symbol(JSX.IntrinsicElements.div, Decl(ui.tsx, 7, 37)) + + +>Component : Symbol(Component, Decl(ui.tsx, 13, 13)) + +
+>div : Symbol(JSX.IntrinsicElements.div, Decl(ui.tsx, 7, 37)) + +); + +=== /index.ts === +import { x, y } from "pkg"; +>x : Symbol(x, Decl(index.ts, 0, 8)) +>y : Symbol(y, Decl(index.ts, 0, 11)) + +x; +>x : Symbol(x, Decl(index.ts, 0, 8)) + +y; +>y : Symbol(y, Decl(index.ts, 0, 11)) + diff --git a/testdata/baselines/reference/compiler/externalLibraryMultilineJsxTsIgnore.types b/testdata/baselines/reference/compiler/externalLibraryMultilineJsxTsIgnore.types new file mode 100644 index 00000000000..e3ace77f016 --- /dev/null +++ b/testdata/baselines/reference/compiler/externalLibraryMultilineJsxTsIgnore.types @@ -0,0 +1,92 @@ +//// [tests/cases/compiler/externalLibraryMultilineJsxTsIgnore.ts] //// + +=== /node_modules/pkg/exports.d.ts === +export { x, y } from "./src/ui"; +>x : JSX.Element +>y : JSX.Element + +=== /node_modules/pkg/src/ui.tsx === +declare global { +>global : any + + namespace JSX { + interface Element {} + interface IntrinsicAttributes {} + interface ElementChildrenAttribute { + children: {}; +>children : {} + } + interface IntrinsicElements { + div: {}; +>div : {} + } + } +} + +declare const Component: (props: {}) => JSX.Element; +>Component : (props: {}) => JSX.Element +>props : {} +>JSX : any + +export const x = ( +>x : JSX.Element +>( // @ts-ignore
) : JSX.Element + + // @ts-ignore +
: JSX.Element +>Component : (props: {}) => JSX.Element + + invalid="" +>invalid : string + + > +
+>
: JSX.Element +>div : any + + +>Component : (props: {}) => JSX.Element + +); + +export const y = ( +>y : JSX.Element +>(
{/* @ts-ignore */}
) : JSX.Element + +
+>
{/* @ts-ignore */}
: JSX.Element +>div : any + + {/* @ts-ignore */} +
: JSX.Element +>Component : (props: {}) => JSX.Element + + invalid="" +>invalid : string + + > +
+>
: JSX.Element +>div : any + + +>Component : (props: {}) => JSX.Element + +
+>div : any + +); + +=== /index.ts === +import { x, y } from "pkg"; +>x : JSX.Element +>y : JSX.Element + +x; +>x : JSX.Element + +y; +>y : JSX.Element + diff --git a/testdata/tests/cases/compiler/externalLibraryMultilineJsxTsIgnore.ts b/testdata/tests/cases/compiler/externalLibraryMultilineJsxTsIgnore.ts new file mode 100644 index 00000000000..ada88098f8e --- /dev/null +++ b/testdata/tests/cases/compiler/externalLibraryMultilineJsxTsIgnore.ts @@ -0,0 +1,58 @@ +// @strict: true +// @skipLibCheck: true +// @jsx: preserve +// @module: esnext +// @moduleResolution: bundler +// @noEmit: true + +// @filename: /node_modules/pkg/package.json +{ + "name": "pkg", + "version": "1.0.0", + "types": "./exports.d.ts" +} + +// @filename: /node_modules/pkg/exports.d.ts +export { x, y } from "./src/ui"; + +// @filename: /node_modules/pkg/src/ui.tsx +declare global { + namespace JSX { + interface Element {} + interface IntrinsicAttributes {} + interface ElementChildrenAttribute { + children: {}; + } + interface IntrinsicElements { + div: {}; + } + } +} + +declare const Component: (props: {}) => JSX.Element; + +export const x = ( + // @ts-ignore + +
+ +); + +export const y = ( +
+ {/* @ts-ignore */} + +
+ +
+); + +// @filename: /index.ts +import { x, y } from "pkg"; + +x; +y; From 00ced4f547564b97ead79156e2001d3b12d35709 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 23 Jul 2026 19:29:56 +0000 Subject: [PATCH 3/3] Align JSX excess diagnostics with Strada Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com> --- internal/checker/relater.go | 14 ++++++++++- internal/compiler/program.go | 46 ++++++++---------------------------- 2 files changed, 23 insertions(+), 37 deletions(-) diff --git a/internal/checker/relater.go b/internal/checker/relater.go index e649e25b10b..efd9f52b3d0 100644 --- a/internal/checker/relater.go +++ b/internal/checker/relater.go @@ -2729,7 +2729,19 @@ func (r *Relater) hasExcessProperties(source *Type, target *Type, reportErrors b } checkTypes = reducedTarget.Distributed() } - for _, prop := range r.c.getPropertiesOfType(source) { + properties := r.c.getPropertiesOfType(source) + if isComparingJsxAttributes { + // Strada's insertion-ordered spread puts synthetic JSX children before explicit attributes. + for i, prop := range properties { + if i != 0 && prop.ValueDeclaration != nil && ast.IsPropertySignatureDeclaration(prop.ValueDeclaration) && ast.IsJsxAttributes(prop.ValueDeclaration.Parent) { + properties = slices.Clone(properties) + copy(properties[1:i+1], properties[:i]) + properties[0] = prop + break + } + } + } + for _, prop := range properties { if shouldCheckAsExcessProperty(prop, source.symbol) && !isIgnoredJsxProperty(source, prop) { if !r.c.isKnownProperty(reducedTarget, prop.Name, isComparingJsxAttributes) { if reportErrors { diff --git a/internal/compiler/program.go b/internal/compiler/program.go index a4d9ee15e4e..74b2504d9ec 100644 --- a/internal/compiler/program.go +++ b/internal/compiler/program.go @@ -1375,29 +1375,17 @@ func (p *Program) getDiagnosticsWithPrecedingDirectives(sourceFile *ast.SourceFi filtered := make([]*ast.Diagnostic, 0, len(diags)) for _, diagnostic := range diags { ignoreDiagnostic := false - startLines := []int{scanner.ComputeLineOfPosition(lineStarts, diagnostic.Pos()) - 1} - if openingElement := getJsxOpeningElementForDiagnostic(sourceFile, diagnostic.Pos()); openingElement != nil { - openingElementLine := scanner.ComputeLineOfPosition(lineStarts, openingElement.TagName().Pos()) - 1 - if openingElementLine != startLines[0] { - startLines = append(startLines, openingElementLine) - } - } - for _, startLine := range startLines { - for line := startLine; line >= 0; line-- { - // If line contains a @ts-ignore or @ts-expect-error directive, ignore this diagnostic and change - // the directive kind to @ts-ignore to indicate it was used. - if directive, ok := directivesByLine[line]; ok { - ignoreDiagnostic = true - directive.Kind = ast.CommentDirectiveKindIgnore - directivesByLine[line] = directive - break - } - // Stop searching backwards when we encounter a line that isn't blank or a comment. - if !isCommentOrBlankLine(sourceFile.Text(), int(lineStarts[line])) { - break - } + for line := scanner.ComputeLineOfPosition(lineStarts, diagnostic.Pos()) - 1; line >= 0; line-- { + // If line contains a @ts-ignore or @ts-expect-error directive, ignore this diagnostic and change + // the directive kind to @ts-ignore to indicate it was used. + if directive, ok := directivesByLine[line]; ok { + ignoreDiagnostic = true + directive.Kind = ast.CommentDirectiveKindIgnore + directivesByLine[line] = directive + break } - if ignoreDiagnostic { + // Stop searching backwards when we encounter a line that isn't blank or a comment. + if !isCommentOrBlankLine(sourceFile.Text(), int(lineStarts[line])) { break } } @@ -1408,20 +1396,6 @@ func (p *Program) getDiagnosticsWithPrecedingDirectives(sourceFile *ast.SourceFi return filtered, directivesByLine } -func getJsxOpeningElementForDiagnostic(sourceFile *ast.SourceFile, pos int) *ast.Node { - node := ast.GetNodeAtPosition(sourceFile, pos, false) - attribute := ast.FindAncestor(node, ast.IsJsxAttribute) - if attribute == nil || attribute.Parent == nil || !ast.IsJsxOpeningElement(attribute.Parent.Parent) { - return nil - } - openingElement := attribute.Parent.Parent - parent := openingElement.Parent - if parent != nil && ast.IsJsxElement(parent) && parent.AsJsxElement().OpeningElement == openingElement && len(ast.GetSemanticJsxChildren(parent.Children().Nodes)) != 0 { - return openingElement - } - return nil -} - func (p *Program) getDeclarationDiagnosticsForFile(ctx context.Context, sourceFile *ast.SourceFile) []*ast.Diagnostic { if sourceFile.IsDeclarationFile { return []*ast.Diagnostic{}