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
+>( ) : JSX.Element
+
+
+>
: 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{}