diff --git a/internal/treesitter/parser_test.go b/internal/treesitter/parser_test.go
index e436411..725edab 100644
--- a/internal/treesitter/parser_test.go
+++ b/internal/treesitter/parser_test.go
@@ -90,6 +90,17 @@ func TestParse(t *testing.T) {
}
})
+ t.Run("valid typescript source", func(t *testing.T) {
+ src := []byte(`interface User { id: number; name: string; } const u: User = { id: 1, name: "Alice" };`)
+ ast, err := Parse(src, "main.ts")
+ if err != nil {
+ t.Fatalf("Parse(): unexpected error: %v", err)
+ }
+ if ast == nil {
+ t.Error("Parse(): expected non-nil AST root")
+ }
+ })
+
t.Run("valid jsx source", func(t *testing.T) {
src := []byte(`const App = () =>
Hello
;`)
ast, err := Parse(src, "App.jsx")
@@ -101,6 +112,17 @@ func TestParse(t *testing.T) {
}
})
+ t.Run("valid tsx source", func(t *testing.T) {
+ src := []byte(`export const App = (): JSX.Element => Hello World
;`)
+ ast, err := Parse(src, "main.tsx")
+ if err != nil {
+ t.Fatalf("Parse(): unexpected error: %v", err)
+ }
+ if ast == nil {
+ t.Error("Parse(): expected non-nil AST root")
+ }
+ })
+
t.Run("unknown extension", func(t *testing.T) {
src := []byte(`some text`)
ast, err := Parse(src, "main.xyz")
diff --git a/internal/treesitter/rules_test.go b/internal/treesitter/rules_test.go
index 693a7ac..84a50e2 100644
--- a/internal/treesitter/rules_test.go
+++ b/internal/treesitter/rules_test.go
@@ -59,6 +59,21 @@ func TestRulesAliased(t *testing.T) {
"type", "interface", "namespace", "enum", "abstract", "readonly",
},
},
+ {
+ lang: "tsx",
+ operators: []string{
+ "+", "-", "*", "/", "%", "**",
+ "==", "!=", "===", "!==", "<", "<=", ">", ">=",
+ "&&", "||", "??",
+ "=", "+=", "-=", "*=", "/=", "%=", "**=",
+ "&&=", "||=", "??=",
+ "&", "|", "^", "<<", ">>", ">>>",
+ "!", "~", "++", "--",
+ "=>",
+ "type", "interface", "namespace", "enum", "abstract", "readonly",
+ "/>", "",
+ },
+ },
}
for _, tt := range tests {
diff --git a/internal/treesitter/tsx/rules.yml b/internal/treesitter/tsx/rules.yml
new file mode 100644
index 0000000..8100e7c
--- /dev/null
+++ b/internal/treesitter/tsx/rules.yml
@@ -0,0 +1,151 @@
+flattened:
+ - string
+ - template_string
+ - comment
+aliased:
+ # Comparison operators
+ "==": comparison_operator_literal
+ "!=": comparison_operator_literal
+ "===": comparison_operator_literal
+ "!==": comparison_operator_literal
+ "<": comparison_operator_literal
+ "<=": comparison_operator_literal
+ ">": comparison_operator_literal
+ ">=": comparison_operator_literal
+
+ # Logical operators
+ "&&": logical_operator_literal
+ "||": logical_operator_literal
+ "??": logical_operator_literal
+
+ # Assignment operators
+ "=": assignment_operator_literal
+ "+=": assignment_operator_literal
+ "-=": assignment_operator_literal
+ "*=": assignment_operator_literal
+ "/=": assignment_operator_literal
+ "%=": assignment_operator_literal
+ "**=": assignment_operator_literal
+ "&&=": assignment_operator_literal
+ "||=": assignment_operator_literal
+ "??=": assignment_operator_literal
+ "&=": assignment_operator_literal
+ "|=": assignment_operator_literal
+ "^=": assignment_operator_literal
+ "<<=": assignment_operator_literal
+ ">>=": assignment_operator_literal
+ ">>>=": assignment_operator_literal
+
+ # Arithmetic operators
+ "+": arithmetic_operator_literal
+ "-": arithmetic_operator_literal
+ "*": arithmetic_operator_literal
+ "/": arithmetic_operator_literal
+ "%": arithmetic_operator_literal
+ "**": arithmetic_operator_literal
+
+ # Bitwise operators
+ "&": bitwise_operator_literal
+ "|": bitwise_operator_literal
+ "^": bitwise_operator_literal
+ "<<": bitwise_operator_literal
+ ">>": bitwise_operator_literal
+ ">>>": bitwise_operator_literal
+
+ # Unary / Update operators
+ "!": unary_operator_literal
+ "~": bitwise_operator_literal
+ "++": update_operator_literal
+ "--": update_operator_literal
+ "=>": arrow
+
+ # Keywords
+ "if": "condition_declaration"
+ "else": "else"
+ "for": "for"
+ "while": "while"
+ "do": "do"
+ "switch": "switch"
+ "case": "case"
+ "default": "default"
+ "try": "try"
+ "catch": "catch"
+ "finally": "finally"
+ "throw": "throw"
+ "return": "return"
+ "break": "break"
+ "continue": "continue"
+ "function": "function"
+ "class": "class"
+ "const": "const"
+ "let": "let"
+ "var": "var"
+ "type": "type"
+ "interface": "interface"
+ "namespace": "namespace"
+ "enum": "enum"
+ "abstract": "abstract"
+ "readonly": "readonly"
+ "export": "export"
+ "import": "import"
+ "from": "from"
+ "as": "as"
+ "async": "async"
+ "await": "await"
+
+ # Punctuation
+ ".": dot
+ ":": colon
+ ",": comma
+ ";": semicolon
+ "?.": optional_chaining
+
+ # JSX element tags
+ "/>": jsx_self_closing_tag_slash_gt
+ "": jsx_closing_tag_lt_slash
+
+ignored:
+ - "\n"
+ - "escape_sequence"
+
+scaffolding:
+ - program
+ - statement_block
+ - formal_parameters
+ - arguments
+ - parenthesized_expression
+ - expression_statement
+ - function_declaration
+ - method_definition
+ - arrow_function
+ - class_declaration
+ - class_body
+ - interface_declaration
+ - interface_body
+ - enum_declaration
+ - enum_body
+ - type_alias_declaration
+ - if_statement
+ - for_statement
+ - for_in_statement
+ - switch_statement
+ - switch_body
+ - switch_case
+ - try_statement
+ - catch_clause
+ - import_statement
+ - export_statement
+ - object
+ - array
+ - type_arguments
+ - type_parameters
+ # JSX Scaffolding
+ - jsx_element
+ - jsx_self_closing_element
+ - jsx_opening_element
+ - jsx_closing_element
+ - jsx_fragment
+ - jsx_opening_fragment
+ - jsx_closing_fragment
+ - jsx_attribute
+ - jsx_expression
diff --git a/internal/treesitter/typescript/rules.yml b/internal/treesitter/typescript/rules.yml
new file mode 100644
index 0000000..3f886eb
--- /dev/null
+++ b/internal/treesitter/typescript/rules.yml
@@ -0,0 +1,137 @@
+flattened:
+ - string
+ - template_string
+ - comment
+aliased:
+ # Comparison operators
+ "==": comparison_operator_literal
+ "!=": comparison_operator_literal
+ "===": comparison_operator_literal
+ "!==": comparison_operator_literal
+ "<": comparison_operator_literal
+ "<=": comparison_operator_literal
+ ">": comparison_operator_literal
+ ">=": comparison_operator_literal
+
+ # Logical operators
+ "&&": logical_operator_literal
+ "||": logical_operator_literal
+ "??": logical_operator_literal
+
+ # Assignment operators
+ "=": assignment_operator_literal
+ "+=": assignment_operator_literal
+ "-=": assignment_operator_literal
+ "*=": assignment_operator_literal
+ "/=": assignment_operator_literal
+ "%=": assignment_operator_literal
+ "**=": assignment_operator_literal
+ "&&=": assignment_operator_literal
+ "||=": assignment_operator_literal
+ "??=": assignment_operator_literal
+ "&=": assignment_operator_literal
+ "|=": assignment_operator_literal
+ "^=": assignment_operator_literal
+ "<<=": assignment_operator_literal
+ ">>=": assignment_operator_literal
+ ">>>=": assignment_operator_literal
+
+ # Arithmetic operators
+ "+": arithmetic_operator_literal
+ "-": arithmetic_operator_literal
+ "*": arithmetic_operator_literal
+ "/": arithmetic_operator_literal
+ "%": arithmetic_operator_literal
+ "**": arithmetic_operator_literal
+
+ # Bitwise operators
+ "&": bitwise_operator_literal
+ "|": bitwise_operator_literal
+ "^": bitwise_operator_literal
+ "<<": bitwise_operator_literal
+ ">>": bitwise_operator_literal
+ ">>>": bitwise_operator_literal
+
+ # Unary / Update operators
+ "!": unary_operator_literal
+ "~": bitwise_operator_literal
+ "++": update_operator_literal
+ "--": update_operator_literal
+ "=>": arrow
+
+ # Keywords
+ "if": "condition_declaration"
+ "else": "else"
+ "for": "for"
+ "while": "while"
+ "do": "do"
+ "switch": "switch"
+ "case": "case"
+ "default": "default"
+ "try": "try"
+ "catch": "catch"
+ "finally": "finally"
+ "throw": "throw"
+ "return": "return"
+ "break": "break"
+ "continue": "continue"
+ "function": "function"
+ "class": "class"
+ "const": "const"
+ "let": "let"
+ "var": "var"
+ "type": "type"
+ "interface": "interface"
+ "namespace": "namespace"
+ "enum": "enum"
+ "abstract": "abstract"
+ "readonly": "readonly"
+ "export": "export"
+ "import": "import"
+ "from": "from"
+ "as": "as"
+ "async": "async"
+ "await": "await"
+
+ # Punctuation
+ ".": dot
+ ":": colon
+ ",": comma
+ ";": semicolon
+ "?.": optional_chaining
+
+ignored:
+ - "\n"
+ - "escape_sequence"
+
+scaffolding:
+ - program
+ - statement_block
+ - formal_parameters
+ - arguments
+ - parenthesized_expression
+ - expression_statement
+ - function_declaration
+ - method_definition
+ - arrow_function
+ - class_declaration
+ - class_body
+ - interface_declaration
+ - interface_body
+ - enum_declaration
+ - enum_body
+ - type_alias_declaration
+ - if_statement
+ - for_statement
+ - for_in_statement
+ - switch_statement
+ - switch_body
+ - switch_case
+ - try_statement
+ - catch_clause
+ - import_statement
+ - export_statement
+ - object
+ - array
+ - type_arguments
+ - type_parameters
diff --git a/tests/testdata/ts_react_router_filter_removed/expected.json b/tests/testdata/ts_react_router_filter_removed/expected.json
new file mode 100644
index 0000000..ced44f3
--- /dev/null
+++ b/tests/testdata/ts_react_router_filter_removed/expected.json
@@ -0,0 +1,1220 @@
+{
+ "version": "v1",
+ "actions": [
+ {
+ "action": "insert",
+ "node": {
+ "tree": "after",
+ "path": [
+ 17,
+ 5,
+ 4,
+ 1,
+ 3,
+ 0,
+ 0,
+ 1,
+ 0,
+ 2,
+ 0
+ ],
+ "type": "binary_expression",
+ "start_byte": 5560,
+ "end_byte": 5582
+ },
+ "parent": {
+ "tree": "after",
+ "path": [
+ 17,
+ 5,
+ 4,
+ 1,
+ 3,
+ 0,
+ 0,
+ 1,
+ 0,
+ 2
+ ],
+ "type": "binary_expression",
+ "start_byte": 5560,
+ "end_byte": 5608
+ },
+ "position": 0,
+ "subtree": true
+ },
+ {
+ "action": "insert",
+ "node": {
+ "tree": "after",
+ "path": [
+ 17,
+ 5,
+ 4,
+ 1,
+ 3,
+ 0,
+ 0,
+ 1,
+ 0,
+ 2,
+ 1
+ ],
+ "type": "logical_operator_literal",
+ "label": "\u0026\u0026",
+ "start_byte": 5583,
+ "end_byte": 5585
+ },
+ "parent": {
+ "tree": "after",
+ "path": [
+ 17,
+ 5,
+ 4,
+ 1,
+ 3,
+ 0,
+ 0,
+ 1,
+ 0,
+ 2
+ ],
+ "type": "binary_expression",
+ "start_byte": 5560,
+ "end_byte": 5608
+ },
+ "position": 1
+ },
+ {
+ "action": "move",
+ "node": {
+ "tree": "before",
+ "path": [
+ 17,
+ 5,
+ 4,
+ 1,
+ 3,
+ 0,
+ 0,
+ 1,
+ 0,
+ 2
+ ],
+ "type": "call_expression",
+ "start_byte": 5560,
+ "end_byte": 5582
+ },
+ "parent": {
+ "tree": "after",
+ "path": [
+ 17,
+ 5,
+ 4,
+ 1,
+ 3,
+ 0,
+ 0,
+ 1,
+ 0,
+ 2
+ ],
+ "type": "binary_expression",
+ "start_byte": 5560,
+ "end_byte": 5608
+ },
+ "position": 2,
+ "old_parent": {
+ "tree": "before",
+ "path": [
+ 17,
+ 5,
+ 4,
+ 1,
+ 3,
+ 0,
+ 0,
+ 1,
+ 0
+ ],
+ "type": "arrow_function",
+ "start_byte": 5553,
+ "end_byte": 5582
+ },
+ "old_position": 2,
+ "subtree": true,
+ "dest_start_byte": 5586,
+ "dest_end_byte": 5608
+ }
+ ],
+ "line_alignment": [
+ {
+ "left_line": 0,
+ "right_line": 0
+ },
+ {
+ "left_line": 1,
+ "right_line": 1
+ },
+ {
+ "left_line": 2,
+ "right_line": 2
+ },
+ {
+ "left_line": 3,
+ "right_line": 3
+ },
+ {
+ "left_line": 4,
+ "right_line": 4
+ },
+ {
+ "left_line": 5,
+ "right_line": 5
+ },
+ {
+ "left_line": 6,
+ "right_line": 6
+ },
+ {
+ "left_line": 7,
+ "right_line": 7
+ },
+ {
+ "left_line": 8,
+ "right_line": 8
+ },
+ {
+ "left_line": 9,
+ "right_line": 9
+ },
+ {
+ "left_line": 10,
+ "right_line": 10
+ },
+ {
+ "left_line": 11,
+ "right_line": 11
+ },
+ {
+ "left_line": 12,
+ "right_line": 12
+ },
+ {
+ "left_line": 13,
+ "right_line": 13
+ },
+ {
+ "left_line": 14,
+ "right_line": 14
+ },
+ {
+ "left_line": 15,
+ "right_line": 15
+ },
+ {
+ "left_line": 16,
+ "right_line": 16
+ },
+ {
+ "left_line": 17,
+ "right_line": 17
+ },
+ {
+ "left_line": 18,
+ "right_line": 18
+ },
+ {
+ "left_line": 19,
+ "right_line": 19
+ },
+ {
+ "left_line": 20,
+ "right_line": 20
+ },
+ {
+ "left_line": 21,
+ "right_line": 21
+ },
+ {
+ "left_line": 22,
+ "right_line": 22
+ },
+ {
+ "left_line": 23,
+ "right_line": 23
+ },
+ {
+ "left_line": 24,
+ "right_line": 24
+ },
+ {
+ "left_line": 25,
+ "right_line": 25
+ },
+ {
+ "left_line": 26,
+ "right_line": 26
+ },
+ {
+ "left_line": 27,
+ "right_line": 27
+ },
+ {
+ "left_line": 28,
+ "right_line": 28
+ },
+ {
+ "left_line": 29,
+ "right_line": 29
+ },
+ {
+ "left_line": 30,
+ "right_line": 30
+ },
+ {
+ "left_line": 31,
+ "right_line": 31
+ },
+ {
+ "left_line": 32,
+ "right_line": 32
+ },
+ {
+ "left_line": 33,
+ "right_line": 33
+ },
+ {
+ "left_line": 34,
+ "right_line": 34
+ },
+ {
+ "left_line": 35,
+ "right_line": 35
+ },
+ {
+ "left_line": 36,
+ "right_line": 36
+ },
+ {
+ "left_line": 37,
+ "right_line": 37
+ },
+ {
+ "left_line": 38,
+ "right_line": 38
+ },
+ {
+ "left_line": 39,
+ "right_line": 39
+ },
+ {
+ "left_line": 40,
+ "right_line": 40
+ },
+ {
+ "left_line": 41,
+ "right_line": 41
+ },
+ {
+ "left_line": 42,
+ "right_line": 42
+ },
+ {
+ "left_line": 43,
+ "right_line": 43
+ },
+ {
+ "left_line": 44,
+ "right_line": 44
+ },
+ {
+ "left_line": 45,
+ "right_line": 45
+ },
+ {
+ "left_line": 46,
+ "right_line": 46
+ },
+ {
+ "left_line": 47,
+ "right_line": 47
+ },
+ {
+ "left_line": 48,
+ "right_line": 48
+ },
+ {
+ "left_line": 49,
+ "right_line": 49
+ },
+ {
+ "left_line": 50,
+ "right_line": 50
+ },
+ {
+ "left_line": 51,
+ "right_line": 51
+ },
+ {
+ "left_line": 52,
+ "right_line": 52
+ },
+ {
+ "left_line": 53,
+ "right_line": 53
+ },
+ {
+ "left_line": 54,
+ "right_line": 54
+ },
+ {
+ "left_line": 55,
+ "right_line": 55
+ },
+ {
+ "left_line": 56,
+ "right_line": 56
+ },
+ {
+ "left_line": 57,
+ "right_line": 57
+ },
+ {
+ "left_line": 58,
+ "right_line": 58
+ },
+ {
+ "left_line": 59,
+ "right_line": 59
+ },
+ {
+ "left_line": 60,
+ "right_line": 60
+ },
+ {
+ "left_line": 61,
+ "right_line": 61
+ },
+ {
+ "left_line": 62,
+ "right_line": 62
+ },
+ {
+ "left_line": 63,
+ "right_line": 63
+ },
+ {
+ "left_line": 64,
+ "right_line": 64
+ },
+ {
+ "left_line": 65,
+ "right_line": 65
+ },
+ {
+ "left_line": 66,
+ "right_line": 66
+ },
+ {
+ "left_line": 67,
+ "right_line": 67
+ },
+ {
+ "left_line": 68,
+ "right_line": 68
+ },
+ {
+ "left_line": 69,
+ "right_line": 69
+ },
+ {
+ "left_line": 70,
+ "right_line": 70
+ },
+ {
+ "left_line": 71,
+ "right_line": 71
+ },
+ {
+ "left_line": 72,
+ "right_line": 72
+ },
+ {
+ "left_line": 73,
+ "right_line": 73
+ },
+ {
+ "left_line": 74,
+ "right_line": 74
+ },
+ {
+ "left_line": 75,
+ "right_line": 75
+ },
+ {
+ "left_line": 76,
+ "right_line": 76
+ },
+ {
+ "left_line": 77,
+ "right_line": 77
+ },
+ {
+ "left_line": 78,
+ "right_line": 78
+ },
+ {
+ "left_line": 79,
+ "right_line": 79
+ },
+ {
+ "left_line": 80,
+ "right_line": 80
+ },
+ {
+ "left_line": 81,
+ "right_line": 81
+ },
+ {
+ "left_line": 82,
+ "right_line": 82
+ },
+ {
+ "left_line": 83,
+ "right_line": 83
+ },
+ {
+ "left_line": 84,
+ "right_line": 84
+ },
+ {
+ "left_line": 85,
+ "right_line": 85
+ },
+ {
+ "left_line": 86,
+ "right_line": 86
+ },
+ {
+ "left_line": 87,
+ "right_line": 87
+ },
+ {
+ "left_line": 88,
+ "right_line": 88
+ },
+ {
+ "left_line": 89,
+ "right_line": 89
+ },
+ {
+ "left_line": 90,
+ "right_line": 90
+ },
+ {
+ "left_line": 91,
+ "right_line": 91
+ },
+ {
+ "left_line": 92,
+ "right_line": 92
+ },
+ {
+ "left_line": 93,
+ "right_line": 93
+ },
+ {
+ "left_line": 94,
+ "right_line": 94
+ },
+ {
+ "left_line": 95,
+ "right_line": 95
+ },
+ {
+ "left_line": 96,
+ "right_line": 96
+ },
+ {
+ "left_line": 97,
+ "right_line": 97
+ },
+ {
+ "left_line": 98,
+ "right_line": 98
+ },
+ {
+ "left_line": 99,
+ "right_line": 99
+ },
+ {
+ "left_line": 100,
+ "right_line": 100
+ },
+ {
+ "left_line": 101,
+ "right_line": 101
+ },
+ {
+ "left_line": 102,
+ "right_line": 102
+ },
+ {
+ "left_line": 103,
+ "right_line": 103
+ },
+ {
+ "left_line": 104,
+ "right_line": 104
+ },
+ {
+ "left_line": 105,
+ "right_line": 105
+ },
+ {
+ "left_line": 106,
+ "right_line": 106
+ },
+ {
+ "left_line": 107,
+ "right_line": 107
+ },
+ {
+ "left_line": 108,
+ "right_line": 108
+ },
+ {
+ "left_line": 109,
+ "right_line": 109
+ },
+ {
+ "left_line": 110,
+ "right_line": 110
+ },
+ {
+ "left_line": 111,
+ "right_line": 111
+ },
+ {
+ "left_line": 112,
+ "right_line": 112
+ },
+ {
+ "left_line": 113,
+ "right_line": 113
+ },
+ {
+ "left_line": 114,
+ "right_line": 114
+ },
+ {
+ "left_line": 115,
+ "right_line": 115
+ },
+ {
+ "left_line": 116,
+ "right_line": 116
+ },
+ {
+ "left_line": 117,
+ "right_line": 117
+ },
+ {
+ "left_line": 118,
+ "right_line": 118
+ },
+ {
+ "left_line": 119,
+ "right_line": 119
+ },
+ {
+ "left_line": 120,
+ "right_line": 120
+ },
+ {
+ "left_line": 121,
+ "right_line": 121
+ },
+ {
+ "left_line": 122,
+ "right_line": 122
+ },
+ {
+ "left_line": 123,
+ "right_line": 123
+ },
+ {
+ "left_line": 124,
+ "right_line": 124
+ },
+ {
+ "left_line": 125,
+ "right_line": 125
+ },
+ {
+ "left_line": 126,
+ "right_line": 126
+ },
+ {
+ "left_line": 127,
+ "right_line": 127
+ },
+ {
+ "left_line": 128,
+ "right_line": 128
+ },
+ {
+ "left_line": 129,
+ "right_line": 129
+ },
+ {
+ "left_line": 130,
+ "right_line": 130
+ },
+ {
+ "left_line": 131,
+ "right_line": 131
+ },
+ {
+ "left_line": 132,
+ "right_line": 132
+ },
+ {
+ "left_line": 133,
+ "right_line": 133
+ },
+ {
+ "left_line": 134,
+ "right_line": 134
+ },
+ {
+ "left_line": 135,
+ "right_line": 135
+ },
+ {
+ "left_line": 136,
+ "right_line": 136
+ },
+ {
+ "left_line": 137,
+ "right_line": 137
+ },
+ {
+ "left_line": 138,
+ "right_line": 138
+ },
+ {
+ "left_line": 139,
+ "right_line": 139
+ },
+ {
+ "left_line": 140,
+ "right_line": 140
+ },
+ {
+ "left_line": 141,
+ "right_line": 141
+ },
+ {
+ "left_line": 142,
+ "right_line": 142
+ },
+ {
+ "left_line": 143,
+ "right_line": 143
+ },
+ {
+ "left_line": 144,
+ "right_line": 144
+ },
+ {
+ "left_line": 145,
+ "right_line": 145
+ },
+ {
+ "left_line": 146,
+ "right_line": 146
+ },
+ {
+ "left_line": 147,
+ "right_line": 147
+ },
+ {
+ "left_line": 148,
+ "right_line": 148
+ },
+ {
+ "left_line": 149,
+ "right_line": 149
+ },
+ {
+ "left_line": 150,
+ "right_line": 150
+ },
+ {
+ "left_line": 151,
+ "right_line": 151
+ },
+ {
+ "left_line": 152,
+ "right_line": 152
+ },
+ {
+ "left_line": 153,
+ "right_line": 153
+ },
+ {
+ "left_line": 154,
+ "right_line": 154
+ },
+ {
+ "left_line": 155,
+ "right_line": 155
+ },
+ {
+ "left_line": 156,
+ "right_line": 156
+ },
+ {
+ "left_line": 157,
+ "right_line": 157
+ },
+ {
+ "left_line": 158,
+ "right_line": 158
+ },
+ {
+ "left_line": 159,
+ "right_line": 159
+ },
+ {
+ "left_line": 160,
+ "right_line": 160
+ },
+ {
+ "left_line": 161,
+ "right_line": 161
+ },
+ {
+ "left_line": 162,
+ "right_line": 162
+ },
+ {
+ "left_line": 163,
+ "right_line": 163
+ },
+ {
+ "left_line": 164,
+ "right_line": 164
+ },
+ {
+ "left_line": 165,
+ "right_line": 165
+ },
+ {
+ "left_line": 166,
+ "right_line": 166
+ },
+ {
+ "left_line": 167,
+ "right_line": 167
+ },
+ {
+ "left_line": 168,
+ "right_line": 168
+ },
+ {
+ "left_line": 169,
+ "right_line": 169
+ },
+ {
+ "left_line": 170,
+ "right_line": 170
+ },
+ {
+ "left_line": 171,
+ "right_line": 171
+ },
+ {
+ "left_line": 172,
+ "right_line": 172
+ },
+ {
+ "left_line": 173,
+ "right_line": 173
+ },
+ {
+ "left_line": 174,
+ "right_line": 174
+ },
+ {
+ "left_line": 175,
+ "right_line": 175
+ },
+ {
+ "left_line": 176,
+ "right_line": 176
+ },
+ {
+ "left_line": 177,
+ "right_line": 177
+ },
+ {
+ "left_line": 178,
+ "right_line": 178
+ },
+ {
+ "left_line": 179,
+ "right_line": 179
+ },
+ {
+ "left_line": 180,
+ "right_line": 180
+ },
+ {
+ "left_line": 181,
+ "right_line": 181
+ },
+ {
+ "left_line": 182,
+ "right_line": 182
+ },
+ {
+ "left_line": 183,
+ "right_line": 183
+ },
+ {
+ "left_line": 184,
+ "right_line": 184
+ },
+ {
+ "left_line": 185,
+ "right_line": 185
+ },
+ {
+ "left_line": 186,
+ "right_line": 186
+ },
+ {
+ "left_line": 187,
+ "right_line": 187
+ },
+ {
+ "left_line": 188,
+ "right_line": 188
+ },
+ {
+ "left_line": 189,
+ "right_line": 189
+ },
+ {
+ "left_line": 190,
+ "right_line": 190
+ },
+ {
+ "left_line": 191,
+ "right_line": 191
+ },
+ {
+ "left_line": 192,
+ "right_line": 192
+ },
+ {
+ "left_line": 193,
+ "right_line": 193
+ },
+ {
+ "left_line": 194,
+ "right_line": 194
+ },
+ {
+ "left_line": 195,
+ "right_line": 195
+ },
+ {
+ "left_line": 196,
+ "right_line": 196
+ },
+ {
+ "left_line": 197,
+ "right_line": 197
+ },
+ {
+ "left_line": 198,
+ "right_line": 198
+ },
+ {
+ "left_line": 199,
+ "right_line": 199
+ },
+ {
+ "left_line": 200,
+ "right_line": 200
+ },
+ {
+ "left_line": 201,
+ "right_line": 201
+ },
+ {
+ "left_line": 202,
+ "right_line": 202
+ },
+ {
+ "left_line": 203,
+ "right_line": 203
+ },
+ {
+ "left_line": 204,
+ "right_line": 204
+ },
+ {
+ "left_line": 205,
+ "right_line": 205
+ },
+ {
+ "left_line": 206,
+ "right_line": 206
+ },
+ {
+ "left_line": 207,
+ "right_line": 207
+ },
+ {
+ "left_line": 208,
+ "right_line": 208
+ },
+ {
+ "left_line": 209,
+ "right_line": 209
+ },
+ {
+ "left_line": 210,
+ "right_line": 210
+ },
+ {
+ "left_line": 211,
+ "right_line": 211
+ },
+ {
+ "left_line": 212,
+ "right_line": 212
+ },
+ {
+ "left_line": 213,
+ "right_line": 213
+ },
+ {
+ "left_line": 214,
+ "right_line": 214
+ },
+ {
+ "left_line": 215,
+ "right_line": 215
+ },
+ {
+ "left_line": 216,
+ "right_line": 216
+ },
+ {
+ "left_line": 217,
+ "right_line": 217
+ },
+ {
+ "left_line": 218,
+ "right_line": 218
+ },
+ {
+ "left_line": 219,
+ "right_line": 219
+ },
+ {
+ "left_line": 220,
+ "right_line": 220
+ },
+ {
+ "left_line": 221,
+ "right_line": 221
+ },
+ {
+ "left_line": 222,
+ "right_line": 222
+ },
+ {
+ "left_line": 223,
+ "right_line": 223
+ },
+ {
+ "left_line": 224,
+ "right_line": 224
+ },
+ {
+ "left_line": 225,
+ "right_line": 225
+ },
+ {
+ "left_line": 226,
+ "right_line": 226
+ },
+ {
+ "left_line": 227,
+ "right_line": 227
+ },
+ {
+ "left_line": 228,
+ "right_line": 228
+ },
+ {
+ "left_line": 229,
+ "right_line": 229
+ },
+ {
+ "left_line": 230,
+ "right_line": 230
+ },
+ {
+ "left_line": 231,
+ "right_line": 231
+ },
+ {
+ "left_line": 232,
+ "right_line": 232
+ },
+ {
+ "left_line": 233,
+ "right_line": 233
+ },
+ {
+ "left_line": 234,
+ "right_line": 234
+ },
+ {
+ "left_line": 235,
+ "right_line": 235
+ },
+ {
+ "left_line": 236,
+ "right_line": 236
+ },
+ {
+ "left_line": 237,
+ "right_line": 237
+ },
+ {
+ "left_line": 238,
+ "right_line": 238
+ },
+ {
+ "left_line": 239,
+ "right_line": 239
+ },
+ {
+ "left_line": 240,
+ "right_line": 240
+ },
+ {
+ "left_line": 241,
+ "right_line": 241
+ },
+ {
+ "left_line": 242,
+ "right_line": 242
+ },
+ {
+ "left_line": 243,
+ "right_line": 243
+ },
+ {
+ "left_line": 244,
+ "right_line": 244
+ },
+ {
+ "left_line": 245,
+ "right_line": 245
+ },
+ {
+ "left_line": 246,
+ "right_line": 246
+ },
+ {
+ "left_line": 247,
+ "right_line": 247
+ },
+ {
+ "left_line": 248,
+ "right_line": 248
+ },
+ {
+ "left_line": 249,
+ "right_line": 249
+ },
+ {
+ "left_line": 250,
+ "right_line": 250
+ },
+ {
+ "left_line": 251,
+ "right_line": 251
+ },
+ {
+ "left_line": 252,
+ "right_line": 252
+ },
+ {
+ "left_line": 253,
+ "right_line": 253
+ },
+ {
+ "left_line": 254,
+ "right_line": 254
+ },
+ {
+ "left_line": 255,
+ "right_line": 255
+ },
+ {
+ "left_line": 256,
+ "right_line": 256
+ },
+ {
+ "left_line": 257,
+ "right_line": 257
+ },
+ {
+ "left_line": 258,
+ "right_line": 258
+ },
+ {
+ "left_line": 259,
+ "right_line": 259
+ },
+ {
+ "left_line": 260,
+ "right_line": 260
+ },
+ {
+ "left_line": 261,
+ "right_line": 261
+ },
+ {
+ "left_line": 262,
+ "right_line": 262
+ },
+ {
+ "left_line": 263,
+ "right_line": 263
+ },
+ {
+ "left_line": 264,
+ "right_line": 264
+ },
+ {
+ "left_line": 265,
+ "right_line": 265
+ },
+ {
+ "left_line": 266,
+ "right_line": 266
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/testdata/ts_react_router_filter_removed/metadata.json b/tests/testdata/ts_react_router_filter_removed/metadata.json
new file mode 100644
index 0000000..a9a87de
--- /dev/null
+++ b/tests/testdata/ts_react_router_filter_removed/metadata.json
@@ -0,0 +1,12 @@
+{
+ "repo": "https://github.com/remix-run/react-router",
+ "parent": "db526f08df9e7678e1265269f782b3dec62454f1",
+ "commit": "966103936377bd9e51496f077496166ddc12627c",
+ "message": "Filter removed change files from pr check workflow",
+ "author": "Matt Brophy",
+ "date": "2026-06-29T11:36:15-04:00",
+ "language": "typescript",
+ "files_changed": 1,
+ "insertions": 0,
+ "deletions": 0
+}
\ No newline at end of file
diff --git a/tests/testdata/ts_react_router_filter_removed/new.ts b/tests/testdata/ts_react_router_filter_removed/new.ts
new file mode 100644
index 0000000..0da0c9d
--- /dev/null
+++ b/tests/testdata/ts_react_router_filter_removed/new.ts
@@ -0,0 +1,266 @@
+/**
+ * Runs a set of checks against a PR and applies the resulting actions.
+ *
+ * Two-phase to avoid running with write permissions on PRs from forks.
+ * See https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
+ *
+ * check Inspects the PR via the GitHub API and writes a list of
+ * "actions" to pr-checks-result.json. Safe to run with a
+ * read-only token (Workflow A: 🔍 Check PR).
+ *
+ * actions Reads pr-checks-result.json and applies each action. Runs
+ * in Workflow B (PR (Actions)) under `workflow_run` with
+ * write permissions but never executes any PR code.
+ *
+ * Usage:
+ * node scripts/pr.ts check
+ * node scripts/pr.ts actions
+ *
+ * Environment (check):
+ * GITHUB_TOKEN - Required (read-only PR scope is enough).
+ * PR_NUMBER - Required. github.event.pull_request.number
+ * PR_BASE - Required. github.event.pull_request.base.ref
+ * EVENT_ACTION - Required. github.event.action (opened|synchronize|reopened|labeled)
+ * LABEL_NAME - Optional. github.event.label.name (set when EVENT_ACTION=labeled)
+ *
+ * Environment (actions):
+ * GITHUB_TOKEN - Required (issues:write + pull-requests:write).
+ */
+import * as fs from "node:fs";
+import * as util from "node:util";
+
+import {
+ closePr,
+ createPrComment,
+ getPrComments,
+ getPrFiles,
+ removePrLabel,
+ updatePrComment,
+} from "./utils/github.ts";
+
+type Action =
+ | { type: "upsert-sticky-comment"; marker: string; body: string }
+ | { type: "create-comment"; body: string }
+ | { type: "remove-label"; label: string }
+ | { type: "close-pr" };
+
+type CheckContext = {
+ prNumber: number;
+ baseBranch: string;
+ eventAction: string;
+ labelName: string;
+};
+
+type Check = (ctx: CheckContext) => Promise;
+
+const CHANGE_FILE_MARKER = "";
+
+type ChangeFileSummary = {
+ type: string;
+ firstLine: string;
+};
+
+const CHANGE_FILE_FOUND_COMMENT = `${CHANGE_FILE_MARKER}
+### ✅ Change File Found
+
+One or more [change files](https://github.com/remix-run/react-router/blob/main/docs/community/contributing.md#change-files) found.
+`;
+
+const CHANGE_FILE_MISSING_COMMENT = `${CHANGE_FILE_MARKER}
+### ⚠️ No Change File Found
+
+This PR doesn't include a [change file](https://github.com/remix-run/react-router/blob/main/docs/community/contributing.md#change-files) which is used for automated release notes.
+If your change affects users, please add one (or more) change files and commit the generated file(s).
+
+\`\`\`sh
+pnpm run changes:add
+\`\`\`
+
+> This script requires Node 24+. If you are on a lower version, please [add a file manually](https://github.com/remix-run/react-router/blob/main/docs/community/contributing.md#change-files)
+
+> Not every PR needs a change file — you can skip this step if the change is internal-only
+> (tests, tooling, docs)`;
+
+const CLOSE_FEATURE_PR_COMMENT = `\
+To align with our new [Open Governance](https://remix.run/blog/rr-governance) model, we are now asking that all new features go through the [Proposal/RFC process](https://github.com/remix-run/react-router/blob/main/GOVERNANCE.md#new-feature-process) and that we don't open PRs until a proposal has been accepted and advanced to Stage 1.
+
+If this feature doesn't have a Proposal, please [open one](https://github.com/remix-run/react-router/discussions/new?category=proposals) so we can evaluate/discuss the proposed feature. You can link to this PR as an example of a potential implementation and we can re-open it if the proposal advances.
+
+If this PR already has a Proposal but it has not yet been accepted, let's continue the discussion in the Proposal until it gets accepted and then we can look to open a PR. Feel free to link to this PR or to a branch in a forked repo to show what a potential implementation might look like.
+
+If you have any questions, you can always reach out on [Discord](https://remix.run/discord). Thanks again for providing feedback and helping us make React Router even better!
+`;
+
+let { positionals } = util.parseArgs({ allowPositionals: true });
+let [mode, arg] = positionals;
+
+if (mode === "check") {
+ await runChecks();
+} else if (mode === "actions") {
+ if (!arg) usage();
+ await runActions(arg);
+} else {
+ usage();
+}
+
+// ---------- Checks ----------
+
+async function runChecks() {
+ let prNumber = parseInt(requireEnv("PR_NUMBER"), 10);
+ if (isNaN(prNumber)) {
+ console.error("PR_NUMBER must be numeric");
+ process.exit(1);
+ }
+
+ let checks: Check[] = [changeFileCheck, featurePrCheck];
+
+ let ctx: CheckContext = {
+ prNumber,
+ baseBranch: requireEnv("PR_BASE"),
+ eventAction: requireEnv("EVENT_ACTION"),
+ labelName: process.env.LABEL_NAME ?? "",
+ };
+ console.log("ctx:", ctx);
+
+ let result: { prNumber: number; actions: Action[] } = {
+ prNumber,
+ actions: [],
+ };
+
+ for (let check of checks) {
+ result.actions.push(...(await check(ctx)));
+ }
+
+ // Matches pr-checks.yml/pr-actions.yml workflow artifact name
+ let filename = "pr-checks-result.json";
+ console.log(`Writing ${filename}:`, JSON.stringify(result));
+ fs.writeFileSync(filename, JSON.stringify(result));
+}
+
+async function changeFileCheck(ctx: CheckContext): Promise {
+ if (ctx.baseBranch !== "main") return [];
+ if (!["opened", "synchronize", "reopened"].includes(ctx.eventAction)) {
+ return [];
+ }
+
+ let files = await getPrFiles(ctx.prNumber);
+ let regex =
+ /^packages\/[^/]+\/\.changes\/(major|minor|patch|unstable)\.[^/]+\.md$/;
+ let summaries: ChangeFileSummary[] = files
+ .filter((f) => f.status !== "removed" && regex.test(f.filename))
+ .map((f) => {
+ let type = f.filename.match(regex)?.[1] ?? "unknown";
+ let firstLine =
+ fs.readFileSync(f.filename, "utf8").split(/\r?\n/, 1)[0].trim() ??
+ "_No first line found._";
+
+ return {
+ type,
+ firstLine,
+ };
+ });
+
+ console.log(`changeFileCheck: found ${summaries.length} change files`);
+
+ let body = CHANGE_FILE_MISSING_COMMENT;
+
+ if (summaries.length > 0) {
+ body = [
+ CHANGE_FILE_FOUND_COMMENT,
+ "| Type | Change |",
+ "| --- | --- |",
+ ...summaries.map(
+ (s) => `| \`${s.type}\` | ${s.firstLine.replaceAll("|", "\\|")} |`,
+ ),
+ ].join("\n");
+ }
+
+ return [
+ {
+ type: "upsert-sticky-comment",
+ marker: CHANGE_FILE_MARKER,
+ body,
+ },
+ ];
+}
+
+async function featurePrCheck(ctx: CheckContext): Promise {
+ if (ctx.eventAction !== "labeled") return [];
+ if (ctx.labelName !== "feature-request") return [];
+
+ console.log(`featurePrCheck: closing PR ${ctx.prNumber}`);
+ return [
+ { type: "create-comment", body: CLOSE_FEATURE_PR_COMMENT },
+ { type: "remove-label", label: ctx.labelName },
+ { type: "close-pr" },
+ ];
+}
+
+// ---------- Action dispatch ----------
+
+async function runActions(resultPath: string) {
+ let { prNumber, actions } = JSON.parse(
+ fs.readFileSync(resultPath, "utf8"),
+ ) as { prNumber: number; actions: Action[] };
+
+ if (actions.length === 0) {
+ console.log("No actions to apply");
+ return;
+ }
+
+ for (let action of actions) {
+ switch (action.type) {
+ case "upsert-sticky-comment": {
+ let comments = await getPrComments(prNumber);
+ let existing = comments.find(
+ (c) =>
+ c.user?.login === "github-actions[bot]" &&
+ c.body?.includes(action.marker),
+ );
+ if (existing) {
+ console.log(`Updating sticky comment #${existing.id}`);
+ await updatePrComment(existing.id, action.body);
+ } else {
+ console.log("Creating sticky comment");
+ await createPrComment(prNumber, action.body);
+ }
+ return;
+ }
+ case "create-comment": {
+ console.log("Creating comment");
+ await createPrComment(prNumber, action.body);
+ return;
+ }
+ case "remove-label": {
+ console.log(`Removing label '${action.label}'`);
+ await removePrLabel(prNumber, action.label);
+ return;
+ }
+ case "close-pr": {
+ console.log(`Closing PR ${prNumber}`);
+ await closePr(prNumber);
+ return;
+ }
+ }
+ }
+}
+
+// Utils
+
+function usage(): never {
+ console.error(
+ "Usage:\n" +
+ " node scripts/pr.ts check\n" +
+ " node scripts/pr.ts actions ",
+ );
+ process.exit(1);
+}
+
+function requireEnv(name: string): string {
+ let value = process.env[name];
+ if (!value) {
+ console.error(`Missing required env var: ${name}`);
+ process.exit(1);
+ }
+ return value;
+}
diff --git a/tests/testdata/ts_react_router_filter_removed/old.ts b/tests/testdata/ts_react_router_filter_removed/old.ts
new file mode 100644
index 0000000..7575e54
--- /dev/null
+++ b/tests/testdata/ts_react_router_filter_removed/old.ts
@@ -0,0 +1,266 @@
+/**
+ * Runs a set of checks against a PR and applies the resulting actions.
+ *
+ * Two-phase to avoid running with write permissions on PRs from forks.
+ * See https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
+ *
+ * check Inspects the PR via the GitHub API and writes a list of
+ * "actions" to pr-checks-result.json. Safe to run with a
+ * read-only token (Workflow A: 🔍 Check PR).
+ *
+ * actions Reads pr-checks-result.json and applies each action. Runs
+ * in Workflow B (PR (Actions)) under `workflow_run` with
+ * write permissions but never executes any PR code.
+ *
+ * Usage:
+ * node scripts/pr.ts check
+ * node scripts/pr.ts actions
+ *
+ * Environment (check):
+ * GITHUB_TOKEN - Required (read-only PR scope is enough).
+ * PR_NUMBER - Required. github.event.pull_request.number
+ * PR_BASE - Required. github.event.pull_request.base.ref
+ * EVENT_ACTION - Required. github.event.action (opened|synchronize|reopened|labeled)
+ * LABEL_NAME - Optional. github.event.label.name (set when EVENT_ACTION=labeled)
+ *
+ * Environment (actions):
+ * GITHUB_TOKEN - Required (issues:write + pull-requests:write).
+ */
+import * as fs from "node:fs";
+import * as util from "node:util";
+
+import {
+ closePr,
+ createPrComment,
+ getPrComments,
+ getPrFiles,
+ removePrLabel,
+ updatePrComment,
+} from "./utils/github.ts";
+
+type Action =
+ | { type: "upsert-sticky-comment"; marker: string; body: string }
+ | { type: "create-comment"; body: string }
+ | { type: "remove-label"; label: string }
+ | { type: "close-pr" };
+
+type CheckContext = {
+ prNumber: number;
+ baseBranch: string;
+ eventAction: string;
+ labelName: string;
+};
+
+type Check = (ctx: CheckContext) => Promise;
+
+const CHANGE_FILE_MARKER = "";
+
+type ChangeFileSummary = {
+ type: string;
+ firstLine: string;
+};
+
+const CHANGE_FILE_FOUND_COMMENT = `${CHANGE_FILE_MARKER}
+### ✅ Change File Found
+
+One or more [change files](https://github.com/remix-run/react-router/blob/main/docs/community/contributing.md#change-files) found.
+`;
+
+const CHANGE_FILE_MISSING_COMMENT = `${CHANGE_FILE_MARKER}
+### ⚠️ No Change File Found
+
+This PR doesn't include a [change file](https://github.com/remix-run/react-router/blob/main/docs/community/contributing.md#change-files) which is used for automated release notes.
+If your change affects users, please add one (or more) change files and commit the generated file(s).
+
+\`\`\`sh
+pnpm run changes:add
+\`\`\`
+
+> This script requires Node 24+. If you are on a lower version, please [add a file manually](https://github.com/remix-run/react-router/blob/main/docs/community/contributing.md#change-files)
+
+> Not every PR needs a change file — you can skip this step if the change is internal-only
+> (tests, tooling, docs)`;
+
+const CLOSE_FEATURE_PR_COMMENT = `\
+To align with our new [Open Governance](https://remix.run/blog/rr-governance) model, we are now asking that all new features go through the [Proposal/RFC process](https://github.com/remix-run/react-router/blob/main/GOVERNANCE.md#new-feature-process) and that we don't open PRs until a proposal has been accepted and advanced to Stage 1.
+
+If this feature doesn't have a Proposal, please [open one](https://github.com/remix-run/react-router/discussions/new?category=proposals) so we can evaluate/discuss the proposed feature. You can link to this PR as an example of a potential implementation and we can re-open it if the proposal advances.
+
+If this PR already has a Proposal but it has not yet been accepted, let's continue the discussion in the Proposal until it gets accepted and then we can look to open a PR. Feel free to link to this PR or to a branch in a forked repo to show what a potential implementation might look like.
+
+If you have any questions, you can always reach out on [Discord](https://remix.run/discord). Thanks again for providing feedback and helping us make React Router even better!
+`;
+
+let { positionals } = util.parseArgs({ allowPositionals: true });
+let [mode, arg] = positionals;
+
+if (mode === "check") {
+ await runChecks();
+} else if (mode === "actions") {
+ if (!arg) usage();
+ await runActions(arg);
+} else {
+ usage();
+}
+
+// ---------- Checks ----------
+
+async function runChecks() {
+ let prNumber = parseInt(requireEnv("PR_NUMBER"), 10);
+ if (isNaN(prNumber)) {
+ console.error("PR_NUMBER must be numeric");
+ process.exit(1);
+ }
+
+ let checks: Check[] = [changeFileCheck, featurePrCheck];
+
+ let ctx: CheckContext = {
+ prNumber,
+ baseBranch: requireEnv("PR_BASE"),
+ eventAction: requireEnv("EVENT_ACTION"),
+ labelName: process.env.LABEL_NAME ?? "",
+ };
+ console.log("ctx:", ctx);
+
+ let result: { prNumber: number; actions: Action[] } = {
+ prNumber,
+ actions: [],
+ };
+
+ for (let check of checks) {
+ result.actions.push(...(await check(ctx)));
+ }
+
+ // Matches pr-checks.yml/pr-actions.yml workflow artifact name
+ let filename = "pr-checks-result.json";
+ console.log(`Writing ${filename}:`, JSON.stringify(result));
+ fs.writeFileSync(filename, JSON.stringify(result));
+}
+
+async function changeFileCheck(ctx: CheckContext): Promise {
+ if (ctx.baseBranch !== "main") return [];
+ if (!["opened", "synchronize", "reopened"].includes(ctx.eventAction)) {
+ return [];
+ }
+
+ let files = await getPrFiles(ctx.prNumber);
+ let regex =
+ /^packages\/[^/]+\/\.changes\/(major|minor|patch|unstable)\.[^/]+\.md$/;
+ let summaries: ChangeFileSummary[] = files
+ .filter((f) => regex.test(f.filename))
+ .map((f) => {
+ let type = f.filename.match(regex)?.[1] ?? "unknown";
+ let firstLine =
+ fs.readFileSync(f.filename, "utf8").split(/\r?\n/, 1)[0].trim() ??
+ "_No first line found._";
+
+ return {
+ type,
+ firstLine,
+ };
+ });
+
+ console.log(`changeFileCheck: found ${summaries.length} change files`);
+
+ let body = CHANGE_FILE_MISSING_COMMENT;
+
+ if (summaries.length > 0) {
+ body = [
+ CHANGE_FILE_FOUND_COMMENT,
+ "| Type | Change |",
+ "| --- | --- |",
+ ...summaries.map(
+ (s) => `| \`${s.type}\` | ${s.firstLine.replaceAll("|", "\\|")} |`,
+ ),
+ ].join("\n");
+ }
+
+ return [
+ {
+ type: "upsert-sticky-comment",
+ marker: CHANGE_FILE_MARKER,
+ body,
+ },
+ ];
+}
+
+async function featurePrCheck(ctx: CheckContext): Promise {
+ if (ctx.eventAction !== "labeled") return [];
+ if (ctx.labelName !== "feature-request") return [];
+
+ console.log(`featurePrCheck: closing PR ${ctx.prNumber}`);
+ return [
+ { type: "create-comment", body: CLOSE_FEATURE_PR_COMMENT },
+ { type: "remove-label", label: ctx.labelName },
+ { type: "close-pr" },
+ ];
+}
+
+// ---------- Action dispatch ----------
+
+async function runActions(resultPath: string) {
+ let { prNumber, actions } = JSON.parse(
+ fs.readFileSync(resultPath, "utf8"),
+ ) as { prNumber: number; actions: Action[] };
+
+ if (actions.length === 0) {
+ console.log("No actions to apply");
+ return;
+ }
+
+ for (let action of actions) {
+ switch (action.type) {
+ case "upsert-sticky-comment": {
+ let comments = await getPrComments(prNumber);
+ let existing = comments.find(
+ (c) =>
+ c.user?.login === "github-actions[bot]" &&
+ c.body?.includes(action.marker),
+ );
+ if (existing) {
+ console.log(`Updating sticky comment #${existing.id}`);
+ await updatePrComment(existing.id, action.body);
+ } else {
+ console.log("Creating sticky comment");
+ await createPrComment(prNumber, action.body);
+ }
+ return;
+ }
+ case "create-comment": {
+ console.log("Creating comment");
+ await createPrComment(prNumber, action.body);
+ return;
+ }
+ case "remove-label": {
+ console.log(`Removing label '${action.label}'`);
+ await removePrLabel(prNumber, action.label);
+ return;
+ }
+ case "close-pr": {
+ console.log(`Closing PR ${prNumber}`);
+ await closePr(prNumber);
+ return;
+ }
+ }
+ }
+}
+
+// Utils
+
+function usage(): never {
+ console.error(
+ "Usage:\n" +
+ " node scripts/pr.ts check\n" +
+ " node scripts/pr.ts actions ",
+ );
+ process.exit(1);
+}
+
+function requireEnv(name: string): string {
+ let value = process.env[name];
+ if (!value) {
+ console.error(`Missing required env var: ${name}`);
+ process.exit(1);
+ }
+ return value;
+}
diff --git a/tests/testdata/ts_react_router_take_cla/expected.json b/tests/testdata/ts_react_router_take_cla/expected.json
new file mode 100644
index 0000000..e4f1efa
--- /dev/null
+++ b/tests/testdata/ts_react_router_take_cla/expected.json
@@ -0,0 +1,1724 @@
+{
+ "version": "v1",
+ "actions": [
+ {
+ "action": "delete",
+ "node": {
+ "tree": "before",
+ "path": [
+ 23,
+ 5,
+ 6
+ ],
+ "type": "comment",
+ "label": "// Dry runs to start so we can monitor the flow alongside the bot",
+ "start_byte": 7871,
+ "end_byte": 7936
+ }
+ },
+ {
+ "action": "delete",
+ "node": {
+ "tree": "before",
+ "path": [
+ 23,
+ 5,
+ 7
+ ],
+ "type": "lexical_declaration",
+ "start_byte": 7939,
+ "end_byte": 7958
+ },
+ "subtree": true
+ },
+ {
+ "action": "delete",
+ "node": {
+ "tree": "before",
+ "path": [
+ 23,
+ 5,
+ 8
+ ],
+ "type": "if_statement",
+ "start_byte": 7961,
+ "end_byte": 8283
+ },
+ "subtree": true
+ }
+ ],
+ "line_alignment": [
+ {
+ "left_line": 0,
+ "right_line": 0
+ },
+ {
+ "left_line": 1,
+ "right_line": 1
+ },
+ {
+ "left_line": 2,
+ "right_line": 2
+ },
+ {
+ "left_line": 3,
+ "right_line": 3
+ },
+ {
+ "left_line": 4,
+ "right_line": 4
+ },
+ {
+ "left_line": 5,
+ "right_line": 5
+ },
+ {
+ "left_line": 6,
+ "right_line": 6
+ },
+ {
+ "left_line": 7,
+ "right_line": 7
+ },
+ {
+ "left_line": 8,
+ "right_line": 8
+ },
+ {
+ "left_line": 9,
+ "right_line": 9
+ },
+ {
+ "left_line": 10,
+ "right_line": 10
+ },
+ {
+ "left_line": 11,
+ "right_line": 11
+ },
+ {
+ "left_line": 12,
+ "right_line": 12
+ },
+ {
+ "left_line": 13,
+ "right_line": 13
+ },
+ {
+ "left_line": 14,
+ "right_line": 14
+ },
+ {
+ "left_line": 15,
+ "right_line": 15
+ },
+ {
+ "left_line": 16,
+ "right_line": 16
+ },
+ {
+ "left_line": 17,
+ "right_line": 17
+ },
+ {
+ "left_line": 18,
+ "right_line": 18
+ },
+ {
+ "left_line": 19,
+ "right_line": 19
+ },
+ {
+ "left_line": 20,
+ "right_line": 20
+ },
+ {
+ "left_line": 21,
+ "right_line": 21
+ },
+ {
+ "left_line": 22,
+ "right_line": 22
+ },
+ {
+ "left_line": 23,
+ "right_line": 23
+ },
+ {
+ "left_line": 24,
+ "right_line": 24
+ },
+ {
+ "left_line": 25,
+ "right_line": 25
+ },
+ {
+ "left_line": 26,
+ "right_line": 26
+ },
+ {
+ "left_line": 27,
+ "right_line": 27
+ },
+ {
+ "left_line": 28,
+ "right_line": 28
+ },
+ {
+ "left_line": 29,
+ "right_line": 29
+ },
+ {
+ "left_line": 30,
+ "right_line": 30
+ },
+ {
+ "left_line": 31,
+ "right_line": 31
+ },
+ {
+ "left_line": 32,
+ "right_line": 32
+ },
+ {
+ "left_line": 33,
+ "right_line": 33
+ },
+ {
+ "left_line": 34,
+ "right_line": 34
+ },
+ {
+ "left_line": 35,
+ "right_line": 35
+ },
+ {
+ "left_line": 36,
+ "right_line": 36
+ },
+ {
+ "left_line": 37,
+ "right_line": 37
+ },
+ {
+ "left_line": 38,
+ "right_line": 38
+ },
+ {
+ "left_line": 39,
+ "right_line": 39
+ },
+ {
+ "left_line": 40,
+ "right_line": 40
+ },
+ {
+ "left_line": 41,
+ "right_line": 41
+ },
+ {
+ "left_line": 42,
+ "right_line": 42
+ },
+ {
+ "left_line": 43,
+ "right_line": 43
+ },
+ {
+ "left_line": 44,
+ "right_line": 44
+ },
+ {
+ "left_line": 45,
+ "right_line": 45
+ },
+ {
+ "left_line": 46,
+ "right_line": 46
+ },
+ {
+ "left_line": 47,
+ "right_line": 47
+ },
+ {
+ "left_line": 48,
+ "right_line": 48
+ },
+ {
+ "left_line": 49,
+ "right_line": 49
+ },
+ {
+ "left_line": 50,
+ "right_line": 50
+ },
+ {
+ "left_line": 51,
+ "right_line": 51
+ },
+ {
+ "left_line": 52,
+ "right_line": 52
+ },
+ {
+ "left_line": 53,
+ "right_line": 53
+ },
+ {
+ "left_line": 54,
+ "right_line": 54
+ },
+ {
+ "left_line": 55,
+ "right_line": 55
+ },
+ {
+ "left_line": 56,
+ "right_line": 56
+ },
+ {
+ "left_line": 57,
+ "right_line": 57
+ },
+ {
+ "left_line": 58,
+ "right_line": 58
+ },
+ {
+ "left_line": 59,
+ "right_line": 59
+ },
+ {
+ "left_line": 60,
+ "right_line": 60
+ },
+ {
+ "left_line": 61,
+ "right_line": 61
+ },
+ {
+ "left_line": 62,
+ "right_line": 62
+ },
+ {
+ "left_line": 63,
+ "right_line": 63
+ },
+ {
+ "left_line": 64,
+ "right_line": 64
+ },
+ {
+ "left_line": 65,
+ "right_line": 65
+ },
+ {
+ "left_line": 66,
+ "right_line": 66
+ },
+ {
+ "left_line": 67,
+ "right_line": 67
+ },
+ {
+ "left_line": 68,
+ "right_line": 68
+ },
+ {
+ "left_line": 69,
+ "right_line": 69
+ },
+ {
+ "left_line": 70,
+ "right_line": 70
+ },
+ {
+ "left_line": 71,
+ "right_line": 71
+ },
+ {
+ "left_line": 72,
+ "right_line": 72
+ },
+ {
+ "left_line": 73,
+ "right_line": 73
+ },
+ {
+ "left_line": 74,
+ "right_line": 74
+ },
+ {
+ "left_line": 75,
+ "right_line": 75
+ },
+ {
+ "left_line": 76,
+ "right_line": 76
+ },
+ {
+ "left_line": 77,
+ "right_line": 77
+ },
+ {
+ "left_line": 78,
+ "right_line": 78
+ },
+ {
+ "left_line": 79,
+ "right_line": 79
+ },
+ {
+ "left_line": 80,
+ "right_line": 80
+ },
+ {
+ "left_line": 81,
+ "right_line": 81
+ },
+ {
+ "left_line": 82,
+ "right_line": 82
+ },
+ {
+ "left_line": 83,
+ "right_line": 83
+ },
+ {
+ "left_line": 84,
+ "right_line": 84
+ },
+ {
+ "left_line": 85,
+ "right_line": 85
+ },
+ {
+ "left_line": 86,
+ "right_line": 86
+ },
+ {
+ "left_line": 87,
+ "right_line": 87
+ },
+ {
+ "left_line": 88,
+ "right_line": 88
+ },
+ {
+ "left_line": 89,
+ "right_line": 89
+ },
+ {
+ "left_line": 90,
+ "right_line": 90
+ },
+ {
+ "left_line": 91,
+ "right_line": 91
+ },
+ {
+ "left_line": 92,
+ "right_line": 92
+ },
+ {
+ "left_line": 93,
+ "right_line": 93
+ },
+ {
+ "left_line": 94,
+ "right_line": 94
+ },
+ {
+ "left_line": 95,
+ "right_line": 95
+ },
+ {
+ "left_line": 96,
+ "right_line": 96
+ },
+ {
+ "left_line": 97,
+ "right_line": 97
+ },
+ {
+ "left_line": 98,
+ "right_line": 98
+ },
+ {
+ "left_line": 99,
+ "right_line": 99
+ },
+ {
+ "left_line": 100,
+ "right_line": 100
+ },
+ {
+ "left_line": 101,
+ "right_line": 101
+ },
+ {
+ "left_line": 102,
+ "right_line": 102
+ },
+ {
+ "left_line": 103,
+ "right_line": 103
+ },
+ {
+ "left_line": 104,
+ "right_line": 104
+ },
+ {
+ "left_line": 105,
+ "right_line": 105
+ },
+ {
+ "left_line": 106,
+ "right_line": 106
+ },
+ {
+ "left_line": 107,
+ "right_line": 107
+ },
+ {
+ "left_line": 108,
+ "right_line": 108
+ },
+ {
+ "left_line": 109,
+ "right_line": 109
+ },
+ {
+ "left_line": 110,
+ "right_line": 110
+ },
+ {
+ "left_line": 111,
+ "right_line": 111
+ },
+ {
+ "left_line": 112,
+ "right_line": 112
+ },
+ {
+ "left_line": 113,
+ "right_line": 113
+ },
+ {
+ "left_line": 114,
+ "right_line": 114
+ },
+ {
+ "left_line": 115,
+ "right_line": 115
+ },
+ {
+ "left_line": 116,
+ "right_line": 116
+ },
+ {
+ "left_line": 117,
+ "right_line": 117
+ },
+ {
+ "left_line": 118,
+ "right_line": 118
+ },
+ {
+ "left_line": 119,
+ "right_line": 119
+ },
+ {
+ "left_line": 120,
+ "right_line": 120
+ },
+ {
+ "left_line": 121,
+ "right_line": 121
+ },
+ {
+ "left_line": 122,
+ "right_line": 122
+ },
+ {
+ "left_line": 123,
+ "right_line": 123
+ },
+ {
+ "left_line": 124,
+ "right_line": 124
+ },
+ {
+ "left_line": 125,
+ "right_line": 125
+ },
+ {
+ "left_line": 126,
+ "right_line": 126
+ },
+ {
+ "left_line": 127,
+ "right_line": 127
+ },
+ {
+ "left_line": 128,
+ "right_line": 128
+ },
+ {
+ "left_line": 129,
+ "right_line": 129
+ },
+ {
+ "left_line": 130,
+ "right_line": 130
+ },
+ {
+ "left_line": 131,
+ "right_line": 131
+ },
+ {
+ "left_line": 132,
+ "right_line": 132
+ },
+ {
+ "left_line": 133,
+ "right_line": 133
+ },
+ {
+ "left_line": 134,
+ "right_line": 134
+ },
+ {
+ "left_line": 135,
+ "right_line": 135
+ },
+ {
+ "left_line": 136,
+ "right_line": 136
+ },
+ {
+ "left_line": 137,
+ "right_line": 137
+ },
+ {
+ "left_line": 138,
+ "right_line": 138
+ },
+ {
+ "left_line": 139,
+ "right_line": 139
+ },
+ {
+ "left_line": 140,
+ "right_line": 140
+ },
+ {
+ "left_line": 141,
+ "right_line": 141
+ },
+ {
+ "left_line": 142,
+ "right_line": 142
+ },
+ {
+ "left_line": 143,
+ "right_line": 143
+ },
+ {
+ "left_line": 144,
+ "right_line": 144
+ },
+ {
+ "left_line": 145,
+ "right_line": 145
+ },
+ {
+ "left_line": 146,
+ "right_line": 146
+ },
+ {
+ "left_line": 147,
+ "right_line": 147
+ },
+ {
+ "left_line": 148,
+ "right_line": 148
+ },
+ {
+ "left_line": 149,
+ "right_line": 149
+ },
+ {
+ "left_line": 150,
+ "right_line": 150
+ },
+ {
+ "left_line": 151,
+ "right_line": 151
+ },
+ {
+ "left_line": 152,
+ "right_line": 152
+ },
+ {
+ "left_line": 153,
+ "right_line": 153
+ },
+ {
+ "left_line": 154,
+ "right_line": 154
+ },
+ {
+ "left_line": 155,
+ "right_line": 155
+ },
+ {
+ "left_line": 156,
+ "right_line": 156
+ },
+ {
+ "left_line": 157,
+ "right_line": 157
+ },
+ {
+ "left_line": 158,
+ "right_line": 158
+ },
+ {
+ "left_line": 159,
+ "right_line": 159
+ },
+ {
+ "left_line": 160,
+ "right_line": 160
+ },
+ {
+ "left_line": 161,
+ "right_line": 161
+ },
+ {
+ "left_line": 162,
+ "right_line": 162
+ },
+ {
+ "left_line": 163,
+ "right_line": 163
+ },
+ {
+ "left_line": 164,
+ "right_line": 164
+ },
+ {
+ "left_line": 165,
+ "right_line": 165
+ },
+ {
+ "left_line": 166,
+ "right_line": 166
+ },
+ {
+ "left_line": 167,
+ "right_line": 167
+ },
+ {
+ "left_line": 168,
+ "right_line": 168
+ },
+ {
+ "left_line": 169,
+ "right_line": 169
+ },
+ {
+ "left_line": 170,
+ "right_line": 170
+ },
+ {
+ "left_line": 171,
+ "right_line": 171
+ },
+ {
+ "left_line": 172,
+ "right_line": 172
+ },
+ {
+ "left_line": 173,
+ "right_line": 173
+ },
+ {
+ "left_line": 174,
+ "right_line": 174
+ },
+ {
+ "left_line": 175,
+ "right_line": 175
+ },
+ {
+ "left_line": 176,
+ "right_line": 176
+ },
+ {
+ "left_line": 177,
+ "right_line": 177
+ },
+ {
+ "left_line": 178,
+ "right_line": 178
+ },
+ {
+ "left_line": 179,
+ "right_line": 179
+ },
+ {
+ "left_line": 180,
+ "right_line": 180
+ },
+ {
+ "left_line": 181,
+ "right_line": 181
+ },
+ {
+ "left_line": 182,
+ "right_line": 182
+ },
+ {
+ "left_line": 183,
+ "right_line": 183
+ },
+ {
+ "left_line": 184,
+ "right_line": 184
+ },
+ {
+ "left_line": 185,
+ "right_line": 185
+ },
+ {
+ "left_line": 186,
+ "right_line": 186
+ },
+ {
+ "left_line": 187,
+ "right_line": 187
+ },
+ {
+ "left_line": 188,
+ "right_line": 188
+ },
+ {
+ "left_line": 189,
+ "right_line": 189
+ },
+ {
+ "left_line": 190,
+ "right_line": 190
+ },
+ {
+ "left_line": 191,
+ "right_line": 191
+ },
+ {
+ "left_line": 192,
+ "right_line": 192
+ },
+ {
+ "left_line": 193,
+ "right_line": 193
+ },
+ {
+ "left_line": 194,
+ "right_line": 194
+ },
+ {
+ "left_line": 195,
+ "right_line": 195
+ },
+ {
+ "left_line": 196,
+ "right_line": 196
+ },
+ {
+ "left_line": 197,
+ "right_line": 197
+ },
+ {
+ "left_line": 198,
+ "right_line": 198
+ },
+ {
+ "left_line": 199,
+ "right_line": 199
+ },
+ {
+ "left_line": 200,
+ "right_line": 200
+ },
+ {
+ "left_line": 201,
+ "right_line": 201
+ },
+ {
+ "left_line": 202,
+ "right_line": 202
+ },
+ {
+ "left_line": 203,
+ "right_line": 203
+ },
+ {
+ "left_line": 204,
+ "right_line": 204
+ },
+ {
+ "left_line": 205,
+ "right_line": 205
+ },
+ {
+ "left_line": 206,
+ "right_line": 206
+ },
+ {
+ "left_line": 207,
+ "right_line": 207
+ },
+ {
+ "left_line": 208,
+ "right_line": 208
+ },
+ {
+ "left_line": 209,
+ "right_line": 209
+ },
+ {
+ "left_line": 210,
+ "right_line": 210
+ },
+ {
+ "left_line": 211,
+ "right_line": 211
+ },
+ {
+ "left_line": 212,
+ "right_line": 212
+ },
+ {
+ "left_line": 213,
+ "right_line": 213
+ },
+ {
+ "left_line": 214,
+ "right_line": 214
+ },
+ {
+ "left_line": 215,
+ "right_line": 215
+ },
+ {
+ "left_line": 216,
+ "right_line": 216
+ },
+ {
+ "left_line": 217,
+ "right_line": 217
+ },
+ {
+ "left_line": 218,
+ "right_line": 218
+ },
+ {
+ "left_line": 219,
+ "right_line": 219
+ },
+ {
+ "left_line": 220,
+ "right_line": 220
+ },
+ {
+ "left_line": 221,
+ "right_line": 221
+ },
+ {
+ "left_line": 222,
+ "right_line": 222
+ },
+ {
+ "left_line": 223,
+ "right_line": 223
+ },
+ {
+ "left_line": 224,
+ "right_line": 224
+ },
+ {
+ "left_line": 225,
+ "right_line": -1
+ },
+ {
+ "left_line": 226,
+ "right_line": -1
+ },
+ {
+ "left_line": 227,
+ "right_line": -1
+ },
+ {
+ "left_line": 228,
+ "right_line": -1
+ },
+ {
+ "left_line": 229,
+ "right_line": -1
+ },
+ {
+ "left_line": 230,
+ "right_line": -1
+ },
+ {
+ "left_line": 231,
+ "right_line": -1
+ },
+ {
+ "left_line": 232,
+ "right_line": -1
+ },
+ {
+ "left_line": 233,
+ "right_line": -1
+ },
+ {
+ "left_line": 234,
+ "right_line": -1
+ },
+ {
+ "left_line": 235,
+ "right_line": -1
+ },
+ {
+ "left_line": 236,
+ "right_line": -1
+ },
+ {
+ "left_line": 237,
+ "right_line": -1
+ },
+ {
+ "left_line": 238,
+ "right_line": -1
+ },
+ {
+ "left_line": 239,
+ "right_line": -1
+ },
+ {
+ "left_line": 240,
+ "right_line": 225
+ },
+ {
+ "left_line": 241,
+ "right_line": 226
+ },
+ {
+ "left_line": 242,
+ "right_line": 227
+ },
+ {
+ "left_line": 243,
+ "right_line": 228
+ },
+ {
+ "left_line": 244,
+ "right_line": 229
+ },
+ {
+ "left_line": 245,
+ "right_line": 230
+ },
+ {
+ "left_line": 246,
+ "right_line": 231
+ },
+ {
+ "left_line": 247,
+ "right_line": 232
+ },
+ {
+ "left_line": 248,
+ "right_line": 233
+ },
+ {
+ "left_line": 249,
+ "right_line": 234
+ },
+ {
+ "left_line": 250,
+ "right_line": 235
+ },
+ {
+ "left_line": 251,
+ "right_line": 236
+ },
+ {
+ "left_line": 252,
+ "right_line": 237
+ },
+ {
+ "left_line": 253,
+ "right_line": 238
+ },
+ {
+ "left_line": 254,
+ "right_line": 239
+ },
+ {
+ "left_line": 255,
+ "right_line": 240
+ },
+ {
+ "left_line": 256,
+ "right_line": 241
+ },
+ {
+ "left_line": 257,
+ "right_line": 242
+ },
+ {
+ "left_line": 258,
+ "right_line": 243
+ },
+ {
+ "left_line": 259,
+ "right_line": 244
+ },
+ {
+ "left_line": 260,
+ "right_line": 245
+ },
+ {
+ "left_line": 261,
+ "right_line": 246
+ },
+ {
+ "left_line": 262,
+ "right_line": 247
+ },
+ {
+ "left_line": 263,
+ "right_line": 248
+ },
+ {
+ "left_line": 264,
+ "right_line": 249
+ },
+ {
+ "left_line": 265,
+ "right_line": 250
+ },
+ {
+ "left_line": 266,
+ "right_line": 251
+ },
+ {
+ "left_line": 267,
+ "right_line": 252
+ },
+ {
+ "left_line": 268,
+ "right_line": 253
+ },
+ {
+ "left_line": 269,
+ "right_line": 254
+ },
+ {
+ "left_line": 270,
+ "right_line": 255
+ },
+ {
+ "left_line": 271,
+ "right_line": 256
+ },
+ {
+ "left_line": 272,
+ "right_line": 257
+ },
+ {
+ "left_line": 273,
+ "right_line": 258
+ },
+ {
+ "left_line": 274,
+ "right_line": 259
+ },
+ {
+ "left_line": 275,
+ "right_line": 260
+ },
+ {
+ "left_line": 276,
+ "right_line": 261
+ },
+ {
+ "left_line": 277,
+ "right_line": 262
+ },
+ {
+ "left_line": 278,
+ "right_line": 263
+ },
+ {
+ "left_line": 279,
+ "right_line": 264
+ },
+ {
+ "left_line": 280,
+ "right_line": 265
+ },
+ {
+ "left_line": 281,
+ "right_line": 266
+ },
+ {
+ "left_line": 282,
+ "right_line": 267
+ },
+ {
+ "left_line": 283,
+ "right_line": 268
+ },
+ {
+ "left_line": 284,
+ "right_line": 269
+ },
+ {
+ "left_line": 285,
+ "right_line": 270
+ },
+ {
+ "left_line": 286,
+ "right_line": 271
+ },
+ {
+ "left_line": 287,
+ "right_line": 272
+ },
+ {
+ "left_line": 288,
+ "right_line": 273
+ },
+ {
+ "left_line": 289,
+ "right_line": 274
+ },
+ {
+ "left_line": 290,
+ "right_line": 275
+ },
+ {
+ "left_line": 291,
+ "right_line": 276
+ },
+ {
+ "left_line": 292,
+ "right_line": 277
+ },
+ {
+ "left_line": 293,
+ "right_line": 278
+ },
+ {
+ "left_line": 294,
+ "right_line": 279
+ },
+ {
+ "left_line": 295,
+ "right_line": 280
+ },
+ {
+ "left_line": 296,
+ "right_line": 281
+ },
+ {
+ "left_line": 297,
+ "right_line": 282
+ },
+ {
+ "left_line": 298,
+ "right_line": 283
+ },
+ {
+ "left_line": 299,
+ "right_line": 284
+ },
+ {
+ "left_line": 300,
+ "right_line": 285
+ },
+ {
+ "left_line": 301,
+ "right_line": 286
+ },
+ {
+ "left_line": 302,
+ "right_line": 287
+ },
+ {
+ "left_line": 303,
+ "right_line": 288
+ },
+ {
+ "left_line": 304,
+ "right_line": 289
+ },
+ {
+ "left_line": 305,
+ "right_line": 290
+ },
+ {
+ "left_line": 306,
+ "right_line": 291
+ },
+ {
+ "left_line": 307,
+ "right_line": 292
+ },
+ {
+ "left_line": 308,
+ "right_line": 293
+ },
+ {
+ "left_line": 309,
+ "right_line": 294
+ },
+ {
+ "left_line": 310,
+ "right_line": 295
+ },
+ {
+ "left_line": 311,
+ "right_line": 296
+ },
+ {
+ "left_line": 312,
+ "right_line": 297
+ },
+ {
+ "left_line": 313,
+ "right_line": 298
+ },
+ {
+ "left_line": 314,
+ "right_line": 299
+ },
+ {
+ "left_line": 315,
+ "right_line": 300
+ },
+ {
+ "left_line": 316,
+ "right_line": 301
+ },
+ {
+ "left_line": 317,
+ "right_line": 302
+ },
+ {
+ "left_line": 318,
+ "right_line": 303
+ },
+ {
+ "left_line": 319,
+ "right_line": 304
+ },
+ {
+ "left_line": 320,
+ "right_line": 305
+ },
+ {
+ "left_line": 321,
+ "right_line": 306
+ },
+ {
+ "left_line": 322,
+ "right_line": 307
+ },
+ {
+ "left_line": 323,
+ "right_line": 308
+ },
+ {
+ "left_line": 324,
+ "right_line": 309
+ },
+ {
+ "left_line": 325,
+ "right_line": 310
+ },
+ {
+ "left_line": 326,
+ "right_line": 311
+ },
+ {
+ "left_line": 327,
+ "right_line": 312
+ },
+ {
+ "left_line": 328,
+ "right_line": 313
+ },
+ {
+ "left_line": 329,
+ "right_line": 314
+ },
+ {
+ "left_line": 330,
+ "right_line": 315
+ },
+ {
+ "left_line": 331,
+ "right_line": 316
+ },
+ {
+ "left_line": 332,
+ "right_line": 317
+ },
+ {
+ "left_line": 333,
+ "right_line": 318
+ },
+ {
+ "left_line": 334,
+ "right_line": 319
+ },
+ {
+ "left_line": 335,
+ "right_line": 320
+ },
+ {
+ "left_line": 336,
+ "right_line": 321
+ },
+ {
+ "left_line": 337,
+ "right_line": 322
+ },
+ {
+ "left_line": 338,
+ "right_line": 323
+ },
+ {
+ "left_line": 339,
+ "right_line": 324
+ },
+ {
+ "left_line": 340,
+ "right_line": 325
+ },
+ {
+ "left_line": 341,
+ "right_line": 326
+ },
+ {
+ "left_line": 342,
+ "right_line": 327
+ },
+ {
+ "left_line": 343,
+ "right_line": 328
+ },
+ {
+ "left_line": 344,
+ "right_line": 329
+ },
+ {
+ "left_line": 345,
+ "right_line": 330
+ },
+ {
+ "left_line": 346,
+ "right_line": 331
+ },
+ {
+ "left_line": 347,
+ "right_line": 332
+ },
+ {
+ "left_line": 348,
+ "right_line": 333
+ },
+ {
+ "left_line": 349,
+ "right_line": 334
+ },
+ {
+ "left_line": 350,
+ "right_line": 335
+ },
+ {
+ "left_line": 351,
+ "right_line": 336
+ },
+ {
+ "left_line": 352,
+ "right_line": 337
+ },
+ {
+ "left_line": 353,
+ "right_line": 338
+ },
+ {
+ "left_line": 354,
+ "right_line": 339
+ },
+ {
+ "left_line": 355,
+ "right_line": 340
+ },
+ {
+ "left_line": 356,
+ "right_line": 341
+ },
+ {
+ "left_line": 357,
+ "right_line": 342
+ },
+ {
+ "left_line": 358,
+ "right_line": 343
+ },
+ {
+ "left_line": 359,
+ "right_line": 344
+ },
+ {
+ "left_line": 360,
+ "right_line": 345
+ },
+ {
+ "left_line": 361,
+ "right_line": 346
+ },
+ {
+ "left_line": 362,
+ "right_line": 347
+ },
+ {
+ "left_line": 363,
+ "right_line": 348
+ },
+ {
+ "left_line": 364,
+ "right_line": 349
+ },
+ {
+ "left_line": 365,
+ "right_line": 350
+ },
+ {
+ "left_line": 366,
+ "right_line": 351
+ },
+ {
+ "left_line": 367,
+ "right_line": 352
+ },
+ {
+ "left_line": 368,
+ "right_line": 353
+ },
+ {
+ "left_line": 369,
+ "right_line": 354
+ },
+ {
+ "left_line": 370,
+ "right_line": 355
+ },
+ {
+ "left_line": 371,
+ "right_line": 356
+ },
+ {
+ "left_line": 372,
+ "right_line": 357
+ },
+ {
+ "left_line": 373,
+ "right_line": 358
+ },
+ {
+ "left_line": 374,
+ "right_line": 359
+ },
+ {
+ "left_line": 375,
+ "right_line": 360
+ },
+ {
+ "left_line": 376,
+ "right_line": 361
+ },
+ {
+ "left_line": 377,
+ "right_line": 362
+ },
+ {
+ "left_line": 378,
+ "right_line": 363
+ },
+ {
+ "left_line": 379,
+ "right_line": 364
+ },
+ {
+ "left_line": 380,
+ "right_line": 365
+ },
+ {
+ "left_line": 381,
+ "right_line": 366
+ },
+ {
+ "left_line": 382,
+ "right_line": 367
+ },
+ {
+ "left_line": 383,
+ "right_line": 368
+ },
+ {
+ "left_line": 384,
+ "right_line": 369
+ },
+ {
+ "left_line": 385,
+ "right_line": 370
+ },
+ {
+ "left_line": 386,
+ "right_line": 371
+ },
+ {
+ "left_line": 387,
+ "right_line": 372
+ },
+ {
+ "left_line": 388,
+ "right_line": 373
+ },
+ {
+ "left_line": 389,
+ "right_line": 374
+ },
+ {
+ "left_line": 390,
+ "right_line": 375
+ },
+ {
+ "left_line": 391,
+ "right_line": 376
+ },
+ {
+ "left_line": 392,
+ "right_line": 377
+ },
+ {
+ "left_line": 393,
+ "right_line": 378
+ },
+ {
+ "left_line": 394,
+ "right_line": 379
+ },
+ {
+ "left_line": 395,
+ "right_line": 380
+ },
+ {
+ "left_line": 396,
+ "right_line": 381
+ },
+ {
+ "left_line": 397,
+ "right_line": 382
+ },
+ {
+ "left_line": 398,
+ "right_line": 383
+ },
+ {
+ "left_line": 399,
+ "right_line": 384
+ },
+ {
+ "left_line": 400,
+ "right_line": 385
+ },
+ {
+ "left_line": 401,
+ "right_line": 386
+ },
+ {
+ "left_line": 402,
+ "right_line": 387
+ },
+ {
+ "left_line": 403,
+ "right_line": 388
+ },
+ {
+ "left_line": 404,
+ "right_line": 389
+ },
+ {
+ "left_line": 405,
+ "right_line": 390
+ },
+ {
+ "left_line": 406,
+ "right_line": 391
+ },
+ {
+ "left_line": 407,
+ "right_line": 392
+ },
+ {
+ "left_line": 408,
+ "right_line": 393
+ },
+ {
+ "left_line": 409,
+ "right_line": 394
+ },
+ {
+ "left_line": 410,
+ "right_line": 395
+ },
+ {
+ "left_line": 411,
+ "right_line": 396
+ },
+ {
+ "left_line": 412,
+ "right_line": 397
+ },
+ {
+ "left_line": 413,
+ "right_line": 398
+ },
+ {
+ "left_line": 414,
+ "right_line": 399
+ },
+ {
+ "left_line": 415,
+ "right_line": 400
+ },
+ {
+ "left_line": 416,
+ "right_line": 401
+ },
+ {
+ "left_line": 417,
+ "right_line": 402
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/testdata/ts_react_router_take_cla/metadata.json b/tests/testdata/ts_react_router_take_cla/metadata.json
new file mode 100644
index 0000000..227a944
--- /dev/null
+++ b/tests/testdata/ts_react_router_take_cla/metadata.json
@@ -0,0 +1,12 @@
+{
+ "repo": "https://github.com/remix-run/react-router",
+ "parent": "70e2d2fe4c5aa246997ea7427f3a2f05ea8edb75",
+ "commit": "bd8f05c8a7c67a14ce56970ddc217bbc3a1e2228",
+ "message": "Take CLA check out of dry run mode",
+ "author": "Matt Brophy",
+ "date": "2026-07-02T10:15:10-04:00",
+ "language": "typescript",
+ "files_changed": 1,
+ "insertions": 0,
+ "deletions": 15
+}
\ No newline at end of file
diff --git a/tests/testdata/ts_react_router_take_cla/new.ts b/tests/testdata/ts_react_router_take_cla/new.ts
new file mode 100644
index 0000000..5139084
--- /dev/null
+++ b/tests/testdata/ts_react_router_take_cla/new.ts
@@ -0,0 +1,402 @@
+/**
+ * Runs a set of checks against a PR and applies the resulting actions.
+ *
+ * Two-phase to avoid running with write permissions on PRs from forks.
+ * See https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
+ *
+ * check Inspects the PR via the GitHub API and writes a list of
+ * "actions" to the given result file. Safe to run with a
+ * read-only token (Workflow A: 🔍 Check PR).
+ *
+ * actions Reads the given result file and applies each action. Runs
+ * in Workflow B (PR (Actions)) under `workflow_run` with
+ * write permissions but never executes any PR code.
+ *
+ * Usage:
+ * node scripts/pr.ts check
+ * node scripts/pr.ts actions
+ *
+ * Environment (check):
+ * GITHUB_TOKEN - Required (read-only PR scope is enough).
+ * PR_NUMBER - Required. github.event.pull_request.number
+ * PR_BASE - Required. github.event.pull_request.base.ref
+ * PR_AUTHOR - Required. github.event.pull_request.user.login
+ * PR_HEAD_OWNER - Required. github.event.pull_request.head.repo.owner.login
+ * PR_HEAD_REPO - Required. github.event.pull_request.head.repo.name
+ * PR_HEAD_REF - Required. github.event.pull_request.head.ref
+ * EVENT_ACTION - Required. github.event.action (opened|synchronize|reopened|labeled)
+ * LABEL_NAME - Optional. github.event.label.name (set when EVENT_ACTION=labeled)
+ *
+ * Environment (actions):
+ * GITHUB_TOKEN - Required (issues:write + pull-requests:write).
+ */
+import * as fs from "node:fs";
+import * as util from "node:util";
+
+import {
+ addPrLabels,
+ closePr,
+ createPrComment,
+ getPrComments,
+ getPrFiles,
+ removePrLabel,
+ updatePrComment,
+} from "./utils/github.ts";
+
+type Action =
+ | { type: "upsert-sticky-comment"; marker: string; body: string }
+ | { type: "create-comment"; body: string }
+ | { type: "add-label"; label: string }
+ | { type: "remove-label"; label: string }
+ | { type: "close-pr" };
+
+type CheckContext = {
+ prNumber: number;
+ baseBranch: string;
+ author: string;
+ headOwner: string;
+ headRepo: string;
+ headRef: string;
+ eventAction: string;
+ labelName: string;
+};
+
+type CheckResult = {
+ actions: Action[];
+ failureMessage?: string;
+};
+
+type Check = (ctx: CheckContext) => Promise;
+
+const CHANGE_FILE_MARKER = "";
+const CLA_MARKER = "";
+const CLA_SIGNED_LABEL = "CLA Signed";
+
+type ChangeFileSummary = {
+ type: string;
+ firstLine: string;
+};
+
+const CHANGE_FILE_FOUND_COMMENT = `${CHANGE_FILE_MARKER}
+### ✅ Change File Found
+
+One or more [change files](https://github.com/remix-run/react-router/blob/main/docs/community/contributing.md#change-files) found.
+`;
+
+const CHANGE_FILE_MISSING_COMMENT = `${CHANGE_FILE_MARKER}
+### ⚠️ No Change File Found
+
+This PR doesn't include a [change file](https://github.com/remix-run/react-router/blob/main/docs/community/contributing.md#change-files) which is used for automated release notes.
+If your change affects users, please add one (or more) change files and commit the generated file(s).
+
+\`\`\`sh
+pnpm run changes:add
+\`\`\`
+
+> This script requires Node 24+. If you are on a lower version, please [add a file manually](https://github.com/remix-run/react-router/blob/main/docs/community/contributing.md#change-files)
+
+> Not every PR needs a change file — you can skip this step if the change is internal-only
+> (tests, tooling, docs)`;
+
+const CLOSE_FEATURE_PR_COMMENT = `\
+To align with our new [Open Governance](https://remix.run/blog/rr-governance) model, we are now asking that all new features go through the [Proposal/RFC process](https://github.com/remix-run/react-router/blob/main/GOVERNANCE.md#new-feature-process) and that we don't open PRs until a proposal has been accepted and advanced to Stage 1.
+
+If this feature doesn't have a Proposal, please [open one](https://github.com/remix-run/react-router/discussions/new?category=proposals) so we can evaluate/discuss the proposed feature. You can link to this PR as an example of a potential implementation and we can re-open it if the proposal advances.
+
+If this PR already has a Proposal but it has not yet been accepted, let's continue the discussion in the Proposal until it gets accepted and then we can look to open a PR. Feel free to link to this PR or to a branch in a forked repo to show what a potential implementation might look like.
+
+If you have any questions, you can always reach out on [Discord](https://remix.run/discord). Thanks again for providing feedback and helping us make React Router even better!
+`;
+
+const CLA_SIGNED_COMMENT = `${CLA_MARKER}
+### ✅ CLA Signed
+
+Thanks for signing the [Contributor License Agreement](https://github.com/remix-run/react-router/blob/main/CLA.md).`;
+
+function getClaMissingComment(ctx: CheckContext) {
+ return `${CLA_MARKER}
+### ⚠️ CLA Signature Required
+
+Hi @${ctx.author}, thanks for contributing to React Router!
+
+Before we consider your pull request, we ask that you sign our **Contributor License Agreement** (CLA). We require this only once.
+
+You may [review the CLA](https://github.com/remix-run/react-router/blob/main/CLA.md) and sign it by [adding your GitHub username to contributors.yml](https://github.com/${ctx.headOwner}/${ctx.headRepo}/edit/${ctx.headRef}/contributors.yml).
+
+Once the CLA is signed, the \`${CLA_SIGNED_LABEL}\` label will be added to the pull request and the CI check will pass.
+
+Thanks!
+
+\\- The Remix team
+`;
+}
+
+let { positionals } = util.parseArgs({ allowPositionals: true });
+let [mode, filename] = positionals;
+
+if (!filename) {
+ usage();
+}
+
+if (mode === "check") {
+ await runChecks();
+} else if (mode === "actions") {
+ await runActions();
+} else {
+ usage();
+}
+
+// ---------- Checks ----------
+
+async function runChecks() {
+ let prNumber = parseInt(requireEnv("PR_NUMBER"), 10);
+ if (isNaN(prNumber)) {
+ console.error("PR_NUMBER must be numeric");
+ process.exit(1);
+ }
+
+ let checks: Check[] = [claCheck, changeFileCheck, featurePrCheck];
+
+ let ctx: CheckContext = {
+ prNumber,
+ baseBranch: requireEnv("PR_BASE"),
+ author: requireEnv("PR_AUTHOR"),
+ headOwner: requireEnv("PR_HEAD_OWNER"),
+ headRepo: requireEnv("PR_HEAD_REPO"),
+ headRef: requireEnv("PR_HEAD_REF"),
+ eventAction: requireEnv("EVENT_ACTION"),
+ labelName: process.env.LABEL_NAME ?? "",
+ };
+ console.log("ctx:", ctx);
+
+ let result: { prNumber: number; actions: Action[] } = {
+ prNumber,
+ actions: [],
+ };
+ let failures = "";
+
+ for (let check of checks) {
+ let checkResult = await check(ctx);
+ result.actions.push(...checkResult.actions);
+ failures += checkResult.failureMessage
+ ? ` - ${checkResult.failureMessage}\n`
+ : "";
+ }
+
+ console.log(`Checks result:`);
+ console.log(
+ JSON.stringify(
+ result,
+ (_, value) =>
+ typeof value === "string" && value.length > 60
+ ? value.substring(0, 60) + "..."
+ : value,
+ 2,
+ ),
+ );
+
+ console.log(`Writing ${filename}`);
+ fs.writeFileSync(filename, JSON.stringify(result));
+
+ if (failures.length > 0) {
+ console.error(`Failures:\n${failures}`);
+ process.exit(1);
+ }
+}
+
+async function claCheck(ctx: CheckContext): Promise {
+ if (!["opened", "synchronize", "reopened"].includes(ctx.eventAction)) {
+ return { actions: [] };
+ }
+
+ let author = ctx.author.toLowerCase();
+ if (author === "dependabot[bot]") {
+ console.log(`claCheck: ignoring ${ctx.author}`);
+ return { actions: [] };
+ }
+
+ let contributors = fs
+ .readFileSync("contributors.yml", "utf8")
+ .split(/\r?\n/)
+ .map((line) => line.match(/^\s*-\s*(.+?)\s*$/)?.[1]?.toLowerCase())
+ .filter((contributor): contributor is string => Boolean(contributor));
+ let signedCla = contributors.includes(author);
+ console.log(`claCheck: ${ctx.author} signed CLA: ${signedCla}`);
+
+ if (signedCla) {
+ return {
+ actions: [
+ { type: "add-label", label: CLA_SIGNED_LABEL },
+ {
+ type: "upsert-sticky-comment",
+ marker: CLA_MARKER,
+ body: CLA_SIGNED_COMMENT,
+ },
+ ],
+ };
+ }
+
+ return {
+ actions: [
+ {
+ type: "upsert-sticky-comment",
+ marker: CLA_MARKER,
+ body: getClaMissingComment(ctx),
+ },
+ ],
+ failureMessage: `CLA check failed: ${ctx.author} is not listed in contributors.yml`,
+ };
+}
+
+async function changeFileCheck(ctx: CheckContext): Promise {
+ if (ctx.baseBranch !== "main") return { actions: [] };
+ if (!["opened", "synchronize", "reopened"].includes(ctx.eventAction)) {
+ return { actions: [] };
+ }
+
+ let files = await getPrFiles(ctx.prNumber);
+ let touchesPackageFiles = files.some((f) =>
+ f.filename.startsWith("packages/"),
+ );
+ let regex =
+ /^packages\/[^/]+\/\.changes\/(major|minor|patch|unstable)\.[^/]+\.md$/;
+ let summaries: ChangeFileSummary[] = files
+ .filter((f) => f.status !== "removed" && regex.test(f.filename))
+ .map((f) => {
+ let type = f.filename.match(regex)?.[1] ?? "unknown";
+ let firstLine =
+ fs.readFileSync(f.filename, "utf8").split(/\r?\n/, 1)[0].trim() ??
+ "_No first line found._";
+
+ return {
+ type,
+ firstLine,
+ };
+ });
+
+ console.log(`changeFileCheck: found ${summaries.length} change files`);
+
+ if (summaries.length === 0 && !touchesPackageFiles) {
+ console.log("changeFileCheck: no package files changed");
+ return { actions: [] };
+ }
+
+ let body = CHANGE_FILE_MISSING_COMMENT;
+
+ if (summaries.length > 0) {
+ body = [
+ CHANGE_FILE_FOUND_COMMENT,
+ "| Type | Change |",
+ "| --- | --- |",
+ ...summaries.map(
+ (s) => `| \`${s.type}\` | ${s.firstLine.replaceAll("|", "\\|")} |`,
+ ),
+ ].join("\n");
+ }
+
+ return {
+ actions: [
+ {
+ type: "upsert-sticky-comment",
+ marker: CHANGE_FILE_MARKER,
+ body,
+ },
+ ],
+ };
+}
+
+async function featurePrCheck(ctx: CheckContext): Promise {
+ if (ctx.eventAction !== "labeled") return { actions: [] };
+ if (ctx.labelName !== "feature-request") return { actions: [] };
+
+ console.log(`featurePrCheck: closing PR ${ctx.prNumber}`);
+ return {
+ actions: [
+ { type: "create-comment", body: CLOSE_FEATURE_PR_COMMENT },
+ { type: "remove-label", label: ctx.labelName },
+ { type: "close-pr" },
+ ],
+ };
+}
+
+// ---------- Action dispatch ----------
+
+async function runActions() {
+ if (!fs.existsSync(filename)) {
+ console.log(`No result file found at ${filename}; skipping actions`);
+ return;
+ }
+
+ let { prNumber, actions } = JSON.parse(fs.readFileSync(filename, "utf8")) as {
+ prNumber: number;
+ actions: Action[];
+ };
+
+ if (actions.length === 0) {
+ console.log("No actions to apply");
+ return;
+ }
+
+ console.log(actions);
+
+ for (let action of actions) {
+ switch (action.type) {
+ case "upsert-sticky-comment": {
+ let comments = await getPrComments(prNumber);
+ let existing = comments.find(
+ (c) =>
+ c.user?.login === "github-actions[bot]" &&
+ c.body?.includes(action.marker),
+ );
+ if (existing) {
+ console.log(`Updating sticky comment #${existing.id}`);
+ await updatePrComment(existing.id, action.body);
+ } else {
+ console.log("Creating sticky comment");
+ await createPrComment(prNumber, action.body);
+ }
+ break;
+ }
+ case "create-comment": {
+ console.log("Creating comment");
+ await createPrComment(prNumber, action.body);
+ break;
+ }
+ case "add-label": {
+ console.log(`Adding label '${action.label}'`);
+ await addPrLabels(prNumber, [action.label]);
+ break;
+ }
+ case "remove-label": {
+ console.log(`Removing label '${action.label}'`);
+ await removePrLabel(prNumber, action.label);
+ break;
+ }
+ case "close-pr": {
+ console.log(`Closing PR ${prNumber}`);
+ await closePr(prNumber);
+ break;
+ }
+ }
+ }
+}
+
+// Utils
+
+function usage(): never {
+ console.error(
+ "Usage:\n" +
+ " node scripts/pr.ts check \n" +
+ " node scripts/pr.ts actions ",
+ );
+ process.exit(1);
+}
+
+function requireEnv(name: string): string {
+ let value = process.env[name];
+ if (!value) {
+ console.error(`Missing required env var: ${name}`);
+ process.exit(1);
+ }
+ return value;
+}
diff --git a/tests/testdata/ts_react_router_take_cla/old.ts b/tests/testdata/ts_react_router_take_cla/old.ts
new file mode 100644
index 0000000..baf7cb9
--- /dev/null
+++ b/tests/testdata/ts_react_router_take_cla/old.ts
@@ -0,0 +1,417 @@
+/**
+ * Runs a set of checks against a PR and applies the resulting actions.
+ *
+ * Two-phase to avoid running with write permissions on PRs from forks.
+ * See https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
+ *
+ * check Inspects the PR via the GitHub API and writes a list of
+ * "actions" to the given result file. Safe to run with a
+ * read-only token (Workflow A: 🔍 Check PR).
+ *
+ * actions Reads the given result file and applies each action. Runs
+ * in Workflow B (PR (Actions)) under `workflow_run` with
+ * write permissions but never executes any PR code.
+ *
+ * Usage:
+ * node scripts/pr.ts check
+ * node scripts/pr.ts actions
+ *
+ * Environment (check):
+ * GITHUB_TOKEN - Required (read-only PR scope is enough).
+ * PR_NUMBER - Required. github.event.pull_request.number
+ * PR_BASE - Required. github.event.pull_request.base.ref
+ * PR_AUTHOR - Required. github.event.pull_request.user.login
+ * PR_HEAD_OWNER - Required. github.event.pull_request.head.repo.owner.login
+ * PR_HEAD_REPO - Required. github.event.pull_request.head.repo.name
+ * PR_HEAD_REF - Required. github.event.pull_request.head.ref
+ * EVENT_ACTION - Required. github.event.action (opened|synchronize|reopened|labeled)
+ * LABEL_NAME - Optional. github.event.label.name (set when EVENT_ACTION=labeled)
+ *
+ * Environment (actions):
+ * GITHUB_TOKEN - Required (issues:write + pull-requests:write).
+ */
+import * as fs from "node:fs";
+import * as util from "node:util";
+
+import {
+ addPrLabels,
+ closePr,
+ createPrComment,
+ getPrComments,
+ getPrFiles,
+ removePrLabel,
+ updatePrComment,
+} from "./utils/github.ts";
+
+type Action =
+ | { type: "upsert-sticky-comment"; marker: string; body: string }
+ | { type: "create-comment"; body: string }
+ | { type: "add-label"; label: string }
+ | { type: "remove-label"; label: string }
+ | { type: "close-pr" };
+
+type CheckContext = {
+ prNumber: number;
+ baseBranch: string;
+ author: string;
+ headOwner: string;
+ headRepo: string;
+ headRef: string;
+ eventAction: string;
+ labelName: string;
+};
+
+type CheckResult = {
+ actions: Action[];
+ failureMessage?: string;
+};
+
+type Check = (ctx: CheckContext) => Promise;
+
+const CHANGE_FILE_MARKER = "";
+const CLA_MARKER = "";
+const CLA_SIGNED_LABEL = "CLA Signed";
+
+type ChangeFileSummary = {
+ type: string;
+ firstLine: string;
+};
+
+const CHANGE_FILE_FOUND_COMMENT = `${CHANGE_FILE_MARKER}
+### ✅ Change File Found
+
+One or more [change files](https://github.com/remix-run/react-router/blob/main/docs/community/contributing.md#change-files) found.
+`;
+
+const CHANGE_FILE_MISSING_COMMENT = `${CHANGE_FILE_MARKER}
+### ⚠️ No Change File Found
+
+This PR doesn't include a [change file](https://github.com/remix-run/react-router/blob/main/docs/community/contributing.md#change-files) which is used for automated release notes.
+If your change affects users, please add one (or more) change files and commit the generated file(s).
+
+\`\`\`sh
+pnpm run changes:add
+\`\`\`
+
+> This script requires Node 24+. If you are on a lower version, please [add a file manually](https://github.com/remix-run/react-router/blob/main/docs/community/contributing.md#change-files)
+
+> Not every PR needs a change file — you can skip this step if the change is internal-only
+> (tests, tooling, docs)`;
+
+const CLOSE_FEATURE_PR_COMMENT = `\
+To align with our new [Open Governance](https://remix.run/blog/rr-governance) model, we are now asking that all new features go through the [Proposal/RFC process](https://github.com/remix-run/react-router/blob/main/GOVERNANCE.md#new-feature-process) and that we don't open PRs until a proposal has been accepted and advanced to Stage 1.
+
+If this feature doesn't have a Proposal, please [open one](https://github.com/remix-run/react-router/discussions/new?category=proposals) so we can evaluate/discuss the proposed feature. You can link to this PR as an example of a potential implementation and we can re-open it if the proposal advances.
+
+If this PR already has a Proposal but it has not yet been accepted, let's continue the discussion in the Proposal until it gets accepted and then we can look to open a PR. Feel free to link to this PR or to a branch in a forked repo to show what a potential implementation might look like.
+
+If you have any questions, you can always reach out on [Discord](https://remix.run/discord). Thanks again for providing feedback and helping us make React Router even better!
+`;
+
+const CLA_SIGNED_COMMENT = `${CLA_MARKER}
+### ✅ CLA Signed
+
+Thanks for signing the [Contributor License Agreement](https://github.com/remix-run/react-router/blob/main/CLA.md).`;
+
+function getClaMissingComment(ctx: CheckContext) {
+ return `${CLA_MARKER}
+### ⚠️ CLA Signature Required
+
+Hi @${ctx.author}, thanks for contributing to React Router!
+
+Before we consider your pull request, we ask that you sign our **Contributor License Agreement** (CLA). We require this only once.
+
+You may [review the CLA](https://github.com/remix-run/react-router/blob/main/CLA.md) and sign it by [adding your GitHub username to contributors.yml](https://github.com/${ctx.headOwner}/${ctx.headRepo}/edit/${ctx.headRef}/contributors.yml).
+
+Once the CLA is signed, the \`${CLA_SIGNED_LABEL}\` label will be added to the pull request and the CI check will pass.
+
+Thanks!
+
+\\- The Remix team
+`;
+}
+
+let { positionals } = util.parseArgs({ allowPositionals: true });
+let [mode, filename] = positionals;
+
+if (!filename) {
+ usage();
+}
+
+if (mode === "check") {
+ await runChecks();
+} else if (mode === "actions") {
+ await runActions();
+} else {
+ usage();
+}
+
+// ---------- Checks ----------
+
+async function runChecks() {
+ let prNumber = parseInt(requireEnv("PR_NUMBER"), 10);
+ if (isNaN(prNumber)) {
+ console.error("PR_NUMBER must be numeric");
+ process.exit(1);
+ }
+
+ let checks: Check[] = [claCheck, changeFileCheck, featurePrCheck];
+
+ let ctx: CheckContext = {
+ prNumber,
+ baseBranch: requireEnv("PR_BASE"),
+ author: requireEnv("PR_AUTHOR"),
+ headOwner: requireEnv("PR_HEAD_OWNER"),
+ headRepo: requireEnv("PR_HEAD_REPO"),
+ headRef: requireEnv("PR_HEAD_REF"),
+ eventAction: requireEnv("EVENT_ACTION"),
+ labelName: process.env.LABEL_NAME ?? "",
+ };
+ console.log("ctx:", ctx);
+
+ let result: { prNumber: number; actions: Action[] } = {
+ prNumber,
+ actions: [],
+ };
+ let failures = "";
+
+ for (let check of checks) {
+ let checkResult = await check(ctx);
+ result.actions.push(...checkResult.actions);
+ failures += checkResult.failureMessage
+ ? ` - ${checkResult.failureMessage}\n`
+ : "";
+ }
+
+ console.log(`Checks result:`);
+ console.log(
+ JSON.stringify(
+ result,
+ (_, value) =>
+ typeof value === "string" && value.length > 60
+ ? value.substring(0, 60) + "..."
+ : value,
+ 2,
+ ),
+ );
+
+ console.log(`Writing ${filename}`);
+ fs.writeFileSync(filename, JSON.stringify(result));
+
+ if (failures.length > 0) {
+ console.error(`Failures:\n${failures}`);
+ process.exit(1);
+ }
+}
+
+async function claCheck(ctx: CheckContext): Promise {
+ if (!["opened", "synchronize", "reopened"].includes(ctx.eventAction)) {
+ return { actions: [] };
+ }
+
+ let author = ctx.author.toLowerCase();
+ if (author === "dependabot[bot]") {
+ console.log(`claCheck: ignoring ${ctx.author}`);
+ return { actions: [] };
+ }
+
+ let contributors = fs
+ .readFileSync("contributors.yml", "utf8")
+ .split(/\r?\n/)
+ .map((line) => line.match(/^\s*-\s*(.+?)\s*$/)?.[1]?.toLowerCase())
+ .filter((contributor): contributor is string => Boolean(contributor));
+ let signedCla = contributors.includes(author);
+ console.log(`claCheck: ${ctx.author} signed CLA: ${signedCla}`);
+
+ // Dry runs to start so we can monitor the flow alongside the bot
+ let DRY_RUN = true;
+ if (DRY_RUN) {
+ if (signedCla) {
+ console.log(
+ `claCheck: dry run; would add '${CLA_SIGNED_LABEL}' label and comment that the CLA is signed`,
+ );
+ } else {
+ console.log(
+ `claCheck: dry run; would request CLA signature and fail PR checks`,
+ );
+ }
+ return { actions: [] };
+ }
+
+ if (signedCla) {
+ return {
+ actions: [
+ { type: "add-label", label: CLA_SIGNED_LABEL },
+ {
+ type: "upsert-sticky-comment",
+ marker: CLA_MARKER,
+ body: CLA_SIGNED_COMMENT,
+ },
+ ],
+ };
+ }
+
+ return {
+ actions: [
+ {
+ type: "upsert-sticky-comment",
+ marker: CLA_MARKER,
+ body: getClaMissingComment(ctx),
+ },
+ ],
+ failureMessage: `CLA check failed: ${ctx.author} is not listed in contributors.yml`,
+ };
+}
+
+async function changeFileCheck(ctx: CheckContext): Promise {
+ if (ctx.baseBranch !== "main") return { actions: [] };
+ if (!["opened", "synchronize", "reopened"].includes(ctx.eventAction)) {
+ return { actions: [] };
+ }
+
+ let files = await getPrFiles(ctx.prNumber);
+ let touchesPackageFiles = files.some((f) =>
+ f.filename.startsWith("packages/"),
+ );
+ let regex =
+ /^packages\/[^/]+\/\.changes\/(major|minor|patch|unstable)\.[^/]+\.md$/;
+ let summaries: ChangeFileSummary[] = files
+ .filter((f) => f.status !== "removed" && regex.test(f.filename))
+ .map((f) => {
+ let type = f.filename.match(regex)?.[1] ?? "unknown";
+ let firstLine =
+ fs.readFileSync(f.filename, "utf8").split(/\r?\n/, 1)[0].trim() ??
+ "_No first line found._";
+
+ return {
+ type,
+ firstLine,
+ };
+ });
+
+ console.log(`changeFileCheck: found ${summaries.length} change files`);
+
+ if (summaries.length === 0 && !touchesPackageFiles) {
+ console.log("changeFileCheck: no package files changed");
+ return { actions: [] };
+ }
+
+ let body = CHANGE_FILE_MISSING_COMMENT;
+
+ if (summaries.length > 0) {
+ body = [
+ CHANGE_FILE_FOUND_COMMENT,
+ "| Type | Change |",
+ "| --- | --- |",
+ ...summaries.map(
+ (s) => `| \`${s.type}\` | ${s.firstLine.replaceAll("|", "\\|")} |`,
+ ),
+ ].join("\n");
+ }
+
+ return {
+ actions: [
+ {
+ type: "upsert-sticky-comment",
+ marker: CHANGE_FILE_MARKER,
+ body,
+ },
+ ],
+ };
+}
+
+async function featurePrCheck(ctx: CheckContext): Promise {
+ if (ctx.eventAction !== "labeled") return { actions: [] };
+ if (ctx.labelName !== "feature-request") return { actions: [] };
+
+ console.log(`featurePrCheck: closing PR ${ctx.prNumber}`);
+ return {
+ actions: [
+ { type: "create-comment", body: CLOSE_FEATURE_PR_COMMENT },
+ { type: "remove-label", label: ctx.labelName },
+ { type: "close-pr" },
+ ],
+ };
+}
+
+// ---------- Action dispatch ----------
+
+async function runActions() {
+ if (!fs.existsSync(filename)) {
+ console.log(`No result file found at ${filename}; skipping actions`);
+ return;
+ }
+
+ let { prNumber, actions } = JSON.parse(fs.readFileSync(filename, "utf8")) as {
+ prNumber: number;
+ actions: Action[];
+ };
+
+ if (actions.length === 0) {
+ console.log("No actions to apply");
+ return;
+ }
+
+ console.log(actions);
+
+ for (let action of actions) {
+ switch (action.type) {
+ case "upsert-sticky-comment": {
+ let comments = await getPrComments(prNumber);
+ let existing = comments.find(
+ (c) =>
+ c.user?.login === "github-actions[bot]" &&
+ c.body?.includes(action.marker),
+ );
+ if (existing) {
+ console.log(`Updating sticky comment #${existing.id}`);
+ await updatePrComment(existing.id, action.body);
+ } else {
+ console.log("Creating sticky comment");
+ await createPrComment(prNumber, action.body);
+ }
+ break;
+ }
+ case "create-comment": {
+ console.log("Creating comment");
+ await createPrComment(prNumber, action.body);
+ break;
+ }
+ case "add-label": {
+ console.log(`Adding label '${action.label}'`);
+ await addPrLabels(prNumber, [action.label]);
+ break;
+ }
+ case "remove-label": {
+ console.log(`Removing label '${action.label}'`);
+ await removePrLabel(prNumber, action.label);
+ break;
+ }
+ case "close-pr": {
+ console.log(`Closing PR ${prNumber}`);
+ await closePr(prNumber);
+ break;
+ }
+ }
+ }
+}
+
+// Utils
+
+function usage(): never {
+ console.error(
+ "Usage:\n" +
+ " node scripts/pr.ts check \n" +
+ " node scripts/pr.ts actions ",
+ );
+ process.exit(1);
+}
+
+function requireEnv(name: string): string {
+ let value = process.env[name];
+ if (!value) {
+ console.error(`Missing required env var: ${name}`);
+ process.exit(1);
+ }
+ return value;
+}
diff --git a/tests/testdata/ts_react_router_update_link/expected.json b/tests/testdata/ts_react_router_update_link/expected.json
new file mode 100644
index 0000000..de74934
--- /dev/null
+++ b/tests/testdata/ts_react_router_update_link/expected.json
@@ -0,0 +1,5196 @@
+{
+ "version": "v1",
+ "actions": [
+ {
+ "action": "update",
+ "node": {
+ "tree": "before",
+ "path": [
+ 42,
+ 4,
+ 0,
+ 0,
+ 1,
+ 0,
+ 1,
+ 0,
+ 2
+ ],
+ "type": "template_string",
+ "label": "` -\u003e https://reactrouter.com/7.16.0/upgrading/future#future${flag.toLowerCase()}`",
+ "start_byte": 22717,
+ "end_byte": 22802
+ },
+ "old_value": "` -\u003e https://reactrouter.com/7.16.0/upgrading/future#future${flag.toLowerCase()}`",
+ "new_value": "` -\u003e https://reactrouter.com/v7/upgrading/future#future${flag.toLowerCase()}`",
+ "dest_node": {
+ "tree": "after",
+ "path": [
+ 42,
+ 4,
+ 0,
+ 0,
+ 1,
+ 0,
+ 1,
+ 0,
+ 2
+ ],
+ "type": "template_string",
+ "label": "` -\u003e https://reactrouter.com/v7/upgrading/future#future${flag.toLowerCase()}`",
+ "start_byte": 22717,
+ "end_byte": 22798
+ }
+ }
+ ],
+ "line_alignment": [
+ {
+ "left_line": 0,
+ "right_line": 0
+ },
+ {
+ "left_line": 1,
+ "right_line": 1
+ },
+ {
+ "left_line": 2,
+ "right_line": 2
+ },
+ {
+ "left_line": 3,
+ "right_line": 3
+ },
+ {
+ "left_line": 4,
+ "right_line": 4
+ },
+ {
+ "left_line": 5,
+ "right_line": 5
+ },
+ {
+ "left_line": 6,
+ "right_line": 6
+ },
+ {
+ "left_line": 7,
+ "right_line": 7
+ },
+ {
+ "left_line": 8,
+ "right_line": 8
+ },
+ {
+ "left_line": 9,
+ "right_line": 9
+ },
+ {
+ "left_line": 10,
+ "right_line": 10
+ },
+ {
+ "left_line": 11,
+ "right_line": 11
+ },
+ {
+ "left_line": 12,
+ "right_line": 12
+ },
+ {
+ "left_line": 13,
+ "right_line": 13
+ },
+ {
+ "left_line": 14,
+ "right_line": 14
+ },
+ {
+ "left_line": 15,
+ "right_line": 15
+ },
+ {
+ "left_line": 16,
+ "right_line": 16
+ },
+ {
+ "left_line": 17,
+ "right_line": 17
+ },
+ {
+ "left_line": 18,
+ "right_line": 18
+ },
+ {
+ "left_line": 19,
+ "right_line": 19
+ },
+ {
+ "left_line": 20,
+ "right_line": 20
+ },
+ {
+ "left_line": 21,
+ "right_line": 21
+ },
+ {
+ "left_line": 22,
+ "right_line": 22
+ },
+ {
+ "left_line": 23,
+ "right_line": 23
+ },
+ {
+ "left_line": 24,
+ "right_line": 24
+ },
+ {
+ "left_line": 25,
+ "right_line": 25
+ },
+ {
+ "left_line": 26,
+ "right_line": 26
+ },
+ {
+ "left_line": 27,
+ "right_line": 27
+ },
+ {
+ "left_line": 28,
+ "right_line": 28
+ },
+ {
+ "left_line": 29,
+ "right_line": 29
+ },
+ {
+ "left_line": 30,
+ "right_line": 30
+ },
+ {
+ "left_line": 31,
+ "right_line": 31
+ },
+ {
+ "left_line": 32,
+ "right_line": 32
+ },
+ {
+ "left_line": 33,
+ "right_line": 33
+ },
+ {
+ "left_line": 34,
+ "right_line": 34
+ },
+ {
+ "left_line": 35,
+ "right_line": 35
+ },
+ {
+ "left_line": 36,
+ "right_line": 36
+ },
+ {
+ "left_line": 37,
+ "right_line": 37
+ },
+ {
+ "left_line": 38,
+ "right_line": 38
+ },
+ {
+ "left_line": 39,
+ "right_line": 39
+ },
+ {
+ "left_line": 40,
+ "right_line": 40
+ },
+ {
+ "left_line": 41,
+ "right_line": 41
+ },
+ {
+ "left_line": 42,
+ "right_line": 42
+ },
+ {
+ "left_line": 43,
+ "right_line": 43
+ },
+ {
+ "left_line": 44,
+ "right_line": 44
+ },
+ {
+ "left_line": 45,
+ "right_line": 45
+ },
+ {
+ "left_line": 46,
+ "right_line": 46
+ },
+ {
+ "left_line": 47,
+ "right_line": 47
+ },
+ {
+ "left_line": 48,
+ "right_line": 48
+ },
+ {
+ "left_line": 49,
+ "right_line": 49
+ },
+ {
+ "left_line": 50,
+ "right_line": 50
+ },
+ {
+ "left_line": 51,
+ "right_line": 51
+ },
+ {
+ "left_line": 52,
+ "right_line": 52
+ },
+ {
+ "left_line": 53,
+ "right_line": 53
+ },
+ {
+ "left_line": 54,
+ "right_line": 54
+ },
+ {
+ "left_line": 55,
+ "right_line": 55
+ },
+ {
+ "left_line": 56,
+ "right_line": 56
+ },
+ {
+ "left_line": 57,
+ "right_line": 57
+ },
+ {
+ "left_line": 58,
+ "right_line": 58
+ },
+ {
+ "left_line": 59,
+ "right_line": 59
+ },
+ {
+ "left_line": 60,
+ "right_line": 60
+ },
+ {
+ "left_line": 61,
+ "right_line": 61
+ },
+ {
+ "left_line": 62,
+ "right_line": 62
+ },
+ {
+ "left_line": 63,
+ "right_line": 63
+ },
+ {
+ "left_line": 64,
+ "right_line": 64
+ },
+ {
+ "left_line": 65,
+ "right_line": 65
+ },
+ {
+ "left_line": 66,
+ "right_line": 66
+ },
+ {
+ "left_line": 67,
+ "right_line": 67
+ },
+ {
+ "left_line": 68,
+ "right_line": 68
+ },
+ {
+ "left_line": 69,
+ "right_line": 69
+ },
+ {
+ "left_line": 70,
+ "right_line": 70
+ },
+ {
+ "left_line": 71,
+ "right_line": 71
+ },
+ {
+ "left_line": 72,
+ "right_line": 72
+ },
+ {
+ "left_line": 73,
+ "right_line": 73
+ },
+ {
+ "left_line": 74,
+ "right_line": 74
+ },
+ {
+ "left_line": 75,
+ "right_line": 75
+ },
+ {
+ "left_line": 76,
+ "right_line": 76
+ },
+ {
+ "left_line": 77,
+ "right_line": 77
+ },
+ {
+ "left_line": 78,
+ "right_line": 78
+ },
+ {
+ "left_line": 79,
+ "right_line": 79
+ },
+ {
+ "left_line": 80,
+ "right_line": 80
+ },
+ {
+ "left_line": 81,
+ "right_line": 81
+ },
+ {
+ "left_line": 82,
+ "right_line": 82
+ },
+ {
+ "left_line": 83,
+ "right_line": 83
+ },
+ {
+ "left_line": 84,
+ "right_line": 84
+ },
+ {
+ "left_line": 85,
+ "right_line": 85
+ },
+ {
+ "left_line": 86,
+ "right_line": 86
+ },
+ {
+ "left_line": 87,
+ "right_line": 87
+ },
+ {
+ "left_line": 88,
+ "right_line": 88
+ },
+ {
+ "left_line": 89,
+ "right_line": 89
+ },
+ {
+ "left_line": 90,
+ "right_line": 90
+ },
+ {
+ "left_line": 91,
+ "right_line": 91
+ },
+ {
+ "left_line": 92,
+ "right_line": 92
+ },
+ {
+ "left_line": 93,
+ "right_line": 93
+ },
+ {
+ "left_line": 94,
+ "right_line": 94
+ },
+ {
+ "left_line": 95,
+ "right_line": 95
+ },
+ {
+ "left_line": 96,
+ "right_line": 96
+ },
+ {
+ "left_line": 97,
+ "right_line": 97
+ },
+ {
+ "left_line": 98,
+ "right_line": 98
+ },
+ {
+ "left_line": 99,
+ "right_line": 99
+ },
+ {
+ "left_line": 100,
+ "right_line": 100
+ },
+ {
+ "left_line": 101,
+ "right_line": 101
+ },
+ {
+ "left_line": 102,
+ "right_line": 102
+ },
+ {
+ "left_line": 103,
+ "right_line": 103
+ },
+ {
+ "left_line": 104,
+ "right_line": 104
+ },
+ {
+ "left_line": 105,
+ "right_line": 105
+ },
+ {
+ "left_line": 106,
+ "right_line": 106
+ },
+ {
+ "left_line": 107,
+ "right_line": 107
+ },
+ {
+ "left_line": 108,
+ "right_line": 108
+ },
+ {
+ "left_line": 109,
+ "right_line": 109
+ },
+ {
+ "left_line": 110,
+ "right_line": 110
+ },
+ {
+ "left_line": 111,
+ "right_line": 111
+ },
+ {
+ "left_line": 112,
+ "right_line": 112
+ },
+ {
+ "left_line": 113,
+ "right_line": 113
+ },
+ {
+ "left_line": 114,
+ "right_line": 114
+ },
+ {
+ "left_line": 115,
+ "right_line": 115
+ },
+ {
+ "left_line": 116,
+ "right_line": 116
+ },
+ {
+ "left_line": 117,
+ "right_line": 117
+ },
+ {
+ "left_line": 118,
+ "right_line": 118
+ },
+ {
+ "left_line": 119,
+ "right_line": 119
+ },
+ {
+ "left_line": 120,
+ "right_line": 120
+ },
+ {
+ "left_line": 121,
+ "right_line": 121
+ },
+ {
+ "left_line": 122,
+ "right_line": 122
+ },
+ {
+ "left_line": 123,
+ "right_line": 123
+ },
+ {
+ "left_line": 124,
+ "right_line": 124
+ },
+ {
+ "left_line": 125,
+ "right_line": 125
+ },
+ {
+ "left_line": 126,
+ "right_line": 126
+ },
+ {
+ "left_line": 127,
+ "right_line": 127
+ },
+ {
+ "left_line": 128,
+ "right_line": 128
+ },
+ {
+ "left_line": 129,
+ "right_line": 129
+ },
+ {
+ "left_line": 130,
+ "right_line": 130
+ },
+ {
+ "left_line": 131,
+ "right_line": 131
+ },
+ {
+ "left_line": 132,
+ "right_line": 132
+ },
+ {
+ "left_line": 133,
+ "right_line": 133
+ },
+ {
+ "left_line": 134,
+ "right_line": 134
+ },
+ {
+ "left_line": 135,
+ "right_line": 135
+ },
+ {
+ "left_line": 136,
+ "right_line": 136
+ },
+ {
+ "left_line": 137,
+ "right_line": 137
+ },
+ {
+ "left_line": 138,
+ "right_line": 138
+ },
+ {
+ "left_line": 139,
+ "right_line": 139
+ },
+ {
+ "left_line": 140,
+ "right_line": 140
+ },
+ {
+ "left_line": 141,
+ "right_line": 141
+ },
+ {
+ "left_line": 142,
+ "right_line": 142
+ },
+ {
+ "left_line": 143,
+ "right_line": 143
+ },
+ {
+ "left_line": 144,
+ "right_line": 144
+ },
+ {
+ "left_line": 145,
+ "right_line": 145
+ },
+ {
+ "left_line": 146,
+ "right_line": 146
+ },
+ {
+ "left_line": 147,
+ "right_line": 147
+ },
+ {
+ "left_line": 148,
+ "right_line": 148
+ },
+ {
+ "left_line": 149,
+ "right_line": 149
+ },
+ {
+ "left_line": 150,
+ "right_line": 150
+ },
+ {
+ "left_line": 151,
+ "right_line": 151
+ },
+ {
+ "left_line": 152,
+ "right_line": 152
+ },
+ {
+ "left_line": 153,
+ "right_line": 153
+ },
+ {
+ "left_line": 154,
+ "right_line": 154
+ },
+ {
+ "left_line": 155,
+ "right_line": 155
+ },
+ {
+ "left_line": 156,
+ "right_line": 156
+ },
+ {
+ "left_line": 157,
+ "right_line": 157
+ },
+ {
+ "left_line": 158,
+ "right_line": 158
+ },
+ {
+ "left_line": 159,
+ "right_line": 159
+ },
+ {
+ "left_line": 160,
+ "right_line": 160
+ },
+ {
+ "left_line": 161,
+ "right_line": 161
+ },
+ {
+ "left_line": 162,
+ "right_line": 162
+ },
+ {
+ "left_line": 163,
+ "right_line": 163
+ },
+ {
+ "left_line": 164,
+ "right_line": 164
+ },
+ {
+ "left_line": 165,
+ "right_line": 165
+ },
+ {
+ "left_line": 166,
+ "right_line": 166
+ },
+ {
+ "left_line": 167,
+ "right_line": 167
+ },
+ {
+ "left_line": 168,
+ "right_line": 168
+ },
+ {
+ "left_line": 169,
+ "right_line": 169
+ },
+ {
+ "left_line": 170,
+ "right_line": 170
+ },
+ {
+ "left_line": 171,
+ "right_line": 171
+ },
+ {
+ "left_line": 172,
+ "right_line": 172
+ },
+ {
+ "left_line": 173,
+ "right_line": 173
+ },
+ {
+ "left_line": 174,
+ "right_line": 174
+ },
+ {
+ "left_line": 175,
+ "right_line": 175
+ },
+ {
+ "left_line": 176,
+ "right_line": 176
+ },
+ {
+ "left_line": 177,
+ "right_line": 177
+ },
+ {
+ "left_line": 178,
+ "right_line": 178
+ },
+ {
+ "left_line": 179,
+ "right_line": 179
+ },
+ {
+ "left_line": 180,
+ "right_line": 180
+ },
+ {
+ "left_line": 181,
+ "right_line": 181
+ },
+ {
+ "left_line": 182,
+ "right_line": 182
+ },
+ {
+ "left_line": 183,
+ "right_line": 183
+ },
+ {
+ "left_line": 184,
+ "right_line": 184
+ },
+ {
+ "left_line": 185,
+ "right_line": 185
+ },
+ {
+ "left_line": 186,
+ "right_line": 186
+ },
+ {
+ "left_line": 187,
+ "right_line": 187
+ },
+ {
+ "left_line": 188,
+ "right_line": 188
+ },
+ {
+ "left_line": 189,
+ "right_line": 189
+ },
+ {
+ "left_line": 190,
+ "right_line": 190
+ },
+ {
+ "left_line": 191,
+ "right_line": 191
+ },
+ {
+ "left_line": 192,
+ "right_line": 192
+ },
+ {
+ "left_line": 193,
+ "right_line": 193
+ },
+ {
+ "left_line": 194,
+ "right_line": 194
+ },
+ {
+ "left_line": 195,
+ "right_line": 195
+ },
+ {
+ "left_line": 196,
+ "right_line": 196
+ },
+ {
+ "left_line": 197,
+ "right_line": 197
+ },
+ {
+ "left_line": 198,
+ "right_line": 198
+ },
+ {
+ "left_line": 199,
+ "right_line": 199
+ },
+ {
+ "left_line": 200,
+ "right_line": 200
+ },
+ {
+ "left_line": 201,
+ "right_line": 201
+ },
+ {
+ "left_line": 202,
+ "right_line": 202
+ },
+ {
+ "left_line": 203,
+ "right_line": 203
+ },
+ {
+ "left_line": 204,
+ "right_line": 204
+ },
+ {
+ "left_line": 205,
+ "right_line": 205
+ },
+ {
+ "left_line": 206,
+ "right_line": 206
+ },
+ {
+ "left_line": 207,
+ "right_line": 207
+ },
+ {
+ "left_line": 208,
+ "right_line": 208
+ },
+ {
+ "left_line": 209,
+ "right_line": 209
+ },
+ {
+ "left_line": 210,
+ "right_line": 210
+ },
+ {
+ "left_line": 211,
+ "right_line": 211
+ },
+ {
+ "left_line": 212,
+ "right_line": 212
+ },
+ {
+ "left_line": 213,
+ "right_line": 213
+ },
+ {
+ "left_line": 214,
+ "right_line": 214
+ },
+ {
+ "left_line": 215,
+ "right_line": 215
+ },
+ {
+ "left_line": 216,
+ "right_line": 216
+ },
+ {
+ "left_line": 217,
+ "right_line": 217
+ },
+ {
+ "left_line": 218,
+ "right_line": 218
+ },
+ {
+ "left_line": 219,
+ "right_line": 219
+ },
+ {
+ "left_line": 220,
+ "right_line": 220
+ },
+ {
+ "left_line": 221,
+ "right_line": 221
+ },
+ {
+ "left_line": 222,
+ "right_line": 222
+ },
+ {
+ "left_line": 223,
+ "right_line": 223
+ },
+ {
+ "left_line": 224,
+ "right_line": 224
+ },
+ {
+ "left_line": 225,
+ "right_line": 225
+ },
+ {
+ "left_line": 226,
+ "right_line": 226
+ },
+ {
+ "left_line": 227,
+ "right_line": 227
+ },
+ {
+ "left_line": 228,
+ "right_line": 228
+ },
+ {
+ "left_line": 229,
+ "right_line": 229
+ },
+ {
+ "left_line": 230,
+ "right_line": 230
+ },
+ {
+ "left_line": 231,
+ "right_line": 231
+ },
+ {
+ "left_line": 232,
+ "right_line": 232
+ },
+ {
+ "left_line": 233,
+ "right_line": 233
+ },
+ {
+ "left_line": 234,
+ "right_line": 234
+ },
+ {
+ "left_line": 235,
+ "right_line": 235
+ },
+ {
+ "left_line": 236,
+ "right_line": 236
+ },
+ {
+ "left_line": 237,
+ "right_line": 237
+ },
+ {
+ "left_line": 238,
+ "right_line": 238
+ },
+ {
+ "left_line": 239,
+ "right_line": 239
+ },
+ {
+ "left_line": 240,
+ "right_line": 240
+ },
+ {
+ "left_line": 241,
+ "right_line": 241
+ },
+ {
+ "left_line": 242,
+ "right_line": 242
+ },
+ {
+ "left_line": 243,
+ "right_line": 243
+ },
+ {
+ "left_line": 244,
+ "right_line": 244
+ },
+ {
+ "left_line": 245,
+ "right_line": 245
+ },
+ {
+ "left_line": 246,
+ "right_line": 246
+ },
+ {
+ "left_line": 247,
+ "right_line": 247
+ },
+ {
+ "left_line": 248,
+ "right_line": 248
+ },
+ {
+ "left_line": 249,
+ "right_line": 249
+ },
+ {
+ "left_line": 250,
+ "right_line": 250
+ },
+ {
+ "left_line": 251,
+ "right_line": 251
+ },
+ {
+ "left_line": 252,
+ "right_line": 252
+ },
+ {
+ "left_line": 253,
+ "right_line": 253
+ },
+ {
+ "left_line": 254,
+ "right_line": 254
+ },
+ {
+ "left_line": 255,
+ "right_line": 255
+ },
+ {
+ "left_line": 256,
+ "right_line": 256
+ },
+ {
+ "left_line": 257,
+ "right_line": 257
+ },
+ {
+ "left_line": 258,
+ "right_line": 258
+ },
+ {
+ "left_line": 259,
+ "right_line": 259
+ },
+ {
+ "left_line": 260,
+ "right_line": 260
+ },
+ {
+ "left_line": 261,
+ "right_line": 261
+ },
+ {
+ "left_line": 262,
+ "right_line": 262
+ },
+ {
+ "left_line": 263,
+ "right_line": 263
+ },
+ {
+ "left_line": 264,
+ "right_line": 264
+ },
+ {
+ "left_line": 265,
+ "right_line": 265
+ },
+ {
+ "left_line": 266,
+ "right_line": 266
+ },
+ {
+ "left_line": 267,
+ "right_line": 267
+ },
+ {
+ "left_line": 268,
+ "right_line": 268
+ },
+ {
+ "left_line": 269,
+ "right_line": 269
+ },
+ {
+ "left_line": 270,
+ "right_line": 270
+ },
+ {
+ "left_line": 271,
+ "right_line": 271
+ },
+ {
+ "left_line": 272,
+ "right_line": 272
+ },
+ {
+ "left_line": 273,
+ "right_line": 273
+ },
+ {
+ "left_line": 274,
+ "right_line": 274
+ },
+ {
+ "left_line": 275,
+ "right_line": 275
+ },
+ {
+ "left_line": 276,
+ "right_line": 276
+ },
+ {
+ "left_line": 277,
+ "right_line": 277
+ },
+ {
+ "left_line": 278,
+ "right_line": 278
+ },
+ {
+ "left_line": 279,
+ "right_line": 279
+ },
+ {
+ "left_line": 280,
+ "right_line": 280
+ },
+ {
+ "left_line": 281,
+ "right_line": 281
+ },
+ {
+ "left_line": 282,
+ "right_line": 282
+ },
+ {
+ "left_line": 283,
+ "right_line": 283
+ },
+ {
+ "left_line": 284,
+ "right_line": 284
+ },
+ {
+ "left_line": 285,
+ "right_line": 285
+ },
+ {
+ "left_line": 286,
+ "right_line": 286
+ },
+ {
+ "left_line": 287,
+ "right_line": 287
+ },
+ {
+ "left_line": 288,
+ "right_line": 288
+ },
+ {
+ "left_line": 289,
+ "right_line": 289
+ },
+ {
+ "left_line": 290,
+ "right_line": 290
+ },
+ {
+ "left_line": 291,
+ "right_line": 291
+ },
+ {
+ "left_line": 292,
+ "right_line": 292
+ },
+ {
+ "left_line": 293,
+ "right_line": 293
+ },
+ {
+ "left_line": 294,
+ "right_line": 294
+ },
+ {
+ "left_line": 295,
+ "right_line": 295
+ },
+ {
+ "left_line": 296,
+ "right_line": 296
+ },
+ {
+ "left_line": 297,
+ "right_line": 297
+ },
+ {
+ "left_line": 298,
+ "right_line": 298
+ },
+ {
+ "left_line": 299,
+ "right_line": 299
+ },
+ {
+ "left_line": 300,
+ "right_line": 300
+ },
+ {
+ "left_line": 301,
+ "right_line": 301
+ },
+ {
+ "left_line": 302,
+ "right_line": 302
+ },
+ {
+ "left_line": 303,
+ "right_line": 303
+ },
+ {
+ "left_line": 304,
+ "right_line": 304
+ },
+ {
+ "left_line": 305,
+ "right_line": 305
+ },
+ {
+ "left_line": 306,
+ "right_line": 306
+ },
+ {
+ "left_line": 307,
+ "right_line": 307
+ },
+ {
+ "left_line": 308,
+ "right_line": 308
+ },
+ {
+ "left_line": 309,
+ "right_line": 309
+ },
+ {
+ "left_line": 310,
+ "right_line": 310
+ },
+ {
+ "left_line": 311,
+ "right_line": 311
+ },
+ {
+ "left_line": 312,
+ "right_line": 312
+ },
+ {
+ "left_line": 313,
+ "right_line": 313
+ },
+ {
+ "left_line": 314,
+ "right_line": 314
+ },
+ {
+ "left_line": 315,
+ "right_line": 315
+ },
+ {
+ "left_line": 316,
+ "right_line": 316
+ },
+ {
+ "left_line": 317,
+ "right_line": 317
+ },
+ {
+ "left_line": 318,
+ "right_line": 318
+ },
+ {
+ "left_line": 319,
+ "right_line": 319
+ },
+ {
+ "left_line": 320,
+ "right_line": 320
+ },
+ {
+ "left_line": 321,
+ "right_line": 321
+ },
+ {
+ "left_line": 322,
+ "right_line": 322
+ },
+ {
+ "left_line": 323,
+ "right_line": 323
+ },
+ {
+ "left_line": 324,
+ "right_line": 324
+ },
+ {
+ "left_line": 325,
+ "right_line": 325
+ },
+ {
+ "left_line": 326,
+ "right_line": 326
+ },
+ {
+ "left_line": 327,
+ "right_line": 327
+ },
+ {
+ "left_line": 328,
+ "right_line": 328
+ },
+ {
+ "left_line": 329,
+ "right_line": 329
+ },
+ {
+ "left_line": 330,
+ "right_line": 330
+ },
+ {
+ "left_line": 331,
+ "right_line": 331
+ },
+ {
+ "left_line": 332,
+ "right_line": 332
+ },
+ {
+ "left_line": 333,
+ "right_line": 333
+ },
+ {
+ "left_line": 334,
+ "right_line": 334
+ },
+ {
+ "left_line": 335,
+ "right_line": 335
+ },
+ {
+ "left_line": 336,
+ "right_line": 336
+ },
+ {
+ "left_line": 337,
+ "right_line": 337
+ },
+ {
+ "left_line": 338,
+ "right_line": 338
+ },
+ {
+ "left_line": 339,
+ "right_line": 339
+ },
+ {
+ "left_line": 340,
+ "right_line": 340
+ },
+ {
+ "left_line": 341,
+ "right_line": 341
+ },
+ {
+ "left_line": 342,
+ "right_line": 342
+ },
+ {
+ "left_line": 343,
+ "right_line": 343
+ },
+ {
+ "left_line": 344,
+ "right_line": 344
+ },
+ {
+ "left_line": 345,
+ "right_line": 345
+ },
+ {
+ "left_line": 346,
+ "right_line": 346
+ },
+ {
+ "left_line": 347,
+ "right_line": 347
+ },
+ {
+ "left_line": 348,
+ "right_line": 348
+ },
+ {
+ "left_line": 349,
+ "right_line": 349
+ },
+ {
+ "left_line": 350,
+ "right_line": 350
+ },
+ {
+ "left_line": 351,
+ "right_line": 351
+ },
+ {
+ "left_line": 352,
+ "right_line": 352
+ },
+ {
+ "left_line": 353,
+ "right_line": 353
+ },
+ {
+ "left_line": 354,
+ "right_line": 354
+ },
+ {
+ "left_line": 355,
+ "right_line": 355
+ },
+ {
+ "left_line": 356,
+ "right_line": 356
+ },
+ {
+ "left_line": 357,
+ "right_line": 357
+ },
+ {
+ "left_line": 358,
+ "right_line": 358
+ },
+ {
+ "left_line": 359,
+ "right_line": 359
+ },
+ {
+ "left_line": 360,
+ "right_line": 360
+ },
+ {
+ "left_line": 361,
+ "right_line": 361
+ },
+ {
+ "left_line": 362,
+ "right_line": 362
+ },
+ {
+ "left_line": 363,
+ "right_line": 363
+ },
+ {
+ "left_line": 364,
+ "right_line": 364
+ },
+ {
+ "left_line": 365,
+ "right_line": 365
+ },
+ {
+ "left_line": 366,
+ "right_line": 366
+ },
+ {
+ "left_line": 367,
+ "right_line": 367
+ },
+ {
+ "left_line": 368,
+ "right_line": 368
+ },
+ {
+ "left_line": 369,
+ "right_line": 369
+ },
+ {
+ "left_line": 370,
+ "right_line": 370
+ },
+ {
+ "left_line": 371,
+ "right_line": 371
+ },
+ {
+ "left_line": 372,
+ "right_line": 372
+ },
+ {
+ "left_line": 373,
+ "right_line": 373
+ },
+ {
+ "left_line": 374,
+ "right_line": 374
+ },
+ {
+ "left_line": 375,
+ "right_line": 375
+ },
+ {
+ "left_line": 376,
+ "right_line": 376
+ },
+ {
+ "left_line": 377,
+ "right_line": 377
+ },
+ {
+ "left_line": 378,
+ "right_line": 378
+ },
+ {
+ "left_line": 379,
+ "right_line": 379
+ },
+ {
+ "left_line": 380,
+ "right_line": 380
+ },
+ {
+ "left_line": 381,
+ "right_line": 381
+ },
+ {
+ "left_line": 382,
+ "right_line": 382
+ },
+ {
+ "left_line": 383,
+ "right_line": 383
+ },
+ {
+ "left_line": 384,
+ "right_line": 384
+ },
+ {
+ "left_line": 385,
+ "right_line": 385
+ },
+ {
+ "left_line": 386,
+ "right_line": 386
+ },
+ {
+ "left_line": 387,
+ "right_line": 387
+ },
+ {
+ "left_line": 388,
+ "right_line": 388
+ },
+ {
+ "left_line": 389,
+ "right_line": 389
+ },
+ {
+ "left_line": 390,
+ "right_line": 390
+ },
+ {
+ "left_line": 391,
+ "right_line": 391
+ },
+ {
+ "left_line": 392,
+ "right_line": 392
+ },
+ {
+ "left_line": 393,
+ "right_line": 393
+ },
+ {
+ "left_line": 394,
+ "right_line": 394
+ },
+ {
+ "left_line": 395,
+ "right_line": 395
+ },
+ {
+ "left_line": 396,
+ "right_line": 396
+ },
+ {
+ "left_line": 397,
+ "right_line": 397
+ },
+ {
+ "left_line": 398,
+ "right_line": 398
+ },
+ {
+ "left_line": 399,
+ "right_line": 399
+ },
+ {
+ "left_line": 400,
+ "right_line": 400
+ },
+ {
+ "left_line": 401,
+ "right_line": 401
+ },
+ {
+ "left_line": 402,
+ "right_line": 402
+ },
+ {
+ "left_line": 403,
+ "right_line": 403
+ },
+ {
+ "left_line": 404,
+ "right_line": 404
+ },
+ {
+ "left_line": 405,
+ "right_line": 405
+ },
+ {
+ "left_line": 406,
+ "right_line": 406
+ },
+ {
+ "left_line": 407,
+ "right_line": 407
+ },
+ {
+ "left_line": 408,
+ "right_line": 408
+ },
+ {
+ "left_line": 409,
+ "right_line": 409
+ },
+ {
+ "left_line": 410,
+ "right_line": 410
+ },
+ {
+ "left_line": 411,
+ "right_line": 411
+ },
+ {
+ "left_line": 412,
+ "right_line": 412
+ },
+ {
+ "left_line": 413,
+ "right_line": 413
+ },
+ {
+ "left_line": 414,
+ "right_line": 414
+ },
+ {
+ "left_line": 415,
+ "right_line": 415
+ },
+ {
+ "left_line": 416,
+ "right_line": 416
+ },
+ {
+ "left_line": 417,
+ "right_line": 417
+ },
+ {
+ "left_line": 418,
+ "right_line": 418
+ },
+ {
+ "left_line": 419,
+ "right_line": 419
+ },
+ {
+ "left_line": 420,
+ "right_line": 420
+ },
+ {
+ "left_line": 421,
+ "right_line": 421
+ },
+ {
+ "left_line": 422,
+ "right_line": 422
+ },
+ {
+ "left_line": 423,
+ "right_line": 423
+ },
+ {
+ "left_line": 424,
+ "right_line": 424
+ },
+ {
+ "left_line": 425,
+ "right_line": 425
+ },
+ {
+ "left_line": 426,
+ "right_line": 426
+ },
+ {
+ "left_line": 427,
+ "right_line": 427
+ },
+ {
+ "left_line": 428,
+ "right_line": 428
+ },
+ {
+ "left_line": 429,
+ "right_line": 429
+ },
+ {
+ "left_line": 430,
+ "right_line": 430
+ },
+ {
+ "left_line": 431,
+ "right_line": 431
+ },
+ {
+ "left_line": 432,
+ "right_line": 432
+ },
+ {
+ "left_line": 433,
+ "right_line": 433
+ },
+ {
+ "left_line": 434,
+ "right_line": 434
+ },
+ {
+ "left_line": 435,
+ "right_line": 435
+ },
+ {
+ "left_line": 436,
+ "right_line": 436
+ },
+ {
+ "left_line": 437,
+ "right_line": 437
+ },
+ {
+ "left_line": 438,
+ "right_line": 438
+ },
+ {
+ "left_line": 439,
+ "right_line": 439
+ },
+ {
+ "left_line": 440,
+ "right_line": 440
+ },
+ {
+ "left_line": 441,
+ "right_line": 441
+ },
+ {
+ "left_line": 442,
+ "right_line": 442
+ },
+ {
+ "left_line": 443,
+ "right_line": 443
+ },
+ {
+ "left_line": 444,
+ "right_line": 444
+ },
+ {
+ "left_line": 445,
+ "right_line": 445
+ },
+ {
+ "left_line": 446,
+ "right_line": 446
+ },
+ {
+ "left_line": 447,
+ "right_line": 447
+ },
+ {
+ "left_line": 448,
+ "right_line": 448
+ },
+ {
+ "left_line": 449,
+ "right_line": 449
+ },
+ {
+ "left_line": 450,
+ "right_line": 450
+ },
+ {
+ "left_line": 451,
+ "right_line": 451
+ },
+ {
+ "left_line": 452,
+ "right_line": 452
+ },
+ {
+ "left_line": 453,
+ "right_line": 453
+ },
+ {
+ "left_line": 454,
+ "right_line": 454
+ },
+ {
+ "left_line": 455,
+ "right_line": 455
+ },
+ {
+ "left_line": 456,
+ "right_line": 456
+ },
+ {
+ "left_line": 457,
+ "right_line": 457
+ },
+ {
+ "left_line": 458,
+ "right_line": 458
+ },
+ {
+ "left_line": 459,
+ "right_line": 459
+ },
+ {
+ "left_line": 460,
+ "right_line": 460
+ },
+ {
+ "left_line": 461,
+ "right_line": 461
+ },
+ {
+ "left_line": 462,
+ "right_line": 462
+ },
+ {
+ "left_line": 463,
+ "right_line": 463
+ },
+ {
+ "left_line": 464,
+ "right_line": 464
+ },
+ {
+ "left_line": 465,
+ "right_line": 465
+ },
+ {
+ "left_line": 466,
+ "right_line": 466
+ },
+ {
+ "left_line": 467,
+ "right_line": 467
+ },
+ {
+ "left_line": 468,
+ "right_line": 468
+ },
+ {
+ "left_line": 469,
+ "right_line": 469
+ },
+ {
+ "left_line": 470,
+ "right_line": 470
+ },
+ {
+ "left_line": 471,
+ "right_line": 471
+ },
+ {
+ "left_line": 472,
+ "right_line": 472
+ },
+ {
+ "left_line": 473,
+ "right_line": 473
+ },
+ {
+ "left_line": 474,
+ "right_line": 474
+ },
+ {
+ "left_line": 475,
+ "right_line": 475
+ },
+ {
+ "left_line": 476,
+ "right_line": 476
+ },
+ {
+ "left_line": 477,
+ "right_line": 477
+ },
+ {
+ "left_line": 478,
+ "right_line": 478
+ },
+ {
+ "left_line": 479,
+ "right_line": 479
+ },
+ {
+ "left_line": 480,
+ "right_line": 480
+ },
+ {
+ "left_line": 481,
+ "right_line": 481
+ },
+ {
+ "left_line": 482,
+ "right_line": 482
+ },
+ {
+ "left_line": 483,
+ "right_line": 483
+ },
+ {
+ "left_line": 484,
+ "right_line": 484
+ },
+ {
+ "left_line": 485,
+ "right_line": 485
+ },
+ {
+ "left_line": 486,
+ "right_line": 486
+ },
+ {
+ "left_line": 487,
+ "right_line": 487
+ },
+ {
+ "left_line": 488,
+ "right_line": 488
+ },
+ {
+ "left_line": 489,
+ "right_line": 489
+ },
+ {
+ "left_line": 490,
+ "right_line": 490
+ },
+ {
+ "left_line": 491,
+ "right_line": 491
+ },
+ {
+ "left_line": 492,
+ "right_line": 492
+ },
+ {
+ "left_line": 493,
+ "right_line": 493
+ },
+ {
+ "left_line": 494,
+ "right_line": 494
+ },
+ {
+ "left_line": 495,
+ "right_line": 495
+ },
+ {
+ "left_line": 496,
+ "right_line": 496
+ },
+ {
+ "left_line": 497,
+ "right_line": 497
+ },
+ {
+ "left_line": 498,
+ "right_line": 498
+ },
+ {
+ "left_line": 499,
+ "right_line": 499
+ },
+ {
+ "left_line": 500,
+ "right_line": 500
+ },
+ {
+ "left_line": 501,
+ "right_line": 501
+ },
+ {
+ "left_line": 502,
+ "right_line": 502
+ },
+ {
+ "left_line": 503,
+ "right_line": 503
+ },
+ {
+ "left_line": 504,
+ "right_line": 504
+ },
+ {
+ "left_line": 505,
+ "right_line": 505
+ },
+ {
+ "left_line": 506,
+ "right_line": 506
+ },
+ {
+ "left_line": 507,
+ "right_line": 507
+ },
+ {
+ "left_line": 508,
+ "right_line": 508
+ },
+ {
+ "left_line": 509,
+ "right_line": 509
+ },
+ {
+ "left_line": 510,
+ "right_line": 510
+ },
+ {
+ "left_line": 511,
+ "right_line": 511
+ },
+ {
+ "left_line": 512,
+ "right_line": 512
+ },
+ {
+ "left_line": 513,
+ "right_line": 513
+ },
+ {
+ "left_line": 514,
+ "right_line": 514
+ },
+ {
+ "left_line": 515,
+ "right_line": 515
+ },
+ {
+ "left_line": 516,
+ "right_line": 516
+ },
+ {
+ "left_line": 517,
+ "right_line": 517
+ },
+ {
+ "left_line": 518,
+ "right_line": 518
+ },
+ {
+ "left_line": 519,
+ "right_line": 519
+ },
+ {
+ "left_line": 520,
+ "right_line": 520
+ },
+ {
+ "left_line": 521,
+ "right_line": 521
+ },
+ {
+ "left_line": 522,
+ "right_line": 522
+ },
+ {
+ "left_line": 523,
+ "right_line": 523
+ },
+ {
+ "left_line": 524,
+ "right_line": 524
+ },
+ {
+ "left_line": 525,
+ "right_line": 525
+ },
+ {
+ "left_line": 526,
+ "right_line": 526
+ },
+ {
+ "left_line": 527,
+ "right_line": 527
+ },
+ {
+ "left_line": 528,
+ "right_line": 528
+ },
+ {
+ "left_line": 529,
+ "right_line": 529
+ },
+ {
+ "left_line": 530,
+ "right_line": 530
+ },
+ {
+ "left_line": 531,
+ "right_line": 531
+ },
+ {
+ "left_line": 532,
+ "right_line": 532
+ },
+ {
+ "left_line": 533,
+ "right_line": 533
+ },
+ {
+ "left_line": 534,
+ "right_line": 534
+ },
+ {
+ "left_line": 535,
+ "right_line": 535
+ },
+ {
+ "left_line": 536,
+ "right_line": 536
+ },
+ {
+ "left_line": 537,
+ "right_line": 537
+ },
+ {
+ "left_line": 538,
+ "right_line": 538
+ },
+ {
+ "left_line": 539,
+ "right_line": 539
+ },
+ {
+ "left_line": 540,
+ "right_line": 540
+ },
+ {
+ "left_line": 541,
+ "right_line": 541
+ },
+ {
+ "left_line": 542,
+ "right_line": 542
+ },
+ {
+ "left_line": 543,
+ "right_line": 543
+ },
+ {
+ "left_line": 544,
+ "right_line": 544
+ },
+ {
+ "left_line": 545,
+ "right_line": 545
+ },
+ {
+ "left_line": 546,
+ "right_line": 546
+ },
+ {
+ "left_line": 547,
+ "right_line": 547
+ },
+ {
+ "left_line": 548,
+ "right_line": 548
+ },
+ {
+ "left_line": 549,
+ "right_line": 549
+ },
+ {
+ "left_line": 550,
+ "right_line": 550
+ },
+ {
+ "left_line": 551,
+ "right_line": 551
+ },
+ {
+ "left_line": 552,
+ "right_line": 552
+ },
+ {
+ "left_line": 553,
+ "right_line": 553
+ },
+ {
+ "left_line": 554,
+ "right_line": 554
+ },
+ {
+ "left_line": 555,
+ "right_line": 555
+ },
+ {
+ "left_line": 556,
+ "right_line": 556
+ },
+ {
+ "left_line": 557,
+ "right_line": 557
+ },
+ {
+ "left_line": 558,
+ "right_line": 558
+ },
+ {
+ "left_line": 559,
+ "right_line": 559
+ },
+ {
+ "left_line": 560,
+ "right_line": 560
+ },
+ {
+ "left_line": 561,
+ "right_line": 561
+ },
+ {
+ "left_line": 562,
+ "right_line": 562
+ },
+ {
+ "left_line": 563,
+ "right_line": 563
+ },
+ {
+ "left_line": 564,
+ "right_line": 564
+ },
+ {
+ "left_line": 565,
+ "right_line": 565
+ },
+ {
+ "left_line": 566,
+ "right_line": 566
+ },
+ {
+ "left_line": 567,
+ "right_line": 567
+ },
+ {
+ "left_line": 568,
+ "right_line": 568
+ },
+ {
+ "left_line": 569,
+ "right_line": 569
+ },
+ {
+ "left_line": 570,
+ "right_line": 570
+ },
+ {
+ "left_line": 571,
+ "right_line": 571
+ },
+ {
+ "left_line": 572,
+ "right_line": 572
+ },
+ {
+ "left_line": 573,
+ "right_line": 573
+ },
+ {
+ "left_line": 574,
+ "right_line": 574
+ },
+ {
+ "left_line": 575,
+ "right_line": 575
+ },
+ {
+ "left_line": 576,
+ "right_line": 576
+ },
+ {
+ "left_line": 577,
+ "right_line": 577
+ },
+ {
+ "left_line": 578,
+ "right_line": 578
+ },
+ {
+ "left_line": 579,
+ "right_line": 579
+ },
+ {
+ "left_line": 580,
+ "right_line": 580
+ },
+ {
+ "left_line": 581,
+ "right_line": 581
+ },
+ {
+ "left_line": 582,
+ "right_line": 582
+ },
+ {
+ "left_line": 583,
+ "right_line": 583
+ },
+ {
+ "left_line": 584,
+ "right_line": 584
+ },
+ {
+ "left_line": 585,
+ "right_line": 585
+ },
+ {
+ "left_line": 586,
+ "right_line": 586
+ },
+ {
+ "left_line": 587,
+ "right_line": 587
+ },
+ {
+ "left_line": 588,
+ "right_line": 588
+ },
+ {
+ "left_line": 589,
+ "right_line": 589
+ },
+ {
+ "left_line": 590,
+ "right_line": 590
+ },
+ {
+ "left_line": 591,
+ "right_line": 591
+ },
+ {
+ "left_line": 592,
+ "right_line": 592
+ },
+ {
+ "left_line": 593,
+ "right_line": 593
+ },
+ {
+ "left_line": 594,
+ "right_line": 594
+ },
+ {
+ "left_line": 595,
+ "right_line": 595
+ },
+ {
+ "left_line": 596,
+ "right_line": 596
+ },
+ {
+ "left_line": 597,
+ "right_line": 597
+ },
+ {
+ "left_line": 598,
+ "right_line": 598
+ },
+ {
+ "left_line": 599,
+ "right_line": 599
+ },
+ {
+ "left_line": 600,
+ "right_line": 600
+ },
+ {
+ "left_line": 601,
+ "right_line": 601
+ },
+ {
+ "left_line": 602,
+ "right_line": 602
+ },
+ {
+ "left_line": 603,
+ "right_line": 603
+ },
+ {
+ "left_line": 604,
+ "right_line": 604
+ },
+ {
+ "left_line": 605,
+ "right_line": 605
+ },
+ {
+ "left_line": 606,
+ "right_line": 606
+ },
+ {
+ "left_line": 607,
+ "right_line": 607
+ },
+ {
+ "left_line": 608,
+ "right_line": 608
+ },
+ {
+ "left_line": 609,
+ "right_line": 609
+ },
+ {
+ "left_line": 610,
+ "right_line": 610
+ },
+ {
+ "left_line": 611,
+ "right_line": 611
+ },
+ {
+ "left_line": 612,
+ "right_line": 612
+ },
+ {
+ "left_line": 613,
+ "right_line": 613
+ },
+ {
+ "left_line": 614,
+ "right_line": 614
+ },
+ {
+ "left_line": 615,
+ "right_line": 615
+ },
+ {
+ "left_line": 616,
+ "right_line": 616
+ },
+ {
+ "left_line": 617,
+ "right_line": 617
+ },
+ {
+ "left_line": 618,
+ "right_line": 618
+ },
+ {
+ "left_line": 619,
+ "right_line": 619
+ },
+ {
+ "left_line": 620,
+ "right_line": 620
+ },
+ {
+ "left_line": 621,
+ "right_line": 621
+ },
+ {
+ "left_line": 622,
+ "right_line": 622
+ },
+ {
+ "left_line": 623,
+ "right_line": 623
+ },
+ {
+ "left_line": 624,
+ "right_line": 624
+ },
+ {
+ "left_line": 625,
+ "right_line": 625
+ },
+ {
+ "left_line": 626,
+ "right_line": 626
+ },
+ {
+ "left_line": 627,
+ "right_line": 627
+ },
+ {
+ "left_line": 628,
+ "right_line": 628
+ },
+ {
+ "left_line": 629,
+ "right_line": 629
+ },
+ {
+ "left_line": 630,
+ "right_line": 630
+ },
+ {
+ "left_line": 631,
+ "right_line": 631
+ },
+ {
+ "left_line": 632,
+ "right_line": 632
+ },
+ {
+ "left_line": 633,
+ "right_line": 633
+ },
+ {
+ "left_line": 634,
+ "right_line": 634
+ },
+ {
+ "left_line": 635,
+ "right_line": 635
+ },
+ {
+ "left_line": 636,
+ "right_line": 636
+ },
+ {
+ "left_line": 637,
+ "right_line": 637
+ },
+ {
+ "left_line": 638,
+ "right_line": 638
+ },
+ {
+ "left_line": 639,
+ "right_line": 639
+ },
+ {
+ "left_line": 640,
+ "right_line": 640
+ },
+ {
+ "left_line": 641,
+ "right_line": 641
+ },
+ {
+ "left_line": 642,
+ "right_line": 642
+ },
+ {
+ "left_line": 643,
+ "right_line": 643
+ },
+ {
+ "left_line": 644,
+ "right_line": 644
+ },
+ {
+ "left_line": 645,
+ "right_line": 645
+ },
+ {
+ "left_line": 646,
+ "right_line": 646
+ },
+ {
+ "left_line": 647,
+ "right_line": 647
+ },
+ {
+ "left_line": 648,
+ "right_line": 648
+ },
+ {
+ "left_line": 649,
+ "right_line": 649
+ },
+ {
+ "left_line": 650,
+ "right_line": 650
+ },
+ {
+ "left_line": 651,
+ "right_line": 651
+ },
+ {
+ "left_line": 652,
+ "right_line": 652
+ },
+ {
+ "left_line": 653,
+ "right_line": 653
+ },
+ {
+ "left_line": 654,
+ "right_line": 654
+ },
+ {
+ "left_line": 655,
+ "right_line": 655
+ },
+ {
+ "left_line": 656,
+ "right_line": 656
+ },
+ {
+ "left_line": 657,
+ "right_line": 657
+ },
+ {
+ "left_line": 658,
+ "right_line": 658
+ },
+ {
+ "left_line": 659,
+ "right_line": 659
+ },
+ {
+ "left_line": 660,
+ "right_line": 660
+ },
+ {
+ "left_line": 661,
+ "right_line": 661
+ },
+ {
+ "left_line": 662,
+ "right_line": 662
+ },
+ {
+ "left_line": 663,
+ "right_line": 663
+ },
+ {
+ "left_line": 664,
+ "right_line": 664
+ },
+ {
+ "left_line": 665,
+ "right_line": 665
+ },
+ {
+ "left_line": 666,
+ "right_line": 666
+ },
+ {
+ "left_line": 667,
+ "right_line": 667
+ },
+ {
+ "left_line": 668,
+ "right_line": 668
+ },
+ {
+ "left_line": 669,
+ "right_line": 669
+ },
+ {
+ "left_line": 670,
+ "right_line": 670
+ },
+ {
+ "left_line": 671,
+ "right_line": 671
+ },
+ {
+ "left_line": 672,
+ "right_line": 672
+ },
+ {
+ "left_line": 673,
+ "right_line": 673
+ },
+ {
+ "left_line": 674,
+ "right_line": 674
+ },
+ {
+ "left_line": 675,
+ "right_line": 675
+ },
+ {
+ "left_line": 676,
+ "right_line": 676
+ },
+ {
+ "left_line": 677,
+ "right_line": 677
+ },
+ {
+ "left_line": 678,
+ "right_line": 678
+ },
+ {
+ "left_line": 679,
+ "right_line": 679
+ },
+ {
+ "left_line": 680,
+ "right_line": 680
+ },
+ {
+ "left_line": 681,
+ "right_line": 681
+ },
+ {
+ "left_line": 682,
+ "right_line": 682
+ },
+ {
+ "left_line": 683,
+ "right_line": 683
+ },
+ {
+ "left_line": 684,
+ "right_line": 684
+ },
+ {
+ "left_line": 685,
+ "right_line": 685
+ },
+ {
+ "left_line": 686,
+ "right_line": 686
+ },
+ {
+ "left_line": 687,
+ "right_line": 687
+ },
+ {
+ "left_line": 688,
+ "right_line": 688
+ },
+ {
+ "left_line": 689,
+ "right_line": 689
+ },
+ {
+ "left_line": 690,
+ "right_line": 690
+ },
+ {
+ "left_line": 691,
+ "right_line": 691
+ },
+ {
+ "left_line": 692,
+ "right_line": 692
+ },
+ {
+ "left_line": 693,
+ "right_line": 693
+ },
+ {
+ "left_line": 694,
+ "right_line": 694
+ },
+ {
+ "left_line": 695,
+ "right_line": 695
+ },
+ {
+ "left_line": 696,
+ "right_line": 696
+ },
+ {
+ "left_line": 697,
+ "right_line": 697
+ },
+ {
+ "left_line": 698,
+ "right_line": 698
+ },
+ {
+ "left_line": 699,
+ "right_line": 699
+ },
+ {
+ "left_line": 700,
+ "right_line": 700
+ },
+ {
+ "left_line": 701,
+ "right_line": 701
+ },
+ {
+ "left_line": 702,
+ "right_line": 702
+ },
+ {
+ "left_line": 703,
+ "right_line": 703
+ },
+ {
+ "left_line": 704,
+ "right_line": 704
+ },
+ {
+ "left_line": 705,
+ "right_line": 705
+ },
+ {
+ "left_line": 706,
+ "right_line": 706
+ },
+ {
+ "left_line": 707,
+ "right_line": 707
+ },
+ {
+ "left_line": 708,
+ "right_line": 708
+ },
+ {
+ "left_line": 709,
+ "right_line": 709
+ },
+ {
+ "left_line": 710,
+ "right_line": 710
+ },
+ {
+ "left_line": 711,
+ "right_line": 711
+ },
+ {
+ "left_line": 712,
+ "right_line": 712
+ },
+ {
+ "left_line": 713,
+ "right_line": 713
+ },
+ {
+ "left_line": 714,
+ "right_line": 714
+ },
+ {
+ "left_line": 715,
+ "right_line": 715
+ },
+ {
+ "left_line": 716,
+ "right_line": 716
+ },
+ {
+ "left_line": 717,
+ "right_line": 717
+ },
+ {
+ "left_line": 718,
+ "right_line": 718
+ },
+ {
+ "left_line": 719,
+ "right_line": 719
+ },
+ {
+ "left_line": 720,
+ "right_line": 720
+ },
+ {
+ "left_line": 721,
+ "right_line": 721
+ },
+ {
+ "left_line": 722,
+ "right_line": 722
+ },
+ {
+ "left_line": 723,
+ "right_line": 723
+ },
+ {
+ "left_line": 724,
+ "right_line": 724
+ },
+ {
+ "left_line": 725,
+ "right_line": 725
+ },
+ {
+ "left_line": 726,
+ "right_line": 726
+ },
+ {
+ "left_line": 727,
+ "right_line": 727
+ },
+ {
+ "left_line": 728,
+ "right_line": 728
+ },
+ {
+ "left_line": 729,
+ "right_line": 729
+ },
+ {
+ "left_line": 730,
+ "right_line": 730
+ },
+ {
+ "left_line": 731,
+ "right_line": 731
+ },
+ {
+ "left_line": 732,
+ "right_line": 732
+ },
+ {
+ "left_line": 733,
+ "right_line": 733
+ },
+ {
+ "left_line": 734,
+ "right_line": 734
+ },
+ {
+ "left_line": 735,
+ "right_line": 735
+ },
+ {
+ "left_line": 736,
+ "right_line": 736
+ },
+ {
+ "left_line": 737,
+ "right_line": 737
+ },
+ {
+ "left_line": 738,
+ "right_line": 738
+ },
+ {
+ "left_line": 739,
+ "right_line": 739
+ },
+ {
+ "left_line": 740,
+ "right_line": 740
+ },
+ {
+ "left_line": 741,
+ "right_line": 741
+ },
+ {
+ "left_line": 742,
+ "right_line": 742
+ },
+ {
+ "left_line": 743,
+ "right_line": 743
+ },
+ {
+ "left_line": 744,
+ "right_line": 744
+ },
+ {
+ "left_line": 745,
+ "right_line": 745
+ },
+ {
+ "left_line": 746,
+ "right_line": 746
+ },
+ {
+ "left_line": 747,
+ "right_line": 747
+ },
+ {
+ "left_line": 748,
+ "right_line": 748
+ },
+ {
+ "left_line": 749,
+ "right_line": 749
+ },
+ {
+ "left_line": 750,
+ "right_line": 750
+ },
+ {
+ "left_line": 751,
+ "right_line": 751
+ },
+ {
+ "left_line": 752,
+ "right_line": 752
+ },
+ {
+ "left_line": 753,
+ "right_line": 753
+ },
+ {
+ "left_line": 754,
+ "right_line": 754
+ },
+ {
+ "left_line": 755,
+ "right_line": 755
+ },
+ {
+ "left_line": 756,
+ "right_line": 756
+ },
+ {
+ "left_line": 757,
+ "right_line": 757
+ },
+ {
+ "left_line": 758,
+ "right_line": 758
+ },
+ {
+ "left_line": 759,
+ "right_line": 759
+ },
+ {
+ "left_line": 760,
+ "right_line": 760
+ },
+ {
+ "left_line": 761,
+ "right_line": 761
+ },
+ {
+ "left_line": 762,
+ "right_line": 762
+ },
+ {
+ "left_line": 763,
+ "right_line": 763
+ },
+ {
+ "left_line": 764,
+ "right_line": 764
+ },
+ {
+ "left_line": 765,
+ "right_line": 765
+ },
+ {
+ "left_line": 766,
+ "right_line": 766
+ },
+ {
+ "left_line": 767,
+ "right_line": 767
+ },
+ {
+ "left_line": 768,
+ "right_line": 768
+ },
+ {
+ "left_line": 769,
+ "right_line": 769
+ },
+ {
+ "left_line": 770,
+ "right_line": 770
+ },
+ {
+ "left_line": 771,
+ "right_line": 771
+ },
+ {
+ "left_line": 772,
+ "right_line": 772
+ },
+ {
+ "left_line": 773,
+ "right_line": 773
+ },
+ {
+ "left_line": 774,
+ "right_line": 774
+ },
+ {
+ "left_line": 775,
+ "right_line": 775
+ },
+ {
+ "left_line": 776,
+ "right_line": 776
+ },
+ {
+ "left_line": 777,
+ "right_line": 777
+ },
+ {
+ "left_line": 778,
+ "right_line": 778
+ },
+ {
+ "left_line": 779,
+ "right_line": 779
+ },
+ {
+ "left_line": 780,
+ "right_line": 780
+ },
+ {
+ "left_line": 781,
+ "right_line": 781
+ },
+ {
+ "left_line": 782,
+ "right_line": 782
+ },
+ {
+ "left_line": 783,
+ "right_line": 783
+ },
+ {
+ "left_line": 784,
+ "right_line": 784
+ },
+ {
+ "left_line": 785,
+ "right_line": 785
+ },
+ {
+ "left_line": 786,
+ "right_line": 786
+ },
+ {
+ "left_line": 787,
+ "right_line": 787
+ },
+ {
+ "left_line": 788,
+ "right_line": 788
+ },
+ {
+ "left_line": 789,
+ "right_line": 789
+ },
+ {
+ "left_line": 790,
+ "right_line": 790
+ },
+ {
+ "left_line": 791,
+ "right_line": 791
+ },
+ {
+ "left_line": 792,
+ "right_line": 792
+ },
+ {
+ "left_line": 793,
+ "right_line": 793
+ },
+ {
+ "left_line": 794,
+ "right_line": 794
+ },
+ {
+ "left_line": 795,
+ "right_line": 795
+ },
+ {
+ "left_line": 796,
+ "right_line": 796
+ },
+ {
+ "left_line": 797,
+ "right_line": 797
+ },
+ {
+ "left_line": 798,
+ "right_line": 798
+ },
+ {
+ "left_line": 799,
+ "right_line": 799
+ },
+ {
+ "left_line": 800,
+ "right_line": 800
+ },
+ {
+ "left_line": 801,
+ "right_line": 801
+ },
+ {
+ "left_line": 802,
+ "right_line": 802
+ },
+ {
+ "left_line": 803,
+ "right_line": 803
+ },
+ {
+ "left_line": 804,
+ "right_line": 804
+ },
+ {
+ "left_line": 805,
+ "right_line": 805
+ },
+ {
+ "left_line": 806,
+ "right_line": 806
+ },
+ {
+ "left_line": 807,
+ "right_line": 807
+ },
+ {
+ "left_line": 808,
+ "right_line": 808
+ },
+ {
+ "left_line": 809,
+ "right_line": 809
+ },
+ {
+ "left_line": 810,
+ "right_line": 810
+ },
+ {
+ "left_line": 811,
+ "right_line": 811
+ },
+ {
+ "left_line": 812,
+ "right_line": 812
+ },
+ {
+ "left_line": 813,
+ "right_line": 813
+ },
+ {
+ "left_line": 814,
+ "right_line": 814
+ },
+ {
+ "left_line": 815,
+ "right_line": 815
+ },
+ {
+ "left_line": 816,
+ "right_line": 816
+ },
+ {
+ "left_line": 817,
+ "right_line": 817
+ },
+ {
+ "left_line": 818,
+ "right_line": 818
+ },
+ {
+ "left_line": 819,
+ "right_line": 819
+ },
+ {
+ "left_line": 820,
+ "right_line": 820
+ },
+ {
+ "left_line": 821,
+ "right_line": 821
+ },
+ {
+ "left_line": 822,
+ "right_line": 822
+ },
+ {
+ "left_line": 823,
+ "right_line": 823
+ },
+ {
+ "left_line": 824,
+ "right_line": 824
+ },
+ {
+ "left_line": 825,
+ "right_line": 825
+ },
+ {
+ "left_line": 826,
+ "right_line": 826
+ },
+ {
+ "left_line": 827,
+ "right_line": 827
+ },
+ {
+ "left_line": 828,
+ "right_line": 828
+ },
+ {
+ "left_line": 829,
+ "right_line": 829
+ },
+ {
+ "left_line": 830,
+ "right_line": 830
+ },
+ {
+ "left_line": 831,
+ "right_line": 831
+ },
+ {
+ "left_line": 832,
+ "right_line": 832
+ },
+ {
+ "left_line": 833,
+ "right_line": 833
+ },
+ {
+ "left_line": 834,
+ "right_line": 834
+ },
+ {
+ "left_line": 835,
+ "right_line": 835
+ },
+ {
+ "left_line": 836,
+ "right_line": 836
+ },
+ {
+ "left_line": 837,
+ "right_line": 837
+ },
+ {
+ "left_line": 838,
+ "right_line": 838
+ },
+ {
+ "left_line": 839,
+ "right_line": 839
+ },
+ {
+ "left_line": 840,
+ "right_line": 840
+ },
+ {
+ "left_line": 841,
+ "right_line": 841
+ },
+ {
+ "left_line": 842,
+ "right_line": 842
+ },
+ {
+ "left_line": 843,
+ "right_line": 843
+ },
+ {
+ "left_line": 844,
+ "right_line": 844
+ },
+ {
+ "left_line": 845,
+ "right_line": 845
+ },
+ {
+ "left_line": 846,
+ "right_line": 846
+ },
+ {
+ "left_line": 847,
+ "right_line": 847
+ },
+ {
+ "left_line": 848,
+ "right_line": 848
+ },
+ {
+ "left_line": 849,
+ "right_line": 849
+ },
+ {
+ "left_line": 850,
+ "right_line": 850
+ },
+ {
+ "left_line": 851,
+ "right_line": 851
+ },
+ {
+ "left_line": 852,
+ "right_line": 852
+ },
+ {
+ "left_line": 853,
+ "right_line": 853
+ },
+ {
+ "left_line": 854,
+ "right_line": 854
+ },
+ {
+ "left_line": 855,
+ "right_line": 855
+ },
+ {
+ "left_line": 856,
+ "right_line": 856
+ },
+ {
+ "left_line": 857,
+ "right_line": 857
+ },
+ {
+ "left_line": 858,
+ "right_line": 858
+ },
+ {
+ "left_line": 859,
+ "right_line": 859
+ },
+ {
+ "left_line": 860,
+ "right_line": 860
+ },
+ {
+ "left_line": 861,
+ "right_line": 861
+ },
+ {
+ "left_line": 862,
+ "right_line": 862
+ },
+ {
+ "left_line": 863,
+ "right_line": 863
+ },
+ {
+ "left_line": 864,
+ "right_line": 864
+ },
+ {
+ "left_line": 865,
+ "right_line": 865
+ },
+ {
+ "left_line": 866,
+ "right_line": 866
+ },
+ {
+ "left_line": 867,
+ "right_line": 867
+ },
+ {
+ "left_line": 868,
+ "right_line": 868
+ },
+ {
+ "left_line": 869,
+ "right_line": 869
+ },
+ {
+ "left_line": 870,
+ "right_line": 870
+ },
+ {
+ "left_line": 871,
+ "right_line": 871
+ },
+ {
+ "left_line": 872,
+ "right_line": 872
+ },
+ {
+ "left_line": 873,
+ "right_line": 873
+ },
+ {
+ "left_line": 874,
+ "right_line": 874
+ },
+ {
+ "left_line": 875,
+ "right_line": 875
+ },
+ {
+ "left_line": 876,
+ "right_line": 876
+ },
+ {
+ "left_line": 877,
+ "right_line": 877
+ },
+ {
+ "left_line": 878,
+ "right_line": 878
+ },
+ {
+ "left_line": 879,
+ "right_line": 879
+ },
+ {
+ "left_line": 880,
+ "right_line": 880
+ },
+ {
+ "left_line": 881,
+ "right_line": 881
+ },
+ {
+ "left_line": 882,
+ "right_line": 882
+ },
+ {
+ "left_line": 883,
+ "right_line": 883
+ },
+ {
+ "left_line": 884,
+ "right_line": 884
+ },
+ {
+ "left_line": 885,
+ "right_line": 885
+ },
+ {
+ "left_line": 886,
+ "right_line": 886
+ },
+ {
+ "left_line": 887,
+ "right_line": 887
+ },
+ {
+ "left_line": 888,
+ "right_line": 888
+ },
+ {
+ "left_line": 889,
+ "right_line": 889
+ },
+ {
+ "left_line": 890,
+ "right_line": 890
+ },
+ {
+ "left_line": 891,
+ "right_line": 891
+ },
+ {
+ "left_line": 892,
+ "right_line": 892
+ },
+ {
+ "left_line": 893,
+ "right_line": 893
+ },
+ {
+ "left_line": 894,
+ "right_line": 894
+ },
+ {
+ "left_line": 895,
+ "right_line": 895
+ },
+ {
+ "left_line": 896,
+ "right_line": 896
+ },
+ {
+ "left_line": 897,
+ "right_line": 897
+ },
+ {
+ "left_line": 898,
+ "right_line": 898
+ },
+ {
+ "left_line": 899,
+ "right_line": 899
+ },
+ {
+ "left_line": 900,
+ "right_line": 900
+ },
+ {
+ "left_line": 901,
+ "right_line": 901
+ },
+ {
+ "left_line": 902,
+ "right_line": 902
+ },
+ {
+ "left_line": 903,
+ "right_line": 903
+ },
+ {
+ "left_line": 904,
+ "right_line": 904
+ },
+ {
+ "left_line": 905,
+ "right_line": 905
+ },
+ {
+ "left_line": 906,
+ "right_line": 906
+ },
+ {
+ "left_line": 907,
+ "right_line": 907
+ },
+ {
+ "left_line": 908,
+ "right_line": 908
+ },
+ {
+ "left_line": 909,
+ "right_line": 909
+ },
+ {
+ "left_line": 910,
+ "right_line": 910
+ },
+ {
+ "left_line": 911,
+ "right_line": 911
+ },
+ {
+ "left_line": 912,
+ "right_line": 912
+ },
+ {
+ "left_line": 913,
+ "right_line": 913
+ },
+ {
+ "left_line": 914,
+ "right_line": 914
+ },
+ {
+ "left_line": 915,
+ "right_line": 915
+ },
+ {
+ "left_line": 916,
+ "right_line": 916
+ },
+ {
+ "left_line": 917,
+ "right_line": 917
+ },
+ {
+ "left_line": 918,
+ "right_line": 918
+ },
+ {
+ "left_line": 919,
+ "right_line": 919
+ },
+ {
+ "left_line": 920,
+ "right_line": 920
+ },
+ {
+ "left_line": 921,
+ "right_line": 921
+ },
+ {
+ "left_line": 922,
+ "right_line": 922
+ },
+ {
+ "left_line": 923,
+ "right_line": 923
+ },
+ {
+ "left_line": 924,
+ "right_line": 924
+ },
+ {
+ "left_line": 925,
+ "right_line": 925
+ },
+ {
+ "left_line": 926,
+ "right_line": 926
+ },
+ {
+ "left_line": 927,
+ "right_line": 927
+ },
+ {
+ "left_line": 928,
+ "right_line": 928
+ },
+ {
+ "left_line": 929,
+ "right_line": 929
+ },
+ {
+ "left_line": 930,
+ "right_line": 930
+ },
+ {
+ "left_line": 931,
+ "right_line": 931
+ },
+ {
+ "left_line": 932,
+ "right_line": 932
+ },
+ {
+ "left_line": 933,
+ "right_line": 933
+ },
+ {
+ "left_line": 934,
+ "right_line": 934
+ },
+ {
+ "left_line": 935,
+ "right_line": 935
+ },
+ {
+ "left_line": 936,
+ "right_line": 936
+ },
+ {
+ "left_line": 937,
+ "right_line": 937
+ },
+ {
+ "left_line": 938,
+ "right_line": 938
+ },
+ {
+ "left_line": 939,
+ "right_line": 939
+ },
+ {
+ "left_line": 940,
+ "right_line": 940
+ },
+ {
+ "left_line": 941,
+ "right_line": 941
+ },
+ {
+ "left_line": 942,
+ "right_line": 942
+ },
+ {
+ "left_line": 943,
+ "right_line": 943
+ },
+ {
+ "left_line": 944,
+ "right_line": 944
+ },
+ {
+ "left_line": 945,
+ "right_line": 945
+ },
+ {
+ "left_line": 946,
+ "right_line": 946
+ },
+ {
+ "left_line": 947,
+ "right_line": 947
+ },
+ {
+ "left_line": 948,
+ "right_line": 948
+ },
+ {
+ "left_line": 949,
+ "right_line": 949
+ },
+ {
+ "left_line": 950,
+ "right_line": 950
+ },
+ {
+ "left_line": 951,
+ "right_line": 951
+ },
+ {
+ "left_line": 952,
+ "right_line": 952
+ },
+ {
+ "left_line": 953,
+ "right_line": 953
+ },
+ {
+ "left_line": 954,
+ "right_line": 954
+ },
+ {
+ "left_line": 955,
+ "right_line": 955
+ },
+ {
+ "left_line": 956,
+ "right_line": 956
+ },
+ {
+ "left_line": 957,
+ "right_line": 957
+ },
+ {
+ "left_line": 958,
+ "right_line": 958
+ },
+ {
+ "left_line": 959,
+ "right_line": 959
+ },
+ {
+ "left_line": 960,
+ "right_line": 960
+ },
+ {
+ "left_line": 961,
+ "right_line": 961
+ },
+ {
+ "left_line": 962,
+ "right_line": 962
+ },
+ {
+ "left_line": 963,
+ "right_line": 963
+ },
+ {
+ "left_line": 964,
+ "right_line": 964
+ },
+ {
+ "left_line": 965,
+ "right_line": 965
+ },
+ {
+ "left_line": 966,
+ "right_line": 966
+ },
+ {
+ "left_line": 967,
+ "right_line": 967
+ },
+ {
+ "left_line": 968,
+ "right_line": 968
+ },
+ {
+ "left_line": 969,
+ "right_line": 969
+ },
+ {
+ "left_line": 970,
+ "right_line": 970
+ },
+ {
+ "left_line": 971,
+ "right_line": 971
+ },
+ {
+ "left_line": 972,
+ "right_line": 972
+ },
+ {
+ "left_line": 973,
+ "right_line": 973
+ },
+ {
+ "left_line": 974,
+ "right_line": 974
+ },
+ {
+ "left_line": 975,
+ "right_line": 975
+ },
+ {
+ "left_line": 976,
+ "right_line": 976
+ },
+ {
+ "left_line": 977,
+ "right_line": 977
+ },
+ {
+ "left_line": 978,
+ "right_line": 978
+ },
+ {
+ "left_line": 979,
+ "right_line": 979
+ },
+ {
+ "left_line": 980,
+ "right_line": 980
+ },
+ {
+ "left_line": 981,
+ "right_line": 981
+ },
+ {
+ "left_line": 982,
+ "right_line": 982
+ },
+ {
+ "left_line": 983,
+ "right_line": 983
+ },
+ {
+ "left_line": 984,
+ "right_line": 984
+ },
+ {
+ "left_line": 985,
+ "right_line": 985
+ },
+ {
+ "left_line": 986,
+ "right_line": 986
+ },
+ {
+ "left_line": 987,
+ "right_line": 987
+ },
+ {
+ "left_line": 988,
+ "right_line": 988
+ },
+ {
+ "left_line": 989,
+ "right_line": 989
+ },
+ {
+ "left_line": 990,
+ "right_line": 990
+ },
+ {
+ "left_line": 991,
+ "right_line": 991
+ },
+ {
+ "left_line": 992,
+ "right_line": 992
+ },
+ {
+ "left_line": 993,
+ "right_line": 993
+ },
+ {
+ "left_line": 994,
+ "right_line": 994
+ },
+ {
+ "left_line": 995,
+ "right_line": 995
+ },
+ {
+ "left_line": 996,
+ "right_line": 996
+ },
+ {
+ "left_line": 997,
+ "right_line": 997
+ },
+ {
+ "left_line": 998,
+ "right_line": 998
+ },
+ {
+ "left_line": 999,
+ "right_line": 999
+ },
+ {
+ "left_line": 1000,
+ "right_line": 1000
+ },
+ {
+ "left_line": 1001,
+ "right_line": 1001
+ },
+ {
+ "left_line": 1002,
+ "right_line": 1002
+ },
+ {
+ "left_line": 1003,
+ "right_line": 1003
+ },
+ {
+ "left_line": 1004,
+ "right_line": 1004
+ },
+ {
+ "left_line": 1005,
+ "right_line": 1005
+ },
+ {
+ "left_line": 1006,
+ "right_line": 1006
+ },
+ {
+ "left_line": 1007,
+ "right_line": 1007
+ },
+ {
+ "left_line": 1008,
+ "right_line": 1008
+ },
+ {
+ "left_line": 1009,
+ "right_line": 1009
+ },
+ {
+ "left_line": 1010,
+ "right_line": 1010
+ },
+ {
+ "left_line": 1011,
+ "right_line": 1011
+ },
+ {
+ "left_line": 1012,
+ "right_line": 1012
+ },
+ {
+ "left_line": 1013,
+ "right_line": 1013
+ },
+ {
+ "left_line": 1014,
+ "right_line": 1014
+ },
+ {
+ "left_line": 1015,
+ "right_line": 1015
+ },
+ {
+ "left_line": 1016,
+ "right_line": 1016
+ },
+ {
+ "left_line": 1017,
+ "right_line": 1017
+ },
+ {
+ "left_line": 1018,
+ "right_line": 1018
+ },
+ {
+ "left_line": 1019,
+ "right_line": 1019
+ },
+ {
+ "left_line": 1020,
+ "right_line": 1020
+ },
+ {
+ "left_line": 1021,
+ "right_line": 1021
+ },
+ {
+ "left_line": 1022,
+ "right_line": 1022
+ },
+ {
+ "left_line": 1023,
+ "right_line": 1023
+ },
+ {
+ "left_line": 1024,
+ "right_line": 1024
+ },
+ {
+ "left_line": 1025,
+ "right_line": 1025
+ },
+ {
+ "left_line": 1026,
+ "right_line": 1026
+ },
+ {
+ "left_line": 1027,
+ "right_line": 1027
+ },
+ {
+ "left_line": 1028,
+ "right_line": 1028
+ },
+ {
+ "left_line": 1029,
+ "right_line": 1029
+ },
+ {
+ "left_line": 1030,
+ "right_line": 1030
+ },
+ {
+ "left_line": 1031,
+ "right_line": 1031
+ },
+ {
+ "left_line": 1032,
+ "right_line": 1032
+ },
+ {
+ "left_line": 1033,
+ "right_line": 1033
+ },
+ {
+ "left_line": 1034,
+ "right_line": 1034
+ },
+ {
+ "left_line": 1035,
+ "right_line": 1035
+ },
+ {
+ "left_line": 1036,
+ "right_line": 1036
+ },
+ {
+ "left_line": 1037,
+ "right_line": 1037
+ },
+ {
+ "left_line": 1038,
+ "right_line": 1038
+ },
+ {
+ "left_line": 1039,
+ "right_line": 1039
+ },
+ {
+ "left_line": 1040,
+ "right_line": 1040
+ },
+ {
+ "left_line": 1041,
+ "right_line": 1041
+ },
+ {
+ "left_line": 1042,
+ "right_line": 1042
+ },
+ {
+ "left_line": 1043,
+ "right_line": 1043
+ },
+ {
+ "left_line": 1044,
+ "right_line": 1044
+ },
+ {
+ "left_line": 1045,
+ "right_line": 1045
+ },
+ {
+ "left_line": 1046,
+ "right_line": 1046
+ },
+ {
+ "left_line": 1047,
+ "right_line": 1047
+ },
+ {
+ "left_line": 1048,
+ "right_line": 1048
+ },
+ {
+ "left_line": 1049,
+ "right_line": 1049
+ },
+ {
+ "left_line": 1050,
+ "right_line": 1050
+ },
+ {
+ "left_line": 1051,
+ "right_line": 1051
+ },
+ {
+ "left_line": 1052,
+ "right_line": 1052
+ },
+ {
+ "left_line": 1053,
+ "right_line": 1053
+ },
+ {
+ "left_line": 1054,
+ "right_line": 1054
+ },
+ {
+ "left_line": 1055,
+ "right_line": 1055
+ },
+ {
+ "left_line": 1056,
+ "right_line": 1056
+ },
+ {
+ "left_line": 1057,
+ "right_line": 1057
+ },
+ {
+ "left_line": 1058,
+ "right_line": 1058
+ },
+ {
+ "left_line": 1059,
+ "right_line": 1059
+ },
+ {
+ "left_line": 1060,
+ "right_line": 1060
+ },
+ {
+ "left_line": 1061,
+ "right_line": 1061
+ },
+ {
+ "left_line": 1062,
+ "right_line": 1062
+ },
+ {
+ "left_line": 1063,
+ "right_line": 1063
+ },
+ {
+ "left_line": 1064,
+ "right_line": 1064
+ },
+ {
+ "left_line": 1065,
+ "right_line": 1065
+ },
+ {
+ "left_line": 1066,
+ "right_line": 1066
+ },
+ {
+ "left_line": 1067,
+ "right_line": 1067
+ },
+ {
+ "left_line": 1068,
+ "right_line": 1068
+ },
+ {
+ "left_line": 1069,
+ "right_line": 1069
+ },
+ {
+ "left_line": 1070,
+ "right_line": 1070
+ },
+ {
+ "left_line": 1071,
+ "right_line": 1071
+ },
+ {
+ "left_line": 1072,
+ "right_line": 1072
+ },
+ {
+ "left_line": 1073,
+ "right_line": 1073
+ },
+ {
+ "left_line": 1074,
+ "right_line": 1074
+ },
+ {
+ "left_line": 1075,
+ "right_line": 1075
+ },
+ {
+ "left_line": 1076,
+ "right_line": 1076
+ },
+ {
+ "left_line": 1077,
+ "right_line": 1077
+ },
+ {
+ "left_line": 1078,
+ "right_line": 1078
+ },
+ {
+ "left_line": 1079,
+ "right_line": 1079
+ },
+ {
+ "left_line": 1080,
+ "right_line": 1080
+ },
+ {
+ "left_line": 1081,
+ "right_line": 1081
+ },
+ {
+ "left_line": 1082,
+ "right_line": 1082
+ },
+ {
+ "left_line": 1083,
+ "right_line": 1083
+ },
+ {
+ "left_line": 1084,
+ "right_line": 1084
+ },
+ {
+ "left_line": 1085,
+ "right_line": 1085
+ },
+ {
+ "left_line": 1086,
+ "right_line": 1086
+ },
+ {
+ "left_line": 1087,
+ "right_line": 1087
+ },
+ {
+ "left_line": 1088,
+ "right_line": 1088
+ },
+ {
+ "left_line": 1089,
+ "right_line": 1089
+ },
+ {
+ "left_line": 1090,
+ "right_line": 1090
+ },
+ {
+ "left_line": 1091,
+ "right_line": 1091
+ },
+ {
+ "left_line": 1092,
+ "right_line": 1092
+ },
+ {
+ "left_line": 1093,
+ "right_line": 1093
+ },
+ {
+ "left_line": 1094,
+ "right_line": 1094
+ },
+ {
+ "left_line": 1095,
+ "right_line": 1095
+ },
+ {
+ "left_line": 1096,
+ "right_line": 1096
+ },
+ {
+ "left_line": 1097,
+ "right_line": 1097
+ },
+ {
+ "left_line": 1098,
+ "right_line": 1098
+ },
+ {
+ "left_line": 1099,
+ "right_line": 1099
+ },
+ {
+ "left_line": 1100,
+ "right_line": 1100
+ },
+ {
+ "left_line": 1101,
+ "right_line": 1101
+ },
+ {
+ "left_line": 1102,
+ "right_line": 1102
+ },
+ {
+ "left_line": 1103,
+ "right_line": 1103
+ },
+ {
+ "left_line": 1104,
+ "right_line": 1104
+ },
+ {
+ "left_line": 1105,
+ "right_line": 1105
+ },
+ {
+ "left_line": 1106,
+ "right_line": 1106
+ },
+ {
+ "left_line": 1107,
+ "right_line": 1107
+ },
+ {
+ "left_line": 1108,
+ "right_line": 1108
+ },
+ {
+ "left_line": 1109,
+ "right_line": 1109
+ },
+ {
+ "left_line": 1110,
+ "right_line": 1110
+ },
+ {
+ "left_line": 1111,
+ "right_line": 1111
+ },
+ {
+ "left_line": 1112,
+ "right_line": 1112
+ },
+ {
+ "left_line": 1113,
+ "right_line": 1113
+ },
+ {
+ "left_line": 1114,
+ "right_line": 1114
+ },
+ {
+ "left_line": 1115,
+ "right_line": 1115
+ },
+ {
+ "left_line": 1116,
+ "right_line": 1116
+ },
+ {
+ "left_line": 1117,
+ "right_line": 1117
+ },
+ {
+ "left_line": 1118,
+ "right_line": 1118
+ },
+ {
+ "left_line": 1119,
+ "right_line": 1119
+ },
+ {
+ "left_line": 1120,
+ "right_line": 1120
+ },
+ {
+ "left_line": 1121,
+ "right_line": 1121
+ },
+ {
+ "left_line": 1122,
+ "right_line": 1122
+ },
+ {
+ "left_line": 1123,
+ "right_line": 1123
+ },
+ {
+ "left_line": 1124,
+ "right_line": 1124
+ },
+ {
+ "left_line": 1125,
+ "right_line": 1125
+ },
+ {
+ "left_line": 1126,
+ "right_line": 1126
+ },
+ {
+ "left_line": 1127,
+ "right_line": 1127
+ },
+ {
+ "left_line": 1128,
+ "right_line": 1128
+ },
+ {
+ "left_line": 1129,
+ "right_line": 1129
+ },
+ {
+ "left_line": 1130,
+ "right_line": 1130
+ },
+ {
+ "left_line": 1131,
+ "right_line": 1131
+ },
+ {
+ "left_line": 1132,
+ "right_line": 1132
+ },
+ {
+ "left_line": 1133,
+ "right_line": 1133
+ },
+ {
+ "left_line": 1134,
+ "right_line": 1134
+ },
+ {
+ "left_line": 1135,
+ "right_line": 1135
+ },
+ {
+ "left_line": 1136,
+ "right_line": 1136
+ },
+ {
+ "left_line": 1137,
+ "right_line": 1137
+ },
+ {
+ "left_line": 1138,
+ "right_line": 1138
+ },
+ {
+ "left_line": 1139,
+ "right_line": 1139
+ },
+ {
+ "left_line": 1140,
+ "right_line": 1140
+ },
+ {
+ "left_line": 1141,
+ "right_line": 1141
+ },
+ {
+ "left_line": 1142,
+ "right_line": 1142
+ },
+ {
+ "left_line": 1143,
+ "right_line": 1143
+ },
+ {
+ "left_line": 1144,
+ "right_line": 1144
+ },
+ {
+ "left_line": 1145,
+ "right_line": 1145
+ },
+ {
+ "left_line": 1146,
+ "right_line": 1146
+ },
+ {
+ "left_line": 1147,
+ "right_line": 1147
+ },
+ {
+ "left_line": 1148,
+ "right_line": 1148
+ },
+ {
+ "left_line": 1149,
+ "right_line": 1149
+ },
+ {
+ "left_line": 1150,
+ "right_line": 1150
+ },
+ {
+ "left_line": 1151,
+ "right_line": 1151
+ },
+ {
+ "left_line": 1152,
+ "right_line": 1152
+ },
+ {
+ "left_line": 1153,
+ "right_line": 1153
+ },
+ {
+ "left_line": 1154,
+ "right_line": 1154
+ },
+ {
+ "left_line": 1155,
+ "right_line": 1155
+ },
+ {
+ "left_line": 1156,
+ "right_line": 1156
+ },
+ {
+ "left_line": 1157,
+ "right_line": 1157
+ },
+ {
+ "left_line": 1158,
+ "right_line": 1158
+ },
+ {
+ "left_line": 1159,
+ "right_line": 1159
+ },
+ {
+ "left_line": 1160,
+ "right_line": 1160
+ },
+ {
+ "left_line": 1161,
+ "right_line": 1161
+ },
+ {
+ "left_line": 1162,
+ "right_line": 1162
+ },
+ {
+ "left_line": 1163,
+ "right_line": 1163
+ },
+ {
+ "left_line": 1164,
+ "right_line": 1164
+ },
+ {
+ "left_line": 1165,
+ "right_line": 1165
+ },
+ {
+ "left_line": 1166,
+ "right_line": 1166
+ },
+ {
+ "left_line": 1167,
+ "right_line": 1167
+ },
+ {
+ "left_line": 1168,
+ "right_line": 1168
+ },
+ {
+ "left_line": 1169,
+ "right_line": 1169
+ },
+ {
+ "left_line": 1170,
+ "right_line": 1170
+ },
+ {
+ "left_line": 1171,
+ "right_line": 1171
+ },
+ {
+ "left_line": 1172,
+ "right_line": 1172
+ },
+ {
+ "left_line": 1173,
+ "right_line": 1173
+ },
+ {
+ "left_line": 1174,
+ "right_line": 1174
+ },
+ {
+ "left_line": 1175,
+ "right_line": 1175
+ },
+ {
+ "left_line": 1176,
+ "right_line": 1176
+ },
+ {
+ "left_line": 1177,
+ "right_line": 1177
+ },
+ {
+ "left_line": 1178,
+ "right_line": 1178
+ },
+ {
+ "left_line": 1179,
+ "right_line": 1179
+ },
+ {
+ "left_line": 1180,
+ "right_line": 1180
+ },
+ {
+ "left_line": 1181,
+ "right_line": 1181
+ },
+ {
+ "left_line": 1182,
+ "right_line": 1182
+ },
+ {
+ "left_line": 1183,
+ "right_line": 1183
+ },
+ {
+ "left_line": 1184,
+ "right_line": 1184
+ },
+ {
+ "left_line": 1185,
+ "right_line": 1185
+ },
+ {
+ "left_line": 1186,
+ "right_line": 1186
+ },
+ {
+ "left_line": 1187,
+ "right_line": 1187
+ },
+ {
+ "left_line": 1188,
+ "right_line": 1188
+ },
+ {
+ "left_line": 1189,
+ "right_line": 1189
+ },
+ {
+ "left_line": 1190,
+ "right_line": 1190
+ },
+ {
+ "left_line": 1191,
+ "right_line": 1191
+ },
+ {
+ "left_line": 1192,
+ "right_line": 1192
+ },
+ {
+ "left_line": 1193,
+ "right_line": 1193
+ },
+ {
+ "left_line": 1194,
+ "right_line": 1194
+ },
+ {
+ "left_line": 1195,
+ "right_line": 1195
+ },
+ {
+ "left_line": 1196,
+ "right_line": 1196
+ },
+ {
+ "left_line": 1197,
+ "right_line": 1197
+ },
+ {
+ "left_line": 1198,
+ "right_line": 1198
+ },
+ {
+ "left_line": 1199,
+ "right_line": 1199
+ },
+ {
+ "left_line": 1200,
+ "right_line": 1200
+ },
+ {
+ "left_line": 1201,
+ "right_line": 1201
+ },
+ {
+ "left_line": 1202,
+ "right_line": 1202
+ },
+ {
+ "left_line": 1203,
+ "right_line": 1203
+ },
+ {
+ "left_line": 1204,
+ "right_line": 1204
+ },
+ {
+ "left_line": 1205,
+ "right_line": 1205
+ },
+ {
+ "left_line": 1206,
+ "right_line": 1206
+ },
+ {
+ "left_line": 1207,
+ "right_line": 1207
+ },
+ {
+ "left_line": 1208,
+ "right_line": 1208
+ },
+ {
+ "left_line": 1209,
+ "right_line": 1209
+ },
+ {
+ "left_line": 1210,
+ "right_line": 1210
+ },
+ {
+ "left_line": 1211,
+ "right_line": 1211
+ },
+ {
+ "left_line": 1212,
+ "right_line": 1212
+ },
+ {
+ "left_line": 1213,
+ "right_line": 1213
+ },
+ {
+ "left_line": 1214,
+ "right_line": 1214
+ },
+ {
+ "left_line": 1215,
+ "right_line": 1215
+ },
+ {
+ "left_line": 1216,
+ "right_line": 1216
+ },
+ {
+ "left_line": 1217,
+ "right_line": 1217
+ },
+ {
+ "left_line": 1218,
+ "right_line": 1218
+ },
+ {
+ "left_line": 1219,
+ "right_line": 1219
+ },
+ {
+ "left_line": 1220,
+ "right_line": 1220
+ },
+ {
+ "left_line": 1221,
+ "right_line": 1221
+ },
+ {
+ "left_line": 1222,
+ "right_line": 1222
+ },
+ {
+ "left_line": 1223,
+ "right_line": 1223
+ },
+ {
+ "left_line": 1224,
+ "right_line": 1224
+ },
+ {
+ "left_line": 1225,
+ "right_line": 1225
+ },
+ {
+ "left_line": 1226,
+ "right_line": 1226
+ },
+ {
+ "left_line": 1227,
+ "right_line": 1227
+ },
+ {
+ "left_line": 1228,
+ "right_line": 1228
+ },
+ {
+ "left_line": 1229,
+ "right_line": 1229
+ },
+ {
+ "left_line": 1230,
+ "right_line": 1230
+ },
+ {
+ "left_line": 1231,
+ "right_line": 1231
+ },
+ {
+ "left_line": 1232,
+ "right_line": 1232
+ },
+ {
+ "left_line": 1233,
+ "right_line": 1233
+ },
+ {
+ "left_line": 1234,
+ "right_line": 1234
+ },
+ {
+ "left_line": 1235,
+ "right_line": 1235
+ },
+ {
+ "left_line": 1236,
+ "right_line": 1236
+ },
+ {
+ "left_line": 1237,
+ "right_line": 1237
+ },
+ {
+ "left_line": 1238,
+ "right_line": 1238
+ },
+ {
+ "left_line": 1239,
+ "right_line": 1239
+ },
+ {
+ "left_line": 1240,
+ "right_line": 1240
+ },
+ {
+ "left_line": 1241,
+ "right_line": 1241
+ },
+ {
+ "left_line": 1242,
+ "right_line": 1242
+ },
+ {
+ "left_line": 1243,
+ "right_line": 1243
+ },
+ {
+ "left_line": 1244,
+ "right_line": 1244
+ },
+ {
+ "left_line": 1245,
+ "right_line": 1245
+ },
+ {
+ "left_line": 1246,
+ "right_line": 1246
+ },
+ {
+ "left_line": 1247,
+ "right_line": 1247
+ },
+ {
+ "left_line": 1248,
+ "right_line": 1248
+ },
+ {
+ "left_line": 1249,
+ "right_line": 1249
+ },
+ {
+ "left_line": 1250,
+ "right_line": 1250
+ },
+ {
+ "left_line": 1251,
+ "right_line": 1251
+ },
+ {
+ "left_line": 1252,
+ "right_line": 1252
+ },
+ {
+ "left_line": 1253,
+ "right_line": 1253
+ },
+ {
+ "left_line": 1254,
+ "right_line": 1254
+ },
+ {
+ "left_line": 1255,
+ "right_line": 1255
+ },
+ {
+ "left_line": 1256,
+ "right_line": 1256
+ },
+ {
+ "left_line": 1257,
+ "right_line": 1257
+ },
+ {
+ "left_line": 1258,
+ "right_line": 1258
+ },
+ {
+ "left_line": 1259,
+ "right_line": 1259
+ },
+ {
+ "left_line": 1260,
+ "right_line": 1260
+ },
+ {
+ "left_line": 1261,
+ "right_line": 1261
+ },
+ {
+ "left_line": 1262,
+ "right_line": 1262
+ },
+ {
+ "left_line": 1263,
+ "right_line": 1263
+ },
+ {
+ "left_line": 1264,
+ "right_line": 1264
+ },
+ {
+ "left_line": 1265,
+ "right_line": 1265
+ },
+ {
+ "left_line": 1266,
+ "right_line": 1266
+ },
+ {
+ "left_line": 1267,
+ "right_line": 1267
+ },
+ {
+ "left_line": 1268,
+ "right_line": 1268
+ },
+ {
+ "left_line": 1269,
+ "right_line": 1269
+ },
+ {
+ "left_line": 1270,
+ "right_line": 1270
+ },
+ {
+ "left_line": 1271,
+ "right_line": 1271
+ },
+ {
+ "left_line": 1272,
+ "right_line": 1272
+ },
+ {
+ "left_line": 1273,
+ "right_line": 1273
+ },
+ {
+ "left_line": 1274,
+ "right_line": 1274
+ },
+ {
+ "left_line": 1275,
+ "right_line": 1275
+ },
+ {
+ "left_line": 1276,
+ "right_line": 1276
+ },
+ {
+ "left_line": 1277,
+ "right_line": 1277
+ },
+ {
+ "left_line": 1278,
+ "right_line": 1278
+ },
+ {
+ "left_line": 1279,
+ "right_line": 1279
+ },
+ {
+ "left_line": 1280,
+ "right_line": 1280
+ },
+ {
+ "left_line": 1281,
+ "right_line": 1281
+ },
+ {
+ "left_line": 1282,
+ "right_line": 1282
+ },
+ {
+ "left_line": 1283,
+ "right_line": 1283
+ },
+ {
+ "left_line": 1284,
+ "right_line": 1284
+ },
+ {
+ "left_line": 1285,
+ "right_line": 1285
+ },
+ {
+ "left_line": 1286,
+ "right_line": 1286
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/testdata/ts_react_router_update_link/metadata.json b/tests/testdata/ts_react_router_update_link/metadata.json
new file mode 100644
index 0000000..524ed2d
--- /dev/null
+++ b/tests/testdata/ts_react_router_update_link/metadata.json
@@ -0,0 +1,12 @@
+{
+ "repo": "https://github.com/remix-run/react-router",
+ "parent": "963affeb924f2256bd20c0c5c87e4c4b4fcbd188",
+ "commit": "7965e50072eb7d3a28b770a5e131c45ed9367926",
+ "message": "Update link to v7 instead of a specific version (#15147)",
+ "author": "Matt Brophy",
+ "date": "2026-06-04T10:17:03-04:00",
+ "language": "typescript",
+ "files_changed": 1,
+ "insertions": 0,
+ "deletions": 0
+}
\ No newline at end of file
diff --git a/tests/testdata/ts_react_router_update_link/new.ts b/tests/testdata/ts_react_router_update_link/new.ts
new file mode 100644
index 0000000..bc3148d
--- /dev/null
+++ b/tests/testdata/ts_react_router_update_link/new.ts
@@ -0,0 +1,1286 @@
+import fs from "node:fs";
+import { execSync } from "node:child_process";
+import * as ViteNode from "../vite/vite-node";
+import type * as Vite from "vite";
+import Path from "pathe";
+import chokidar, {
+ type FSWatcher,
+ type EmitArgs as ChokidarEmitArgs,
+} from "chokidar";
+import colors from "picocolors";
+import pick from "lodash/pick";
+import omit from "lodash/omit";
+import cloneDeep from "lodash/cloneDeep";
+import isEqual from "lodash/isEqual";
+
+import {
+ type RouteManifest,
+ type RouteManifestEntry,
+ type RouteConfigEntry,
+ setAppDirectory,
+ validateRouteConfig,
+ configRoutesToRouteManifest,
+} from "./routes";
+import { detectPackageManager } from "../cli/detectPackageManager";
+
+const excludedConfigPresetKeys = ["presets"] as const satisfies ReadonlyArray<
+ keyof ReactRouterConfig
+>;
+
+type ExcludedConfigPresetKey = (typeof excludedConfigPresetKeys)[number];
+
+type ConfigPreset = Omit;
+
+export type Preset = {
+ name: string;
+ reactRouterConfig?: (args: {
+ reactRouterUserConfig: ReactRouterConfig;
+ }) => ConfigPreset | Promise;
+ reactRouterConfigResolved?: (args: {
+ reactRouterConfig: ResolvedReactRouterConfig;
+ }) => void | Promise;
+};
+
+// Only expose a subset of route properties to the "serverBundles" function
+const branchRouteProperties = [
+ "id",
+ "path",
+ "file",
+ "index",
+] as const satisfies ReadonlyArray;
+type BranchRoute = Pick<
+ RouteManifestEntry,
+ (typeof branchRouteProperties)[number]
+>;
+
+export const configRouteToBranchRoute = (
+ configRoute: RouteManifestEntry,
+): BranchRoute => pick(configRoute, branchRouteProperties);
+
+export type ServerBundlesFunction = (args: {
+ branch: BranchRoute[];
+}) => string | Promise;
+
+type BaseBuildManifest = {
+ routes: RouteManifest;
+};
+
+type DefaultBuildManifest = BaseBuildManifest & {
+ serverBundles?: never;
+ routeIdToServerBundleId?: never;
+};
+
+type ServerBundlesBuildManifest = BaseBuildManifest & {
+ serverBundles: {
+ [serverBundleId: string]: {
+ id: string;
+ file: string;
+ };
+ };
+ routeIdToServerBundleId: Record;
+};
+
+type ServerModuleFormat = "esm" | "cjs";
+
+type ValidateConfigFunction = (config: ReactRouterConfig) => string | void;
+
+interface FutureConfig {
+ unstable_optimizeDeps: boolean;
+ v8_passThroughRequests: boolean;
+ v8_trailingSlashAwareDataRequests: boolean;
+ /**
+ * Prerender with Vite Preview server
+ */
+ unstable_previewServerPrerendering?: boolean;
+ /**
+ * Enable route middleware
+ */
+ v8_middleware: boolean;
+ /**
+ * Automatically split route modules into multiple chunks when possible.
+ */
+ v8_splitRouteModules: boolean | "enforce";
+ /**
+ * Use Vite Environment API
+ */
+ v8_viteEnvironmentApi: boolean;
+}
+
+export type BuildManifest = DefaultBuildManifest | ServerBundlesBuildManifest;
+
+type BuildEndHook = (args: {
+ buildManifest: BuildManifest | undefined;
+ reactRouterConfig: ResolvedReactRouterConfig;
+ viteConfig: Vite.ResolvedConfig;
+}) => void | Promise;
+
+export type PrerenderPaths =
+ | boolean
+ | Array
+ | ((args: {
+ getStaticPaths: () => string[];
+ }) => Array | Promise>);
+
+/**
+ * Config to be exported via the default export from `react-router.config.ts`.
+ */
+export type ReactRouterConfig = {
+ /**
+ * The path to the `app` directory, relative to the root directory. Defaults
+ * to `"app"`.
+ */
+ appDirectory?: string;
+
+ /**
+ * The output format of the server build. Defaults to "esm".
+ */
+ serverModuleFormat?: ServerModuleFormat;
+
+ /**
+ * Enabled future flags
+ */
+ future?: [keyof FutureConfig] extends [never]
+ ? // Partial doesn't work when it's empty so just prevent any keys
+ { [key: string]: never }
+ : Partial;
+
+ /**
+ * The React Router app basename. Defaults to `"/"`.
+ */
+ basename?: string;
+ /**
+ * The path to the build directory, relative to the project. Defaults to
+ * `"build"`.
+ */
+ buildDirectory?: string;
+ /**
+ * A function that is called after the full React Router build is complete.
+ */
+ buildEnd?: BuildEndHook;
+ /**
+ * An array of URLs to prerender to HTML files at build time. Can also be a
+ * function returning an array to dynamically generate URLs.
+ *
+ * `concurrency` defaults to 1, which means "no concurrency" - fully serial execution.
+ * Setting it to a value more than 1 enables concurrent prerendering.
+ * Setting it to a value higher than one can increase the speed of the build,
+ * but may consume more resources, and send more concurrent requests to the
+ * server/CMS.
+ */
+ prerender?:
+ | PrerenderPaths
+ | {
+ paths: PrerenderPaths;
+ concurrency?: number;
+ };
+ /**
+ * An array of React Router plugin config presets to ease integration with
+ * other platforms and tools.
+ */
+ presets?: Array;
+ /**
+ * Control the "Lazy Route Discovery" behavior
+ *
+ * - `routeDiscovery.mode`: By default, this resolves to `lazy` which will
+ * lazily discover routes as the user navigates around your application.
+ * You can set this to `initial` to opt-out of this behavior and load all
+ * routes with the initial HTML document load.
+ * - `routeDiscovery.manifestPath`: The path to serve the manifest file from.
+ * Only applies to `mode: "lazy"` and defaults to `/__manifest`.
+ */
+ routeDiscovery?:
+ | {
+ mode: "lazy";
+ manifestPath?: string;
+ }
+ | {
+ mode: "initial";
+ };
+ /**
+ * The file name of the server build output. This file
+ * should end in a `.js` extension and should be deployed to your server.
+ * Defaults to `"index.js"`.
+ */
+ serverBuildFile?: string;
+ /**
+ * A function for assigning routes to different server bundles. This
+ * function should return a server bundle ID which will be used as the
+ * bundle's directory name within the server build directory.
+ */
+ serverBundles?: ServerBundlesFunction;
+ /**
+ * Enable server-side rendering for your application. Disable to use "SPA
+ * Mode", which will request the `/` path at build-time and save it as an
+ * `index.html` file with your assets so your application can be deployed as a
+ * SPA without server-rendering. Default's to `true`.
+ */
+ ssr?: boolean;
+
+ /**
+ * Enable subresource integrity hashes on asset script tags. Defaults to
+ * `false`.
+ */
+ subResourceIntegrity?: boolean;
+
+ /**
+ * An array of allowed origin hosts for action submissions to UI routes (does not apply
+ * to resource routes). Supports micromatch glob patterns (`*` to match one segment,
+ * `**` to match multiple).
+ *
+ * ```tsx
+ * export default {
+ * allowedActionOrigins: [
+ * "example.com",
+ * "*.example.com", // sub.example.com
+ * "**.example.com", // sub.domain.example.com
+ * ],
+ * } satisfies Config;
+ * ```
+ *
+ * If you need to set this value at runtime, you can do in by setting the value
+ * on the server build in your custom server. For example, when using `express`:
+ *
+ * ```ts
+ * import express from "express";
+ * import { createRequestHandler } from "@react-router/express";
+ * import type { ServerBuild } from "react-router";
+ *
+ * export const app = express();
+ *
+ * async function getBuild() {
+ * let build: ServerBuild = await import(
+ * "virtual:react-router/server-build"
+ * );
+ * return {
+ * ...build,
+ * allowedActionOrigins:
+ * process.env.NODE_ENV === "development"
+ * ? undefined
+ * : ["staging.example.com", "www.example.com"],
+ * };
+ * }
+ *
+ * app.use(createRequestHandler({ build: getBuild }));
+ */
+ allowedActionOrigins?: string[];
+};
+
+export type ResolvedReactRouterConfig = Readonly<{
+ /**
+ * The absolute path to the application source directory.
+ */
+ appDirectory: string;
+ /**
+ * The React Router app basename. Defaults to `"/"`.
+ */
+ basename: string;
+ /**
+ * The absolute path to the build directory.
+ */
+ buildDirectory: string;
+ /**
+ * A function that is called after the full React Router build is complete.
+ */
+ buildEnd?: BuildEndHook;
+ /**
+ * Enabled future flags
+ */
+ future: FutureConfig;
+ /**
+ * An array of URLs to prerender to HTML files at build time. Can also be a
+ * function returning an array to dynamically generate URLs.
+ */
+ prerender: ReactRouterConfig["prerender"];
+ /**
+ * Control the "Lazy Route Discovery" behavior
+ *
+ * - `routeDiscovery.mode`: By default, this resolves to `lazy` which will
+ * lazily discover routes as the user navigates around your application.
+ * You can set this to `initial` to opt-out of this behavior and load all
+ * routes with the initial HTML document load.
+ * - `routeDiscovery.manifestPath`: The path to serve the manifest file from.
+ * Only applies to `mode: "lazy"` and defaults to `/__manifest`.
+ */
+ routeDiscovery: ReactRouterConfig["routeDiscovery"];
+ /**
+ * An object of all available routes, keyed by route id.
+ */
+ routes: RouteManifest;
+ /**
+ * The file name of the server build output. This file
+ * should end in a `.js` extension and should be deployed to your server.
+ * Defaults to `"index.js"`.
+ */
+ serverBuildFile: string;
+ /**
+ * A function for assigning routes to different server bundles. This
+ * function should return a server bundle ID which will be used as the
+ * bundle's directory name within the server build directory.
+ */
+ serverBundles?: ServerBundlesFunction;
+ /**
+ * The output format of the server build. Defaults to "esm".
+ */
+ serverModuleFormat: ServerModuleFormat;
+ /**
+ * Enable server-side rendering for your application. Disable to use "SPA
+ * Mode", which will request the `/` path at build-time and save it as an
+ * `index.html` file with your assets so your application can be deployed as a
+ * SPA without server-rendering. Default's to `true`.
+ */
+ ssr: boolean;
+ /**
+ * Whether to generate subresource integrity hashes for asset script tags.
+ */
+ subResourceIntegrity: boolean;
+ /**
+ * The allowed origins for actions / mutations. Does not apply to routes
+ * without a component. micromatch glob patterns are supported.
+ */
+ allowedActionOrigins: string[] | false;
+ /**
+ * The resolved array of route config entries exported from `routes.ts`
+ */
+ unstable_routeConfig: RouteConfigEntry[];
+}>;
+
+let mergeReactRouterConfig = (
+ ...configs: ReactRouterConfig[]
+): ReactRouterConfig => {
+ let reducer = (
+ configA: ReactRouterConfig,
+ configB: ReactRouterConfig,
+ ): ReactRouterConfig => {
+ let mergeRequired = (key: keyof ReactRouterConfig) =>
+ configA[key] !== undefined && configB[key] !== undefined;
+
+ return {
+ ...configA,
+ ...configB,
+ ...(mergeRequired("buildEnd")
+ ? {
+ buildEnd: async (...args) => {
+ await Promise.all([
+ configA.buildEnd?.(...args),
+ configB.buildEnd?.(...args),
+ ]);
+ },
+ }
+ : {}),
+ ...(mergeRequired("future")
+ ? {
+ future: {
+ ...configA.future,
+ ...configB.future,
+ },
+ }
+ : {}),
+ ...(mergeRequired("presets")
+ ? {
+ presets: [...(configA.presets ?? []), ...(configB.presets ?? [])],
+ }
+ : {}),
+ };
+ };
+
+ return configs.reduce(reducer, {});
+};
+
+// Inlined from https://github.com/jsdf/deep-freeze
+let deepFreeze = (o: any) => {
+ Object.freeze(o);
+ let oIsFunction = typeof o === "function";
+ let hasOwnProp = Object.prototype.hasOwnProperty;
+ Object.getOwnPropertyNames(o).forEach(function (prop) {
+ if (
+ hasOwnProp.call(o, prop) &&
+ (oIsFunction
+ ? prop !== "caller" && prop !== "callee" && prop !== "arguments"
+ : true) &&
+ o[prop] !== null &&
+ (typeof o[prop] === "object" || typeof o[prop] === "function") &&
+ !Object.isFrozen(o[prop])
+ ) {
+ deepFreeze(o[prop]);
+ }
+ });
+ return o;
+};
+
+type Result =
+ | {
+ ok: true;
+ value: T;
+ error?: undefined;
+ }
+ | {
+ ok: false;
+ value?: undefined;
+ error: string;
+ };
+
+type ConfigResult = Result;
+
+function ok(value: T): Result {
+ return { ok: true, value };
+}
+
+function err(error: string): Result {
+ return { ok: false, error };
+}
+
+async function resolveConfig({
+ root,
+ viteNodeContext,
+ reactRouterConfigFile,
+ skipRoutes,
+ validateConfig,
+ shouldLogFutureFlagWarnings,
+}: {
+ root: string;
+ viteNodeContext: ViteNode.Context;
+ reactRouterConfigFile?: string;
+ skipRoutes?: boolean;
+ validateConfig?: ValidateConfigFunction;
+ shouldLogFutureFlagWarnings?: boolean;
+}): Promise {
+ let reactRouterUserConfig: ReactRouterConfig = {};
+
+ if (reactRouterConfigFile) {
+ try {
+ if (!fs.existsSync(reactRouterConfigFile)) {
+ return err(`${reactRouterConfigFile} no longer exists`);
+ }
+
+ let configModule = await viteNodeContext.runner.executeFile(
+ reactRouterConfigFile,
+ );
+
+ if (configModule.default === undefined) {
+ return err(`${reactRouterConfigFile} must provide a default export`);
+ }
+
+ if (typeof configModule.default !== "object") {
+ return err(`${reactRouterConfigFile} must export a config`);
+ }
+
+ reactRouterUserConfig = configModule.default;
+
+ if (validateConfig) {
+ const error = validateConfig(reactRouterUserConfig);
+ if (error) {
+ return err(error);
+ }
+ }
+ } catch (error) {
+ return err(`Error loading ${reactRouterConfigFile}: ${error}`);
+ }
+ }
+
+ // Prevent mutations to the user config
+ reactRouterUserConfig = deepFreeze(cloneDeep(reactRouterUserConfig));
+
+ let presets: ReactRouterConfig[] = (
+ await Promise.all(
+ (reactRouterUserConfig.presets ?? []).map(async (preset) => {
+ if (!preset.name) {
+ throw new Error(
+ "React Router presets must have a `name` property defined.",
+ );
+ }
+
+ if (!preset.reactRouterConfig) {
+ return null;
+ }
+
+ let configPreset: ReactRouterConfig = omit(
+ await preset.reactRouterConfig({ reactRouterUserConfig }),
+ excludedConfigPresetKeys,
+ );
+
+ return configPreset;
+ }),
+ )
+ ).filter(function isNotNull(value: T | null): value is T {
+ return value !== null;
+ });
+
+ let defaults = {
+ basename: "/",
+ buildDirectory: "build",
+ serverBuildFile: "index.js",
+ serverModuleFormat: "esm",
+ ssr: true,
+ } as const satisfies Partial;
+
+ let userAndPresetConfigs = mergeReactRouterConfig(
+ ...presets,
+ reactRouterUserConfig,
+ );
+
+ let {
+ appDirectory: userAppDirectory,
+ basename,
+ buildDirectory: userBuildDirectory,
+ buildEnd,
+ prerender,
+ routeDiscovery: userRouteDiscovery,
+ serverBuildFile,
+ serverBundles,
+ serverModuleFormat,
+ ssr,
+ } = {
+ ...defaults, // Default values should be completely overridden by user/preset config, not merged
+ ...userAndPresetConfigs,
+ };
+
+ if (!ssr && serverBundles) {
+ serverBundles = undefined;
+ }
+
+ if (prerender) {
+ let isValidPrerenderPathsConfig = (p: unknown) =>
+ typeof p === "boolean" || typeof p === "function" || Array.isArray(p);
+
+ let isValidPrerenderConfig =
+ isValidPrerenderPathsConfig(prerender) ||
+ (typeof prerender === "object" &&
+ "paths" in prerender &&
+ isValidPrerenderPathsConfig(prerender.paths));
+
+ if (!isValidPrerenderConfig) {
+ return err(
+ "The `prerender`/`prerender.paths` config must be a boolean, an array " +
+ "of string paths, or a function returning a boolean or array of string paths.",
+ );
+ }
+
+ if (typeof prerender === "object" && "unstable_concurrency" in prerender) {
+ return err(
+ "The `prerender.unstable_concurrency` config field has been stabilized as `prerender.concurrency`",
+ );
+ }
+
+ let isValidConcurrencyConfig =
+ typeof prerender != "object" ||
+ !("concurrency" in prerender) ||
+ (typeof prerender.concurrency === "number" &&
+ Number.isInteger(prerender.concurrency) &&
+ prerender.concurrency > 0);
+
+ if (!isValidConcurrencyConfig) {
+ return err(
+ "The `prerender.concurrency` config must be a positive integer if specified.",
+ );
+ }
+ }
+
+ let routeDiscovery: ResolvedReactRouterConfig["routeDiscovery"];
+ if (userRouteDiscovery == null) {
+ if (ssr) {
+ routeDiscovery = {
+ mode: "lazy",
+ manifestPath: "/__manifest",
+ };
+ } else {
+ routeDiscovery = { mode: "initial" };
+ }
+ } else if (userRouteDiscovery.mode === "initial") {
+ routeDiscovery = userRouteDiscovery;
+ } else if (userRouteDiscovery.mode === "lazy") {
+ if (!ssr) {
+ return err(
+ 'The `routeDiscovery.mode` config cannot be set to "lazy" when setting `ssr:false`',
+ );
+ }
+
+ let { manifestPath } = userRouteDiscovery;
+ if (manifestPath != null && !manifestPath.startsWith("/")) {
+ return err(
+ "The `routeDiscovery.manifestPath` config must be a root-relative " +
+ 'pathname beginning with a slash (i.e., "/__manifest")',
+ );
+ }
+
+ routeDiscovery = userRouteDiscovery;
+ }
+
+ let appDirectory = Path.resolve(root, userAppDirectory || "app");
+ let buildDirectory = Path.resolve(root, userBuildDirectory);
+
+ let rootRouteFile = findEntry(appDirectory, "root", { absolute: true });
+ if (!rootRouteFile) {
+ let rootRouteDisplayPath = Path.relative(
+ root,
+ Path.join(appDirectory, "root.tsx"),
+ );
+ return err(
+ `Could not find a root route module in the app directory as "${rootRouteDisplayPath}"`,
+ );
+ }
+
+ let routes: RouteManifest;
+ let routeConfig: RouteConfigEntry[] = [];
+
+ if (skipRoutes) {
+ routes = {};
+ } else {
+ let routeConfigFile = findEntry(appDirectory, "routes");
+
+ try {
+ if (!routeConfigFile) {
+ let routeConfigDisplayPath = Path.relative(
+ root,
+ Path.join(appDirectory, "routes.ts"),
+ );
+ return err(
+ `Route config file not found at "${routeConfigDisplayPath}".`,
+ );
+ }
+
+ setAppDirectory(appDirectory);
+ let routeConfigExport = (
+ await viteNodeContext.runner.executeFile(
+ Path.join(appDirectory, routeConfigFile),
+ )
+ ).default;
+ let result = validateRouteConfig({
+ routeConfigFile,
+ routeConfig: await routeConfigExport,
+ });
+
+ if (!result.valid) {
+ return err(result.message);
+ }
+
+ // Nest the route config under the resolved root route
+ routeConfig = [
+ {
+ id: "root",
+ path: "",
+ file: Path.relative(appDirectory, rootRouteFile),
+ children: result.routeConfig,
+ },
+ ];
+
+ routes = configRoutesToRouteManifest(appDirectory, routeConfig);
+ } catch (error: any) {
+ return err(
+ [
+ colors.red(`Route config in "${routeConfigFile}" is invalid.`),
+ "",
+ error.loc?.file && error.loc?.column && error.frame
+ ? [
+ Path.relative(appDirectory, error.loc.file) +
+ ":" +
+ error.loc.line +
+ ":" +
+ error.loc.column,
+ error.frame.trim?.(),
+ ]
+ : error.stack,
+ ]
+ .flat()
+ .join("\n"),
+ );
+ }
+ }
+
+ // Check for renamed flags and provide helpful error messages
+ let futureConfig = userAndPresetConfigs.future;
+ if (futureConfig) {
+ if ("unstable_splitRouteModules" in futureConfig) {
+ return err(
+ "The `future.unstable_splitRouteModules` flag has been stabilized as `future.v8_splitRouteModules`",
+ );
+ }
+ if ("unstable_viteEnvironmentApi" in futureConfig) {
+ return err(
+ "The `future.unstable_viteEnvironmentApi` flag has been stabilized as `future.v8_viteEnvironmentApi`",
+ );
+ }
+ if ("unstable_passThroughRequests" in futureConfig) {
+ return err(
+ "The `future.unstable_passThroughRequests` flag has been stabilized as `future.v8_passThroughRequests`",
+ );
+ }
+ if ("unstable_trailingSlashAwareDataRequests" in futureConfig) {
+ return err(
+ "The `future.unstable_trailingSlashAwareDataRequests` flag has been stabilized as `future.v8_trailingSlashAwareDataRequests`",
+ );
+ }
+ if ("unstable_subResourceIntegrity" in futureConfig) {
+ return err(
+ "The `future.unstable_subResourceIntegrity` flag has been stabilized and moved to a top-level `config.subResourceIntegrity` field",
+ );
+ }
+ }
+
+ let future: FutureConfig = {
+ unstable_optimizeDeps:
+ userAndPresetConfigs.future?.unstable_optimizeDeps ?? false,
+ v8_passThroughRequests:
+ userAndPresetConfigs.future?.v8_passThroughRequests ?? false,
+ v8_trailingSlashAwareDataRequests:
+ userAndPresetConfigs.future?.v8_trailingSlashAwareDataRequests ?? false,
+ unstable_previewServerPrerendering:
+ userAndPresetConfigs.future?.unstable_previewServerPrerendering ?? false,
+ v8_middleware: userAndPresetConfigs.future?.v8_middleware ?? false,
+ v8_splitRouteModules:
+ userAndPresetConfigs.future?.v8_splitRouteModules ?? false,
+ v8_viteEnvironmentApi:
+ (userAndPresetConfigs.future?.v8_viteEnvironmentApi ||
+ userAndPresetConfigs.future?.unstable_previewServerPrerendering) ??
+ false,
+ };
+
+ let allowedActionOrigins = userAndPresetConfigs.allowedActionOrigins ?? false;
+ let subResourceIntegrity = userAndPresetConfigs.subResourceIntegrity ?? false;
+
+ let reactRouterConfig: ResolvedReactRouterConfig = deepFreeze({
+ appDirectory,
+ basename,
+ buildDirectory,
+ buildEnd,
+ future,
+ prerender,
+ routes,
+ routeDiscovery,
+ serverBuildFile,
+ serverBundles,
+ serverModuleFormat,
+ ssr,
+ subResourceIntegrity,
+ allowedActionOrigins,
+ unstable_routeConfig: routeConfig,
+ } satisfies ResolvedReactRouterConfig);
+
+ for (let preset of reactRouterUserConfig.presets ?? []) {
+ await preset.reactRouterConfigResolved?.({ reactRouterConfig });
+ }
+
+ if (shouldLogFutureFlagWarnings) {
+ logFutureFlagWarnings(userAndPresetConfigs.future || {});
+ }
+
+ return ok(reactRouterConfig);
+}
+
+function logFutureFlagWarning(flag: string, message: string): void {
+ console.log(
+ colors.yellow(
+ ` ⚠️ Future Flag Warning: ${message}\n` +
+ ` You can use the \`future.${flag}\` flag to opt in early.\n` +
+ ` -> https://reactrouter.com/v7/upgrading/future#future${flag.toLowerCase()}`,
+ ),
+ );
+}
+
+export function logFutureFlagWarnings(future: Partial): void {
+ if (future.v8_middleware === undefined) {
+ logFutureFlagWarning(
+ "v8_middleware",
+ "Route middleware support is changing in React Router v8.",
+ );
+ }
+ if (future.v8_splitRouteModules === undefined) {
+ logFutureFlagWarning(
+ "v8_splitRouteModules",
+ "Route module splitting behavior is changing in React Router v8.",
+ );
+ }
+ if (future.v8_viteEnvironmentApi === undefined) {
+ logFutureFlagWarning(
+ "v8_viteEnvironmentApi",
+ "Vite Environment API usage is changing in React Router v8.",
+ );
+ }
+ if (future.v8_passThroughRequests === undefined) {
+ logFutureFlagWarning(
+ "v8_passThroughRequests",
+ "Request handling behavior is changing in React Router v8.",
+ );
+ }
+ if (future.v8_trailingSlashAwareDataRequests === undefined) {
+ logFutureFlagWarning(
+ "v8_trailingSlashAwareDataRequests",
+ "Data request URL formats are changing in React Router v8.",
+ );
+ }
+}
+
+type ChokidarEventName = ChokidarEmitArgs[0];
+
+type ChangeHandler = (args: {
+ result: ConfigResult;
+ configCodeChanged: boolean;
+ routeConfigCodeChanged: boolean;
+ configChanged: boolean;
+ routeConfigChanged: boolean;
+ path: string;
+ event: ChokidarEventName;
+}) => void;
+
+export type ConfigLoader = {
+ getConfig: () => Promise;
+ onChange: (handler: ChangeHandler) => () => void;
+ close: () => Promise;
+};
+
+export async function createConfigLoader({
+ rootDirectory: root,
+ watch,
+ mode,
+ skipRoutes,
+ validateConfig,
+ shouldLogFutureFlagWarnings,
+}: {
+ watch: boolean;
+ rootDirectory?: string;
+ mode: string;
+ skipRoutes?: boolean;
+ validateConfig?: ValidateConfigFunction;
+ shouldLogFutureFlagWarnings?: boolean;
+}): Promise {
+ root = Path.normalize(root ?? process.env.REACT_ROUTER_ROOT ?? process.cwd());
+
+ let vite = await import("vite");
+ let viteNodeContext = await ViteNode.createContext({
+ root,
+ mode,
+ // Filter out any info level logs from vite-node
+ customLogger: vite.createLogger("warn", {
+ prefix: "[react-router]",
+ }),
+ });
+
+ let reactRouterConfigFile: string | undefined;
+
+ let updateReactRouterConfigFile = () => {
+ reactRouterConfigFile = findEntry(root, "react-router.config", {
+ absolute: true,
+ });
+ };
+
+ updateReactRouterConfigFile();
+
+ let getConfig = () =>
+ resolveConfig({
+ root,
+ viteNodeContext,
+ reactRouterConfigFile,
+ skipRoutes,
+ validateConfig,
+ shouldLogFutureFlagWarnings,
+ });
+
+ let appDirectory: string;
+
+ let initialConfigResult = await resolveConfig({
+ root,
+ viteNodeContext,
+ reactRouterConfigFile,
+ skipRoutes,
+ validateConfig,
+ });
+
+ if (!initialConfigResult.ok) {
+ throw new Error(initialConfigResult.error);
+ }
+
+ appDirectory = Path.normalize(initialConfigResult.value.appDirectory);
+
+ let currentConfig = initialConfigResult.value;
+
+ let fsWatcher: FSWatcher | undefined;
+ let changeHandlers: ChangeHandler[] = [];
+
+ return {
+ getConfig,
+ onChange: (handler: ChangeHandler) => {
+ if (!watch) {
+ throw new Error(
+ "onChange is not supported when watch mode is disabled",
+ );
+ }
+
+ changeHandlers.push(handler);
+
+ if (!fsWatcher) {
+ fsWatcher = chokidar.watch([root, appDirectory], {
+ ignoreInitial: true,
+ ignored: (path) => isIgnoredByWatcher(path, { root, appDirectory }),
+ });
+
+ fsWatcher.on("error", (error: unknown) => {
+ let message = error instanceof Error ? error.message : String(error);
+ console.warn(colors.yellow(`File watcher error: ${message}`));
+ });
+
+ fsWatcher.on("all", async (...args) => {
+ let [event, rawFilepath] = args;
+ let filepath = Path.normalize(rawFilepath);
+
+ let fileAddedOrRemoved = event === "add" || event === "unlink";
+
+ let appFileAddedOrRemoved =
+ fileAddedOrRemoved &&
+ filepath.startsWith(Path.normalize(appDirectory));
+
+ let rootRelativeFilepath = Path.relative(root, filepath);
+
+ let configFileAddedOrRemoved =
+ fileAddedOrRemoved &&
+ isEntryFile("react-router.config", rootRelativeFilepath);
+
+ if (configFileAddedOrRemoved) {
+ updateReactRouterConfigFile();
+ }
+
+ let moduleGraphChanged =
+ configFileAddedOrRemoved ||
+ Boolean(
+ viteNodeContext.devServer?.moduleGraph.getModuleById(filepath),
+ );
+
+ // Bail out if no relevant changes detected
+ if (!moduleGraphChanged && !appFileAddedOrRemoved) {
+ return;
+ }
+
+ viteNodeContext.devServer?.moduleGraph.invalidateAll();
+ viteNodeContext.runner?.moduleCache.clear();
+
+ let result = await getConfig();
+
+ let prevAppDirectory = appDirectory;
+ appDirectory = Path.normalize(
+ (result.value ?? currentConfig).appDirectory,
+ );
+
+ if (appDirectory !== prevAppDirectory) {
+ fsWatcher!.unwatch(prevAppDirectory);
+ fsWatcher!.add(appDirectory);
+ }
+
+ let configCodeChanged =
+ configFileAddedOrRemoved ||
+ (reactRouterConfigFile !== undefined &&
+ isEntryFileDependency(
+ viteNodeContext.devServer.moduleGraph,
+ reactRouterConfigFile,
+ filepath,
+ ));
+
+ let routeConfigFile = !skipRoutes
+ ? findEntry(appDirectory, "routes", {
+ absolute: true,
+ })
+ : undefined;
+ let routeConfigCodeChanged =
+ routeConfigFile !== undefined &&
+ isEntryFileDependency(
+ viteNodeContext.devServer.moduleGraph,
+ routeConfigFile,
+ filepath,
+ );
+
+ let configChanged =
+ result.ok &&
+ !isEqual(omitRoutes(currentConfig), omitRoutes(result.value));
+
+ let routeConfigChanged =
+ result.ok && !isEqual(currentConfig?.routes, result.value.routes);
+
+ for (let handler of changeHandlers) {
+ handler({
+ result,
+ configCodeChanged,
+ routeConfigCodeChanged,
+ configChanged,
+ routeConfigChanged,
+ path: filepath,
+ event,
+ });
+ }
+
+ if (result.ok) {
+ currentConfig = result.value;
+ }
+ });
+ }
+
+ return () => {
+ changeHandlers = changeHandlers.filter(
+ (changeHandler) => changeHandler !== handler,
+ );
+ };
+ },
+ close: async () => {
+ changeHandlers = [];
+ await viteNodeContext.devServer.close();
+ await fsWatcher?.close();
+ },
+ };
+}
+
+export async function loadConfig({
+ rootDirectory,
+ mode,
+ skipRoutes,
+}: {
+ rootDirectory: string;
+ mode: string;
+ skipRoutes?: boolean;
+}) {
+ let configLoader = await createConfigLoader({
+ rootDirectory,
+ mode,
+ skipRoutes,
+ watch: false,
+ });
+ let config = await configLoader.getConfig();
+ await configLoader.close();
+ return config;
+}
+
+export async function resolveEntryFiles({
+ rootDirectory,
+ reactRouterConfig,
+}: {
+ rootDirectory: string;
+ reactRouterConfig: ResolvedReactRouterConfig;
+}) {
+ let { appDirectory } = reactRouterConfig;
+
+ let defaultsDirectory = Path.resolve(
+ Path.dirname(require.resolve("@react-router/dev/package.json")),
+ "dist",
+ "config",
+ "defaults",
+ );
+
+ let userEntryClientFile = findEntry(appDirectory, "entry.client");
+ let userEntryServerFile = findEntry(appDirectory, "entry.server");
+
+ let entryServerFile: string;
+ let entryClientFile = userEntryClientFile || "entry.client.tsx";
+
+ if (userEntryServerFile) {
+ entryServerFile = userEntryServerFile;
+ } else {
+ let packageJsonPath = findEntry(rootDirectory, "package", {
+ extensions: [".json"],
+ absolute: true,
+ walkParents: true,
+ });
+
+ if (!packageJsonPath) {
+ throw new Error(
+ `Could not find package.json in ${rootDirectory} or any of its parent directories. Please add a package.json, or provide a custom entry.server.tsx/jsx file in your app directory.`,
+ );
+ }
+
+ // TODO(v8): Remove - only required for Node 20.18 and below
+ let { readPackageJSON, sortPackage, updatePackage } = await import(
+ "pkg-types"
+ );
+ let packageJsonDirectory = Path.dirname(packageJsonPath);
+ let pkgJson = await readPackageJSON(packageJsonDirectory);
+ let deps = pkgJson.dependencies ?? {};
+
+ if (!deps["@react-router/node"]) {
+ throw new Error(
+ `Could not determine server runtime. Please install @react-router/node, or provide a custom entry.server.tsx/jsx file in your app directory.`,
+ );
+ }
+
+ if (!deps["isbot"]) {
+ console.log(
+ "adding `isbot@5` to your package.json, you should commit this change",
+ );
+
+ await updatePackage(packageJsonPath, (pkg) => {
+ pkg.dependencies ??= {};
+ pkg.dependencies.isbot = "^5";
+ sortPackage(pkg);
+ });
+
+ let packageManager = detectPackageManager() ?? "npm";
+
+ execSync(`${packageManager} install`, {
+ cwd: packageJsonDirectory,
+ stdio: "inherit",
+ });
+ }
+
+ entryServerFile = `entry.server.node.tsx`;
+ }
+
+ let entryClientFilePath = userEntryClientFile
+ ? Path.resolve(reactRouterConfig.appDirectory, userEntryClientFile)
+ : Path.resolve(defaultsDirectory, entryClientFile);
+
+ let entryServerFilePath = userEntryServerFile
+ ? Path.resolve(reactRouterConfig.appDirectory, userEntryServerFile)
+ : Path.resolve(defaultsDirectory, entryServerFile);
+
+ return { entryClientFilePath, entryServerFilePath };
+}
+
+export async function resolveRSCEntryFiles({
+ reactRouterConfig,
+}: {
+ reactRouterConfig: ResolvedReactRouterConfig;
+}) {
+ let { appDirectory } = reactRouterConfig;
+
+ let defaultsDirectory = Path.resolve(
+ Path.dirname(require.resolve("@react-router/dev/package.json")),
+ "dist",
+ "config",
+ "default-rsc-entries",
+ );
+
+ let userEntryClientFile = findEntry(appDirectory, "entry.client", {
+ absolute: true,
+ });
+ let userEntryRSCFile = findEntry(appDirectory, "entry.rsc", {
+ absolute: true,
+ });
+ let userEntrySSRFile = findEntry(appDirectory, "entry.ssr", {
+ absolute: true,
+ });
+
+ let client =
+ userEntryClientFile ?? Path.join(defaultsDirectory, "entry.client.tsx");
+ let rsc = userEntryRSCFile ?? Path.join(defaultsDirectory, "entry.rsc.tsx");
+ let ssr = userEntrySSRFile ?? Path.join(defaultsDirectory, "entry.ssr.tsx");
+
+ return { client, rsc, ssr };
+}
+
+function omitRoutes(
+ config: ResolvedReactRouterConfig,
+): ResolvedReactRouterConfig {
+ return {
+ ...config,
+ routes: {},
+ };
+}
+
+const entryExts = [".js", ".jsx", ".ts", ".tsx", ".mjs", ".mts"];
+
+function isEntryFile(entryBasename: string, filename: string) {
+ return entryExts.some((ext) => filename === `${entryBasename}${ext}`);
+}
+
+function findEntry(
+ dir: string,
+ basename: string,
+ options?: {
+ absolute?: boolean;
+ extensions?: string[];
+ walkParents?: boolean;
+ },
+): string | undefined {
+ let currentDir = Path.resolve(dir);
+ let { root } = Path.parse(currentDir);
+
+ while (true) {
+ for (let ext of options?.extensions ?? entryExts) {
+ let file = Path.resolve(currentDir, basename + ext);
+ if (fs.existsSync(file)) {
+ return (options?.absolute ?? false) ? file : Path.relative(dir, file);
+ }
+ }
+
+ if (!options?.walkParents) {
+ return undefined;
+ }
+
+ let parentDir = Path.dirname(currentDir);
+ // Break out when we've reached the root directory or we're about to get
+ // stuck in a loop where `path.dirname` keeps returning "/"
+ if (currentDir === root || parentDir === currentDir) {
+ return undefined;
+ }
+
+ currentDir = parentDir;
+ }
+}
+
+function isEntryFileDependency(
+ moduleGraph: Vite.ModuleGraph,
+ entryFilepath: string,
+ filepath: string,
+ visited = new Set(),
+): boolean {
+ // Ensure normalized paths
+ entryFilepath = Path.normalize(entryFilepath);
+ filepath = Path.normalize(filepath);
+
+ if (visited.has(filepath)) {
+ return false;
+ }
+
+ visited.add(filepath);
+
+ if (filepath === entryFilepath) {
+ return true;
+ }
+
+ let mod = moduleGraph.getModuleById(filepath);
+
+ if (!mod) {
+ return false;
+ }
+
+ // Recursively check all importers to see if any of them are the entry file
+ for (let importer of mod.importers) {
+ if (!importer.id) {
+ continue;
+ }
+
+ if (
+ importer.id === entryFilepath ||
+ isEntryFileDependency(moduleGraph, entryFilepath, importer.id, visited)
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+export function isIgnoredByWatcher(
+ path: string,
+ { root, appDirectory }: { root: string; appDirectory: string },
+): boolean {
+ let dirname = Path.dirname(path);
+
+ let ignoredByPath =
+ !dirname.startsWith(appDirectory) &&
+ // Ensure we're only watching files outside of the app directory
+ // that are at the root level, not nested in subdirectories
+ path !== root && // Watch the root directory itself
+ dirname !== root; // Watch files at the root level
+
+ if (ignoredByPath) {
+ return true;
+ }
+
+ // Filter out non-regular files (sockets, pipes, etc.) that
+ // crash `fs.watch()` on macOS with errno -102
+ // https://github.com/paulmillr/chokidar/issues/1391
+ try {
+ let stat = fs.statSync(path, { throwIfNoEntry: false });
+ if (stat && !stat.isFile() && !stat.isDirectory()) {
+ return true;
+ }
+ } catch {
+ return true;
+ }
+
+ return false;
+}
diff --git a/tests/testdata/ts_react_router_update_link/old.ts b/tests/testdata/ts_react_router_update_link/old.ts
new file mode 100644
index 0000000..15d0ca2
--- /dev/null
+++ b/tests/testdata/ts_react_router_update_link/old.ts
@@ -0,0 +1,1286 @@
+import fs from "node:fs";
+import { execSync } from "node:child_process";
+import * as ViteNode from "../vite/vite-node";
+import type * as Vite from "vite";
+import Path from "pathe";
+import chokidar, {
+ type FSWatcher,
+ type EmitArgs as ChokidarEmitArgs,
+} from "chokidar";
+import colors from "picocolors";
+import pick from "lodash/pick";
+import omit from "lodash/omit";
+import cloneDeep from "lodash/cloneDeep";
+import isEqual from "lodash/isEqual";
+
+import {
+ type RouteManifest,
+ type RouteManifestEntry,
+ type RouteConfigEntry,
+ setAppDirectory,
+ validateRouteConfig,
+ configRoutesToRouteManifest,
+} from "./routes";
+import { detectPackageManager } from "../cli/detectPackageManager";
+
+const excludedConfigPresetKeys = ["presets"] as const satisfies ReadonlyArray<
+ keyof ReactRouterConfig
+>;
+
+type ExcludedConfigPresetKey = (typeof excludedConfigPresetKeys)[number];
+
+type ConfigPreset = Omit;
+
+export type Preset = {
+ name: string;
+ reactRouterConfig?: (args: {
+ reactRouterUserConfig: ReactRouterConfig;
+ }) => ConfigPreset | Promise;
+ reactRouterConfigResolved?: (args: {
+ reactRouterConfig: ResolvedReactRouterConfig;
+ }) => void | Promise;
+};
+
+// Only expose a subset of route properties to the "serverBundles" function
+const branchRouteProperties = [
+ "id",
+ "path",
+ "file",
+ "index",
+] as const satisfies ReadonlyArray;
+type BranchRoute = Pick<
+ RouteManifestEntry,
+ (typeof branchRouteProperties)[number]
+>;
+
+export const configRouteToBranchRoute = (
+ configRoute: RouteManifestEntry,
+): BranchRoute => pick(configRoute, branchRouteProperties);
+
+export type ServerBundlesFunction = (args: {
+ branch: BranchRoute[];
+}) => string | Promise;
+
+type BaseBuildManifest = {
+ routes: RouteManifest;
+};
+
+type DefaultBuildManifest = BaseBuildManifest & {
+ serverBundles?: never;
+ routeIdToServerBundleId?: never;
+};
+
+type ServerBundlesBuildManifest = BaseBuildManifest & {
+ serverBundles: {
+ [serverBundleId: string]: {
+ id: string;
+ file: string;
+ };
+ };
+ routeIdToServerBundleId: Record;
+};
+
+type ServerModuleFormat = "esm" | "cjs";
+
+type ValidateConfigFunction = (config: ReactRouterConfig) => string | void;
+
+interface FutureConfig {
+ unstable_optimizeDeps: boolean;
+ v8_passThroughRequests: boolean;
+ v8_trailingSlashAwareDataRequests: boolean;
+ /**
+ * Prerender with Vite Preview server
+ */
+ unstable_previewServerPrerendering?: boolean;
+ /**
+ * Enable route middleware
+ */
+ v8_middleware: boolean;
+ /**
+ * Automatically split route modules into multiple chunks when possible.
+ */
+ v8_splitRouteModules: boolean | "enforce";
+ /**
+ * Use Vite Environment API
+ */
+ v8_viteEnvironmentApi: boolean;
+}
+
+export type BuildManifest = DefaultBuildManifest | ServerBundlesBuildManifest;
+
+type BuildEndHook = (args: {
+ buildManifest: BuildManifest | undefined;
+ reactRouterConfig: ResolvedReactRouterConfig;
+ viteConfig: Vite.ResolvedConfig;
+}) => void | Promise;
+
+export type PrerenderPaths =
+ | boolean
+ | Array
+ | ((args: {
+ getStaticPaths: () => string[];
+ }) => Array | Promise>);
+
+/**
+ * Config to be exported via the default export from `react-router.config.ts`.
+ */
+export type ReactRouterConfig = {
+ /**
+ * The path to the `app` directory, relative to the root directory. Defaults
+ * to `"app"`.
+ */
+ appDirectory?: string;
+
+ /**
+ * The output format of the server build. Defaults to "esm".
+ */
+ serverModuleFormat?: ServerModuleFormat;
+
+ /**
+ * Enabled future flags
+ */
+ future?: [keyof FutureConfig] extends [never]
+ ? // Partial doesn't work when it's empty so just prevent any keys
+ { [key: string]: never }
+ : Partial;
+
+ /**
+ * The React Router app basename. Defaults to `"/"`.
+ */
+ basename?: string;
+ /**
+ * The path to the build directory, relative to the project. Defaults to
+ * `"build"`.
+ */
+ buildDirectory?: string;
+ /**
+ * A function that is called after the full React Router build is complete.
+ */
+ buildEnd?: BuildEndHook;
+ /**
+ * An array of URLs to prerender to HTML files at build time. Can also be a
+ * function returning an array to dynamically generate URLs.
+ *
+ * `concurrency` defaults to 1, which means "no concurrency" - fully serial execution.
+ * Setting it to a value more than 1 enables concurrent prerendering.
+ * Setting it to a value higher than one can increase the speed of the build,
+ * but may consume more resources, and send more concurrent requests to the
+ * server/CMS.
+ */
+ prerender?:
+ | PrerenderPaths
+ | {
+ paths: PrerenderPaths;
+ concurrency?: number;
+ };
+ /**
+ * An array of React Router plugin config presets to ease integration with
+ * other platforms and tools.
+ */
+ presets?: Array;
+ /**
+ * Control the "Lazy Route Discovery" behavior
+ *
+ * - `routeDiscovery.mode`: By default, this resolves to `lazy` which will
+ * lazily discover routes as the user navigates around your application.
+ * You can set this to `initial` to opt-out of this behavior and load all
+ * routes with the initial HTML document load.
+ * - `routeDiscovery.manifestPath`: The path to serve the manifest file from.
+ * Only applies to `mode: "lazy"` and defaults to `/__manifest`.
+ */
+ routeDiscovery?:
+ | {
+ mode: "lazy";
+ manifestPath?: string;
+ }
+ | {
+ mode: "initial";
+ };
+ /**
+ * The file name of the server build output. This file
+ * should end in a `.js` extension and should be deployed to your server.
+ * Defaults to `"index.js"`.
+ */
+ serverBuildFile?: string;
+ /**
+ * A function for assigning routes to different server bundles. This
+ * function should return a server bundle ID which will be used as the
+ * bundle's directory name within the server build directory.
+ */
+ serverBundles?: ServerBundlesFunction;
+ /**
+ * Enable server-side rendering for your application. Disable to use "SPA
+ * Mode", which will request the `/` path at build-time and save it as an
+ * `index.html` file with your assets so your application can be deployed as a
+ * SPA without server-rendering. Default's to `true`.
+ */
+ ssr?: boolean;
+
+ /**
+ * Enable subresource integrity hashes on asset script tags. Defaults to
+ * `false`.
+ */
+ subResourceIntegrity?: boolean;
+
+ /**
+ * An array of allowed origin hosts for action submissions to UI routes (does not apply
+ * to resource routes). Supports micromatch glob patterns (`*` to match one segment,
+ * `**` to match multiple).
+ *
+ * ```tsx
+ * export default {
+ * allowedActionOrigins: [
+ * "example.com",
+ * "*.example.com", // sub.example.com
+ * "**.example.com", // sub.domain.example.com
+ * ],
+ * } satisfies Config;
+ * ```
+ *
+ * If you need to set this value at runtime, you can do in by setting the value
+ * on the server build in your custom server. For example, when using `express`:
+ *
+ * ```ts
+ * import express from "express";
+ * import { createRequestHandler } from "@react-router/express";
+ * import type { ServerBuild } from "react-router";
+ *
+ * export const app = express();
+ *
+ * async function getBuild() {
+ * let build: ServerBuild = await import(
+ * "virtual:react-router/server-build"
+ * );
+ * return {
+ * ...build,
+ * allowedActionOrigins:
+ * process.env.NODE_ENV === "development"
+ * ? undefined
+ * : ["staging.example.com", "www.example.com"],
+ * };
+ * }
+ *
+ * app.use(createRequestHandler({ build: getBuild }));
+ */
+ allowedActionOrigins?: string[];
+};
+
+export type ResolvedReactRouterConfig = Readonly<{
+ /**
+ * The absolute path to the application source directory.
+ */
+ appDirectory: string;
+ /**
+ * The React Router app basename. Defaults to `"/"`.
+ */
+ basename: string;
+ /**
+ * The absolute path to the build directory.
+ */
+ buildDirectory: string;
+ /**
+ * A function that is called after the full React Router build is complete.
+ */
+ buildEnd?: BuildEndHook;
+ /**
+ * Enabled future flags
+ */
+ future: FutureConfig;
+ /**
+ * An array of URLs to prerender to HTML files at build time. Can also be a
+ * function returning an array to dynamically generate URLs.
+ */
+ prerender: ReactRouterConfig["prerender"];
+ /**
+ * Control the "Lazy Route Discovery" behavior
+ *
+ * - `routeDiscovery.mode`: By default, this resolves to `lazy` which will
+ * lazily discover routes as the user navigates around your application.
+ * You can set this to `initial` to opt-out of this behavior and load all
+ * routes with the initial HTML document load.
+ * - `routeDiscovery.manifestPath`: The path to serve the manifest file from.
+ * Only applies to `mode: "lazy"` and defaults to `/__manifest`.
+ */
+ routeDiscovery: ReactRouterConfig["routeDiscovery"];
+ /**
+ * An object of all available routes, keyed by route id.
+ */
+ routes: RouteManifest;
+ /**
+ * The file name of the server build output. This file
+ * should end in a `.js` extension and should be deployed to your server.
+ * Defaults to `"index.js"`.
+ */
+ serverBuildFile: string;
+ /**
+ * A function for assigning routes to different server bundles. This
+ * function should return a server bundle ID which will be used as the
+ * bundle's directory name within the server build directory.
+ */
+ serverBundles?: ServerBundlesFunction;
+ /**
+ * The output format of the server build. Defaults to "esm".
+ */
+ serverModuleFormat: ServerModuleFormat;
+ /**
+ * Enable server-side rendering for your application. Disable to use "SPA
+ * Mode", which will request the `/` path at build-time and save it as an
+ * `index.html` file with your assets so your application can be deployed as a
+ * SPA without server-rendering. Default's to `true`.
+ */
+ ssr: boolean;
+ /**
+ * Whether to generate subresource integrity hashes for asset script tags.
+ */
+ subResourceIntegrity: boolean;
+ /**
+ * The allowed origins for actions / mutations. Does not apply to routes
+ * without a component. micromatch glob patterns are supported.
+ */
+ allowedActionOrigins: string[] | false;
+ /**
+ * The resolved array of route config entries exported from `routes.ts`
+ */
+ unstable_routeConfig: RouteConfigEntry[];
+}>;
+
+let mergeReactRouterConfig = (
+ ...configs: ReactRouterConfig[]
+): ReactRouterConfig => {
+ let reducer = (
+ configA: ReactRouterConfig,
+ configB: ReactRouterConfig,
+ ): ReactRouterConfig => {
+ let mergeRequired = (key: keyof ReactRouterConfig) =>
+ configA[key] !== undefined && configB[key] !== undefined;
+
+ return {
+ ...configA,
+ ...configB,
+ ...(mergeRequired("buildEnd")
+ ? {
+ buildEnd: async (...args) => {
+ await Promise.all([
+ configA.buildEnd?.(...args),
+ configB.buildEnd?.(...args),
+ ]);
+ },
+ }
+ : {}),
+ ...(mergeRequired("future")
+ ? {
+ future: {
+ ...configA.future,
+ ...configB.future,
+ },
+ }
+ : {}),
+ ...(mergeRequired("presets")
+ ? {
+ presets: [...(configA.presets ?? []), ...(configB.presets ?? [])],
+ }
+ : {}),
+ };
+ };
+
+ return configs.reduce(reducer, {});
+};
+
+// Inlined from https://github.com/jsdf/deep-freeze
+let deepFreeze = (o: any) => {
+ Object.freeze(o);
+ let oIsFunction = typeof o === "function";
+ let hasOwnProp = Object.prototype.hasOwnProperty;
+ Object.getOwnPropertyNames(o).forEach(function (prop) {
+ if (
+ hasOwnProp.call(o, prop) &&
+ (oIsFunction
+ ? prop !== "caller" && prop !== "callee" && prop !== "arguments"
+ : true) &&
+ o[prop] !== null &&
+ (typeof o[prop] === "object" || typeof o[prop] === "function") &&
+ !Object.isFrozen(o[prop])
+ ) {
+ deepFreeze(o[prop]);
+ }
+ });
+ return o;
+};
+
+type Result =
+ | {
+ ok: true;
+ value: T;
+ error?: undefined;
+ }
+ | {
+ ok: false;
+ value?: undefined;
+ error: string;
+ };
+
+type ConfigResult = Result;
+
+function ok(value: T): Result {
+ return { ok: true, value };
+}
+
+function err(error: string): Result {
+ return { ok: false, error };
+}
+
+async function resolveConfig({
+ root,
+ viteNodeContext,
+ reactRouterConfigFile,
+ skipRoutes,
+ validateConfig,
+ shouldLogFutureFlagWarnings,
+}: {
+ root: string;
+ viteNodeContext: ViteNode.Context;
+ reactRouterConfigFile?: string;
+ skipRoutes?: boolean;
+ validateConfig?: ValidateConfigFunction;
+ shouldLogFutureFlagWarnings?: boolean;
+}): Promise {
+ let reactRouterUserConfig: ReactRouterConfig = {};
+
+ if (reactRouterConfigFile) {
+ try {
+ if (!fs.existsSync(reactRouterConfigFile)) {
+ return err(`${reactRouterConfigFile} no longer exists`);
+ }
+
+ let configModule = await viteNodeContext.runner.executeFile(
+ reactRouterConfigFile,
+ );
+
+ if (configModule.default === undefined) {
+ return err(`${reactRouterConfigFile} must provide a default export`);
+ }
+
+ if (typeof configModule.default !== "object") {
+ return err(`${reactRouterConfigFile} must export a config`);
+ }
+
+ reactRouterUserConfig = configModule.default;
+
+ if (validateConfig) {
+ const error = validateConfig(reactRouterUserConfig);
+ if (error) {
+ return err(error);
+ }
+ }
+ } catch (error) {
+ return err(`Error loading ${reactRouterConfigFile}: ${error}`);
+ }
+ }
+
+ // Prevent mutations to the user config
+ reactRouterUserConfig = deepFreeze(cloneDeep(reactRouterUserConfig));
+
+ let presets: ReactRouterConfig[] = (
+ await Promise.all(
+ (reactRouterUserConfig.presets ?? []).map(async (preset) => {
+ if (!preset.name) {
+ throw new Error(
+ "React Router presets must have a `name` property defined.",
+ );
+ }
+
+ if (!preset.reactRouterConfig) {
+ return null;
+ }
+
+ let configPreset: ReactRouterConfig = omit(
+ await preset.reactRouterConfig({ reactRouterUserConfig }),
+ excludedConfigPresetKeys,
+ );
+
+ return configPreset;
+ }),
+ )
+ ).filter(function isNotNull(value: T | null): value is T {
+ return value !== null;
+ });
+
+ let defaults = {
+ basename: "/",
+ buildDirectory: "build",
+ serverBuildFile: "index.js",
+ serverModuleFormat: "esm",
+ ssr: true,
+ } as const satisfies Partial;
+
+ let userAndPresetConfigs = mergeReactRouterConfig(
+ ...presets,
+ reactRouterUserConfig,
+ );
+
+ let {
+ appDirectory: userAppDirectory,
+ basename,
+ buildDirectory: userBuildDirectory,
+ buildEnd,
+ prerender,
+ routeDiscovery: userRouteDiscovery,
+ serverBuildFile,
+ serverBundles,
+ serverModuleFormat,
+ ssr,
+ } = {
+ ...defaults, // Default values should be completely overridden by user/preset config, not merged
+ ...userAndPresetConfigs,
+ };
+
+ if (!ssr && serverBundles) {
+ serverBundles = undefined;
+ }
+
+ if (prerender) {
+ let isValidPrerenderPathsConfig = (p: unknown) =>
+ typeof p === "boolean" || typeof p === "function" || Array.isArray(p);
+
+ let isValidPrerenderConfig =
+ isValidPrerenderPathsConfig(prerender) ||
+ (typeof prerender === "object" &&
+ "paths" in prerender &&
+ isValidPrerenderPathsConfig(prerender.paths));
+
+ if (!isValidPrerenderConfig) {
+ return err(
+ "The `prerender`/`prerender.paths` config must be a boolean, an array " +
+ "of string paths, or a function returning a boolean or array of string paths.",
+ );
+ }
+
+ if (typeof prerender === "object" && "unstable_concurrency" in prerender) {
+ return err(
+ "The `prerender.unstable_concurrency` config field has been stabilized as `prerender.concurrency`",
+ );
+ }
+
+ let isValidConcurrencyConfig =
+ typeof prerender != "object" ||
+ !("concurrency" in prerender) ||
+ (typeof prerender.concurrency === "number" &&
+ Number.isInteger(prerender.concurrency) &&
+ prerender.concurrency > 0);
+
+ if (!isValidConcurrencyConfig) {
+ return err(
+ "The `prerender.concurrency` config must be a positive integer if specified.",
+ );
+ }
+ }
+
+ let routeDiscovery: ResolvedReactRouterConfig["routeDiscovery"];
+ if (userRouteDiscovery == null) {
+ if (ssr) {
+ routeDiscovery = {
+ mode: "lazy",
+ manifestPath: "/__manifest",
+ };
+ } else {
+ routeDiscovery = { mode: "initial" };
+ }
+ } else if (userRouteDiscovery.mode === "initial") {
+ routeDiscovery = userRouteDiscovery;
+ } else if (userRouteDiscovery.mode === "lazy") {
+ if (!ssr) {
+ return err(
+ 'The `routeDiscovery.mode` config cannot be set to "lazy" when setting `ssr:false`',
+ );
+ }
+
+ let { manifestPath } = userRouteDiscovery;
+ if (manifestPath != null && !manifestPath.startsWith("/")) {
+ return err(
+ "The `routeDiscovery.manifestPath` config must be a root-relative " +
+ 'pathname beginning with a slash (i.e., "/__manifest")',
+ );
+ }
+
+ routeDiscovery = userRouteDiscovery;
+ }
+
+ let appDirectory = Path.resolve(root, userAppDirectory || "app");
+ let buildDirectory = Path.resolve(root, userBuildDirectory);
+
+ let rootRouteFile = findEntry(appDirectory, "root", { absolute: true });
+ if (!rootRouteFile) {
+ let rootRouteDisplayPath = Path.relative(
+ root,
+ Path.join(appDirectory, "root.tsx"),
+ );
+ return err(
+ `Could not find a root route module in the app directory as "${rootRouteDisplayPath}"`,
+ );
+ }
+
+ let routes: RouteManifest;
+ let routeConfig: RouteConfigEntry[] = [];
+
+ if (skipRoutes) {
+ routes = {};
+ } else {
+ let routeConfigFile = findEntry(appDirectory, "routes");
+
+ try {
+ if (!routeConfigFile) {
+ let routeConfigDisplayPath = Path.relative(
+ root,
+ Path.join(appDirectory, "routes.ts"),
+ );
+ return err(
+ `Route config file not found at "${routeConfigDisplayPath}".`,
+ );
+ }
+
+ setAppDirectory(appDirectory);
+ let routeConfigExport = (
+ await viteNodeContext.runner.executeFile(
+ Path.join(appDirectory, routeConfigFile),
+ )
+ ).default;
+ let result = validateRouteConfig({
+ routeConfigFile,
+ routeConfig: await routeConfigExport,
+ });
+
+ if (!result.valid) {
+ return err(result.message);
+ }
+
+ // Nest the route config under the resolved root route
+ routeConfig = [
+ {
+ id: "root",
+ path: "",
+ file: Path.relative(appDirectory, rootRouteFile),
+ children: result.routeConfig,
+ },
+ ];
+
+ routes = configRoutesToRouteManifest(appDirectory, routeConfig);
+ } catch (error: any) {
+ return err(
+ [
+ colors.red(`Route config in "${routeConfigFile}" is invalid.`),
+ "",
+ error.loc?.file && error.loc?.column && error.frame
+ ? [
+ Path.relative(appDirectory, error.loc.file) +
+ ":" +
+ error.loc.line +
+ ":" +
+ error.loc.column,
+ error.frame.trim?.(),
+ ]
+ : error.stack,
+ ]
+ .flat()
+ .join("\n"),
+ );
+ }
+ }
+
+ // Check for renamed flags and provide helpful error messages
+ let futureConfig = userAndPresetConfigs.future;
+ if (futureConfig) {
+ if ("unstable_splitRouteModules" in futureConfig) {
+ return err(
+ "The `future.unstable_splitRouteModules` flag has been stabilized as `future.v8_splitRouteModules`",
+ );
+ }
+ if ("unstable_viteEnvironmentApi" in futureConfig) {
+ return err(
+ "The `future.unstable_viteEnvironmentApi` flag has been stabilized as `future.v8_viteEnvironmentApi`",
+ );
+ }
+ if ("unstable_passThroughRequests" in futureConfig) {
+ return err(
+ "The `future.unstable_passThroughRequests` flag has been stabilized as `future.v8_passThroughRequests`",
+ );
+ }
+ if ("unstable_trailingSlashAwareDataRequests" in futureConfig) {
+ return err(
+ "The `future.unstable_trailingSlashAwareDataRequests` flag has been stabilized as `future.v8_trailingSlashAwareDataRequests`",
+ );
+ }
+ if ("unstable_subResourceIntegrity" in futureConfig) {
+ return err(
+ "The `future.unstable_subResourceIntegrity` flag has been stabilized and moved to a top-level `config.subResourceIntegrity` field",
+ );
+ }
+ }
+
+ let future: FutureConfig = {
+ unstable_optimizeDeps:
+ userAndPresetConfigs.future?.unstable_optimizeDeps ?? false,
+ v8_passThroughRequests:
+ userAndPresetConfigs.future?.v8_passThroughRequests ?? false,
+ v8_trailingSlashAwareDataRequests:
+ userAndPresetConfigs.future?.v8_trailingSlashAwareDataRequests ?? false,
+ unstable_previewServerPrerendering:
+ userAndPresetConfigs.future?.unstable_previewServerPrerendering ?? false,
+ v8_middleware: userAndPresetConfigs.future?.v8_middleware ?? false,
+ v8_splitRouteModules:
+ userAndPresetConfigs.future?.v8_splitRouteModules ?? false,
+ v8_viteEnvironmentApi:
+ (userAndPresetConfigs.future?.v8_viteEnvironmentApi ||
+ userAndPresetConfigs.future?.unstable_previewServerPrerendering) ??
+ false,
+ };
+
+ let allowedActionOrigins = userAndPresetConfigs.allowedActionOrigins ?? false;
+ let subResourceIntegrity = userAndPresetConfigs.subResourceIntegrity ?? false;
+
+ let reactRouterConfig: ResolvedReactRouterConfig = deepFreeze({
+ appDirectory,
+ basename,
+ buildDirectory,
+ buildEnd,
+ future,
+ prerender,
+ routes,
+ routeDiscovery,
+ serverBuildFile,
+ serverBundles,
+ serverModuleFormat,
+ ssr,
+ subResourceIntegrity,
+ allowedActionOrigins,
+ unstable_routeConfig: routeConfig,
+ } satisfies ResolvedReactRouterConfig);
+
+ for (let preset of reactRouterUserConfig.presets ?? []) {
+ await preset.reactRouterConfigResolved?.({ reactRouterConfig });
+ }
+
+ if (shouldLogFutureFlagWarnings) {
+ logFutureFlagWarnings(userAndPresetConfigs.future || {});
+ }
+
+ return ok(reactRouterConfig);
+}
+
+function logFutureFlagWarning(flag: string, message: string): void {
+ console.log(
+ colors.yellow(
+ ` ⚠️ Future Flag Warning: ${message}\n` +
+ ` You can use the \`future.${flag}\` flag to opt in early.\n` +
+ ` -> https://reactrouter.com/7.16.0/upgrading/future#future${flag.toLowerCase()}`,
+ ),
+ );
+}
+
+export function logFutureFlagWarnings(future: Partial): void {
+ if (future.v8_middleware === undefined) {
+ logFutureFlagWarning(
+ "v8_middleware",
+ "Route middleware support is changing in React Router v8.",
+ );
+ }
+ if (future.v8_splitRouteModules === undefined) {
+ logFutureFlagWarning(
+ "v8_splitRouteModules",
+ "Route module splitting behavior is changing in React Router v8.",
+ );
+ }
+ if (future.v8_viteEnvironmentApi === undefined) {
+ logFutureFlagWarning(
+ "v8_viteEnvironmentApi",
+ "Vite Environment API usage is changing in React Router v8.",
+ );
+ }
+ if (future.v8_passThroughRequests === undefined) {
+ logFutureFlagWarning(
+ "v8_passThroughRequests",
+ "Request handling behavior is changing in React Router v8.",
+ );
+ }
+ if (future.v8_trailingSlashAwareDataRequests === undefined) {
+ logFutureFlagWarning(
+ "v8_trailingSlashAwareDataRequests",
+ "Data request URL formats are changing in React Router v8.",
+ );
+ }
+}
+
+type ChokidarEventName = ChokidarEmitArgs[0];
+
+type ChangeHandler = (args: {
+ result: ConfigResult;
+ configCodeChanged: boolean;
+ routeConfigCodeChanged: boolean;
+ configChanged: boolean;
+ routeConfigChanged: boolean;
+ path: string;
+ event: ChokidarEventName;
+}) => void;
+
+export type ConfigLoader = {
+ getConfig: () => Promise;
+ onChange: (handler: ChangeHandler) => () => void;
+ close: () => Promise;
+};
+
+export async function createConfigLoader({
+ rootDirectory: root,
+ watch,
+ mode,
+ skipRoutes,
+ validateConfig,
+ shouldLogFutureFlagWarnings,
+}: {
+ watch: boolean;
+ rootDirectory?: string;
+ mode: string;
+ skipRoutes?: boolean;
+ validateConfig?: ValidateConfigFunction;
+ shouldLogFutureFlagWarnings?: boolean;
+}): Promise {
+ root = Path.normalize(root ?? process.env.REACT_ROUTER_ROOT ?? process.cwd());
+
+ let vite = await import("vite");
+ let viteNodeContext = await ViteNode.createContext({
+ root,
+ mode,
+ // Filter out any info level logs from vite-node
+ customLogger: vite.createLogger("warn", {
+ prefix: "[react-router]",
+ }),
+ });
+
+ let reactRouterConfigFile: string | undefined;
+
+ let updateReactRouterConfigFile = () => {
+ reactRouterConfigFile = findEntry(root, "react-router.config", {
+ absolute: true,
+ });
+ };
+
+ updateReactRouterConfigFile();
+
+ let getConfig = () =>
+ resolveConfig({
+ root,
+ viteNodeContext,
+ reactRouterConfigFile,
+ skipRoutes,
+ validateConfig,
+ shouldLogFutureFlagWarnings,
+ });
+
+ let appDirectory: string;
+
+ let initialConfigResult = await resolveConfig({
+ root,
+ viteNodeContext,
+ reactRouterConfigFile,
+ skipRoutes,
+ validateConfig,
+ });
+
+ if (!initialConfigResult.ok) {
+ throw new Error(initialConfigResult.error);
+ }
+
+ appDirectory = Path.normalize(initialConfigResult.value.appDirectory);
+
+ let currentConfig = initialConfigResult.value;
+
+ let fsWatcher: FSWatcher | undefined;
+ let changeHandlers: ChangeHandler[] = [];
+
+ return {
+ getConfig,
+ onChange: (handler: ChangeHandler) => {
+ if (!watch) {
+ throw new Error(
+ "onChange is not supported when watch mode is disabled",
+ );
+ }
+
+ changeHandlers.push(handler);
+
+ if (!fsWatcher) {
+ fsWatcher = chokidar.watch([root, appDirectory], {
+ ignoreInitial: true,
+ ignored: (path) => isIgnoredByWatcher(path, { root, appDirectory }),
+ });
+
+ fsWatcher.on("error", (error: unknown) => {
+ let message = error instanceof Error ? error.message : String(error);
+ console.warn(colors.yellow(`File watcher error: ${message}`));
+ });
+
+ fsWatcher.on("all", async (...args) => {
+ let [event, rawFilepath] = args;
+ let filepath = Path.normalize(rawFilepath);
+
+ let fileAddedOrRemoved = event === "add" || event === "unlink";
+
+ let appFileAddedOrRemoved =
+ fileAddedOrRemoved &&
+ filepath.startsWith(Path.normalize(appDirectory));
+
+ let rootRelativeFilepath = Path.relative(root, filepath);
+
+ let configFileAddedOrRemoved =
+ fileAddedOrRemoved &&
+ isEntryFile("react-router.config", rootRelativeFilepath);
+
+ if (configFileAddedOrRemoved) {
+ updateReactRouterConfigFile();
+ }
+
+ let moduleGraphChanged =
+ configFileAddedOrRemoved ||
+ Boolean(
+ viteNodeContext.devServer?.moduleGraph.getModuleById(filepath),
+ );
+
+ // Bail out if no relevant changes detected
+ if (!moduleGraphChanged && !appFileAddedOrRemoved) {
+ return;
+ }
+
+ viteNodeContext.devServer?.moduleGraph.invalidateAll();
+ viteNodeContext.runner?.moduleCache.clear();
+
+ let result = await getConfig();
+
+ let prevAppDirectory = appDirectory;
+ appDirectory = Path.normalize(
+ (result.value ?? currentConfig).appDirectory,
+ );
+
+ if (appDirectory !== prevAppDirectory) {
+ fsWatcher!.unwatch(prevAppDirectory);
+ fsWatcher!.add(appDirectory);
+ }
+
+ let configCodeChanged =
+ configFileAddedOrRemoved ||
+ (reactRouterConfigFile !== undefined &&
+ isEntryFileDependency(
+ viteNodeContext.devServer.moduleGraph,
+ reactRouterConfigFile,
+ filepath,
+ ));
+
+ let routeConfigFile = !skipRoutes
+ ? findEntry(appDirectory, "routes", {
+ absolute: true,
+ })
+ : undefined;
+ let routeConfigCodeChanged =
+ routeConfigFile !== undefined &&
+ isEntryFileDependency(
+ viteNodeContext.devServer.moduleGraph,
+ routeConfigFile,
+ filepath,
+ );
+
+ let configChanged =
+ result.ok &&
+ !isEqual(omitRoutes(currentConfig), omitRoutes(result.value));
+
+ let routeConfigChanged =
+ result.ok && !isEqual(currentConfig?.routes, result.value.routes);
+
+ for (let handler of changeHandlers) {
+ handler({
+ result,
+ configCodeChanged,
+ routeConfigCodeChanged,
+ configChanged,
+ routeConfigChanged,
+ path: filepath,
+ event,
+ });
+ }
+
+ if (result.ok) {
+ currentConfig = result.value;
+ }
+ });
+ }
+
+ return () => {
+ changeHandlers = changeHandlers.filter(
+ (changeHandler) => changeHandler !== handler,
+ );
+ };
+ },
+ close: async () => {
+ changeHandlers = [];
+ await viteNodeContext.devServer.close();
+ await fsWatcher?.close();
+ },
+ };
+}
+
+export async function loadConfig({
+ rootDirectory,
+ mode,
+ skipRoutes,
+}: {
+ rootDirectory: string;
+ mode: string;
+ skipRoutes?: boolean;
+}) {
+ let configLoader = await createConfigLoader({
+ rootDirectory,
+ mode,
+ skipRoutes,
+ watch: false,
+ });
+ let config = await configLoader.getConfig();
+ await configLoader.close();
+ return config;
+}
+
+export async function resolveEntryFiles({
+ rootDirectory,
+ reactRouterConfig,
+}: {
+ rootDirectory: string;
+ reactRouterConfig: ResolvedReactRouterConfig;
+}) {
+ let { appDirectory } = reactRouterConfig;
+
+ let defaultsDirectory = Path.resolve(
+ Path.dirname(require.resolve("@react-router/dev/package.json")),
+ "dist",
+ "config",
+ "defaults",
+ );
+
+ let userEntryClientFile = findEntry(appDirectory, "entry.client");
+ let userEntryServerFile = findEntry(appDirectory, "entry.server");
+
+ let entryServerFile: string;
+ let entryClientFile = userEntryClientFile || "entry.client.tsx";
+
+ if (userEntryServerFile) {
+ entryServerFile = userEntryServerFile;
+ } else {
+ let packageJsonPath = findEntry(rootDirectory, "package", {
+ extensions: [".json"],
+ absolute: true,
+ walkParents: true,
+ });
+
+ if (!packageJsonPath) {
+ throw new Error(
+ `Could not find package.json in ${rootDirectory} or any of its parent directories. Please add a package.json, or provide a custom entry.server.tsx/jsx file in your app directory.`,
+ );
+ }
+
+ // TODO(v8): Remove - only required for Node 20.18 and below
+ let { readPackageJSON, sortPackage, updatePackage } = await import(
+ "pkg-types"
+ );
+ let packageJsonDirectory = Path.dirname(packageJsonPath);
+ let pkgJson = await readPackageJSON(packageJsonDirectory);
+ let deps = pkgJson.dependencies ?? {};
+
+ if (!deps["@react-router/node"]) {
+ throw new Error(
+ `Could not determine server runtime. Please install @react-router/node, or provide a custom entry.server.tsx/jsx file in your app directory.`,
+ );
+ }
+
+ if (!deps["isbot"]) {
+ console.log(
+ "adding `isbot@5` to your package.json, you should commit this change",
+ );
+
+ await updatePackage(packageJsonPath, (pkg) => {
+ pkg.dependencies ??= {};
+ pkg.dependencies.isbot = "^5";
+ sortPackage(pkg);
+ });
+
+ let packageManager = detectPackageManager() ?? "npm";
+
+ execSync(`${packageManager} install`, {
+ cwd: packageJsonDirectory,
+ stdio: "inherit",
+ });
+ }
+
+ entryServerFile = `entry.server.node.tsx`;
+ }
+
+ let entryClientFilePath = userEntryClientFile
+ ? Path.resolve(reactRouterConfig.appDirectory, userEntryClientFile)
+ : Path.resolve(defaultsDirectory, entryClientFile);
+
+ let entryServerFilePath = userEntryServerFile
+ ? Path.resolve(reactRouterConfig.appDirectory, userEntryServerFile)
+ : Path.resolve(defaultsDirectory, entryServerFile);
+
+ return { entryClientFilePath, entryServerFilePath };
+}
+
+export async function resolveRSCEntryFiles({
+ reactRouterConfig,
+}: {
+ reactRouterConfig: ResolvedReactRouterConfig;
+}) {
+ let { appDirectory } = reactRouterConfig;
+
+ let defaultsDirectory = Path.resolve(
+ Path.dirname(require.resolve("@react-router/dev/package.json")),
+ "dist",
+ "config",
+ "default-rsc-entries",
+ );
+
+ let userEntryClientFile = findEntry(appDirectory, "entry.client", {
+ absolute: true,
+ });
+ let userEntryRSCFile = findEntry(appDirectory, "entry.rsc", {
+ absolute: true,
+ });
+ let userEntrySSRFile = findEntry(appDirectory, "entry.ssr", {
+ absolute: true,
+ });
+
+ let client =
+ userEntryClientFile ?? Path.join(defaultsDirectory, "entry.client.tsx");
+ let rsc = userEntryRSCFile ?? Path.join(defaultsDirectory, "entry.rsc.tsx");
+ let ssr = userEntrySSRFile ?? Path.join(defaultsDirectory, "entry.ssr.tsx");
+
+ return { client, rsc, ssr };
+}
+
+function omitRoutes(
+ config: ResolvedReactRouterConfig,
+): ResolvedReactRouterConfig {
+ return {
+ ...config,
+ routes: {},
+ };
+}
+
+const entryExts = [".js", ".jsx", ".ts", ".tsx", ".mjs", ".mts"];
+
+function isEntryFile(entryBasename: string, filename: string) {
+ return entryExts.some((ext) => filename === `${entryBasename}${ext}`);
+}
+
+function findEntry(
+ dir: string,
+ basename: string,
+ options?: {
+ absolute?: boolean;
+ extensions?: string[];
+ walkParents?: boolean;
+ },
+): string | undefined {
+ let currentDir = Path.resolve(dir);
+ let { root } = Path.parse(currentDir);
+
+ while (true) {
+ for (let ext of options?.extensions ?? entryExts) {
+ let file = Path.resolve(currentDir, basename + ext);
+ if (fs.existsSync(file)) {
+ return (options?.absolute ?? false) ? file : Path.relative(dir, file);
+ }
+ }
+
+ if (!options?.walkParents) {
+ return undefined;
+ }
+
+ let parentDir = Path.dirname(currentDir);
+ // Break out when we've reached the root directory or we're about to get
+ // stuck in a loop where `path.dirname` keeps returning "/"
+ if (currentDir === root || parentDir === currentDir) {
+ return undefined;
+ }
+
+ currentDir = parentDir;
+ }
+}
+
+function isEntryFileDependency(
+ moduleGraph: Vite.ModuleGraph,
+ entryFilepath: string,
+ filepath: string,
+ visited = new Set(),
+): boolean {
+ // Ensure normalized paths
+ entryFilepath = Path.normalize(entryFilepath);
+ filepath = Path.normalize(filepath);
+
+ if (visited.has(filepath)) {
+ return false;
+ }
+
+ visited.add(filepath);
+
+ if (filepath === entryFilepath) {
+ return true;
+ }
+
+ let mod = moduleGraph.getModuleById(filepath);
+
+ if (!mod) {
+ return false;
+ }
+
+ // Recursively check all importers to see if any of them are the entry file
+ for (let importer of mod.importers) {
+ if (!importer.id) {
+ continue;
+ }
+
+ if (
+ importer.id === entryFilepath ||
+ isEntryFileDependency(moduleGraph, entryFilepath, importer.id, visited)
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+export function isIgnoredByWatcher(
+ path: string,
+ { root, appDirectory }: { root: string; appDirectory: string },
+): boolean {
+ let dirname = Path.dirname(path);
+
+ let ignoredByPath =
+ !dirname.startsWith(appDirectory) &&
+ // Ensure we're only watching files outside of the app directory
+ // that are at the root level, not nested in subdirectories
+ path !== root && // Watch the root directory itself
+ dirname !== root; // Watch files at the root level
+
+ if (ignoredByPath) {
+ return true;
+ }
+
+ // Filter out non-regular files (sockets, pipes, etc.) that
+ // crash `fs.watch()` on macOS with errno -102
+ // https://github.com/paulmillr/chokidar/issues/1391
+ try {
+ let stat = fs.statSync(path, { throwIfNoEntry: false });
+ if (stat && !stat.isFile() && !stat.isDirectory()) {
+ return true;
+ }
+ } catch {
+ return true;
+ }
+
+ return false;
+}
diff --git a/tests/testdata/ts_trpc_chore_export/expected.json b/tests/testdata/ts_trpc_chore_export/expected.json
new file mode 100644
index 0000000..d35d858
--- /dev/null
+++ b/tests/testdata/ts_trpc_chore_export/expected.json
@@ -0,0 +1,904 @@
+{
+ "version": "v1",
+ "actions": [
+ {
+ "action": "insert",
+ "node": {
+ "tree": "after",
+ "path": [
+ 14
+ ],
+ "type": "export_statement",
+ "start_byte": 932,
+ "end_byte": 1608
+ },
+ "parent": {
+ "tree": "after",
+ "path": null,
+ "type": "program",
+ "start_byte": 0,
+ "end_byte": 6964
+ },
+ "position": 14
+ },
+ {
+ "action": "insert",
+ "node": {
+ "tree": "after",
+ "path": [
+ 14,
+ 0
+ ],
+ "type": "export",
+ "label": "export",
+ "start_byte": 932,
+ "end_byte": 938
+ },
+ "parent": {
+ "tree": "after",
+ "path": [
+ 14
+ ],
+ "type": "export_statement",
+ "start_byte": 932,
+ "end_byte": 1608
+ },
+ "position": 0
+ },
+ {
+ "action": "move",
+ "node": {
+ "tree": "before",
+ "path": [
+ 14
+ ],
+ "type": "type_alias_declaration",
+ "start_byte": 932,
+ "end_byte": 1601
+ },
+ "parent": {
+ "tree": "after",
+ "path": [
+ 14
+ ],
+ "type": "export_statement",
+ "start_byte": 932,
+ "end_byte": 1608
+ },
+ "position": 1,
+ "old_parent": {
+ "tree": "before",
+ "path": null,
+ "type": "program",
+ "start_byte": 0,
+ "end_byte": 6957
+ },
+ "old_position": 14,
+ "subtree": true,
+ "dest_start_byte": 939,
+ "dest_end_byte": 1608
+ }
+ ],
+ "line_alignment": [
+ {
+ "left_line": 0,
+ "right_line": 0
+ },
+ {
+ "left_line": 1,
+ "right_line": 1
+ },
+ {
+ "left_line": 2,
+ "right_line": 2
+ },
+ {
+ "left_line": 3,
+ "right_line": 3
+ },
+ {
+ "left_line": 4,
+ "right_line": 4
+ },
+ {
+ "left_line": 5,
+ "right_line": 5
+ },
+ {
+ "left_line": 6,
+ "right_line": 6
+ },
+ {
+ "left_line": 7,
+ "right_line": 7
+ },
+ {
+ "left_line": 8,
+ "right_line": 8
+ },
+ {
+ "left_line": 9,
+ "right_line": 9
+ },
+ {
+ "left_line": 10,
+ "right_line": 10
+ },
+ {
+ "left_line": 11,
+ "right_line": 11
+ },
+ {
+ "left_line": 12,
+ "right_line": 12
+ },
+ {
+ "left_line": 13,
+ "right_line": 13
+ },
+ {
+ "left_line": 14,
+ "right_line": 14
+ },
+ {
+ "left_line": 15,
+ "right_line": 15
+ },
+ {
+ "left_line": 16,
+ "right_line": 16
+ },
+ {
+ "left_line": 17,
+ "right_line": 17
+ },
+ {
+ "left_line": 18,
+ "right_line": 18
+ },
+ {
+ "left_line": 19,
+ "right_line": 19
+ },
+ {
+ "left_line": 20,
+ "right_line": 20
+ },
+ {
+ "left_line": 21,
+ "right_line": 21
+ },
+ {
+ "left_line": 22,
+ "right_line": 22
+ },
+ {
+ "left_line": 23,
+ "right_line": 23
+ },
+ {
+ "left_line": 24,
+ "right_line": 24
+ },
+ {
+ "left_line": 25,
+ "right_line": 25
+ },
+ {
+ "left_line": 26,
+ "right_line": 26
+ },
+ {
+ "left_line": 27,
+ "right_line": 27
+ },
+ {
+ "left_line": 28,
+ "right_line": 28
+ },
+ {
+ "left_line": 29,
+ "right_line": 29
+ },
+ {
+ "left_line": 30,
+ "right_line": 30
+ },
+ {
+ "left_line": 31,
+ "right_line": 31
+ },
+ {
+ "left_line": 32,
+ "right_line": 32
+ },
+ {
+ "left_line": 33,
+ "right_line": 33
+ },
+ {
+ "left_line": 34,
+ "right_line": 34
+ },
+ {
+ "left_line": 35,
+ "right_line": 35
+ },
+ {
+ "left_line": 36,
+ "right_line": 36
+ },
+ {
+ "left_line": 37,
+ "right_line": 37
+ },
+ {
+ "left_line": 38,
+ "right_line": 38
+ },
+ {
+ "left_line": 39,
+ "right_line": 39
+ },
+ {
+ "left_line": 40,
+ "right_line": 40
+ },
+ {
+ "left_line": 41,
+ "right_line": 41
+ },
+ {
+ "left_line": 42,
+ "right_line": 42
+ },
+ {
+ "left_line": 43,
+ "right_line": 43
+ },
+ {
+ "left_line": 44,
+ "right_line": 44
+ },
+ {
+ "left_line": 45,
+ "right_line": 45
+ },
+ {
+ "left_line": 46,
+ "right_line": 46
+ },
+ {
+ "left_line": 47,
+ "right_line": 47
+ },
+ {
+ "left_line": 48,
+ "right_line": 48
+ },
+ {
+ "left_line": 49,
+ "right_line": 49
+ },
+ {
+ "left_line": 50,
+ "right_line": 50
+ },
+ {
+ "left_line": 51,
+ "right_line": 51
+ },
+ {
+ "left_line": 52,
+ "right_line": 52
+ },
+ {
+ "left_line": 53,
+ "right_line": 53
+ },
+ {
+ "left_line": 54,
+ "right_line": 54
+ },
+ {
+ "left_line": 55,
+ "right_line": 55
+ },
+ {
+ "left_line": 56,
+ "right_line": 56
+ },
+ {
+ "left_line": 57,
+ "right_line": 57
+ },
+ {
+ "left_line": 58,
+ "right_line": 58
+ },
+ {
+ "left_line": 59,
+ "right_line": 59
+ },
+ {
+ "left_line": 60,
+ "right_line": 60
+ },
+ {
+ "left_line": 61,
+ "right_line": 61
+ },
+ {
+ "left_line": 62,
+ "right_line": 62
+ },
+ {
+ "left_line": 63,
+ "right_line": 63
+ },
+ {
+ "left_line": 64,
+ "right_line": 64
+ },
+ {
+ "left_line": 65,
+ "right_line": 65
+ },
+ {
+ "left_line": 66,
+ "right_line": 66
+ },
+ {
+ "left_line": 67,
+ "right_line": 67
+ },
+ {
+ "left_line": 68,
+ "right_line": 68
+ },
+ {
+ "left_line": 69,
+ "right_line": 69
+ },
+ {
+ "left_line": 70,
+ "right_line": 70
+ },
+ {
+ "left_line": 71,
+ "right_line": 71
+ },
+ {
+ "left_line": 72,
+ "right_line": 72
+ },
+ {
+ "left_line": 73,
+ "right_line": 73
+ },
+ {
+ "left_line": 74,
+ "right_line": 74
+ },
+ {
+ "left_line": 75,
+ "right_line": 75
+ },
+ {
+ "left_line": 76,
+ "right_line": 76
+ },
+ {
+ "left_line": 77,
+ "right_line": 77
+ },
+ {
+ "left_line": 78,
+ "right_line": 78
+ },
+ {
+ "left_line": 79,
+ "right_line": 79
+ },
+ {
+ "left_line": 80,
+ "right_line": 80
+ },
+ {
+ "left_line": 81,
+ "right_line": 81
+ },
+ {
+ "left_line": 82,
+ "right_line": 82
+ },
+ {
+ "left_line": 83,
+ "right_line": 83
+ },
+ {
+ "left_line": 84,
+ "right_line": 84
+ },
+ {
+ "left_line": 85,
+ "right_line": 85
+ },
+ {
+ "left_line": 86,
+ "right_line": 86
+ },
+ {
+ "left_line": 87,
+ "right_line": 87
+ },
+ {
+ "left_line": 88,
+ "right_line": 88
+ },
+ {
+ "left_line": 89,
+ "right_line": 89
+ },
+ {
+ "left_line": 90,
+ "right_line": 90
+ },
+ {
+ "left_line": 91,
+ "right_line": 91
+ },
+ {
+ "left_line": 92,
+ "right_line": 92
+ },
+ {
+ "left_line": 93,
+ "right_line": 93
+ },
+ {
+ "left_line": 94,
+ "right_line": 94
+ },
+ {
+ "left_line": 95,
+ "right_line": 95
+ },
+ {
+ "left_line": 96,
+ "right_line": 96
+ },
+ {
+ "left_line": 97,
+ "right_line": 97
+ },
+ {
+ "left_line": 98,
+ "right_line": 98
+ },
+ {
+ "left_line": 99,
+ "right_line": 99
+ },
+ {
+ "left_line": 100,
+ "right_line": 100
+ },
+ {
+ "left_line": 101,
+ "right_line": 101
+ },
+ {
+ "left_line": 102,
+ "right_line": 102
+ },
+ {
+ "left_line": 103,
+ "right_line": 103
+ },
+ {
+ "left_line": 104,
+ "right_line": 104
+ },
+ {
+ "left_line": 105,
+ "right_line": 105
+ },
+ {
+ "left_line": 106,
+ "right_line": 106
+ },
+ {
+ "left_line": 107,
+ "right_line": 107
+ },
+ {
+ "left_line": 108,
+ "right_line": 108
+ },
+ {
+ "left_line": 109,
+ "right_line": 109
+ },
+ {
+ "left_line": 110,
+ "right_line": 110
+ },
+ {
+ "left_line": 111,
+ "right_line": 111
+ },
+ {
+ "left_line": 112,
+ "right_line": 112
+ },
+ {
+ "left_line": 113,
+ "right_line": 113
+ },
+ {
+ "left_line": 114,
+ "right_line": 114
+ },
+ {
+ "left_line": 115,
+ "right_line": 115
+ },
+ {
+ "left_line": 116,
+ "right_line": 116
+ },
+ {
+ "left_line": 117,
+ "right_line": 117
+ },
+ {
+ "left_line": 118,
+ "right_line": 118
+ },
+ {
+ "left_line": 119,
+ "right_line": 119
+ },
+ {
+ "left_line": 120,
+ "right_line": 120
+ },
+ {
+ "left_line": 121,
+ "right_line": 121
+ },
+ {
+ "left_line": 122,
+ "right_line": 122
+ },
+ {
+ "left_line": 123,
+ "right_line": 123
+ },
+ {
+ "left_line": 124,
+ "right_line": 124
+ },
+ {
+ "left_line": 125,
+ "right_line": 125
+ },
+ {
+ "left_line": 126,
+ "right_line": 126
+ },
+ {
+ "left_line": 127,
+ "right_line": 127
+ },
+ {
+ "left_line": 128,
+ "right_line": 128
+ },
+ {
+ "left_line": 129,
+ "right_line": 129
+ },
+ {
+ "left_line": 130,
+ "right_line": 130
+ },
+ {
+ "left_line": 131,
+ "right_line": 131
+ },
+ {
+ "left_line": 132,
+ "right_line": 132
+ },
+ {
+ "left_line": 133,
+ "right_line": 133
+ },
+ {
+ "left_line": 134,
+ "right_line": 134
+ },
+ {
+ "left_line": 135,
+ "right_line": 135
+ },
+ {
+ "left_line": 136,
+ "right_line": 136
+ },
+ {
+ "left_line": 137,
+ "right_line": 137
+ },
+ {
+ "left_line": 138,
+ "right_line": 138
+ },
+ {
+ "left_line": 139,
+ "right_line": 139
+ },
+ {
+ "left_line": 140,
+ "right_line": 140
+ },
+ {
+ "left_line": 141,
+ "right_line": 141
+ },
+ {
+ "left_line": 142,
+ "right_line": 142
+ },
+ {
+ "left_line": 143,
+ "right_line": 143
+ },
+ {
+ "left_line": 144,
+ "right_line": 144
+ },
+ {
+ "left_line": 145,
+ "right_line": 145
+ },
+ {
+ "left_line": 146,
+ "right_line": 146
+ },
+ {
+ "left_line": 147,
+ "right_line": 147
+ },
+ {
+ "left_line": 148,
+ "right_line": 148
+ },
+ {
+ "left_line": 149,
+ "right_line": 149
+ },
+ {
+ "left_line": 150,
+ "right_line": 150
+ },
+ {
+ "left_line": 151,
+ "right_line": 151
+ },
+ {
+ "left_line": 152,
+ "right_line": 152
+ },
+ {
+ "left_line": 153,
+ "right_line": 153
+ },
+ {
+ "left_line": 154,
+ "right_line": 154
+ },
+ {
+ "left_line": 155,
+ "right_line": 155
+ },
+ {
+ "left_line": 156,
+ "right_line": 156
+ },
+ {
+ "left_line": 157,
+ "right_line": 157
+ },
+ {
+ "left_line": 158,
+ "right_line": 158
+ },
+ {
+ "left_line": 159,
+ "right_line": 159
+ },
+ {
+ "left_line": 160,
+ "right_line": 160
+ },
+ {
+ "left_line": 161,
+ "right_line": 161
+ },
+ {
+ "left_line": 162,
+ "right_line": 162
+ },
+ {
+ "left_line": 163,
+ "right_line": 163
+ },
+ {
+ "left_line": 164,
+ "right_line": 164
+ },
+ {
+ "left_line": 165,
+ "right_line": 165
+ },
+ {
+ "left_line": 166,
+ "right_line": 166
+ },
+ {
+ "left_line": 167,
+ "right_line": 167
+ },
+ {
+ "left_line": 168,
+ "right_line": 168
+ },
+ {
+ "left_line": 169,
+ "right_line": 169
+ },
+ {
+ "left_line": 170,
+ "right_line": 170
+ },
+ {
+ "left_line": 171,
+ "right_line": 171
+ },
+ {
+ "left_line": 172,
+ "right_line": 172
+ },
+ {
+ "left_line": 173,
+ "right_line": 173
+ },
+ {
+ "left_line": 174,
+ "right_line": 174
+ },
+ {
+ "left_line": 175,
+ "right_line": 175
+ },
+ {
+ "left_line": 176,
+ "right_line": 176
+ },
+ {
+ "left_line": 177,
+ "right_line": 177
+ },
+ {
+ "left_line": 178,
+ "right_line": 178
+ },
+ {
+ "left_line": 179,
+ "right_line": 179
+ },
+ {
+ "left_line": 180,
+ "right_line": 180
+ },
+ {
+ "left_line": 181,
+ "right_line": 181
+ },
+ {
+ "left_line": 182,
+ "right_line": 182
+ },
+ {
+ "left_line": 183,
+ "right_line": 183
+ },
+ {
+ "left_line": 184,
+ "right_line": 184
+ },
+ {
+ "left_line": 185,
+ "right_line": 185
+ },
+ {
+ "left_line": 186,
+ "right_line": 186
+ },
+ {
+ "left_line": 187,
+ "right_line": 187
+ },
+ {
+ "left_line": 188,
+ "right_line": 188
+ },
+ {
+ "left_line": 189,
+ "right_line": 189
+ },
+ {
+ "left_line": 190,
+ "right_line": 190
+ },
+ {
+ "left_line": 191,
+ "right_line": 191
+ },
+ {
+ "left_line": 192,
+ "right_line": 192
+ },
+ {
+ "left_line": 193,
+ "right_line": 193
+ },
+ {
+ "left_line": 194,
+ "right_line": 194
+ },
+ {
+ "left_line": 195,
+ "right_line": 195
+ },
+ {
+ "left_line": 196,
+ "right_line": 196
+ },
+ {
+ "left_line": 197,
+ "right_line": 197
+ },
+ {
+ "left_line": 198,
+ "right_line": 198
+ },
+ {
+ "left_line": 199,
+ "right_line": 199
+ },
+ {
+ "left_line": 200,
+ "right_line": 200
+ },
+ {
+ "left_line": 201,
+ "right_line": 201
+ },
+ {
+ "left_line": 202,
+ "right_line": 202
+ },
+ {
+ "left_line": 203,
+ "right_line": 203
+ },
+ {
+ "left_line": 204,
+ "right_line": 204
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/testdata/ts_trpc_chore_export/metadata.json b/tests/testdata/ts_trpc_chore_export/metadata.json
new file mode 100644
index 0000000..92fef84
--- /dev/null
+++ b/tests/testdata/ts_trpc_chore_export/metadata.json
@@ -0,0 +1,12 @@
+{
+ "repo": "https://github.com/trpc/trpc",
+ "parent": "9111f0bab80a3f25384b5930b56005c2292cb874",
+ "commit": "62c6ab085f2ab0219811215436427fee2bc85c47",
+ "message": "chore: export `HTTPBatchStreamLinkOptions` (#7250)",
+ "author": "Anatol Zakrividoroga",
+ "date": "2026-03-15T11:58:04Z",
+ "language": "typescript",
+ "files_changed": 1,
+ "insertions": 0,
+ "deletions": 0
+}
\ No newline at end of file
diff --git a/tests/testdata/ts_trpc_chore_export/new.ts b/tests/testdata/ts_trpc_chore_export/new.ts
new file mode 100644
index 0000000..719a0b0
--- /dev/null
+++ b/tests/testdata/ts_trpc_chore_export/new.ts
@@ -0,0 +1,204 @@
+import type { AnyRouter, ProcedureType } from '@trpc/server';
+import { observable } from '@trpc/server/observable';
+import type { TRPCErrorShape, TRPCResponse } from '@trpc/server/rpc';
+import type { AnyClientTypes } from '@trpc/server/unstable-core-do-not-import';
+import { jsonlStreamConsumer } from '@trpc/server/unstable-core-do-not-import';
+import type { BatchLoader } from '../internals/dataLoader';
+import { dataLoader } from '../internals/dataLoader';
+import { allAbortSignals, raceAbortSignals } from '../internals/signals';
+import type { NonEmptyArray } from '../internals/types';
+import { TRPCClientError } from '../TRPCClientError';
+import type { HTTPBatchLinkOptions } from './HTTPBatchLinkOptions';
+import type { HTTPResult } from './internals/httpUtils';
+import {
+ fetchHTTPResponse,
+ getBody,
+ getUrl,
+ resolveHTTPLinkOptions,
+} from './internals/httpUtils';
+import type { Operation, TRPCLink } from './types';
+
+export type HTTPBatchStreamLinkOptions =
+ HTTPBatchLinkOptions & {
+ /**
+ * Which header to use to signal the server that the client wants a streaming response.
+ * - `'trpc-accept'` (default): sends `trpc-accept: application/jsonl` header
+ * - `'accept'`: sends `Accept: application/jsonl` header, which can avoid CORS preflight for cross-origin streaming queries. Be aware that `application/jsonl` is not an official MIME type and so this is not completely spec-compliant - you should test that your infrastructure supports this value.
+ * @default 'trpc-accept'
+ */
+ streamHeader?: 'trpc-accept' | 'accept';
+ };
+
+/**
+ * @see https://trpc.io/docs/client/links/httpBatchStreamLink
+ */
+export function httpBatchStreamLink(
+ opts: HTTPBatchStreamLinkOptions,
+): TRPCLink {
+ const resolvedOpts = resolveHTTPLinkOptions(opts);
+ const maxURLLength = opts.maxURLLength ?? Infinity;
+ const maxItems = opts.maxItems ?? Infinity;
+
+ return () => {
+ const batchLoader = (
+ type: ProcedureType,
+ ): BatchLoader => {
+ return {
+ validate(batchOps) {
+ if (maxURLLength === Infinity && maxItems === Infinity) {
+ // escape hatch for quick calcs
+ return true;
+ }
+ if (batchOps.length > maxItems) {
+ return false;
+ }
+ const path = batchOps.map((op) => op.path).join(',');
+ const inputs = batchOps.map((op) => op.input);
+
+ const url = getUrl({
+ ...resolvedOpts,
+ type,
+ path,
+ inputs,
+ signal: null,
+ });
+
+ return url.length <= maxURLLength;
+ },
+ async fetch(batchOps) {
+ const path = batchOps.map((op) => op.path).join(',');
+ const inputs = batchOps.map((op) => op.input);
+
+ const batchSignals = allAbortSignals(
+ ...batchOps.map((op) => op.signal),
+ );
+ const abortController = new AbortController();
+
+ const responsePromise = fetchHTTPResponse({
+ ...resolvedOpts,
+ signal: raceAbortSignals(batchSignals, abortController.signal),
+ type,
+ contentTypeHeader: 'application/json',
+ trpcAcceptHeader: 'application/jsonl',
+ trpcAcceptHeaderKey: opts.streamHeader ?? 'trpc-accept',
+ getUrl,
+ getBody,
+ inputs,
+ path,
+ headers() {
+ if (!opts.headers) {
+ return {};
+ }
+ if (typeof opts.headers === 'function') {
+ return opts.headers({
+ opList: batchOps as NonEmptyArray,
+ });
+ }
+ return opts.headers;
+ },
+ });
+
+ const res = await responsePromise;
+ const [head] = await jsonlStreamConsumer<
+ Record>
+ >({
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ from: res.body!,
+ deserialize: (data) =>
+ resolvedOpts.transformer.output.deserialize(data),
+ // onError: console.error,
+ formatError(opts) {
+ const error = opts.error as TRPCErrorShape;
+ return TRPCClientError.from({
+ error,
+ });
+ },
+ abortController,
+ });
+ const promises = Object.keys(batchOps).map(
+ async (key): Promise => {
+ let json: TRPCResponse = await Promise.resolve(head[key]);
+
+ if ('result' in json) {
+ /**
+ * Not very pretty, but we need to unwrap nested data as promises
+ * Our stream producer will only resolve top-level async values or async values that are directly nested in another async value
+ */
+ const result = await Promise.resolve(json.result);
+ json = {
+ result: {
+ data: await Promise.resolve(result.data),
+ },
+ };
+ }
+
+ return {
+ json,
+ meta: {
+ response: res,
+ },
+ };
+ },
+ );
+ return promises;
+ },
+ };
+ };
+
+ const query = dataLoader(batchLoader('query'));
+ const mutation = dataLoader(batchLoader('mutation'));
+
+ const loaders = { query, mutation };
+ return ({ op }) => {
+ return observable((observer) => {
+ /* istanbul ignore if -- @preserve */
+ if (op.type === 'subscription') {
+ throw new Error(
+ 'Subscriptions are unsupported by `httpBatchStreamLink` - use `httpSubscriptionLink` or `wsLink`',
+ );
+ }
+ const loader = loaders[op.type];
+ const promise = loader.load(op);
+
+ let _res = undefined as HTTPResult | undefined;
+ promise
+ .then((res) => {
+ _res = res;
+ if ('error' in res.json) {
+ observer.error(
+ TRPCClientError.from(res.json, {
+ meta: res.meta,
+ }),
+ );
+ return;
+ } else if ('result' in res.json) {
+ observer.next({
+ context: res.meta,
+ result: res.json.result,
+ });
+ observer.complete();
+ return;
+ }
+
+ observer.complete();
+ })
+ .catch((err) => {
+ observer.error(
+ TRPCClientError.from(err, {
+ meta: _res?.meta,
+ }),
+ );
+ });
+
+ return () => {
+ // noop
+ };
+ });
+ };
+ };
+}
+
+/**
+ * @deprecated use {@link httpBatchStreamLink} instead
+ */
+export const unstable_httpBatchStreamLink = httpBatchStreamLink;
diff --git a/tests/testdata/ts_trpc_chore_export/old.ts b/tests/testdata/ts_trpc_chore_export/old.ts
new file mode 100644
index 0000000..74082c6
--- /dev/null
+++ b/tests/testdata/ts_trpc_chore_export/old.ts
@@ -0,0 +1,204 @@
+import type { AnyRouter, ProcedureType } from '@trpc/server';
+import { observable } from '@trpc/server/observable';
+import type { TRPCErrorShape, TRPCResponse } from '@trpc/server/rpc';
+import type { AnyClientTypes } from '@trpc/server/unstable-core-do-not-import';
+import { jsonlStreamConsumer } from '@trpc/server/unstable-core-do-not-import';
+import type { BatchLoader } from '../internals/dataLoader';
+import { dataLoader } from '../internals/dataLoader';
+import { allAbortSignals, raceAbortSignals } from '../internals/signals';
+import type { NonEmptyArray } from '../internals/types';
+import { TRPCClientError } from '../TRPCClientError';
+import type { HTTPBatchLinkOptions } from './HTTPBatchLinkOptions';
+import type { HTTPResult } from './internals/httpUtils';
+import {
+ fetchHTTPResponse,
+ getBody,
+ getUrl,
+ resolveHTTPLinkOptions,
+} from './internals/httpUtils';
+import type { Operation, TRPCLink } from './types';
+
+type HTTPBatchStreamLinkOptions =
+ HTTPBatchLinkOptions & {
+ /**
+ * Which header to use to signal the server that the client wants a streaming response.
+ * - `'trpc-accept'` (default): sends `trpc-accept: application/jsonl` header
+ * - `'accept'`: sends `Accept: application/jsonl` header, which can avoid CORS preflight for cross-origin streaming queries. Be aware that `application/jsonl` is not an official MIME type and so this is not completely spec-compliant - you should test that your infrastructure supports this value.
+ * @default 'trpc-accept'
+ */
+ streamHeader?: 'trpc-accept' | 'accept';
+ };
+
+/**
+ * @see https://trpc.io/docs/client/links/httpBatchStreamLink
+ */
+export function httpBatchStreamLink(
+ opts: HTTPBatchStreamLinkOptions,
+): TRPCLink {
+ const resolvedOpts = resolveHTTPLinkOptions(opts);
+ const maxURLLength = opts.maxURLLength ?? Infinity;
+ const maxItems = opts.maxItems ?? Infinity;
+
+ return () => {
+ const batchLoader = (
+ type: ProcedureType,
+ ): BatchLoader => {
+ return {
+ validate(batchOps) {
+ if (maxURLLength === Infinity && maxItems === Infinity) {
+ // escape hatch for quick calcs
+ return true;
+ }
+ if (batchOps.length > maxItems) {
+ return false;
+ }
+ const path = batchOps.map((op) => op.path).join(',');
+ const inputs = batchOps.map((op) => op.input);
+
+ const url = getUrl({
+ ...resolvedOpts,
+ type,
+ path,
+ inputs,
+ signal: null,
+ });
+
+ return url.length <= maxURLLength;
+ },
+ async fetch(batchOps) {
+ const path = batchOps.map((op) => op.path).join(',');
+ const inputs = batchOps.map((op) => op.input);
+
+ const batchSignals = allAbortSignals(
+ ...batchOps.map((op) => op.signal),
+ );
+ const abortController = new AbortController();
+
+ const responsePromise = fetchHTTPResponse({
+ ...resolvedOpts,
+ signal: raceAbortSignals(batchSignals, abortController.signal),
+ type,
+ contentTypeHeader: 'application/json',
+ trpcAcceptHeader: 'application/jsonl',
+ trpcAcceptHeaderKey: opts.streamHeader ?? 'trpc-accept',
+ getUrl,
+ getBody,
+ inputs,
+ path,
+ headers() {
+ if (!opts.headers) {
+ return {};
+ }
+ if (typeof opts.headers === 'function') {
+ return opts.headers({
+ opList: batchOps as NonEmptyArray,
+ });
+ }
+ return opts.headers;
+ },
+ });
+
+ const res = await responsePromise;
+ const [head] = await jsonlStreamConsumer<
+ Record>
+ >({
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ from: res.body!,
+ deserialize: (data) =>
+ resolvedOpts.transformer.output.deserialize(data),
+ // onError: console.error,
+ formatError(opts) {
+ const error = opts.error as TRPCErrorShape;
+ return TRPCClientError.from({
+ error,
+ });
+ },
+ abortController,
+ });
+ const promises = Object.keys(batchOps).map(
+ async (key): Promise => {
+ let json: TRPCResponse = await Promise.resolve(head[key]);
+
+ if ('result' in json) {
+ /**
+ * Not very pretty, but we need to unwrap nested data as promises
+ * Our stream producer will only resolve top-level async values or async values that are directly nested in another async value
+ */
+ const result = await Promise.resolve(json.result);
+ json = {
+ result: {
+ data: await Promise.resolve(result.data),
+ },
+ };
+ }
+
+ return {
+ json,
+ meta: {
+ response: res,
+ },
+ };
+ },
+ );
+ return promises;
+ },
+ };
+ };
+
+ const query = dataLoader(batchLoader('query'));
+ const mutation = dataLoader(batchLoader('mutation'));
+
+ const loaders = { query, mutation };
+ return ({ op }) => {
+ return observable((observer) => {
+ /* istanbul ignore if -- @preserve */
+ if (op.type === 'subscription') {
+ throw new Error(
+ 'Subscriptions are unsupported by `httpBatchStreamLink` - use `httpSubscriptionLink` or `wsLink`',
+ );
+ }
+ const loader = loaders[op.type];
+ const promise = loader.load(op);
+
+ let _res = undefined as HTTPResult | undefined;
+ promise
+ .then((res) => {
+ _res = res;
+ if ('error' in res.json) {
+ observer.error(
+ TRPCClientError.from(res.json, {
+ meta: res.meta,
+ }),
+ );
+ return;
+ } else if ('result' in res.json) {
+ observer.next({
+ context: res.meta,
+ result: res.json.result,
+ });
+ observer.complete();
+ return;
+ }
+
+ observer.complete();
+ })
+ .catch((err) => {
+ observer.error(
+ TRPCClientError.from(err, {
+ meta: _res?.meta,
+ }),
+ );
+ });
+
+ return () => {
+ // noop
+ };
+ });
+ };
+ };
+}
+
+/**
+ * @deprecated use {@link httpBatchStreamLink} instead
+ */
+export const unstable_httpBatchStreamLink = httpBatchStreamLink;
diff --git a/tests/testdata/ts_trpc_export_datatransformer/expected.json b/tests/testdata/ts_trpc_export_datatransformer/expected.json
new file mode 100644
index 0000000..254eaef
--- /dev/null
+++ b/tests/testdata/ts_trpc_export_datatransformer/expected.json
@@ -0,0 +1,669 @@
+{
+ "version": "v1",
+ "actions": [
+ {
+ "action": "insert",
+ "node": {
+ "tree": "after",
+ "path": [
+ 0,
+ 1,
+ 62
+ ],
+ "type": "export_specifier",
+ "start_byte": 1356,
+ "end_byte": 1399
+ },
+ "parent": {
+ "tree": "after",
+ "path": [
+ 0,
+ 1
+ ],
+ "type": "export_clause",
+ "start_byte": 7,
+ "end_byte": 2812
+ },
+ "position": 62,
+ "subtree": true
+ },
+ {
+ "action": "insert",
+ "node": {
+ "tree": "after",
+ "path": [
+ 1,
+ 2,
+ 12
+ ],
+ "type": "comment",
+ "label": "/**\n * @deprecated use `TRPCDataTransformer` instead\n */",
+ "start_byte": 3210,
+ "end_byte": 3270
+ },
+ "parent": {
+ "tree": "after",
+ "path": [
+ 1,
+ 2
+ ],
+ "type": "export_clause",
+ "start_byte": 2868,
+ "end_byte": 3823
+ },
+ "position": 12
+ },
+ {
+ "action": "insert",
+ "node": {
+ "tree": "after",
+ "path": [
+ 1,
+ 2,
+ 13
+ ],
+ "type": "export_specifier",
+ "start_byte": 3273,
+ "end_byte": 3288
+ },
+ "parent": {
+ "tree": "after",
+ "path": [
+ 1,
+ 2
+ ],
+ "type": "export_clause",
+ "start_byte": 2868,
+ "end_byte": 3823
+ },
+ "position": 13,
+ "subtree": true
+ }
+ ],
+ "line_alignment": [
+ {
+ "left_line": 0,
+ "right_line": 0
+ },
+ {
+ "left_line": 1,
+ "right_line": 1
+ },
+ {
+ "left_line": 2,
+ "right_line": 2
+ },
+ {
+ "left_line": 3,
+ "right_line": 3
+ },
+ {
+ "left_line": 4,
+ "right_line": 4
+ },
+ {
+ "left_line": 5,
+ "right_line": 5
+ },
+ {
+ "left_line": 6,
+ "right_line": 6
+ },
+ {
+ "left_line": 7,
+ "right_line": 7
+ },
+ {
+ "left_line": 8,
+ "right_line": 8
+ },
+ {
+ "left_line": 9,
+ "right_line": 9
+ },
+ {
+ "left_line": 10,
+ "right_line": 10
+ },
+ {
+ "left_line": 11,
+ "right_line": 11
+ },
+ {
+ "left_line": 12,
+ "right_line": 12
+ },
+ {
+ "left_line": 13,
+ "right_line": 13
+ },
+ {
+ "left_line": 14,
+ "right_line": 14
+ },
+ {
+ "left_line": 15,
+ "right_line": 15
+ },
+ {
+ "left_line": 16,
+ "right_line": 16
+ },
+ {
+ "left_line": 17,
+ "right_line": 17
+ },
+ {
+ "left_line": 18,
+ "right_line": 18
+ },
+ {
+ "left_line": 19,
+ "right_line": 19
+ },
+ {
+ "left_line": 20,
+ "right_line": 20
+ },
+ {
+ "left_line": 21,
+ "right_line": 21
+ },
+ {
+ "left_line": 22,
+ "right_line": 22
+ },
+ {
+ "left_line": 23,
+ "right_line": 23
+ },
+ {
+ "left_line": 24,
+ "right_line": 24
+ },
+ {
+ "left_line": 25,
+ "right_line": 25
+ },
+ {
+ "left_line": 26,
+ "right_line": 26
+ },
+ {
+ "left_line": 27,
+ "right_line": 27
+ },
+ {
+ "left_line": 28,
+ "right_line": 28
+ },
+ {
+ "left_line": 29,
+ "right_line": 29
+ },
+ {
+ "left_line": 30,
+ "right_line": 30
+ },
+ {
+ "left_line": 31,
+ "right_line": 31
+ },
+ {
+ "left_line": 32,
+ "right_line": 32
+ },
+ {
+ "left_line": 33,
+ "right_line": 33
+ },
+ {
+ "left_line": 34,
+ "right_line": 34
+ },
+ {
+ "left_line": -1,
+ "right_line": 35
+ },
+ {
+ "left_line": 35,
+ "right_line": 36
+ },
+ {
+ "left_line": 36,
+ "right_line": 37
+ },
+ {
+ "left_line": 37,
+ "right_line": 38
+ },
+ {
+ "left_line": 38,
+ "right_line": 39
+ },
+ {
+ "left_line": 39,
+ "right_line": 40
+ },
+ {
+ "left_line": 40,
+ "right_line": 41
+ },
+ {
+ "left_line": 41,
+ "right_line": 42
+ },
+ {
+ "left_line": 42,
+ "right_line": 43
+ },
+ {
+ "left_line": 43,
+ "right_line": 44
+ },
+ {
+ "left_line": 44,
+ "right_line": 45
+ },
+ {
+ "left_line": 45,
+ "right_line": 46
+ },
+ {
+ "left_line": 46,
+ "right_line": 47
+ },
+ {
+ "left_line": 47,
+ "right_line": 48
+ },
+ {
+ "left_line": 48,
+ "right_line": 49
+ },
+ {
+ "left_line": 49,
+ "right_line": 50
+ },
+ {
+ "left_line": 50,
+ "right_line": 51
+ },
+ {
+ "left_line": 51,
+ "right_line": 52
+ },
+ {
+ "left_line": 52,
+ "right_line": 53
+ },
+ {
+ "left_line": 53,
+ "right_line": 54
+ },
+ {
+ "left_line": 54,
+ "right_line": 55
+ },
+ {
+ "left_line": 55,
+ "right_line": 56
+ },
+ {
+ "left_line": 56,
+ "right_line": 57
+ },
+ {
+ "left_line": 57,
+ "right_line": 58
+ },
+ {
+ "left_line": 58,
+ "right_line": 59
+ },
+ {
+ "left_line": 59,
+ "right_line": 60
+ },
+ {
+ "left_line": 60,
+ "right_line": 61
+ },
+ {
+ "left_line": 61,
+ "right_line": 62
+ },
+ {
+ "left_line": 62,
+ "right_line": 63
+ },
+ {
+ "left_line": 63,
+ "right_line": 64
+ },
+ {
+ "left_line": 64,
+ "right_line": 65
+ },
+ {
+ "left_line": 65,
+ "right_line": 66
+ },
+ {
+ "left_line": 66,
+ "right_line": 67
+ },
+ {
+ "left_line": 67,
+ "right_line": 68
+ },
+ {
+ "left_line": 68,
+ "right_line": 69
+ },
+ {
+ "left_line": 69,
+ "right_line": 70
+ },
+ {
+ "left_line": 70,
+ "right_line": 71
+ },
+ {
+ "left_line": 71,
+ "right_line": 72
+ },
+ {
+ "left_line": 72,
+ "right_line": 73
+ },
+ {
+ "left_line": 73,
+ "right_line": 74
+ },
+ {
+ "left_line": 74,
+ "right_line": 75
+ },
+ {
+ "left_line": 75,
+ "right_line": 76
+ },
+ {
+ "left_line": 76,
+ "right_line": 77
+ },
+ {
+ "left_line": 77,
+ "right_line": 78
+ },
+ {
+ "left_line": 78,
+ "right_line": 79
+ },
+ {
+ "left_line": 79,
+ "right_line": 80
+ },
+ {
+ "left_line": 80,
+ "right_line": 81
+ },
+ {
+ "left_line": 81,
+ "right_line": 82
+ },
+ {
+ "left_line": 82,
+ "right_line": 83
+ },
+ {
+ "left_line": 83,
+ "right_line": 84
+ },
+ {
+ "left_line": 84,
+ "right_line": 85
+ },
+ {
+ "left_line": 85,
+ "right_line": 86
+ },
+ {
+ "left_line": 86,
+ "right_line": 87
+ },
+ {
+ "left_line": 87,
+ "right_line": 88
+ },
+ {
+ "left_line": 88,
+ "right_line": 89
+ },
+ {
+ "left_line": 89,
+ "right_line": 90
+ },
+ {
+ "left_line": 90,
+ "right_line": 91
+ },
+ {
+ "left_line": 91,
+ "right_line": 92
+ },
+ {
+ "left_line": 92,
+ "right_line": 93
+ },
+ {
+ "left_line": 93,
+ "right_line": 94
+ },
+ {
+ "left_line": 94,
+ "right_line": 95
+ },
+ {
+ "left_line": 95,
+ "right_line": 96
+ },
+ {
+ "left_line": 96,
+ "right_line": 97
+ },
+ {
+ "left_line": -1,
+ "right_line": 98
+ },
+ {
+ "left_line": -1,
+ "right_line": 99
+ },
+ {
+ "left_line": -1,
+ "right_line": 100
+ },
+ {
+ "left_line": -1,
+ "right_line": 101
+ },
+ {
+ "left_line": 97,
+ "right_line": 102
+ },
+ {
+ "left_line": 98,
+ "right_line": 103
+ },
+ {
+ "left_line": 99,
+ "right_line": 104
+ },
+ {
+ "left_line": 100,
+ "right_line": 105
+ },
+ {
+ "left_line": 101,
+ "right_line": 106
+ },
+ {
+ "left_line": 102,
+ "right_line": 107
+ },
+ {
+ "left_line": 103,
+ "right_line": 108
+ },
+ {
+ "left_line": 104,
+ "right_line": 109
+ },
+ {
+ "left_line": 105,
+ "right_line": 110
+ },
+ {
+ "left_line": 106,
+ "right_line": 111
+ },
+ {
+ "left_line": 107,
+ "right_line": 112
+ },
+ {
+ "left_line": 108,
+ "right_line": 113
+ },
+ {
+ "left_line": 109,
+ "right_line": 114
+ },
+ {
+ "left_line": 110,
+ "right_line": 115
+ },
+ {
+ "left_line": 111,
+ "right_line": 116
+ },
+ {
+ "left_line": 112,
+ "right_line": 117
+ },
+ {
+ "left_line": 113,
+ "right_line": 118
+ },
+ {
+ "left_line": 114,
+ "right_line": 119
+ },
+ {
+ "left_line": 115,
+ "right_line": 120
+ },
+ {
+ "left_line": 116,
+ "right_line": 121
+ },
+ {
+ "left_line": 117,
+ "right_line": 122
+ },
+ {
+ "left_line": 118,
+ "right_line": 123
+ },
+ {
+ "left_line": 119,
+ "right_line": 124
+ },
+ {
+ "left_line": 120,
+ "right_line": 125
+ },
+ {
+ "left_line": 121,
+ "right_line": 126
+ },
+ {
+ "left_line": 122,
+ "right_line": 127
+ },
+ {
+ "left_line": 123,
+ "right_line": 128
+ },
+ {
+ "left_line": 124,
+ "right_line": 129
+ },
+ {
+ "left_line": 125,
+ "right_line": 130
+ },
+ {
+ "left_line": 126,
+ "right_line": 131
+ },
+ {
+ "left_line": 127,
+ "right_line": 132
+ },
+ {
+ "left_line": 128,
+ "right_line": 133
+ },
+ {
+ "left_line": 129,
+ "right_line": 134
+ },
+ {
+ "left_line": 130,
+ "right_line": 135
+ },
+ {
+ "left_line": 131,
+ "right_line": 136
+ },
+ {
+ "left_line": 132,
+ "right_line": 137
+ },
+ {
+ "left_line": 133,
+ "right_line": 138
+ },
+ {
+ "left_line": 134,
+ "right_line": 139
+ },
+ {
+ "left_line": 135,
+ "right_line": 140
+ },
+ {
+ "left_line": 136,
+ "right_line": 141
+ },
+ {
+ "left_line": 137,
+ "right_line": 142
+ },
+ {
+ "left_line": 138,
+ "right_line": 143
+ },
+ {
+ "left_line": 139,
+ "right_line": 144
+ },
+ {
+ "left_line": 140,
+ "right_line": 145
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/testdata/ts_trpc_export_datatransformer/metadata.json b/tests/testdata/ts_trpc_export_datatransformer/metadata.json
new file mode 100644
index 0000000..eb07cd9
--- /dev/null
+++ b/tests/testdata/ts_trpc_export_datatransformer/metadata.json
@@ -0,0 +1,12 @@
+{
+ "repo": "https://github.com/trpc/trpc",
+ "parent": "aaf09d0f0f6bee0e580a0c20e8f9afe977b3dd43",
+ "commit": "e272d689530531a498195276f14dd71bda534afe",
+ "message": "fix: export DataTransformer interface marked as @public (#6927)\n\n# Pull Request Summary: Export DataTransformer Interface #6916 \n\n## Description\nThis PR resolves an issue where the `DataTransformer` interface was\nmarked as `@public` but not exported from either `@trpc/server` or\n`@trpc/client` packages, preventing users from properly typing their\ncustom transformers.\n\n## Issue Fixed\n- **Problem**: Users couldn't import `DataTransformer` interface despite\nit being marked as public API\n- **Workaround**: Users had to use confusing\n`CombinedDataTransformer[\"input\"]` hack\n- **Root Cause**: Missing exports in package entry points\n- \n##\u00a0 Changes Made\n### Server Package (`@trpc/server`)\n- Added `DataTransformer` export to main index\n- Added `TRPCDataTransformer` alias following naming conventions\n- Added backward compatibility deprecation notice\n\n### Client Package (`@trpc/client`)\u00a0\n- Added convenient re-export of `DataTransformer` from server package\n- Maintains single source of truth\n\n## Testing\n- [x] All packages build successfully\n- [x] Type exports verified in built packages\n- [x] Existing transformer tests pass\n- [x] No breaking changes introduced\n- [x] Backward compatibility maintained\n## Usage\n### Before\n```typescript\nimport type { DataTransformer } from '@trpc/server';\ntype MyTransformer = CombinedDataTransformer[\"input\"];\n```\n### After\n```typescript\nimport type { DataTransformer } from '@trpc/server';\nimport type { DataTransformer } from '@trpc/client';\nconst myTransformer: DataTransformer = {\n\u00a0 serialize: (obj) => obj,\n\u00a0 deserialize: (obj) => obj,\n};\n```\n## Safety\n- **Breaking Changes**: None\n- **Runtime Impact**: None (types only)\n- **Backward Compatibility**: Full\n- **Code Style**: Follows project conventions\n## Contribution Guidelines Compliance\n- [x] Used pnpm for package management\n- [x] Ran `pnpm build` successfully\n- [x] Ran `pnpm lint-fix`\n- [x] Added proper documentation\n- [x] Followed existing code patterns\n- [x] Maintained type safety\n- [x] No cryptic errors introduced\n\n\n\n\n## Summary by CodeRabbit\n\n* New Features\n* Introduced TRPCDataTransformer type alias for clearer naming in server\nexports.\n* Client package now publicly re-exports the DataTransformer type for\nconvenience.\n\n* Documentation\n* Deprecated DataTransformer in favor of TRPCDataTransformer across\npublic exports.\n* Clarified that these are type-only changes with no impact on runtime\nbehavior.\n\n\n\n---------\n\nCo-authored-by: I528989 \nCo-authored-by: Alex / KATT ",
+ "author": "Bhaskar Kulshrestha",
+ "date": "2025-09-05T14:30:18Z",
+ "language": "typescript",
+ "files_changed": 1,
+ "insertions": 5,
+ "deletions": 0
+}
\ No newline at end of file
diff --git a/tests/testdata/ts_trpc_export_datatransformer/new.ts b/tests/testdata/ts_trpc_export_datatransformer/new.ts
new file mode 100644
index 0000000..7c39514
--- /dev/null
+++ b/tests/testdata/ts_trpc_export_datatransformer/new.ts
@@ -0,0 +1,145 @@
+export {
+ TRPCError,
+ /**
+ * @deprecated use `experimental_trpcMiddleware` instead
+ */
+ experimental_standaloneMiddleware,
+ experimental_standaloneMiddleware as experimental_trpcMiddleware,
+ initTRPC,
+ // --- FIXME a bunch of these exports are only useful for plugins - move them somewhere else? ----
+ getTRPCErrorFromUnknown,
+ transformTRPCResponse,
+ createFlatProxy as createTRPCFlatProxy,
+ createRecursiveProxy as createTRPCRecursiveProxy,
+ type inferProcedureInput,
+ type inferProcedureOutput,
+ type inferProcedureBuilderResolverOptions,
+ type inferRouterError,
+ type inferRouterInputs,
+ type inferRouterOutputs,
+ type inferRouterContext,
+ type inferClientTypes as inferTRPCClientTypes,
+ type AnyClientTypes as AnyTRPCClientTypes,
+ type inferTransformedProcedureOutput,
+ type inferTransformedSubscriptionOutput,
+ type AnyProcedure as AnyTRPCProcedure,
+ type AnyRouter as AnyTRPCRouter,
+ type RouterDef as TRPCRouterDef,
+ type RouterBuilder as TRPCRouterBuilder,
+ type RouterCallerFactory as TRPCRouterCallerFactory,
+ type RootConfig as TRPCRootConfig,
+ type AnyRootTypes as AnyTRPCRootTypes,
+ type MiddlewareFunction as TRPCMiddlewareFunction,
+ type MiddlewareBuilder as TRPCMiddlewareBuilder,
+ type AnyMiddlewareFunction as AnyTRPCMiddlewareFunction,
+ type CombinedDataTransformer as TRPCCombinedDataTransformer,
+ type DataTransformer as TRPCDataTransformer,
+ type ProcedureType as TRPCProcedureType,
+ type AnyMutationProcedure as AnyTRPCMutationProcedure,
+ type AnyQueryProcedure as AnyTRPCQueryProcedure,
+ type RouterRecord as TRPCRouterRecord,
+ type MergeRouters as TRPCMergeRouters,
+ type AnySubscriptionProcedure as AnyTRPCSubscriptionProcedure,
+ type CreateContextCallback,
+ type MutationProcedure as TRPCMutationProcedure,
+ type QueryProcedure as TRPCQueryProcedure,
+ type BuiltRouter as TRPCBuiltRouter,
+ type SubscriptionProcedure as TRPCSubscriptionProcedure,
+ type TRPCBuilder,
+ type ProcedureBuilder as TRPCProcedureBuilder,
+ type RuntimeConfigOptions as TRPCRuntimeConfigOptions,
+ type TRPCRootObject,
+ type ErrorFormatter as TRPCErrorFormatter,
+ type TRPCErrorShape,
+ type DefaultErrorShape as TRPCDefaultErrorShape,
+ type DefaultErrorData as TRPCDefaultErrorData,
+ type TRPC_ERROR_CODE_KEY,
+ type TRPC_ERROR_CODE_NUMBER,
+ type DecorateCreateRouterOptions as TRPCDecorateCreateRouterOptions,
+ type CreateRouterOptions as TRPCCreateRouterOptions,
+ type RouterCaller as TRPCRouterCaller,
+ StandardSchemaV1Error,
+ /**
+ * @deprecated use `tracked(id, data)` instead
+ */
+ sse,
+ tracked,
+ type TrackedEnvelope,
+ isTrackedEnvelope,
+ lazy,
+ /**
+ * @deprecated use {@link lazy} instead
+ */
+ lazy as experimental_lazy,
+ callProcedure as callTRPCProcedure,
+
+ /**
+ * @internal
+ */
+ type UnsetMarker as TRPCUnsetMarker,
+} from '../../unstable-core-do-not-import';
+
+export type {
+ /**
+ * @deprecated use `AnyTRPCProcedure` instead
+ */
+ AnyProcedure,
+ /**
+ * @deprecated use `AnyTRPCRouter` instead
+ */
+ AnyRouter,
+ /**
+ * @deprecated use `AnyTRPCMiddlewareFunction` instead
+ */
+ AnyMiddlewareFunction,
+ /**
+ * @deprecated use `TRPCCombinedDataTransformer` instead
+ */
+ CombinedDataTransformer,
+ /**
+ * @deprecated use `TRPCDataTransformer` instead
+ */
+ DataTransformer,
+
+ /**
+ * @deprecated This is a utility type will be removed in v12
+ */
+ Dict,
+
+ /**
+ * @deprecated This is a utility type will be removed in v12
+ */
+ DeepPartial,
+ /**
+ * @deprecated use `TRPCProcedureType` instead
+ */
+ ProcedureType,
+ /**
+ * @deprecated use `AnyTRPCMutationProcedure` instead
+ */
+ AnyMutationProcedure,
+
+ /**
+ * @deprecated use `AnyTRPCQueryProcedure` instead
+ */
+ AnyQueryProcedure,
+ /**
+ * @deprecated use `AnyTRPCSubscriptionProcedure` instead
+ */
+ AnySubscriptionProcedure,
+} from '../../unstable-core-do-not-import';
+
+export {
+ /**
+ * @deprecated use `getTRPCErrorShape` instead
+ */
+ getErrorShape,
+ getErrorShape as getTRPCErrorShape,
+} from '../../unstable-core-do-not-import';
+
+/**
+ * @deprecated
+ * Use `Awaited>` instead
+ */
+export type inferAsyncReturnType any> =
+ Awaited>;
diff --git a/tests/testdata/ts_trpc_export_datatransformer/old.ts b/tests/testdata/ts_trpc_export_datatransformer/old.ts
new file mode 100644
index 0000000..2bfbcbd
--- /dev/null
+++ b/tests/testdata/ts_trpc_export_datatransformer/old.ts
@@ -0,0 +1,140 @@
+export {
+ TRPCError,
+ /**
+ * @deprecated use `experimental_trpcMiddleware` instead
+ */
+ experimental_standaloneMiddleware,
+ experimental_standaloneMiddleware as experimental_trpcMiddleware,
+ initTRPC,
+ // --- FIXME a bunch of these exports are only useful for plugins - move them somewhere else? ----
+ getTRPCErrorFromUnknown,
+ transformTRPCResponse,
+ createFlatProxy as createTRPCFlatProxy,
+ createRecursiveProxy as createTRPCRecursiveProxy,
+ type inferProcedureInput,
+ type inferProcedureOutput,
+ type inferProcedureBuilderResolverOptions,
+ type inferRouterError,
+ type inferRouterInputs,
+ type inferRouterOutputs,
+ type inferRouterContext,
+ type inferClientTypes as inferTRPCClientTypes,
+ type AnyClientTypes as AnyTRPCClientTypes,
+ type inferTransformedProcedureOutput,
+ type inferTransformedSubscriptionOutput,
+ type AnyProcedure as AnyTRPCProcedure,
+ type AnyRouter as AnyTRPCRouter,
+ type RouterDef as TRPCRouterDef,
+ type RouterBuilder as TRPCRouterBuilder,
+ type RouterCallerFactory as TRPCRouterCallerFactory,
+ type RootConfig as TRPCRootConfig,
+ type AnyRootTypes as AnyTRPCRootTypes,
+ type MiddlewareFunction as TRPCMiddlewareFunction,
+ type MiddlewareBuilder as TRPCMiddlewareBuilder,
+ type AnyMiddlewareFunction as AnyTRPCMiddlewareFunction,
+ type CombinedDataTransformer as TRPCCombinedDataTransformer,
+ type ProcedureType as TRPCProcedureType,
+ type AnyMutationProcedure as AnyTRPCMutationProcedure,
+ type AnyQueryProcedure as AnyTRPCQueryProcedure,
+ type RouterRecord as TRPCRouterRecord,
+ type MergeRouters as TRPCMergeRouters,
+ type AnySubscriptionProcedure as AnyTRPCSubscriptionProcedure,
+ type CreateContextCallback,
+ type MutationProcedure as TRPCMutationProcedure,
+ type QueryProcedure as TRPCQueryProcedure,
+ type BuiltRouter as TRPCBuiltRouter,
+ type SubscriptionProcedure as TRPCSubscriptionProcedure,
+ type TRPCBuilder,
+ type ProcedureBuilder as TRPCProcedureBuilder,
+ type RuntimeConfigOptions as TRPCRuntimeConfigOptions,
+ type TRPCRootObject,
+ type ErrorFormatter as TRPCErrorFormatter,
+ type TRPCErrorShape,
+ type DefaultErrorShape as TRPCDefaultErrorShape,
+ type DefaultErrorData as TRPCDefaultErrorData,
+ type TRPC_ERROR_CODE_KEY,
+ type TRPC_ERROR_CODE_NUMBER,
+ type DecorateCreateRouterOptions as TRPCDecorateCreateRouterOptions,
+ type CreateRouterOptions as TRPCCreateRouterOptions,
+ type RouterCaller as TRPCRouterCaller,
+ StandardSchemaV1Error,
+ /**
+ * @deprecated use `tracked(id, data)` instead
+ */
+ sse,
+ tracked,
+ type TrackedEnvelope,
+ isTrackedEnvelope,
+ lazy,
+ /**
+ * @deprecated use {@link lazy} instead
+ */
+ lazy as experimental_lazy,
+ callProcedure as callTRPCProcedure,
+
+ /**
+ * @internal
+ */
+ type UnsetMarker as TRPCUnsetMarker,
+} from '../../unstable-core-do-not-import';
+
+export type {
+ /**
+ * @deprecated use `AnyTRPCProcedure` instead
+ */
+ AnyProcedure,
+ /**
+ * @deprecated use `AnyTRPCRouter` instead
+ */
+ AnyRouter,
+ /**
+ * @deprecated use `AnyTRPCMiddlewareFunction` instead
+ */
+ AnyMiddlewareFunction,
+ /**
+ * @deprecated use `TRPCCombinedDataTransformer` instead
+ */
+ CombinedDataTransformer,
+
+ /**
+ * @deprecated This is a utility type will be removed in v12
+ */
+ Dict,
+
+ /**
+ * @deprecated This is a utility type will be removed in v12
+ */
+ DeepPartial,
+ /**
+ * @deprecated use `TRPCProcedureType` instead
+ */
+ ProcedureType,
+ /**
+ * @deprecated use `AnyTRPCMutationProcedure` instead
+ */
+ AnyMutationProcedure,
+
+ /**
+ * @deprecated use `AnyTRPCQueryProcedure` instead
+ */
+ AnyQueryProcedure,
+ /**
+ * @deprecated use `AnyTRPCSubscriptionProcedure` instead
+ */
+ AnySubscriptionProcedure,
+} from '../../unstable-core-do-not-import';
+
+export {
+ /**
+ * @deprecated use `getTRPCErrorShape` instead
+ */
+ getErrorShape,
+ getErrorShape as getTRPCErrorShape,
+} from '../../unstable-core-do-not-import';
+
+/**
+ * @deprecated
+ * Use `Awaited>` instead
+ */
+export type inferAsyncReturnType any> =
+ Awaited>;
diff --git a/tests/testdata/ts_trpc_patchclient_upgrade/expected.json b/tests/testdata/ts_trpc_patchclient_upgrade/expected.json
new file mode 100644
index 0000000..d9b3d11
--- /dev/null
+++ b/tests/testdata/ts_trpc_patchclient_upgrade/expected.json
@@ -0,0 +1,1210 @@
+{
+ "version": "v1",
+ "actions": [
+ {
+ "action": "insert",
+ "node": {
+ "tree": "after",
+ "path": [
+ 14
+ ],
+ "type": "comment",
+ "label": "/**\n * @deprecated Renamed to `unstable_localLink`. This alias will be removed in a future major release.\n */",
+ "start_byte": 8792,
+ "end_byte": 8901
+ },
+ "parent": {
+ "tree": "after",
+ "path": null,
+ "type": "program",
+ "start_byte": 0,
+ "end_byte": 8989
+ },
+ "position": 14
+ },
+ {
+ "action": "insert",
+ "node": {
+ "tree": "after",
+ "path": [
+ 15
+ ],
+ "type": "export_statement",
+ "start_byte": 8902,
+ "end_byte": 8988
+ },
+ "parent": {
+ "tree": "after",
+ "path": null,
+ "type": "program",
+ "start_byte": 0,
+ "end_byte": 8989
+ },
+ "position": 15,
+ "subtree": true
+ },
+ {
+ "action": "update",
+ "node": {
+ "tree": "before",
+ "path": [
+ 13,
+ 1,
+ 1
+ ],
+ "type": "identifier",
+ "label": "experimental_localLink",
+ "start_byte": 1473,
+ "end_byte": 1495
+ },
+ "old_value": "experimental_localLink",
+ "new_value": "unstable_localLink",
+ "dest_node": {
+ "tree": "after",
+ "path": [
+ 13,
+ 1,
+ 1
+ ],
+ "type": "identifier",
+ "label": "unstable_localLink",
+ "start_byte": 1473,
+ "end_byte": 1491
+ }
+ }
+ ],
+ "line_alignment": [
+ {
+ "left_line": 0,
+ "right_line": 0
+ },
+ {
+ "left_line": 1,
+ "right_line": 1
+ },
+ {
+ "left_line": 2,
+ "right_line": 2
+ },
+ {
+ "left_line": 3,
+ "right_line": 3
+ },
+ {
+ "left_line": 4,
+ "right_line": 4
+ },
+ {
+ "left_line": 5,
+ "right_line": 5
+ },
+ {
+ "left_line": 6,
+ "right_line": 6
+ },
+ {
+ "left_line": 7,
+ "right_line": 7
+ },
+ {
+ "left_line": 8,
+ "right_line": 8
+ },
+ {
+ "left_line": 9,
+ "right_line": 9
+ },
+ {
+ "left_line": 10,
+ "right_line": 10
+ },
+ {
+ "left_line": 11,
+ "right_line": 11
+ },
+ {
+ "left_line": 12,
+ "right_line": 12
+ },
+ {
+ "left_line": 13,
+ "right_line": 13
+ },
+ {
+ "left_line": 14,
+ "right_line": 14
+ },
+ {
+ "left_line": 15,
+ "right_line": 15
+ },
+ {
+ "left_line": 16,
+ "right_line": 16
+ },
+ {
+ "left_line": 17,
+ "right_line": 17
+ },
+ {
+ "left_line": 18,
+ "right_line": 18
+ },
+ {
+ "left_line": 19,
+ "right_line": 19
+ },
+ {
+ "left_line": 20,
+ "right_line": 20
+ },
+ {
+ "left_line": 21,
+ "right_line": 21
+ },
+ {
+ "left_line": 22,
+ "right_line": 22
+ },
+ {
+ "left_line": 23,
+ "right_line": 23
+ },
+ {
+ "left_line": 24,
+ "right_line": 24
+ },
+ {
+ "left_line": 25,
+ "right_line": 25
+ },
+ {
+ "left_line": 26,
+ "right_line": 26
+ },
+ {
+ "left_line": 27,
+ "right_line": 27
+ },
+ {
+ "left_line": 28,
+ "right_line": 28
+ },
+ {
+ "left_line": 29,
+ "right_line": 29
+ },
+ {
+ "left_line": 30,
+ "right_line": 30
+ },
+ {
+ "left_line": 31,
+ "right_line": 31
+ },
+ {
+ "left_line": 32,
+ "right_line": 32
+ },
+ {
+ "left_line": 33,
+ "right_line": 33
+ },
+ {
+ "left_line": 34,
+ "right_line": 34
+ },
+ {
+ "left_line": 35,
+ "right_line": 35
+ },
+ {
+ "left_line": 36,
+ "right_line": 36
+ },
+ {
+ "left_line": 37,
+ "right_line": 37
+ },
+ {
+ "left_line": 38,
+ "right_line": 38
+ },
+ {
+ "left_line": 39,
+ "right_line": 39
+ },
+ {
+ "left_line": 40,
+ "right_line": 40
+ },
+ {
+ "left_line": 41,
+ "right_line": 41
+ },
+ {
+ "left_line": 42,
+ "right_line": 42
+ },
+ {
+ "left_line": 43,
+ "right_line": 43
+ },
+ {
+ "left_line": 44,
+ "right_line": 44
+ },
+ {
+ "left_line": 45,
+ "right_line": 45
+ },
+ {
+ "left_line": 46,
+ "right_line": 46
+ },
+ {
+ "left_line": 47,
+ "right_line": 47
+ },
+ {
+ "left_line": 48,
+ "right_line": 48
+ },
+ {
+ "left_line": 49,
+ "right_line": 49
+ },
+ {
+ "left_line": 50,
+ "right_line": 50
+ },
+ {
+ "left_line": 51,
+ "right_line": 51
+ },
+ {
+ "left_line": 52,
+ "right_line": 52
+ },
+ {
+ "left_line": 53,
+ "right_line": 53
+ },
+ {
+ "left_line": 54,
+ "right_line": 54
+ },
+ {
+ "left_line": 55,
+ "right_line": 55
+ },
+ {
+ "left_line": 56,
+ "right_line": 56
+ },
+ {
+ "left_line": 57,
+ "right_line": 57
+ },
+ {
+ "left_line": 58,
+ "right_line": 58
+ },
+ {
+ "left_line": 59,
+ "right_line": 59
+ },
+ {
+ "left_line": 60,
+ "right_line": 60
+ },
+ {
+ "left_line": 61,
+ "right_line": 61
+ },
+ {
+ "left_line": 62,
+ "right_line": 62
+ },
+ {
+ "left_line": 63,
+ "right_line": 63
+ },
+ {
+ "left_line": 64,
+ "right_line": 64
+ },
+ {
+ "left_line": 65,
+ "right_line": 65
+ },
+ {
+ "left_line": 66,
+ "right_line": 66
+ },
+ {
+ "left_line": 67,
+ "right_line": 67
+ },
+ {
+ "left_line": 68,
+ "right_line": 68
+ },
+ {
+ "left_line": 69,
+ "right_line": 69
+ },
+ {
+ "left_line": 70,
+ "right_line": 70
+ },
+ {
+ "left_line": 71,
+ "right_line": 71
+ },
+ {
+ "left_line": 72,
+ "right_line": 72
+ },
+ {
+ "left_line": 73,
+ "right_line": 73
+ },
+ {
+ "left_line": 74,
+ "right_line": 74
+ },
+ {
+ "left_line": 75,
+ "right_line": 75
+ },
+ {
+ "left_line": 76,
+ "right_line": 76
+ },
+ {
+ "left_line": 77,
+ "right_line": 77
+ },
+ {
+ "left_line": 78,
+ "right_line": 78
+ },
+ {
+ "left_line": 79,
+ "right_line": 79
+ },
+ {
+ "left_line": 80,
+ "right_line": 80
+ },
+ {
+ "left_line": 81,
+ "right_line": 81
+ },
+ {
+ "left_line": 82,
+ "right_line": 82
+ },
+ {
+ "left_line": 83,
+ "right_line": 83
+ },
+ {
+ "left_line": 84,
+ "right_line": 84
+ },
+ {
+ "left_line": 85,
+ "right_line": 85
+ },
+ {
+ "left_line": 86,
+ "right_line": 86
+ },
+ {
+ "left_line": 87,
+ "right_line": 87
+ },
+ {
+ "left_line": 88,
+ "right_line": 88
+ },
+ {
+ "left_line": 89,
+ "right_line": 89
+ },
+ {
+ "left_line": 90,
+ "right_line": 90
+ },
+ {
+ "left_line": 91,
+ "right_line": 91
+ },
+ {
+ "left_line": 92,
+ "right_line": 92
+ },
+ {
+ "left_line": 93,
+ "right_line": 93
+ },
+ {
+ "left_line": 94,
+ "right_line": 94
+ },
+ {
+ "left_line": 95,
+ "right_line": 95
+ },
+ {
+ "left_line": 96,
+ "right_line": 96
+ },
+ {
+ "left_line": 97,
+ "right_line": 97
+ },
+ {
+ "left_line": 98,
+ "right_line": 98
+ },
+ {
+ "left_line": 99,
+ "right_line": 99
+ },
+ {
+ "left_line": 100,
+ "right_line": 100
+ },
+ {
+ "left_line": 101,
+ "right_line": 101
+ },
+ {
+ "left_line": 102,
+ "right_line": 102
+ },
+ {
+ "left_line": 103,
+ "right_line": 103
+ },
+ {
+ "left_line": 104,
+ "right_line": 104
+ },
+ {
+ "left_line": 105,
+ "right_line": 105
+ },
+ {
+ "left_line": 106,
+ "right_line": 106
+ },
+ {
+ "left_line": 107,
+ "right_line": 107
+ },
+ {
+ "left_line": 108,
+ "right_line": 108
+ },
+ {
+ "left_line": 109,
+ "right_line": 109
+ },
+ {
+ "left_line": 110,
+ "right_line": 110
+ },
+ {
+ "left_line": 111,
+ "right_line": 111
+ },
+ {
+ "left_line": 112,
+ "right_line": 112
+ },
+ {
+ "left_line": 113,
+ "right_line": 113
+ },
+ {
+ "left_line": 114,
+ "right_line": 114
+ },
+ {
+ "left_line": 115,
+ "right_line": 115
+ },
+ {
+ "left_line": 116,
+ "right_line": 116
+ },
+ {
+ "left_line": 117,
+ "right_line": 117
+ },
+ {
+ "left_line": 118,
+ "right_line": 118
+ },
+ {
+ "left_line": 119,
+ "right_line": 119
+ },
+ {
+ "left_line": 120,
+ "right_line": 120
+ },
+ {
+ "left_line": 121,
+ "right_line": 121
+ },
+ {
+ "left_line": 122,
+ "right_line": 122
+ },
+ {
+ "left_line": 123,
+ "right_line": 123
+ },
+ {
+ "left_line": 124,
+ "right_line": 124
+ },
+ {
+ "left_line": 125,
+ "right_line": 125
+ },
+ {
+ "left_line": 126,
+ "right_line": 126
+ },
+ {
+ "left_line": 127,
+ "right_line": 127
+ },
+ {
+ "left_line": 128,
+ "right_line": 128
+ },
+ {
+ "left_line": 129,
+ "right_line": 129
+ },
+ {
+ "left_line": 130,
+ "right_line": 130
+ },
+ {
+ "left_line": 131,
+ "right_line": 131
+ },
+ {
+ "left_line": 132,
+ "right_line": 132
+ },
+ {
+ "left_line": 133,
+ "right_line": 133
+ },
+ {
+ "left_line": 134,
+ "right_line": 134
+ },
+ {
+ "left_line": 135,
+ "right_line": 135
+ },
+ {
+ "left_line": 136,
+ "right_line": 136
+ },
+ {
+ "left_line": 137,
+ "right_line": 137
+ },
+ {
+ "left_line": 138,
+ "right_line": 138
+ },
+ {
+ "left_line": 139,
+ "right_line": 139
+ },
+ {
+ "left_line": 140,
+ "right_line": 140
+ },
+ {
+ "left_line": 141,
+ "right_line": 141
+ },
+ {
+ "left_line": 142,
+ "right_line": 142
+ },
+ {
+ "left_line": 143,
+ "right_line": 143
+ },
+ {
+ "left_line": 144,
+ "right_line": 144
+ },
+ {
+ "left_line": 145,
+ "right_line": 145
+ },
+ {
+ "left_line": 146,
+ "right_line": 146
+ },
+ {
+ "left_line": 147,
+ "right_line": 147
+ },
+ {
+ "left_line": 148,
+ "right_line": 148
+ },
+ {
+ "left_line": 149,
+ "right_line": 149
+ },
+ {
+ "left_line": 150,
+ "right_line": 150
+ },
+ {
+ "left_line": 151,
+ "right_line": 151
+ },
+ {
+ "left_line": 152,
+ "right_line": 152
+ },
+ {
+ "left_line": 153,
+ "right_line": 153
+ },
+ {
+ "left_line": 154,
+ "right_line": 154
+ },
+ {
+ "left_line": 155,
+ "right_line": 155
+ },
+ {
+ "left_line": 156,
+ "right_line": 156
+ },
+ {
+ "left_line": 157,
+ "right_line": 157
+ },
+ {
+ "left_line": 158,
+ "right_line": 158
+ },
+ {
+ "left_line": 159,
+ "right_line": 159
+ },
+ {
+ "left_line": 160,
+ "right_line": 160
+ },
+ {
+ "left_line": 161,
+ "right_line": 161
+ },
+ {
+ "left_line": 162,
+ "right_line": 162
+ },
+ {
+ "left_line": 163,
+ "right_line": 163
+ },
+ {
+ "left_line": 164,
+ "right_line": 164
+ },
+ {
+ "left_line": 165,
+ "right_line": 165
+ },
+ {
+ "left_line": 166,
+ "right_line": 166
+ },
+ {
+ "left_line": 167,
+ "right_line": 167
+ },
+ {
+ "left_line": 168,
+ "right_line": 168
+ },
+ {
+ "left_line": 169,
+ "right_line": 169
+ },
+ {
+ "left_line": 170,
+ "right_line": 170
+ },
+ {
+ "left_line": 171,
+ "right_line": 171
+ },
+ {
+ "left_line": 172,
+ "right_line": 172
+ },
+ {
+ "left_line": 173,
+ "right_line": 173
+ },
+ {
+ "left_line": 174,
+ "right_line": 174
+ },
+ {
+ "left_line": 175,
+ "right_line": 175
+ },
+ {
+ "left_line": 176,
+ "right_line": 176
+ },
+ {
+ "left_line": 177,
+ "right_line": 177
+ },
+ {
+ "left_line": 178,
+ "right_line": 178
+ },
+ {
+ "left_line": 179,
+ "right_line": 179
+ },
+ {
+ "left_line": 180,
+ "right_line": 180
+ },
+ {
+ "left_line": 181,
+ "right_line": 181
+ },
+ {
+ "left_line": 182,
+ "right_line": 182
+ },
+ {
+ "left_line": 183,
+ "right_line": 183
+ },
+ {
+ "left_line": 184,
+ "right_line": 184
+ },
+ {
+ "left_line": 185,
+ "right_line": 185
+ },
+ {
+ "left_line": 186,
+ "right_line": 186
+ },
+ {
+ "left_line": 187,
+ "right_line": 187
+ },
+ {
+ "left_line": 188,
+ "right_line": 188
+ },
+ {
+ "left_line": 189,
+ "right_line": 189
+ },
+ {
+ "left_line": 190,
+ "right_line": 190
+ },
+ {
+ "left_line": 191,
+ "right_line": 191
+ },
+ {
+ "left_line": 192,
+ "right_line": 192
+ },
+ {
+ "left_line": 193,
+ "right_line": 193
+ },
+ {
+ "left_line": 194,
+ "right_line": 194
+ },
+ {
+ "left_line": 195,
+ "right_line": 195
+ },
+ {
+ "left_line": 196,
+ "right_line": 196
+ },
+ {
+ "left_line": 197,
+ "right_line": 197
+ },
+ {
+ "left_line": 198,
+ "right_line": 198
+ },
+ {
+ "left_line": 199,
+ "right_line": 199
+ },
+ {
+ "left_line": 200,
+ "right_line": 200
+ },
+ {
+ "left_line": 201,
+ "right_line": 201
+ },
+ {
+ "left_line": 202,
+ "right_line": 202
+ },
+ {
+ "left_line": 203,
+ "right_line": 203
+ },
+ {
+ "left_line": 204,
+ "right_line": 204
+ },
+ {
+ "left_line": 205,
+ "right_line": 205
+ },
+ {
+ "left_line": 206,
+ "right_line": 206
+ },
+ {
+ "left_line": 207,
+ "right_line": 207
+ },
+ {
+ "left_line": 208,
+ "right_line": 208
+ },
+ {
+ "left_line": 209,
+ "right_line": 209
+ },
+ {
+ "left_line": 210,
+ "right_line": 210
+ },
+ {
+ "left_line": 211,
+ "right_line": 211
+ },
+ {
+ "left_line": 212,
+ "right_line": 212
+ },
+ {
+ "left_line": 213,
+ "right_line": 213
+ },
+ {
+ "left_line": 214,
+ "right_line": 214
+ },
+ {
+ "left_line": 215,
+ "right_line": 215
+ },
+ {
+ "left_line": 216,
+ "right_line": 216
+ },
+ {
+ "left_line": 217,
+ "right_line": 217
+ },
+ {
+ "left_line": 218,
+ "right_line": 218
+ },
+ {
+ "left_line": 219,
+ "right_line": 219
+ },
+ {
+ "left_line": 220,
+ "right_line": 220
+ },
+ {
+ "left_line": 221,
+ "right_line": 221
+ },
+ {
+ "left_line": 222,
+ "right_line": 222
+ },
+ {
+ "left_line": 223,
+ "right_line": 223
+ },
+ {
+ "left_line": 224,
+ "right_line": 224
+ },
+ {
+ "left_line": 225,
+ "right_line": 225
+ },
+ {
+ "left_line": 226,
+ "right_line": 226
+ },
+ {
+ "left_line": 227,
+ "right_line": 227
+ },
+ {
+ "left_line": 228,
+ "right_line": 228
+ },
+ {
+ "left_line": 229,
+ "right_line": 229
+ },
+ {
+ "left_line": 230,
+ "right_line": 230
+ },
+ {
+ "left_line": 231,
+ "right_line": 231
+ },
+ {
+ "left_line": 232,
+ "right_line": 232
+ },
+ {
+ "left_line": 233,
+ "right_line": 233
+ },
+ {
+ "left_line": 234,
+ "right_line": 234
+ },
+ {
+ "left_line": 235,
+ "right_line": 235
+ },
+ {
+ "left_line": 236,
+ "right_line": 236
+ },
+ {
+ "left_line": 237,
+ "right_line": 237
+ },
+ {
+ "left_line": 238,
+ "right_line": 238
+ },
+ {
+ "left_line": 239,
+ "right_line": 239
+ },
+ {
+ "left_line": 240,
+ "right_line": 240
+ },
+ {
+ "left_line": 241,
+ "right_line": 241
+ },
+ {
+ "left_line": 242,
+ "right_line": 242
+ },
+ {
+ "left_line": 243,
+ "right_line": 243
+ },
+ {
+ "left_line": 244,
+ "right_line": 244
+ },
+ {
+ "left_line": 245,
+ "right_line": 245
+ },
+ {
+ "left_line": 246,
+ "right_line": 246
+ },
+ {
+ "left_line": 247,
+ "right_line": 247
+ },
+ {
+ "left_line": 248,
+ "right_line": 248
+ },
+ {
+ "left_line": 249,
+ "right_line": 249
+ },
+ {
+ "left_line": 250,
+ "right_line": 250
+ },
+ {
+ "left_line": 251,
+ "right_line": 251
+ },
+ {
+ "left_line": 252,
+ "right_line": 252
+ },
+ {
+ "left_line": 253,
+ "right_line": 253
+ },
+ {
+ "left_line": 254,
+ "right_line": 254
+ },
+ {
+ "left_line": 255,
+ "right_line": 255
+ },
+ {
+ "left_line": 256,
+ "right_line": 256
+ },
+ {
+ "left_line": 257,
+ "right_line": 257
+ },
+ {
+ "left_line": 258,
+ "right_line": 258
+ },
+ {
+ "left_line": 259,
+ "right_line": 259
+ },
+ {
+ "left_line": 260,
+ "right_line": 260
+ },
+ {
+ "left_line": 261,
+ "right_line": 261
+ },
+ {
+ "left_line": 262,
+ "right_line": 262
+ },
+ {
+ "left_line": 263,
+ "right_line": 263
+ },
+ {
+ "left_line": 264,
+ "right_line": 264
+ },
+ {
+ "left_line": 265,
+ "right_line": 265
+ },
+ {
+ "left_line": 266,
+ "right_line": 266
+ },
+ {
+ "left_line": 267,
+ "right_line": 267
+ },
+ {
+ "left_line": 268,
+ "right_line": 268
+ },
+ {
+ "left_line": 269,
+ "right_line": 269
+ },
+ {
+ "left_line": 270,
+ "right_line": 270
+ },
+ {
+ "left_line": 271,
+ "right_line": 271
+ },
+ {
+ "left_line": 272,
+ "right_line": 272
+ },
+ {
+ "left_line": 273,
+ "right_line": 273
+ },
+ {
+ "left_line": 274,
+ "right_line": 274
+ },
+ {
+ "left_line": 275,
+ "right_line": 275
+ },
+ {
+ "left_line": 276,
+ "right_line": 276
+ },
+ {
+ "left_line": -1,
+ "right_line": 277
+ },
+ {
+ "left_line": -1,
+ "right_line": 278
+ },
+ {
+ "left_line": -1,
+ "right_line": 279
+ },
+ {
+ "left_line": -1,
+ "right_line": 280
+ },
+ {
+ "left_line": -1,
+ "right_line": 281
+ },
+ {
+ "left_line": 277,
+ "right_line": 282
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/testdata/ts_trpc_patchclient_upgrade/metadata.json b/tests/testdata/ts_trpc_patchclient_upgrade/metadata.json
new file mode 100644
index 0000000..3b6e280
--- /dev/null
+++ b/tests/testdata/ts_trpc_patchclient_upgrade/metadata.json
@@ -0,0 +1,12 @@
+{
+ "repo": "https://github.com/trpc/trpc",
+ "parent": "326b89a3166d0b1f218286ef742736fe824f89f1",
+ "commit": "e70e78413d5a05447c29f8b1e60aaa330607888f",
+ "message": "patch(`client`): upgrade `localLink` from experimental -> `unstable` (#6817)\n\n_Notetoself: not auto-merging as it updates docs and then we gotta\ntrigger a release which I ain't doing right now_\n\n\n## Summary by CodeRabbit\n\n- **Documentation**\n- Updated documentation to reflect the new naming convention for the\nlocal link API, changing references from `experimental_localLink` to\n`unstable_localLink`.\n - Example code and descriptive text now use `unstable_localLink`.\n\n- **Refactor**\n- Renamed the local link API export from `experimental_localLink` to\n`unstable_localLink`.\n- Added a deprecated alias so `experimental_localLink` now points to\n`unstable_localLink`.\n\n\n---------\n\nCo-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>\nCo-authored-by: Julius Marminge \nCo-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>\nCo-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>",
+ "author": "Alex / KATT",
+ "date": "2025-06-12T13:30:12+02:00",
+ "language": "typescript",
+ "files_changed": 1,
+ "insertions": 5,
+ "deletions": 0
+}
\ No newline at end of file
diff --git a/tests/testdata/ts_trpc_patchclient_upgrade/new.ts b/tests/testdata/ts_trpc_patchclient_upgrade/new.ts
new file mode 100644
index 0000000..fa3783b
--- /dev/null
+++ b/tests/testdata/ts_trpc_patchclient_upgrade/new.ts
@@ -0,0 +1,282 @@
+import {
+ getTRPCErrorFromUnknown,
+ getTRPCErrorShape,
+ isTrackedEnvelope,
+} from '@trpc/server';
+import { behaviorSubject, observable } from '@trpc/server/observable';
+import { TRPC_ERROR_CODES_BY_KEY, type TRPCResult } from '@trpc/server/rpc';
+import {
+ callProcedure,
+ isAbortError,
+ isAsyncIterable,
+ iteratorResource,
+ makeResource,
+ retryableRpcCodes,
+ run,
+ type AnyRouter,
+ type ErrorHandlerOptions,
+ type inferClientTypes,
+ type inferRouterContext,
+} from '@trpc/server/unstable-core-do-not-import';
+import { inputWithTrackedEventId } from '../internals/inputWithTrackedEventId';
+import { abortSignalToPromise, raceAbortSignals } from '../internals/signals';
+import { getTransformer } from '../internals/transformer';
+import type { TransformerOptions } from '../internals/transformer';
+import { isTRPCClientError, TRPCClientError } from '../TRPCClientError';
+import type { TRPCConnectionState } from './internals/subscriptions';
+import type { TRPCLink } from './types';
+
+export type LocalLinkOptions = {
+ router: TRouter;
+ createContext: () => Promise>;
+ onError?: (opts: ErrorHandlerOptions>) => void;
+} & TransformerOptions>;
+
+/**
+ * localLink is a terminating link that allows you to make tRPC procedure calls directly in your application without going through HTTP.
+ *
+ * @see https://trpc.io/docs/links/localLink
+ */
+export function unstable_localLink(
+ opts: LocalLinkOptions,
+): TRPCLink {
+ const transformer = getTransformer(opts.transformer);
+
+ const transformChunk = (chunk: unknown) => {
+ if (opts.transformer) {
+ // assume transformer will do the right thing
+ return chunk;
+ }
+ // Special case for undefined, because `JSON.stringify(undefined)` throws
+ if (chunk === undefined) {
+ return chunk;
+ }
+ const serialized = JSON.stringify(transformer.input.serialize(chunk));
+ const deserialized = JSON.parse(transformer.output.deserialize(serialized));
+ return deserialized;
+ };
+
+ return () =>
+ ({ op }) =>
+ observable((observer) => {
+ let ctx: inferRouterContext | undefined = undefined;
+ const ac = new AbortController();
+
+ const signal = raceAbortSignals(op.signal, ac.signal);
+ const signalPromise = abortSignalToPromise(signal);
+
+ signalPromise.catch(() => {
+ // prevent unhandled rejection
+ });
+
+ let input = op.input;
+ async function runProcedure(newInput: unknown): Promise {
+ input = newInput;
+
+ ctx = await opts.createContext();
+
+ return callProcedure({
+ router: opts.router,
+ path: op.path,
+ getRawInput: async () => newInput,
+ ctx,
+ type: op.type,
+ signal,
+ });
+ }
+
+ function onErrorCallback(cause: unknown) {
+ if (isAbortError(cause)) {
+ return;
+ }
+ opts.onError?.({
+ error: getTRPCErrorFromUnknown(cause),
+ type: op.type,
+ path: op.path,
+ input,
+ ctx,
+ });
+ }
+
+ function coerceToTRPCClientError(cause: unknown) {
+ if (isTRPCClientError(cause)) {
+ return cause;
+ }
+ const error = getTRPCErrorFromUnknown(cause);
+
+ const shape = getTRPCErrorShape({
+ config: opts.router._def._config,
+ ctx,
+ error,
+ input,
+ path: op.path,
+ type: op.type,
+ });
+ return TRPCClientError.from({
+ error: transformChunk(shape),
+ });
+ }
+
+ run(async () => {
+ switch (op.type) {
+ case 'query':
+ case 'mutation': {
+ const result = await runProcedure(op.input);
+ if (!isAsyncIterable(result)) {
+ observer.next({
+ result: { data: transformChunk(result) },
+ });
+ observer.complete();
+ break;
+ }
+
+ observer.next({
+ result: {
+ data: (async function* () {
+ await using iterator = iteratorResource(result);
+ using _finally = makeResource({}, () => {
+ observer.complete();
+ });
+ try {
+ while (true) {
+ const res = await Promise.race([
+ iterator.next(),
+ signalPromise,
+ ]);
+ if (res.done) {
+ return transformChunk(res.value);
+ }
+ yield transformChunk(res.value);
+ }
+ } catch (cause) {
+ onErrorCallback(cause);
+ throw coerceToTRPCClientError(cause);
+ }
+ })(),
+ },
+ });
+ break;
+ }
+ case 'subscription': {
+ const connectionState = behaviorSubject<
+ TRPCConnectionState>
+ >({
+ type: 'state',
+ state: 'connecting',
+ error: null,
+ });
+
+ const connectionSub = connectionState.subscribe({
+ next(state) {
+ observer.next({
+ result: state,
+ });
+ },
+ });
+ let lastEventId: string | undefined = undefined;
+
+ using _finally = makeResource({}, async () => {
+ observer.complete();
+
+ connectionState.next({
+ type: 'state',
+ state: 'idle',
+ error: null,
+ });
+ connectionSub.unsubscribe();
+ });
+ while (true) {
+ const result = await runProcedure(
+ inputWithTrackedEventId(op.input, lastEventId),
+ );
+ if (!isAsyncIterable(result)) {
+ throw new Error('Expected an async iterable');
+ }
+ await using iterator = iteratorResource(result);
+
+ observer.next({
+ result: {
+ type: 'started',
+ },
+ });
+ connectionState.next({
+ type: 'state',
+ state: 'pending',
+ error: null,
+ });
+
+ // Use a while loop to handle errors and reconnects
+ while (true) {
+ let res;
+ try {
+ res = await Promise.race([iterator.next(), signalPromise]);
+ } catch (cause) {
+ if (isAbortError(cause)) {
+ return;
+ }
+ const error = getTRPCErrorFromUnknown(cause);
+
+ if (
+ !retryableRpcCodes.includes(
+ TRPC_ERROR_CODES_BY_KEY[error.code],
+ )
+ ) {
+ throw coerceToTRPCClientError(error);
+ }
+
+ onErrorCallback(error);
+ connectionState.next({
+ type: 'state',
+ state: 'connecting',
+ error: coerceToTRPCClientError(error),
+ });
+
+ break;
+ }
+
+ if (res.done) {
+ return;
+ }
+ let chunk: TRPCResult;
+ if (isTrackedEnvelope(res.value)) {
+ lastEventId = res.value[0];
+
+ chunk = {
+ id: res.value[0],
+ data: {
+ id: res.value[0],
+ data: res.value[1],
+ },
+ };
+ } else {
+ chunk = {
+ data: res.value,
+ };
+ }
+
+ observer.next({
+ result: {
+ ...chunk,
+ data: transformChunk(chunk.data),
+ },
+ });
+ }
+ }
+ break;
+ }
+ }
+ }).catch((cause) => {
+ onErrorCallback(cause);
+ observer.error(coerceToTRPCClientError(cause));
+ });
+
+ return () => {
+ ac.abort();
+ };
+ });
+}
+/**
+ * @deprecated Renamed to `unstable_localLink`. This alias will be removed in a future major release.
+ */
+export const experimental_localLink: typeof unstable_localLink =
+ unstable_localLink;
diff --git a/tests/testdata/ts_trpc_patchclient_upgrade/old.ts b/tests/testdata/ts_trpc_patchclient_upgrade/old.ts
new file mode 100644
index 0000000..98a7669
--- /dev/null
+++ b/tests/testdata/ts_trpc_patchclient_upgrade/old.ts
@@ -0,0 +1,277 @@
+import {
+ getTRPCErrorFromUnknown,
+ getTRPCErrorShape,
+ isTrackedEnvelope,
+} from '@trpc/server';
+import { behaviorSubject, observable } from '@trpc/server/observable';
+import { TRPC_ERROR_CODES_BY_KEY, type TRPCResult } from '@trpc/server/rpc';
+import {
+ callProcedure,
+ isAbortError,
+ isAsyncIterable,
+ iteratorResource,
+ makeResource,
+ retryableRpcCodes,
+ run,
+ type AnyRouter,
+ type ErrorHandlerOptions,
+ type inferClientTypes,
+ type inferRouterContext,
+} from '@trpc/server/unstable-core-do-not-import';
+import { inputWithTrackedEventId } from '../internals/inputWithTrackedEventId';
+import { abortSignalToPromise, raceAbortSignals } from '../internals/signals';
+import { getTransformer } from '../internals/transformer';
+import type { TransformerOptions } from '../internals/transformer';
+import { isTRPCClientError, TRPCClientError } from '../TRPCClientError';
+import type { TRPCConnectionState } from './internals/subscriptions';
+import type { TRPCLink } from './types';
+
+export type LocalLinkOptions = {
+ router: TRouter;
+ createContext: () => Promise>;
+ onError?: (opts: ErrorHandlerOptions>) => void;
+} & TransformerOptions>;
+
+/**
+ * localLink is a terminating link that allows you to make tRPC procedure calls directly in your application without going through HTTP.
+ *
+ * @see https://trpc.io/docs/links/localLink
+ */
+export function experimental_localLink(
+ opts: LocalLinkOptions,
+): TRPCLink {
+ const transformer = getTransformer(opts.transformer);
+
+ const transformChunk = (chunk: unknown) => {
+ if (opts.transformer) {
+ // assume transformer will do the right thing
+ return chunk;
+ }
+ // Special case for undefined, because `JSON.stringify(undefined)` throws
+ if (chunk === undefined) {
+ return chunk;
+ }
+ const serialized = JSON.stringify(transformer.input.serialize(chunk));
+ const deserialized = JSON.parse(transformer.output.deserialize(serialized));
+ return deserialized;
+ };
+
+ return () =>
+ ({ op }) =>
+ observable((observer) => {
+ let ctx: inferRouterContext | undefined = undefined;
+ const ac = new AbortController();
+
+ const signal = raceAbortSignals(op.signal, ac.signal);
+ const signalPromise = abortSignalToPromise(signal);
+
+ signalPromise.catch(() => {
+ // prevent unhandled rejection
+ });
+
+ let input = op.input;
+ async function runProcedure(newInput: unknown): Promise {
+ input = newInput;
+
+ ctx = await opts.createContext();
+
+ return callProcedure({
+ router: opts.router,
+ path: op.path,
+ getRawInput: async () => newInput,
+ ctx,
+ type: op.type,
+ signal,
+ });
+ }
+
+ function onErrorCallback(cause: unknown) {
+ if (isAbortError(cause)) {
+ return;
+ }
+ opts.onError?.({
+ error: getTRPCErrorFromUnknown(cause),
+ type: op.type,
+ path: op.path,
+ input,
+ ctx,
+ });
+ }
+
+ function coerceToTRPCClientError(cause: unknown) {
+ if (isTRPCClientError(cause)) {
+ return cause;
+ }
+ const error = getTRPCErrorFromUnknown(cause);
+
+ const shape = getTRPCErrorShape({
+ config: opts.router._def._config,
+ ctx,
+ error,
+ input,
+ path: op.path,
+ type: op.type,
+ });
+ return TRPCClientError.from({
+ error: transformChunk(shape),
+ });
+ }
+
+ run(async () => {
+ switch (op.type) {
+ case 'query':
+ case 'mutation': {
+ const result = await runProcedure(op.input);
+ if (!isAsyncIterable(result)) {
+ observer.next({
+ result: { data: transformChunk(result) },
+ });
+ observer.complete();
+ break;
+ }
+
+ observer.next({
+ result: {
+ data: (async function* () {
+ await using iterator = iteratorResource(result);
+ using _finally = makeResource({}, () => {
+ observer.complete();
+ });
+ try {
+ while (true) {
+ const res = await Promise.race([
+ iterator.next(),
+ signalPromise,
+ ]);
+ if (res.done) {
+ return transformChunk(res.value);
+ }
+ yield transformChunk(res.value);
+ }
+ } catch (cause) {
+ onErrorCallback(cause);
+ throw coerceToTRPCClientError(cause);
+ }
+ })(),
+ },
+ });
+ break;
+ }
+ case 'subscription': {
+ const connectionState = behaviorSubject<
+ TRPCConnectionState>
+ >({
+ type: 'state',
+ state: 'connecting',
+ error: null,
+ });
+
+ const connectionSub = connectionState.subscribe({
+ next(state) {
+ observer.next({
+ result: state,
+ });
+ },
+ });
+ let lastEventId: string | undefined = undefined;
+
+ using _finally = makeResource({}, async () => {
+ observer.complete();
+
+ connectionState.next({
+ type: 'state',
+ state: 'idle',
+ error: null,
+ });
+ connectionSub.unsubscribe();
+ });
+ while (true) {
+ const result = await runProcedure(
+ inputWithTrackedEventId(op.input, lastEventId),
+ );
+ if (!isAsyncIterable(result)) {
+ throw new Error('Expected an async iterable');
+ }
+ await using iterator = iteratorResource(result);
+
+ observer.next({
+ result: {
+ type: 'started',
+ },
+ });
+ connectionState.next({
+ type: 'state',
+ state: 'pending',
+ error: null,
+ });
+
+ // Use a while loop to handle errors and reconnects
+ while (true) {
+ let res;
+ try {
+ res = await Promise.race([iterator.next(), signalPromise]);
+ } catch (cause) {
+ if (isAbortError(cause)) {
+ return;
+ }
+ const error = getTRPCErrorFromUnknown(cause);
+
+ if (
+ !retryableRpcCodes.includes(
+ TRPC_ERROR_CODES_BY_KEY[error.code],
+ )
+ ) {
+ throw coerceToTRPCClientError(error);
+ }
+
+ onErrorCallback(error);
+ connectionState.next({
+ type: 'state',
+ state: 'connecting',
+ error: coerceToTRPCClientError(error),
+ });
+
+ break;
+ }
+
+ if (res.done) {
+ return;
+ }
+ let chunk: TRPCResult;
+ if (isTrackedEnvelope(res.value)) {
+ lastEventId = res.value[0];
+
+ chunk = {
+ id: res.value[0],
+ data: {
+ id: res.value[0],
+ data: res.value[1],
+ },
+ };
+ } else {
+ chunk = {
+ data: res.value,
+ };
+ }
+
+ observer.next({
+ result: {
+ ...chunk,
+ data: transformChunk(chunk.data),
+ },
+ });
+ }
+ }
+ break;
+ }
+ }
+ }).catch((cause) => {
+ onErrorCallback(cause);
+ observer.error(coerceToTRPCClientError(cause));
+ });
+
+ return () => {
+ ac.abort();
+ };
+ });
+}
diff --git a/tests/testdata/ts_zod_improve_mini/expected.json b/tests/testdata/ts_zod_improve_mini/expected.json
new file mode 100644
index 0000000..f2e89c1
--- /dev/null
+++ b/tests/testdata/ts_zod_improve_mini/expected.json
@@ -0,0 +1,7743 @@
+{
+ "version": "v1",
+ "actions": [
+ {
+ "action": "update",
+ "node": {
+ "tree": "before",
+ "path": [
+ 1,
+ 3
+ ],
+ "type": "string",
+ "label": "\"../core/index.js\"",
+ "start_byte": 63,
+ "end_byte": 81
+ },
+ "old_value": "\"../core/index.js\"",
+ "new_value": "\"../core/util.js\"",
+ "dest_node": {
+ "tree": "after",
+ "path": [
+ 1,
+ 3
+ ],
+ "type": "string",
+ "label": "\"../core/util.js\"",
+ "start_byte": 64,
+ "end_byte": 81
+ }
+ },
+ {
+ "action": "insert",
+ "node": {
+ "tree": "after",
+ "path": [
+ 1,
+ 1,
+ 0
+ ],
+ "type": "namespace_import",
+ "start_byte": 49,
+ "end_byte": 58
+ },
+ "parent": {
+ "tree": "after",
+ "path": [
+ 1,
+ 1
+ ],
+ "type": "import_clause",
+ "start_byte": 49,
+ "end_byte": 58
+ },
+ "position": 0,
+ "subtree": true
+ },
+ {
+ "action": "delete",
+ "node": {
+ "tree": "before",
+ "path": [
+ 1,
+ 1,
+ 0
+ ],
+ "type": "named_imports",
+ "start_byte": 49,
+ "end_byte": 57
+ },
+ "subtree": true
+ }
+ ],
+ "line_alignment": [
+ {
+ "left_line": 0,
+ "right_line": 0
+ },
+ {
+ "left_line": 1,
+ "right_line": 1
+ },
+ {
+ "left_line": 2,
+ "right_line": 2
+ },
+ {
+ "left_line": 3,
+ "right_line": 3
+ },
+ {
+ "left_line": 4,
+ "right_line": 4
+ },
+ {
+ "left_line": 5,
+ "right_line": 5
+ },
+ {
+ "left_line": 6,
+ "right_line": 6
+ },
+ {
+ "left_line": 7,
+ "right_line": 7
+ },
+ {
+ "left_line": 8,
+ "right_line": 8
+ },
+ {
+ "left_line": 9,
+ "right_line": 9
+ },
+ {
+ "left_line": 10,
+ "right_line": 10
+ },
+ {
+ "left_line": 11,
+ "right_line": 11
+ },
+ {
+ "left_line": 12,
+ "right_line": 12
+ },
+ {
+ "left_line": 13,
+ "right_line": 13
+ },
+ {
+ "left_line": 14,
+ "right_line": 14
+ },
+ {
+ "left_line": 15,
+ "right_line": 15
+ },
+ {
+ "left_line": 16,
+ "right_line": 16
+ },
+ {
+ "left_line": 17,
+ "right_line": 17
+ },
+ {
+ "left_line": 18,
+ "right_line": 18
+ },
+ {
+ "left_line": 19,
+ "right_line": 19
+ },
+ {
+ "left_line": 20,
+ "right_line": 20
+ },
+ {
+ "left_line": 21,
+ "right_line": 21
+ },
+ {
+ "left_line": 22,
+ "right_line": 22
+ },
+ {
+ "left_line": 23,
+ "right_line": 23
+ },
+ {
+ "left_line": 24,
+ "right_line": 24
+ },
+ {
+ "left_line": 25,
+ "right_line": 25
+ },
+ {
+ "left_line": 26,
+ "right_line": 26
+ },
+ {
+ "left_line": 27,
+ "right_line": 27
+ },
+ {
+ "left_line": 28,
+ "right_line": 28
+ },
+ {
+ "left_line": 29,
+ "right_line": 29
+ },
+ {
+ "left_line": 30,
+ "right_line": 30
+ },
+ {
+ "left_line": 31,
+ "right_line": 31
+ },
+ {
+ "left_line": 32,
+ "right_line": 32
+ },
+ {
+ "left_line": 33,
+ "right_line": 33
+ },
+ {
+ "left_line": 34,
+ "right_line": 34
+ },
+ {
+ "left_line": 35,
+ "right_line": 35
+ },
+ {
+ "left_line": 36,
+ "right_line": 36
+ },
+ {
+ "left_line": 37,
+ "right_line": 37
+ },
+ {
+ "left_line": 38,
+ "right_line": 38
+ },
+ {
+ "left_line": 39,
+ "right_line": 39
+ },
+ {
+ "left_line": 40,
+ "right_line": 40
+ },
+ {
+ "left_line": 41,
+ "right_line": 41
+ },
+ {
+ "left_line": 42,
+ "right_line": 42
+ },
+ {
+ "left_line": 43,
+ "right_line": 43
+ },
+ {
+ "left_line": 44,
+ "right_line": 44
+ },
+ {
+ "left_line": 45,
+ "right_line": 45
+ },
+ {
+ "left_line": 46,
+ "right_line": 46
+ },
+ {
+ "left_line": 47,
+ "right_line": 47
+ },
+ {
+ "left_line": 48,
+ "right_line": 48
+ },
+ {
+ "left_line": 49,
+ "right_line": 49
+ },
+ {
+ "left_line": 50,
+ "right_line": 50
+ },
+ {
+ "left_line": 51,
+ "right_line": 51
+ },
+ {
+ "left_line": 52,
+ "right_line": 52
+ },
+ {
+ "left_line": 53,
+ "right_line": 53
+ },
+ {
+ "left_line": 54,
+ "right_line": 54
+ },
+ {
+ "left_line": 55,
+ "right_line": 55
+ },
+ {
+ "left_line": 56,
+ "right_line": 56
+ },
+ {
+ "left_line": 57,
+ "right_line": 57
+ },
+ {
+ "left_line": 58,
+ "right_line": 58
+ },
+ {
+ "left_line": 59,
+ "right_line": 59
+ },
+ {
+ "left_line": 60,
+ "right_line": 60
+ },
+ {
+ "left_line": 61,
+ "right_line": 61
+ },
+ {
+ "left_line": 62,
+ "right_line": 62
+ },
+ {
+ "left_line": 63,
+ "right_line": 63
+ },
+ {
+ "left_line": 64,
+ "right_line": 64
+ },
+ {
+ "left_line": 65,
+ "right_line": 65
+ },
+ {
+ "left_line": 66,
+ "right_line": 66
+ },
+ {
+ "left_line": 67,
+ "right_line": 67
+ },
+ {
+ "left_line": 68,
+ "right_line": 68
+ },
+ {
+ "left_line": 69,
+ "right_line": 69
+ },
+ {
+ "left_line": 70,
+ "right_line": 70
+ },
+ {
+ "left_line": 71,
+ "right_line": 71
+ },
+ {
+ "left_line": 72,
+ "right_line": 72
+ },
+ {
+ "left_line": 73,
+ "right_line": 73
+ },
+ {
+ "left_line": 74,
+ "right_line": 74
+ },
+ {
+ "left_line": 75,
+ "right_line": 75
+ },
+ {
+ "left_line": 76,
+ "right_line": 76
+ },
+ {
+ "left_line": 77,
+ "right_line": 77
+ },
+ {
+ "left_line": 78,
+ "right_line": 78
+ },
+ {
+ "left_line": 79,
+ "right_line": 79
+ },
+ {
+ "left_line": 80,
+ "right_line": 80
+ },
+ {
+ "left_line": 81,
+ "right_line": 81
+ },
+ {
+ "left_line": 82,
+ "right_line": 82
+ },
+ {
+ "left_line": 83,
+ "right_line": 83
+ },
+ {
+ "left_line": 84,
+ "right_line": 84
+ },
+ {
+ "left_line": 85,
+ "right_line": 85
+ },
+ {
+ "left_line": 86,
+ "right_line": 86
+ },
+ {
+ "left_line": 87,
+ "right_line": 87
+ },
+ {
+ "left_line": 88,
+ "right_line": 88
+ },
+ {
+ "left_line": 89,
+ "right_line": 89
+ },
+ {
+ "left_line": 90,
+ "right_line": 90
+ },
+ {
+ "left_line": 91,
+ "right_line": 91
+ },
+ {
+ "left_line": 92,
+ "right_line": 92
+ },
+ {
+ "left_line": 93,
+ "right_line": 93
+ },
+ {
+ "left_line": 94,
+ "right_line": 94
+ },
+ {
+ "left_line": 95,
+ "right_line": 95
+ },
+ {
+ "left_line": 96,
+ "right_line": 96
+ },
+ {
+ "left_line": 97,
+ "right_line": 97
+ },
+ {
+ "left_line": 98,
+ "right_line": 98
+ },
+ {
+ "left_line": 99,
+ "right_line": 99
+ },
+ {
+ "left_line": 100,
+ "right_line": 100
+ },
+ {
+ "left_line": 101,
+ "right_line": 101
+ },
+ {
+ "left_line": 102,
+ "right_line": 102
+ },
+ {
+ "left_line": 103,
+ "right_line": 103
+ },
+ {
+ "left_line": 104,
+ "right_line": 104
+ },
+ {
+ "left_line": 105,
+ "right_line": 105
+ },
+ {
+ "left_line": 106,
+ "right_line": 106
+ },
+ {
+ "left_line": 107,
+ "right_line": 107
+ },
+ {
+ "left_line": 108,
+ "right_line": 108
+ },
+ {
+ "left_line": 109,
+ "right_line": 109
+ },
+ {
+ "left_line": 110,
+ "right_line": 110
+ },
+ {
+ "left_line": 111,
+ "right_line": 111
+ },
+ {
+ "left_line": 112,
+ "right_line": 112
+ },
+ {
+ "left_line": 113,
+ "right_line": 113
+ },
+ {
+ "left_line": 114,
+ "right_line": 114
+ },
+ {
+ "left_line": 115,
+ "right_line": 115
+ },
+ {
+ "left_line": 116,
+ "right_line": 116
+ },
+ {
+ "left_line": 117,
+ "right_line": 117
+ },
+ {
+ "left_line": 118,
+ "right_line": 118
+ },
+ {
+ "left_line": 119,
+ "right_line": 119
+ },
+ {
+ "left_line": 120,
+ "right_line": 120
+ },
+ {
+ "left_line": 121,
+ "right_line": 121
+ },
+ {
+ "left_line": 122,
+ "right_line": 122
+ },
+ {
+ "left_line": 123,
+ "right_line": 123
+ },
+ {
+ "left_line": 124,
+ "right_line": 124
+ },
+ {
+ "left_line": 125,
+ "right_line": 125
+ },
+ {
+ "left_line": 126,
+ "right_line": 126
+ },
+ {
+ "left_line": 127,
+ "right_line": 127
+ },
+ {
+ "left_line": 128,
+ "right_line": 128
+ },
+ {
+ "left_line": 129,
+ "right_line": 129
+ },
+ {
+ "left_line": 130,
+ "right_line": 130
+ },
+ {
+ "left_line": 131,
+ "right_line": 131
+ },
+ {
+ "left_line": 132,
+ "right_line": 132
+ },
+ {
+ "left_line": 133,
+ "right_line": 133
+ },
+ {
+ "left_line": 134,
+ "right_line": 134
+ },
+ {
+ "left_line": 135,
+ "right_line": 135
+ },
+ {
+ "left_line": 136,
+ "right_line": 136
+ },
+ {
+ "left_line": 137,
+ "right_line": 137
+ },
+ {
+ "left_line": 138,
+ "right_line": 138
+ },
+ {
+ "left_line": 139,
+ "right_line": 139
+ },
+ {
+ "left_line": 140,
+ "right_line": 140
+ },
+ {
+ "left_line": 141,
+ "right_line": 141
+ },
+ {
+ "left_line": 142,
+ "right_line": 142
+ },
+ {
+ "left_line": 143,
+ "right_line": 143
+ },
+ {
+ "left_line": 144,
+ "right_line": 144
+ },
+ {
+ "left_line": 145,
+ "right_line": 145
+ },
+ {
+ "left_line": 146,
+ "right_line": 146
+ },
+ {
+ "left_line": 147,
+ "right_line": 147
+ },
+ {
+ "left_line": 148,
+ "right_line": 148
+ },
+ {
+ "left_line": 149,
+ "right_line": 149
+ },
+ {
+ "left_line": 150,
+ "right_line": 150
+ },
+ {
+ "left_line": 151,
+ "right_line": 151
+ },
+ {
+ "left_line": 152,
+ "right_line": 152
+ },
+ {
+ "left_line": 153,
+ "right_line": 153
+ },
+ {
+ "left_line": 154,
+ "right_line": 154
+ },
+ {
+ "left_line": 155,
+ "right_line": 155
+ },
+ {
+ "left_line": 156,
+ "right_line": 156
+ },
+ {
+ "left_line": 157,
+ "right_line": 157
+ },
+ {
+ "left_line": 158,
+ "right_line": 158
+ },
+ {
+ "left_line": 159,
+ "right_line": 159
+ },
+ {
+ "left_line": 160,
+ "right_line": 160
+ },
+ {
+ "left_line": 161,
+ "right_line": 161
+ },
+ {
+ "left_line": 162,
+ "right_line": 162
+ },
+ {
+ "left_line": 163,
+ "right_line": 163
+ },
+ {
+ "left_line": 164,
+ "right_line": 164
+ },
+ {
+ "left_line": 165,
+ "right_line": 165
+ },
+ {
+ "left_line": 166,
+ "right_line": 166
+ },
+ {
+ "left_line": 167,
+ "right_line": 167
+ },
+ {
+ "left_line": 168,
+ "right_line": 168
+ },
+ {
+ "left_line": 169,
+ "right_line": 169
+ },
+ {
+ "left_line": 170,
+ "right_line": 170
+ },
+ {
+ "left_line": 171,
+ "right_line": 171
+ },
+ {
+ "left_line": 172,
+ "right_line": 172
+ },
+ {
+ "left_line": 173,
+ "right_line": 173
+ },
+ {
+ "left_line": 174,
+ "right_line": 174
+ },
+ {
+ "left_line": 175,
+ "right_line": 175
+ },
+ {
+ "left_line": 176,
+ "right_line": 176
+ },
+ {
+ "left_line": 177,
+ "right_line": 177
+ },
+ {
+ "left_line": 178,
+ "right_line": 178
+ },
+ {
+ "left_line": 179,
+ "right_line": 179
+ },
+ {
+ "left_line": 180,
+ "right_line": 180
+ },
+ {
+ "left_line": 181,
+ "right_line": 181
+ },
+ {
+ "left_line": 182,
+ "right_line": 182
+ },
+ {
+ "left_line": 183,
+ "right_line": 183
+ },
+ {
+ "left_line": 184,
+ "right_line": 184
+ },
+ {
+ "left_line": 185,
+ "right_line": 185
+ },
+ {
+ "left_line": 186,
+ "right_line": 186
+ },
+ {
+ "left_line": 187,
+ "right_line": 187
+ },
+ {
+ "left_line": 188,
+ "right_line": 188
+ },
+ {
+ "left_line": 189,
+ "right_line": 189
+ },
+ {
+ "left_line": 190,
+ "right_line": 190
+ },
+ {
+ "left_line": 191,
+ "right_line": 191
+ },
+ {
+ "left_line": 192,
+ "right_line": 192
+ },
+ {
+ "left_line": 193,
+ "right_line": 193
+ },
+ {
+ "left_line": 194,
+ "right_line": 194
+ },
+ {
+ "left_line": 195,
+ "right_line": 195
+ },
+ {
+ "left_line": 196,
+ "right_line": 196
+ },
+ {
+ "left_line": 197,
+ "right_line": 197
+ },
+ {
+ "left_line": 198,
+ "right_line": 198
+ },
+ {
+ "left_line": 199,
+ "right_line": 199
+ },
+ {
+ "left_line": 200,
+ "right_line": 200
+ },
+ {
+ "left_line": 201,
+ "right_line": 201
+ },
+ {
+ "left_line": 202,
+ "right_line": 202
+ },
+ {
+ "left_line": 203,
+ "right_line": 203
+ },
+ {
+ "left_line": 204,
+ "right_line": 204
+ },
+ {
+ "left_line": 205,
+ "right_line": 205
+ },
+ {
+ "left_line": 206,
+ "right_line": 206
+ },
+ {
+ "left_line": 207,
+ "right_line": 207
+ },
+ {
+ "left_line": 208,
+ "right_line": 208
+ },
+ {
+ "left_line": 209,
+ "right_line": 209
+ },
+ {
+ "left_line": 210,
+ "right_line": 210
+ },
+ {
+ "left_line": 211,
+ "right_line": 211
+ },
+ {
+ "left_line": 212,
+ "right_line": 212
+ },
+ {
+ "left_line": 213,
+ "right_line": 213
+ },
+ {
+ "left_line": 214,
+ "right_line": 214
+ },
+ {
+ "left_line": 215,
+ "right_line": 215
+ },
+ {
+ "left_line": 216,
+ "right_line": 216
+ },
+ {
+ "left_line": 217,
+ "right_line": 217
+ },
+ {
+ "left_line": 218,
+ "right_line": 218
+ },
+ {
+ "left_line": 219,
+ "right_line": 219
+ },
+ {
+ "left_line": 220,
+ "right_line": 220
+ },
+ {
+ "left_line": 221,
+ "right_line": 221
+ },
+ {
+ "left_line": 222,
+ "right_line": 222
+ },
+ {
+ "left_line": 223,
+ "right_line": 223
+ },
+ {
+ "left_line": 224,
+ "right_line": 224
+ },
+ {
+ "left_line": 225,
+ "right_line": 225
+ },
+ {
+ "left_line": 226,
+ "right_line": 226
+ },
+ {
+ "left_line": 227,
+ "right_line": 227
+ },
+ {
+ "left_line": 228,
+ "right_line": 228
+ },
+ {
+ "left_line": 229,
+ "right_line": 229
+ },
+ {
+ "left_line": 230,
+ "right_line": 230
+ },
+ {
+ "left_line": 231,
+ "right_line": 231
+ },
+ {
+ "left_line": 232,
+ "right_line": 232
+ },
+ {
+ "left_line": 233,
+ "right_line": 233
+ },
+ {
+ "left_line": 234,
+ "right_line": 234
+ },
+ {
+ "left_line": 235,
+ "right_line": 235
+ },
+ {
+ "left_line": 236,
+ "right_line": 236
+ },
+ {
+ "left_line": 237,
+ "right_line": 237
+ },
+ {
+ "left_line": 238,
+ "right_line": 238
+ },
+ {
+ "left_line": 239,
+ "right_line": 239
+ },
+ {
+ "left_line": 240,
+ "right_line": 240
+ },
+ {
+ "left_line": 241,
+ "right_line": 241
+ },
+ {
+ "left_line": 242,
+ "right_line": 242
+ },
+ {
+ "left_line": 243,
+ "right_line": 243
+ },
+ {
+ "left_line": 244,
+ "right_line": 244
+ },
+ {
+ "left_line": 245,
+ "right_line": 245
+ },
+ {
+ "left_line": 246,
+ "right_line": 246
+ },
+ {
+ "left_line": 247,
+ "right_line": 247
+ },
+ {
+ "left_line": 248,
+ "right_line": 248
+ },
+ {
+ "left_line": 249,
+ "right_line": 249
+ },
+ {
+ "left_line": 250,
+ "right_line": 250
+ },
+ {
+ "left_line": 251,
+ "right_line": 251
+ },
+ {
+ "left_line": 252,
+ "right_line": 252
+ },
+ {
+ "left_line": 253,
+ "right_line": 253
+ },
+ {
+ "left_line": 254,
+ "right_line": 254
+ },
+ {
+ "left_line": 255,
+ "right_line": 255
+ },
+ {
+ "left_line": 256,
+ "right_line": 256
+ },
+ {
+ "left_line": 257,
+ "right_line": 257
+ },
+ {
+ "left_line": 258,
+ "right_line": 258
+ },
+ {
+ "left_line": 259,
+ "right_line": 259
+ },
+ {
+ "left_line": 260,
+ "right_line": 260
+ },
+ {
+ "left_line": 261,
+ "right_line": 261
+ },
+ {
+ "left_line": 262,
+ "right_line": 262
+ },
+ {
+ "left_line": 263,
+ "right_line": 263
+ },
+ {
+ "left_line": 264,
+ "right_line": 264
+ },
+ {
+ "left_line": 265,
+ "right_line": 265
+ },
+ {
+ "left_line": 266,
+ "right_line": 266
+ },
+ {
+ "left_line": 267,
+ "right_line": 267
+ },
+ {
+ "left_line": 268,
+ "right_line": 268
+ },
+ {
+ "left_line": 269,
+ "right_line": 269
+ },
+ {
+ "left_line": 270,
+ "right_line": 270
+ },
+ {
+ "left_line": 271,
+ "right_line": 271
+ },
+ {
+ "left_line": 272,
+ "right_line": 272
+ },
+ {
+ "left_line": 273,
+ "right_line": 273
+ },
+ {
+ "left_line": 274,
+ "right_line": 274
+ },
+ {
+ "left_line": 275,
+ "right_line": 275
+ },
+ {
+ "left_line": 276,
+ "right_line": 276
+ },
+ {
+ "left_line": 277,
+ "right_line": 277
+ },
+ {
+ "left_line": 278,
+ "right_line": 278
+ },
+ {
+ "left_line": 279,
+ "right_line": 279
+ },
+ {
+ "left_line": 280,
+ "right_line": 280
+ },
+ {
+ "left_line": 281,
+ "right_line": 281
+ },
+ {
+ "left_line": 282,
+ "right_line": 282
+ },
+ {
+ "left_line": 283,
+ "right_line": 283
+ },
+ {
+ "left_line": 284,
+ "right_line": 284
+ },
+ {
+ "left_line": 285,
+ "right_line": 285
+ },
+ {
+ "left_line": 286,
+ "right_line": 286
+ },
+ {
+ "left_line": 287,
+ "right_line": 287
+ },
+ {
+ "left_line": 288,
+ "right_line": 288
+ },
+ {
+ "left_line": 289,
+ "right_line": 289
+ },
+ {
+ "left_line": 290,
+ "right_line": 290
+ },
+ {
+ "left_line": 291,
+ "right_line": 291
+ },
+ {
+ "left_line": 292,
+ "right_line": 292
+ },
+ {
+ "left_line": 293,
+ "right_line": 293
+ },
+ {
+ "left_line": 294,
+ "right_line": 294
+ },
+ {
+ "left_line": 295,
+ "right_line": 295
+ },
+ {
+ "left_line": 296,
+ "right_line": 296
+ },
+ {
+ "left_line": 297,
+ "right_line": 297
+ },
+ {
+ "left_line": 298,
+ "right_line": 298
+ },
+ {
+ "left_line": 299,
+ "right_line": 299
+ },
+ {
+ "left_line": 300,
+ "right_line": 300
+ },
+ {
+ "left_line": 301,
+ "right_line": 301
+ },
+ {
+ "left_line": 302,
+ "right_line": 302
+ },
+ {
+ "left_line": 303,
+ "right_line": 303
+ },
+ {
+ "left_line": 304,
+ "right_line": 304
+ },
+ {
+ "left_line": 305,
+ "right_line": 305
+ },
+ {
+ "left_line": 306,
+ "right_line": 306
+ },
+ {
+ "left_line": 307,
+ "right_line": 307
+ },
+ {
+ "left_line": 308,
+ "right_line": 308
+ },
+ {
+ "left_line": 309,
+ "right_line": 309
+ },
+ {
+ "left_line": 310,
+ "right_line": 310
+ },
+ {
+ "left_line": 311,
+ "right_line": 311
+ },
+ {
+ "left_line": 312,
+ "right_line": 312
+ },
+ {
+ "left_line": 313,
+ "right_line": 313
+ },
+ {
+ "left_line": 314,
+ "right_line": 314
+ },
+ {
+ "left_line": 315,
+ "right_line": 315
+ },
+ {
+ "left_line": 316,
+ "right_line": 316
+ },
+ {
+ "left_line": 317,
+ "right_line": 317
+ },
+ {
+ "left_line": 318,
+ "right_line": 318
+ },
+ {
+ "left_line": 319,
+ "right_line": 319
+ },
+ {
+ "left_line": 320,
+ "right_line": 320
+ },
+ {
+ "left_line": 321,
+ "right_line": 321
+ },
+ {
+ "left_line": 322,
+ "right_line": 322
+ },
+ {
+ "left_line": 323,
+ "right_line": 323
+ },
+ {
+ "left_line": 324,
+ "right_line": 324
+ },
+ {
+ "left_line": 325,
+ "right_line": 325
+ },
+ {
+ "left_line": 326,
+ "right_line": 326
+ },
+ {
+ "left_line": 327,
+ "right_line": 327
+ },
+ {
+ "left_line": 328,
+ "right_line": 328
+ },
+ {
+ "left_line": 329,
+ "right_line": 329
+ },
+ {
+ "left_line": 330,
+ "right_line": 330
+ },
+ {
+ "left_line": 331,
+ "right_line": 331
+ },
+ {
+ "left_line": 332,
+ "right_line": 332
+ },
+ {
+ "left_line": 333,
+ "right_line": 333
+ },
+ {
+ "left_line": 334,
+ "right_line": 334
+ },
+ {
+ "left_line": 335,
+ "right_line": 335
+ },
+ {
+ "left_line": 336,
+ "right_line": 336
+ },
+ {
+ "left_line": 337,
+ "right_line": 337
+ },
+ {
+ "left_line": 338,
+ "right_line": 338
+ },
+ {
+ "left_line": 339,
+ "right_line": 339
+ },
+ {
+ "left_line": 340,
+ "right_line": 340
+ },
+ {
+ "left_line": 341,
+ "right_line": 341
+ },
+ {
+ "left_line": 342,
+ "right_line": 342
+ },
+ {
+ "left_line": 343,
+ "right_line": 343
+ },
+ {
+ "left_line": 344,
+ "right_line": 344
+ },
+ {
+ "left_line": 345,
+ "right_line": 345
+ },
+ {
+ "left_line": 346,
+ "right_line": 346
+ },
+ {
+ "left_line": 347,
+ "right_line": 347
+ },
+ {
+ "left_line": 348,
+ "right_line": 348
+ },
+ {
+ "left_line": 349,
+ "right_line": 349
+ },
+ {
+ "left_line": 350,
+ "right_line": 350
+ },
+ {
+ "left_line": 351,
+ "right_line": 351
+ },
+ {
+ "left_line": 352,
+ "right_line": 352
+ },
+ {
+ "left_line": 353,
+ "right_line": 353
+ },
+ {
+ "left_line": 354,
+ "right_line": 354
+ },
+ {
+ "left_line": 355,
+ "right_line": 355
+ },
+ {
+ "left_line": 356,
+ "right_line": 356
+ },
+ {
+ "left_line": 357,
+ "right_line": 357
+ },
+ {
+ "left_line": 358,
+ "right_line": 358
+ },
+ {
+ "left_line": 359,
+ "right_line": 359
+ },
+ {
+ "left_line": 360,
+ "right_line": 360
+ },
+ {
+ "left_line": 361,
+ "right_line": 361
+ },
+ {
+ "left_line": 362,
+ "right_line": 362
+ },
+ {
+ "left_line": 363,
+ "right_line": 363
+ },
+ {
+ "left_line": 364,
+ "right_line": 364
+ },
+ {
+ "left_line": 365,
+ "right_line": 365
+ },
+ {
+ "left_line": 366,
+ "right_line": 366
+ },
+ {
+ "left_line": 367,
+ "right_line": 367
+ },
+ {
+ "left_line": 368,
+ "right_line": 368
+ },
+ {
+ "left_line": 369,
+ "right_line": 369
+ },
+ {
+ "left_line": 370,
+ "right_line": 370
+ },
+ {
+ "left_line": 371,
+ "right_line": 371
+ },
+ {
+ "left_line": 372,
+ "right_line": 372
+ },
+ {
+ "left_line": 373,
+ "right_line": 373
+ },
+ {
+ "left_line": 374,
+ "right_line": 374
+ },
+ {
+ "left_line": 375,
+ "right_line": 375
+ },
+ {
+ "left_line": 376,
+ "right_line": 376
+ },
+ {
+ "left_line": 377,
+ "right_line": 377
+ },
+ {
+ "left_line": 378,
+ "right_line": 378
+ },
+ {
+ "left_line": 379,
+ "right_line": 379
+ },
+ {
+ "left_line": 380,
+ "right_line": 380
+ },
+ {
+ "left_line": 381,
+ "right_line": 381
+ },
+ {
+ "left_line": 382,
+ "right_line": 382
+ },
+ {
+ "left_line": 383,
+ "right_line": 383
+ },
+ {
+ "left_line": 384,
+ "right_line": 384
+ },
+ {
+ "left_line": 385,
+ "right_line": 385
+ },
+ {
+ "left_line": 386,
+ "right_line": 386
+ },
+ {
+ "left_line": 387,
+ "right_line": 387
+ },
+ {
+ "left_line": 388,
+ "right_line": 388
+ },
+ {
+ "left_line": 389,
+ "right_line": 389
+ },
+ {
+ "left_line": 390,
+ "right_line": 390
+ },
+ {
+ "left_line": 391,
+ "right_line": 391
+ },
+ {
+ "left_line": 392,
+ "right_line": 392
+ },
+ {
+ "left_line": 393,
+ "right_line": 393
+ },
+ {
+ "left_line": 394,
+ "right_line": 394
+ },
+ {
+ "left_line": 395,
+ "right_line": 395
+ },
+ {
+ "left_line": 396,
+ "right_line": 396
+ },
+ {
+ "left_line": 397,
+ "right_line": 397
+ },
+ {
+ "left_line": 398,
+ "right_line": 398
+ },
+ {
+ "left_line": 399,
+ "right_line": 399
+ },
+ {
+ "left_line": 400,
+ "right_line": 400
+ },
+ {
+ "left_line": 401,
+ "right_line": 401
+ },
+ {
+ "left_line": 402,
+ "right_line": 402
+ },
+ {
+ "left_line": 403,
+ "right_line": 403
+ },
+ {
+ "left_line": 404,
+ "right_line": 404
+ },
+ {
+ "left_line": 405,
+ "right_line": 405
+ },
+ {
+ "left_line": 406,
+ "right_line": 406
+ },
+ {
+ "left_line": 407,
+ "right_line": 407
+ },
+ {
+ "left_line": 408,
+ "right_line": 408
+ },
+ {
+ "left_line": 409,
+ "right_line": 409
+ },
+ {
+ "left_line": 410,
+ "right_line": 410
+ },
+ {
+ "left_line": 411,
+ "right_line": 411
+ },
+ {
+ "left_line": 412,
+ "right_line": 412
+ },
+ {
+ "left_line": 413,
+ "right_line": 413
+ },
+ {
+ "left_line": 414,
+ "right_line": 414
+ },
+ {
+ "left_line": 415,
+ "right_line": 415
+ },
+ {
+ "left_line": 416,
+ "right_line": 416
+ },
+ {
+ "left_line": 417,
+ "right_line": 417
+ },
+ {
+ "left_line": 418,
+ "right_line": 418
+ },
+ {
+ "left_line": 419,
+ "right_line": 419
+ },
+ {
+ "left_line": 420,
+ "right_line": 420
+ },
+ {
+ "left_line": 421,
+ "right_line": 421
+ },
+ {
+ "left_line": 422,
+ "right_line": 422
+ },
+ {
+ "left_line": 423,
+ "right_line": 423
+ },
+ {
+ "left_line": 424,
+ "right_line": 424
+ },
+ {
+ "left_line": 425,
+ "right_line": 425
+ },
+ {
+ "left_line": 426,
+ "right_line": 426
+ },
+ {
+ "left_line": 427,
+ "right_line": 427
+ },
+ {
+ "left_line": 428,
+ "right_line": 428
+ },
+ {
+ "left_line": 429,
+ "right_line": 429
+ },
+ {
+ "left_line": 430,
+ "right_line": 430
+ },
+ {
+ "left_line": 431,
+ "right_line": 431
+ },
+ {
+ "left_line": 432,
+ "right_line": 432
+ },
+ {
+ "left_line": 433,
+ "right_line": 433
+ },
+ {
+ "left_line": 434,
+ "right_line": 434
+ },
+ {
+ "left_line": 435,
+ "right_line": 435
+ },
+ {
+ "left_line": 436,
+ "right_line": 436
+ },
+ {
+ "left_line": 437,
+ "right_line": 437
+ },
+ {
+ "left_line": 438,
+ "right_line": 438
+ },
+ {
+ "left_line": 439,
+ "right_line": 439
+ },
+ {
+ "left_line": 440,
+ "right_line": 440
+ },
+ {
+ "left_line": 441,
+ "right_line": 441
+ },
+ {
+ "left_line": 442,
+ "right_line": 442
+ },
+ {
+ "left_line": 443,
+ "right_line": 443
+ },
+ {
+ "left_line": 444,
+ "right_line": 444
+ },
+ {
+ "left_line": 445,
+ "right_line": 445
+ },
+ {
+ "left_line": 446,
+ "right_line": 446
+ },
+ {
+ "left_line": 447,
+ "right_line": 447
+ },
+ {
+ "left_line": 448,
+ "right_line": 448
+ },
+ {
+ "left_line": 449,
+ "right_line": 449
+ },
+ {
+ "left_line": 450,
+ "right_line": 450
+ },
+ {
+ "left_line": 451,
+ "right_line": 451
+ },
+ {
+ "left_line": 452,
+ "right_line": 452
+ },
+ {
+ "left_line": 453,
+ "right_line": 453
+ },
+ {
+ "left_line": 454,
+ "right_line": 454
+ },
+ {
+ "left_line": 455,
+ "right_line": 455
+ },
+ {
+ "left_line": 456,
+ "right_line": 456
+ },
+ {
+ "left_line": 457,
+ "right_line": 457
+ },
+ {
+ "left_line": 458,
+ "right_line": 458
+ },
+ {
+ "left_line": 459,
+ "right_line": 459
+ },
+ {
+ "left_line": 460,
+ "right_line": 460
+ },
+ {
+ "left_line": 461,
+ "right_line": 461
+ },
+ {
+ "left_line": 462,
+ "right_line": 462
+ },
+ {
+ "left_line": 463,
+ "right_line": 463
+ },
+ {
+ "left_line": 464,
+ "right_line": 464
+ },
+ {
+ "left_line": 465,
+ "right_line": 465
+ },
+ {
+ "left_line": 466,
+ "right_line": 466
+ },
+ {
+ "left_line": 467,
+ "right_line": 467
+ },
+ {
+ "left_line": 468,
+ "right_line": 468
+ },
+ {
+ "left_line": 469,
+ "right_line": 469
+ },
+ {
+ "left_line": 470,
+ "right_line": 470
+ },
+ {
+ "left_line": 471,
+ "right_line": 471
+ },
+ {
+ "left_line": 472,
+ "right_line": 472
+ },
+ {
+ "left_line": 473,
+ "right_line": 473
+ },
+ {
+ "left_line": 474,
+ "right_line": 474
+ },
+ {
+ "left_line": 475,
+ "right_line": 475
+ },
+ {
+ "left_line": 476,
+ "right_line": 476
+ },
+ {
+ "left_line": 477,
+ "right_line": 477
+ },
+ {
+ "left_line": 478,
+ "right_line": 478
+ },
+ {
+ "left_line": 479,
+ "right_line": 479
+ },
+ {
+ "left_line": 480,
+ "right_line": 480
+ },
+ {
+ "left_line": 481,
+ "right_line": 481
+ },
+ {
+ "left_line": 482,
+ "right_line": 482
+ },
+ {
+ "left_line": 483,
+ "right_line": 483
+ },
+ {
+ "left_line": 484,
+ "right_line": 484
+ },
+ {
+ "left_line": 485,
+ "right_line": 485
+ },
+ {
+ "left_line": 486,
+ "right_line": 486
+ },
+ {
+ "left_line": 487,
+ "right_line": 487
+ },
+ {
+ "left_line": 488,
+ "right_line": 488
+ },
+ {
+ "left_line": 489,
+ "right_line": 489
+ },
+ {
+ "left_line": 490,
+ "right_line": 490
+ },
+ {
+ "left_line": 491,
+ "right_line": 491
+ },
+ {
+ "left_line": 492,
+ "right_line": 492
+ },
+ {
+ "left_line": 493,
+ "right_line": 493
+ },
+ {
+ "left_line": 494,
+ "right_line": 494
+ },
+ {
+ "left_line": 495,
+ "right_line": 495
+ },
+ {
+ "left_line": 496,
+ "right_line": 496
+ },
+ {
+ "left_line": 497,
+ "right_line": 497
+ },
+ {
+ "left_line": 498,
+ "right_line": 498
+ },
+ {
+ "left_line": 499,
+ "right_line": 499
+ },
+ {
+ "left_line": 500,
+ "right_line": 500
+ },
+ {
+ "left_line": 501,
+ "right_line": 501
+ },
+ {
+ "left_line": 502,
+ "right_line": 502
+ },
+ {
+ "left_line": 503,
+ "right_line": 503
+ },
+ {
+ "left_line": 504,
+ "right_line": 504
+ },
+ {
+ "left_line": 505,
+ "right_line": 505
+ },
+ {
+ "left_line": 506,
+ "right_line": 506
+ },
+ {
+ "left_line": 507,
+ "right_line": 507
+ },
+ {
+ "left_line": 508,
+ "right_line": 508
+ },
+ {
+ "left_line": 509,
+ "right_line": 509
+ },
+ {
+ "left_line": 510,
+ "right_line": 510
+ },
+ {
+ "left_line": 511,
+ "right_line": 511
+ },
+ {
+ "left_line": 512,
+ "right_line": 512
+ },
+ {
+ "left_line": 513,
+ "right_line": 513
+ },
+ {
+ "left_line": 514,
+ "right_line": 514
+ },
+ {
+ "left_line": 515,
+ "right_line": 515
+ },
+ {
+ "left_line": 516,
+ "right_line": 516
+ },
+ {
+ "left_line": 517,
+ "right_line": 517
+ },
+ {
+ "left_line": 518,
+ "right_line": 518
+ },
+ {
+ "left_line": 519,
+ "right_line": 519
+ },
+ {
+ "left_line": 520,
+ "right_line": 520
+ },
+ {
+ "left_line": 521,
+ "right_line": 521
+ },
+ {
+ "left_line": 522,
+ "right_line": 522
+ },
+ {
+ "left_line": 523,
+ "right_line": 523
+ },
+ {
+ "left_line": 524,
+ "right_line": 524
+ },
+ {
+ "left_line": 525,
+ "right_line": 525
+ },
+ {
+ "left_line": 526,
+ "right_line": 526
+ },
+ {
+ "left_line": 527,
+ "right_line": 527
+ },
+ {
+ "left_line": 528,
+ "right_line": 528
+ },
+ {
+ "left_line": 529,
+ "right_line": 529
+ },
+ {
+ "left_line": 530,
+ "right_line": 530
+ },
+ {
+ "left_line": 531,
+ "right_line": 531
+ },
+ {
+ "left_line": 532,
+ "right_line": 532
+ },
+ {
+ "left_line": 533,
+ "right_line": 533
+ },
+ {
+ "left_line": 534,
+ "right_line": 534
+ },
+ {
+ "left_line": 535,
+ "right_line": 535
+ },
+ {
+ "left_line": 536,
+ "right_line": 536
+ },
+ {
+ "left_line": 537,
+ "right_line": 537
+ },
+ {
+ "left_line": 538,
+ "right_line": 538
+ },
+ {
+ "left_line": 539,
+ "right_line": 539
+ },
+ {
+ "left_line": 540,
+ "right_line": 540
+ },
+ {
+ "left_line": 541,
+ "right_line": 541
+ },
+ {
+ "left_line": 542,
+ "right_line": 542
+ },
+ {
+ "left_line": 543,
+ "right_line": 543
+ },
+ {
+ "left_line": 544,
+ "right_line": 544
+ },
+ {
+ "left_line": 545,
+ "right_line": 545
+ },
+ {
+ "left_line": 546,
+ "right_line": 546
+ },
+ {
+ "left_line": 547,
+ "right_line": 547
+ },
+ {
+ "left_line": 548,
+ "right_line": 548
+ },
+ {
+ "left_line": 549,
+ "right_line": 549
+ },
+ {
+ "left_line": 550,
+ "right_line": 550
+ },
+ {
+ "left_line": 551,
+ "right_line": 551
+ },
+ {
+ "left_line": 552,
+ "right_line": 552
+ },
+ {
+ "left_line": 553,
+ "right_line": 553
+ },
+ {
+ "left_line": 554,
+ "right_line": 554
+ },
+ {
+ "left_line": 555,
+ "right_line": 555
+ },
+ {
+ "left_line": 556,
+ "right_line": 556
+ },
+ {
+ "left_line": 557,
+ "right_line": 557
+ },
+ {
+ "left_line": 558,
+ "right_line": 558
+ },
+ {
+ "left_line": 559,
+ "right_line": 559
+ },
+ {
+ "left_line": 560,
+ "right_line": 560
+ },
+ {
+ "left_line": 561,
+ "right_line": 561
+ },
+ {
+ "left_line": 562,
+ "right_line": 562
+ },
+ {
+ "left_line": 563,
+ "right_line": 563
+ },
+ {
+ "left_line": 564,
+ "right_line": 564
+ },
+ {
+ "left_line": 565,
+ "right_line": 565
+ },
+ {
+ "left_line": 566,
+ "right_line": 566
+ },
+ {
+ "left_line": 567,
+ "right_line": 567
+ },
+ {
+ "left_line": 568,
+ "right_line": 568
+ },
+ {
+ "left_line": 569,
+ "right_line": 569
+ },
+ {
+ "left_line": 570,
+ "right_line": 570
+ },
+ {
+ "left_line": 571,
+ "right_line": 571
+ },
+ {
+ "left_line": 572,
+ "right_line": 572
+ },
+ {
+ "left_line": 573,
+ "right_line": 573
+ },
+ {
+ "left_line": 574,
+ "right_line": 574
+ },
+ {
+ "left_line": 575,
+ "right_line": 575
+ },
+ {
+ "left_line": 576,
+ "right_line": 576
+ },
+ {
+ "left_line": 577,
+ "right_line": 577
+ },
+ {
+ "left_line": 578,
+ "right_line": 578
+ },
+ {
+ "left_line": 579,
+ "right_line": 579
+ },
+ {
+ "left_line": 580,
+ "right_line": 580
+ },
+ {
+ "left_line": 581,
+ "right_line": 581
+ },
+ {
+ "left_line": 582,
+ "right_line": 582
+ },
+ {
+ "left_line": 583,
+ "right_line": 583
+ },
+ {
+ "left_line": 584,
+ "right_line": 584
+ },
+ {
+ "left_line": 585,
+ "right_line": 585
+ },
+ {
+ "left_line": 586,
+ "right_line": 586
+ },
+ {
+ "left_line": 587,
+ "right_line": 587
+ },
+ {
+ "left_line": 588,
+ "right_line": 588
+ },
+ {
+ "left_line": 589,
+ "right_line": 589
+ },
+ {
+ "left_line": 590,
+ "right_line": 590
+ },
+ {
+ "left_line": 591,
+ "right_line": 591
+ },
+ {
+ "left_line": 592,
+ "right_line": 592
+ },
+ {
+ "left_line": 593,
+ "right_line": 593
+ },
+ {
+ "left_line": 594,
+ "right_line": 594
+ },
+ {
+ "left_line": 595,
+ "right_line": 595
+ },
+ {
+ "left_line": 596,
+ "right_line": 596
+ },
+ {
+ "left_line": 597,
+ "right_line": 597
+ },
+ {
+ "left_line": 598,
+ "right_line": 598
+ },
+ {
+ "left_line": 599,
+ "right_line": 599
+ },
+ {
+ "left_line": 600,
+ "right_line": 600
+ },
+ {
+ "left_line": 601,
+ "right_line": 601
+ },
+ {
+ "left_line": 602,
+ "right_line": 602
+ },
+ {
+ "left_line": 603,
+ "right_line": 603
+ },
+ {
+ "left_line": 604,
+ "right_line": 604
+ },
+ {
+ "left_line": 605,
+ "right_line": 605
+ },
+ {
+ "left_line": 606,
+ "right_line": 606
+ },
+ {
+ "left_line": 607,
+ "right_line": 607
+ },
+ {
+ "left_line": 608,
+ "right_line": 608
+ },
+ {
+ "left_line": 609,
+ "right_line": 609
+ },
+ {
+ "left_line": 610,
+ "right_line": 610
+ },
+ {
+ "left_line": 611,
+ "right_line": 611
+ },
+ {
+ "left_line": 612,
+ "right_line": 612
+ },
+ {
+ "left_line": 613,
+ "right_line": 613
+ },
+ {
+ "left_line": 614,
+ "right_line": 614
+ },
+ {
+ "left_line": 615,
+ "right_line": 615
+ },
+ {
+ "left_line": 616,
+ "right_line": 616
+ },
+ {
+ "left_line": 617,
+ "right_line": 617
+ },
+ {
+ "left_line": 618,
+ "right_line": 618
+ },
+ {
+ "left_line": 619,
+ "right_line": 619
+ },
+ {
+ "left_line": 620,
+ "right_line": 620
+ },
+ {
+ "left_line": 621,
+ "right_line": 621
+ },
+ {
+ "left_line": 622,
+ "right_line": 622
+ },
+ {
+ "left_line": 623,
+ "right_line": 623
+ },
+ {
+ "left_line": 624,
+ "right_line": 624
+ },
+ {
+ "left_line": 625,
+ "right_line": 625
+ },
+ {
+ "left_line": 626,
+ "right_line": 626
+ },
+ {
+ "left_line": 627,
+ "right_line": 627
+ },
+ {
+ "left_line": 628,
+ "right_line": 628
+ },
+ {
+ "left_line": 629,
+ "right_line": 629
+ },
+ {
+ "left_line": 630,
+ "right_line": 630
+ },
+ {
+ "left_line": 631,
+ "right_line": 631
+ },
+ {
+ "left_line": 632,
+ "right_line": 632
+ },
+ {
+ "left_line": 633,
+ "right_line": 633
+ },
+ {
+ "left_line": 634,
+ "right_line": 634
+ },
+ {
+ "left_line": 635,
+ "right_line": 635
+ },
+ {
+ "left_line": 636,
+ "right_line": 636
+ },
+ {
+ "left_line": 637,
+ "right_line": 637
+ },
+ {
+ "left_line": 638,
+ "right_line": 638
+ },
+ {
+ "left_line": 639,
+ "right_line": 639
+ },
+ {
+ "left_line": 640,
+ "right_line": 640
+ },
+ {
+ "left_line": 641,
+ "right_line": 641
+ },
+ {
+ "left_line": 642,
+ "right_line": 642
+ },
+ {
+ "left_line": 643,
+ "right_line": 643
+ },
+ {
+ "left_line": 644,
+ "right_line": 644
+ },
+ {
+ "left_line": 645,
+ "right_line": 645
+ },
+ {
+ "left_line": 646,
+ "right_line": 646
+ },
+ {
+ "left_line": 647,
+ "right_line": 647
+ },
+ {
+ "left_line": 648,
+ "right_line": 648
+ },
+ {
+ "left_line": 649,
+ "right_line": 649
+ },
+ {
+ "left_line": 650,
+ "right_line": 650
+ },
+ {
+ "left_line": 651,
+ "right_line": 651
+ },
+ {
+ "left_line": 652,
+ "right_line": 652
+ },
+ {
+ "left_line": 653,
+ "right_line": 653
+ },
+ {
+ "left_line": 654,
+ "right_line": 654
+ },
+ {
+ "left_line": 655,
+ "right_line": 655
+ },
+ {
+ "left_line": 656,
+ "right_line": 656
+ },
+ {
+ "left_line": 657,
+ "right_line": 657
+ },
+ {
+ "left_line": 658,
+ "right_line": 658
+ },
+ {
+ "left_line": 659,
+ "right_line": 659
+ },
+ {
+ "left_line": 660,
+ "right_line": 660
+ },
+ {
+ "left_line": 661,
+ "right_line": 661
+ },
+ {
+ "left_line": 662,
+ "right_line": 662
+ },
+ {
+ "left_line": 663,
+ "right_line": 663
+ },
+ {
+ "left_line": 664,
+ "right_line": 664
+ },
+ {
+ "left_line": 665,
+ "right_line": 665
+ },
+ {
+ "left_line": 666,
+ "right_line": 666
+ },
+ {
+ "left_line": 667,
+ "right_line": 667
+ },
+ {
+ "left_line": 668,
+ "right_line": 668
+ },
+ {
+ "left_line": 669,
+ "right_line": 669
+ },
+ {
+ "left_line": 670,
+ "right_line": 670
+ },
+ {
+ "left_line": 671,
+ "right_line": 671
+ },
+ {
+ "left_line": 672,
+ "right_line": 672
+ },
+ {
+ "left_line": 673,
+ "right_line": 673
+ },
+ {
+ "left_line": 674,
+ "right_line": 674
+ },
+ {
+ "left_line": 675,
+ "right_line": 675
+ },
+ {
+ "left_line": 676,
+ "right_line": 676
+ },
+ {
+ "left_line": 677,
+ "right_line": 677
+ },
+ {
+ "left_line": 678,
+ "right_line": 678
+ },
+ {
+ "left_line": 679,
+ "right_line": 679
+ },
+ {
+ "left_line": 680,
+ "right_line": 680
+ },
+ {
+ "left_line": 681,
+ "right_line": 681
+ },
+ {
+ "left_line": 682,
+ "right_line": 682
+ },
+ {
+ "left_line": 683,
+ "right_line": 683
+ },
+ {
+ "left_line": 684,
+ "right_line": 684
+ },
+ {
+ "left_line": 685,
+ "right_line": 685
+ },
+ {
+ "left_line": 686,
+ "right_line": 686
+ },
+ {
+ "left_line": 687,
+ "right_line": 687
+ },
+ {
+ "left_line": 688,
+ "right_line": 688
+ },
+ {
+ "left_line": 689,
+ "right_line": 689
+ },
+ {
+ "left_line": 690,
+ "right_line": 690
+ },
+ {
+ "left_line": 691,
+ "right_line": 691
+ },
+ {
+ "left_line": 692,
+ "right_line": 692
+ },
+ {
+ "left_line": 693,
+ "right_line": 693
+ },
+ {
+ "left_line": 694,
+ "right_line": 694
+ },
+ {
+ "left_line": 695,
+ "right_line": 695
+ },
+ {
+ "left_line": 696,
+ "right_line": 696
+ },
+ {
+ "left_line": 697,
+ "right_line": 697
+ },
+ {
+ "left_line": 698,
+ "right_line": 698
+ },
+ {
+ "left_line": 699,
+ "right_line": 699
+ },
+ {
+ "left_line": 700,
+ "right_line": 700
+ },
+ {
+ "left_line": 701,
+ "right_line": 701
+ },
+ {
+ "left_line": 702,
+ "right_line": 702
+ },
+ {
+ "left_line": 703,
+ "right_line": 703
+ },
+ {
+ "left_line": 704,
+ "right_line": 704
+ },
+ {
+ "left_line": 705,
+ "right_line": 705
+ },
+ {
+ "left_line": 706,
+ "right_line": 706
+ },
+ {
+ "left_line": 707,
+ "right_line": 707
+ },
+ {
+ "left_line": 708,
+ "right_line": 708
+ },
+ {
+ "left_line": 709,
+ "right_line": 709
+ },
+ {
+ "left_line": 710,
+ "right_line": 710
+ },
+ {
+ "left_line": 711,
+ "right_line": 711
+ },
+ {
+ "left_line": 712,
+ "right_line": 712
+ },
+ {
+ "left_line": 713,
+ "right_line": 713
+ },
+ {
+ "left_line": 714,
+ "right_line": 714
+ },
+ {
+ "left_line": 715,
+ "right_line": 715
+ },
+ {
+ "left_line": 716,
+ "right_line": 716
+ },
+ {
+ "left_line": 717,
+ "right_line": 717
+ },
+ {
+ "left_line": 718,
+ "right_line": 718
+ },
+ {
+ "left_line": 719,
+ "right_line": 719
+ },
+ {
+ "left_line": 720,
+ "right_line": 720
+ },
+ {
+ "left_line": 721,
+ "right_line": 721
+ },
+ {
+ "left_line": 722,
+ "right_line": 722
+ },
+ {
+ "left_line": 723,
+ "right_line": 723
+ },
+ {
+ "left_line": 724,
+ "right_line": 724
+ },
+ {
+ "left_line": 725,
+ "right_line": 725
+ },
+ {
+ "left_line": 726,
+ "right_line": 726
+ },
+ {
+ "left_line": 727,
+ "right_line": 727
+ },
+ {
+ "left_line": 728,
+ "right_line": 728
+ },
+ {
+ "left_line": 729,
+ "right_line": 729
+ },
+ {
+ "left_line": 730,
+ "right_line": 730
+ },
+ {
+ "left_line": 731,
+ "right_line": 731
+ },
+ {
+ "left_line": 732,
+ "right_line": 732
+ },
+ {
+ "left_line": 733,
+ "right_line": 733
+ },
+ {
+ "left_line": 734,
+ "right_line": 734
+ },
+ {
+ "left_line": 735,
+ "right_line": 735
+ },
+ {
+ "left_line": 736,
+ "right_line": 736
+ },
+ {
+ "left_line": 737,
+ "right_line": 737
+ },
+ {
+ "left_line": 738,
+ "right_line": 738
+ },
+ {
+ "left_line": 739,
+ "right_line": 739
+ },
+ {
+ "left_line": 740,
+ "right_line": 740
+ },
+ {
+ "left_line": 741,
+ "right_line": 741
+ },
+ {
+ "left_line": 742,
+ "right_line": 742
+ },
+ {
+ "left_line": 743,
+ "right_line": 743
+ },
+ {
+ "left_line": 744,
+ "right_line": 744
+ },
+ {
+ "left_line": 745,
+ "right_line": 745
+ },
+ {
+ "left_line": 746,
+ "right_line": 746
+ },
+ {
+ "left_line": 747,
+ "right_line": 747
+ },
+ {
+ "left_line": 748,
+ "right_line": 748
+ },
+ {
+ "left_line": 749,
+ "right_line": 749
+ },
+ {
+ "left_line": 750,
+ "right_line": 750
+ },
+ {
+ "left_line": 751,
+ "right_line": 751
+ },
+ {
+ "left_line": 752,
+ "right_line": 752
+ },
+ {
+ "left_line": 753,
+ "right_line": 753
+ },
+ {
+ "left_line": 754,
+ "right_line": 754
+ },
+ {
+ "left_line": 755,
+ "right_line": 755
+ },
+ {
+ "left_line": 756,
+ "right_line": 756
+ },
+ {
+ "left_line": 757,
+ "right_line": 757
+ },
+ {
+ "left_line": 758,
+ "right_line": 758
+ },
+ {
+ "left_line": 759,
+ "right_line": 759
+ },
+ {
+ "left_line": 760,
+ "right_line": 760
+ },
+ {
+ "left_line": 761,
+ "right_line": 761
+ },
+ {
+ "left_line": 762,
+ "right_line": 762
+ },
+ {
+ "left_line": 763,
+ "right_line": 763
+ },
+ {
+ "left_line": 764,
+ "right_line": 764
+ },
+ {
+ "left_line": 765,
+ "right_line": 765
+ },
+ {
+ "left_line": 766,
+ "right_line": 766
+ },
+ {
+ "left_line": 767,
+ "right_line": 767
+ },
+ {
+ "left_line": 768,
+ "right_line": 768
+ },
+ {
+ "left_line": 769,
+ "right_line": 769
+ },
+ {
+ "left_line": 770,
+ "right_line": 770
+ },
+ {
+ "left_line": 771,
+ "right_line": 771
+ },
+ {
+ "left_line": 772,
+ "right_line": 772
+ },
+ {
+ "left_line": 773,
+ "right_line": 773
+ },
+ {
+ "left_line": 774,
+ "right_line": 774
+ },
+ {
+ "left_line": 775,
+ "right_line": 775
+ },
+ {
+ "left_line": 776,
+ "right_line": 776
+ },
+ {
+ "left_line": 777,
+ "right_line": 777
+ },
+ {
+ "left_line": 778,
+ "right_line": 778
+ },
+ {
+ "left_line": 779,
+ "right_line": 779
+ },
+ {
+ "left_line": 780,
+ "right_line": 780
+ },
+ {
+ "left_line": 781,
+ "right_line": 781
+ },
+ {
+ "left_line": 782,
+ "right_line": 782
+ },
+ {
+ "left_line": 783,
+ "right_line": 783
+ },
+ {
+ "left_line": 784,
+ "right_line": 784
+ },
+ {
+ "left_line": 785,
+ "right_line": 785
+ },
+ {
+ "left_line": 786,
+ "right_line": 786
+ },
+ {
+ "left_line": 787,
+ "right_line": 787
+ },
+ {
+ "left_line": 788,
+ "right_line": 788
+ },
+ {
+ "left_line": 789,
+ "right_line": 789
+ },
+ {
+ "left_line": 790,
+ "right_line": 790
+ },
+ {
+ "left_line": 791,
+ "right_line": 791
+ },
+ {
+ "left_line": 792,
+ "right_line": 792
+ },
+ {
+ "left_line": 793,
+ "right_line": 793
+ },
+ {
+ "left_line": 794,
+ "right_line": 794
+ },
+ {
+ "left_line": 795,
+ "right_line": 795
+ },
+ {
+ "left_line": 796,
+ "right_line": 796
+ },
+ {
+ "left_line": 797,
+ "right_line": 797
+ },
+ {
+ "left_line": 798,
+ "right_line": 798
+ },
+ {
+ "left_line": 799,
+ "right_line": 799
+ },
+ {
+ "left_line": 800,
+ "right_line": 800
+ },
+ {
+ "left_line": 801,
+ "right_line": 801
+ },
+ {
+ "left_line": 802,
+ "right_line": 802
+ },
+ {
+ "left_line": 803,
+ "right_line": 803
+ },
+ {
+ "left_line": 804,
+ "right_line": 804
+ },
+ {
+ "left_line": 805,
+ "right_line": 805
+ },
+ {
+ "left_line": 806,
+ "right_line": 806
+ },
+ {
+ "left_line": 807,
+ "right_line": 807
+ },
+ {
+ "left_line": 808,
+ "right_line": 808
+ },
+ {
+ "left_line": 809,
+ "right_line": 809
+ },
+ {
+ "left_line": 810,
+ "right_line": 810
+ },
+ {
+ "left_line": 811,
+ "right_line": 811
+ },
+ {
+ "left_line": 812,
+ "right_line": 812
+ },
+ {
+ "left_line": 813,
+ "right_line": 813
+ },
+ {
+ "left_line": 814,
+ "right_line": 814
+ },
+ {
+ "left_line": 815,
+ "right_line": 815
+ },
+ {
+ "left_line": 816,
+ "right_line": 816
+ },
+ {
+ "left_line": 817,
+ "right_line": 817
+ },
+ {
+ "left_line": 818,
+ "right_line": 818
+ },
+ {
+ "left_line": 819,
+ "right_line": 819
+ },
+ {
+ "left_line": 820,
+ "right_line": 820
+ },
+ {
+ "left_line": 821,
+ "right_line": 821
+ },
+ {
+ "left_line": 822,
+ "right_line": 822
+ },
+ {
+ "left_line": 823,
+ "right_line": 823
+ },
+ {
+ "left_line": 824,
+ "right_line": 824
+ },
+ {
+ "left_line": 825,
+ "right_line": 825
+ },
+ {
+ "left_line": 826,
+ "right_line": 826
+ },
+ {
+ "left_line": 827,
+ "right_line": 827
+ },
+ {
+ "left_line": 828,
+ "right_line": 828
+ },
+ {
+ "left_line": 829,
+ "right_line": 829
+ },
+ {
+ "left_line": 830,
+ "right_line": 830
+ },
+ {
+ "left_line": 831,
+ "right_line": 831
+ },
+ {
+ "left_line": 832,
+ "right_line": 832
+ },
+ {
+ "left_line": 833,
+ "right_line": 833
+ },
+ {
+ "left_line": 834,
+ "right_line": 834
+ },
+ {
+ "left_line": 835,
+ "right_line": 835
+ },
+ {
+ "left_line": 836,
+ "right_line": 836
+ },
+ {
+ "left_line": 837,
+ "right_line": 837
+ },
+ {
+ "left_line": 838,
+ "right_line": 838
+ },
+ {
+ "left_line": 839,
+ "right_line": 839
+ },
+ {
+ "left_line": 840,
+ "right_line": 840
+ },
+ {
+ "left_line": 841,
+ "right_line": 841
+ },
+ {
+ "left_line": 842,
+ "right_line": 842
+ },
+ {
+ "left_line": 843,
+ "right_line": 843
+ },
+ {
+ "left_line": 844,
+ "right_line": 844
+ },
+ {
+ "left_line": 845,
+ "right_line": 845
+ },
+ {
+ "left_line": 846,
+ "right_line": 846
+ },
+ {
+ "left_line": 847,
+ "right_line": 847
+ },
+ {
+ "left_line": 848,
+ "right_line": 848
+ },
+ {
+ "left_line": 849,
+ "right_line": 849
+ },
+ {
+ "left_line": 850,
+ "right_line": 850
+ },
+ {
+ "left_line": 851,
+ "right_line": 851
+ },
+ {
+ "left_line": 852,
+ "right_line": 852
+ },
+ {
+ "left_line": 853,
+ "right_line": 853
+ },
+ {
+ "left_line": 854,
+ "right_line": 854
+ },
+ {
+ "left_line": 855,
+ "right_line": 855
+ },
+ {
+ "left_line": 856,
+ "right_line": 856
+ },
+ {
+ "left_line": 857,
+ "right_line": 857
+ },
+ {
+ "left_line": 858,
+ "right_line": 858
+ },
+ {
+ "left_line": 859,
+ "right_line": 859
+ },
+ {
+ "left_line": 860,
+ "right_line": 860
+ },
+ {
+ "left_line": 861,
+ "right_line": 861
+ },
+ {
+ "left_line": 862,
+ "right_line": 862
+ },
+ {
+ "left_line": 863,
+ "right_line": 863
+ },
+ {
+ "left_line": 864,
+ "right_line": 864
+ },
+ {
+ "left_line": 865,
+ "right_line": 865
+ },
+ {
+ "left_line": 866,
+ "right_line": 866
+ },
+ {
+ "left_line": 867,
+ "right_line": 867
+ },
+ {
+ "left_line": 868,
+ "right_line": 868
+ },
+ {
+ "left_line": 869,
+ "right_line": 869
+ },
+ {
+ "left_line": 870,
+ "right_line": 870
+ },
+ {
+ "left_line": 871,
+ "right_line": 871
+ },
+ {
+ "left_line": 872,
+ "right_line": 872
+ },
+ {
+ "left_line": 873,
+ "right_line": 873
+ },
+ {
+ "left_line": 874,
+ "right_line": 874
+ },
+ {
+ "left_line": 875,
+ "right_line": 875
+ },
+ {
+ "left_line": 876,
+ "right_line": 876
+ },
+ {
+ "left_line": 877,
+ "right_line": 877
+ },
+ {
+ "left_line": 878,
+ "right_line": 878
+ },
+ {
+ "left_line": 879,
+ "right_line": 879
+ },
+ {
+ "left_line": 880,
+ "right_line": 880
+ },
+ {
+ "left_line": 881,
+ "right_line": 881
+ },
+ {
+ "left_line": 882,
+ "right_line": 882
+ },
+ {
+ "left_line": 883,
+ "right_line": 883
+ },
+ {
+ "left_line": 884,
+ "right_line": 884
+ },
+ {
+ "left_line": 885,
+ "right_line": 885
+ },
+ {
+ "left_line": 886,
+ "right_line": 886
+ },
+ {
+ "left_line": 887,
+ "right_line": 887
+ },
+ {
+ "left_line": 888,
+ "right_line": 888
+ },
+ {
+ "left_line": 889,
+ "right_line": 889
+ },
+ {
+ "left_line": 890,
+ "right_line": 890
+ },
+ {
+ "left_line": 891,
+ "right_line": 891
+ },
+ {
+ "left_line": 892,
+ "right_line": 892
+ },
+ {
+ "left_line": 893,
+ "right_line": 893
+ },
+ {
+ "left_line": 894,
+ "right_line": 894
+ },
+ {
+ "left_line": 895,
+ "right_line": 895
+ },
+ {
+ "left_line": 896,
+ "right_line": 896
+ },
+ {
+ "left_line": 897,
+ "right_line": 897
+ },
+ {
+ "left_line": 898,
+ "right_line": 898
+ },
+ {
+ "left_line": 899,
+ "right_line": 899
+ },
+ {
+ "left_line": 900,
+ "right_line": 900
+ },
+ {
+ "left_line": 901,
+ "right_line": 901
+ },
+ {
+ "left_line": 902,
+ "right_line": 902
+ },
+ {
+ "left_line": 903,
+ "right_line": 903
+ },
+ {
+ "left_line": 904,
+ "right_line": 904
+ },
+ {
+ "left_line": 905,
+ "right_line": 905
+ },
+ {
+ "left_line": 906,
+ "right_line": 906
+ },
+ {
+ "left_line": 907,
+ "right_line": 907
+ },
+ {
+ "left_line": 908,
+ "right_line": 908
+ },
+ {
+ "left_line": 909,
+ "right_line": 909
+ },
+ {
+ "left_line": 910,
+ "right_line": 910
+ },
+ {
+ "left_line": 911,
+ "right_line": 911
+ },
+ {
+ "left_line": 912,
+ "right_line": 912
+ },
+ {
+ "left_line": 913,
+ "right_line": 913
+ },
+ {
+ "left_line": 914,
+ "right_line": 914
+ },
+ {
+ "left_line": 915,
+ "right_line": 915
+ },
+ {
+ "left_line": 916,
+ "right_line": 916
+ },
+ {
+ "left_line": 917,
+ "right_line": 917
+ },
+ {
+ "left_line": 918,
+ "right_line": 918
+ },
+ {
+ "left_line": 919,
+ "right_line": 919
+ },
+ {
+ "left_line": 920,
+ "right_line": 920
+ },
+ {
+ "left_line": 921,
+ "right_line": 921
+ },
+ {
+ "left_line": 922,
+ "right_line": 922
+ },
+ {
+ "left_line": 923,
+ "right_line": 923
+ },
+ {
+ "left_line": 924,
+ "right_line": 924
+ },
+ {
+ "left_line": 925,
+ "right_line": 925
+ },
+ {
+ "left_line": 926,
+ "right_line": 926
+ },
+ {
+ "left_line": 927,
+ "right_line": 927
+ },
+ {
+ "left_line": 928,
+ "right_line": 928
+ },
+ {
+ "left_line": 929,
+ "right_line": 929
+ },
+ {
+ "left_line": 930,
+ "right_line": 930
+ },
+ {
+ "left_line": 931,
+ "right_line": 931
+ },
+ {
+ "left_line": 932,
+ "right_line": 932
+ },
+ {
+ "left_line": 933,
+ "right_line": 933
+ },
+ {
+ "left_line": 934,
+ "right_line": 934
+ },
+ {
+ "left_line": 935,
+ "right_line": 935
+ },
+ {
+ "left_line": 936,
+ "right_line": 936
+ },
+ {
+ "left_line": 937,
+ "right_line": 937
+ },
+ {
+ "left_line": 938,
+ "right_line": 938
+ },
+ {
+ "left_line": 939,
+ "right_line": 939
+ },
+ {
+ "left_line": 940,
+ "right_line": 940
+ },
+ {
+ "left_line": 941,
+ "right_line": 941
+ },
+ {
+ "left_line": 942,
+ "right_line": 942
+ },
+ {
+ "left_line": 943,
+ "right_line": 943
+ },
+ {
+ "left_line": 944,
+ "right_line": 944
+ },
+ {
+ "left_line": 945,
+ "right_line": 945
+ },
+ {
+ "left_line": 946,
+ "right_line": 946
+ },
+ {
+ "left_line": 947,
+ "right_line": 947
+ },
+ {
+ "left_line": 948,
+ "right_line": 948
+ },
+ {
+ "left_line": 949,
+ "right_line": 949
+ },
+ {
+ "left_line": 950,
+ "right_line": 950
+ },
+ {
+ "left_line": 951,
+ "right_line": 951
+ },
+ {
+ "left_line": 952,
+ "right_line": 952
+ },
+ {
+ "left_line": 953,
+ "right_line": 953
+ },
+ {
+ "left_line": 954,
+ "right_line": 954
+ },
+ {
+ "left_line": 955,
+ "right_line": 955
+ },
+ {
+ "left_line": 956,
+ "right_line": 956
+ },
+ {
+ "left_line": 957,
+ "right_line": 957
+ },
+ {
+ "left_line": 958,
+ "right_line": 958
+ },
+ {
+ "left_line": 959,
+ "right_line": 959
+ },
+ {
+ "left_line": 960,
+ "right_line": 960
+ },
+ {
+ "left_line": 961,
+ "right_line": 961
+ },
+ {
+ "left_line": 962,
+ "right_line": 962
+ },
+ {
+ "left_line": 963,
+ "right_line": 963
+ },
+ {
+ "left_line": 964,
+ "right_line": 964
+ },
+ {
+ "left_line": 965,
+ "right_line": 965
+ },
+ {
+ "left_line": 966,
+ "right_line": 966
+ },
+ {
+ "left_line": 967,
+ "right_line": 967
+ },
+ {
+ "left_line": 968,
+ "right_line": 968
+ },
+ {
+ "left_line": 969,
+ "right_line": 969
+ },
+ {
+ "left_line": 970,
+ "right_line": 970
+ },
+ {
+ "left_line": 971,
+ "right_line": 971
+ },
+ {
+ "left_line": 972,
+ "right_line": 972
+ },
+ {
+ "left_line": 973,
+ "right_line": 973
+ },
+ {
+ "left_line": 974,
+ "right_line": 974
+ },
+ {
+ "left_line": 975,
+ "right_line": 975
+ },
+ {
+ "left_line": 976,
+ "right_line": 976
+ },
+ {
+ "left_line": 977,
+ "right_line": 977
+ },
+ {
+ "left_line": 978,
+ "right_line": 978
+ },
+ {
+ "left_line": 979,
+ "right_line": 979
+ },
+ {
+ "left_line": 980,
+ "right_line": 980
+ },
+ {
+ "left_line": 981,
+ "right_line": 981
+ },
+ {
+ "left_line": 982,
+ "right_line": 982
+ },
+ {
+ "left_line": 983,
+ "right_line": 983
+ },
+ {
+ "left_line": 984,
+ "right_line": 984
+ },
+ {
+ "left_line": 985,
+ "right_line": 985
+ },
+ {
+ "left_line": 986,
+ "right_line": 986
+ },
+ {
+ "left_line": 987,
+ "right_line": 987
+ },
+ {
+ "left_line": 988,
+ "right_line": 988
+ },
+ {
+ "left_line": 989,
+ "right_line": 989
+ },
+ {
+ "left_line": 990,
+ "right_line": 990
+ },
+ {
+ "left_line": 991,
+ "right_line": 991
+ },
+ {
+ "left_line": 992,
+ "right_line": 992
+ },
+ {
+ "left_line": 993,
+ "right_line": 993
+ },
+ {
+ "left_line": 994,
+ "right_line": 994
+ },
+ {
+ "left_line": 995,
+ "right_line": 995
+ },
+ {
+ "left_line": 996,
+ "right_line": 996
+ },
+ {
+ "left_line": 997,
+ "right_line": 997
+ },
+ {
+ "left_line": 998,
+ "right_line": 998
+ },
+ {
+ "left_line": 999,
+ "right_line": 999
+ },
+ {
+ "left_line": 1000,
+ "right_line": 1000
+ },
+ {
+ "left_line": 1001,
+ "right_line": 1001
+ },
+ {
+ "left_line": 1002,
+ "right_line": 1002
+ },
+ {
+ "left_line": 1003,
+ "right_line": 1003
+ },
+ {
+ "left_line": 1004,
+ "right_line": 1004
+ },
+ {
+ "left_line": 1005,
+ "right_line": 1005
+ },
+ {
+ "left_line": 1006,
+ "right_line": 1006
+ },
+ {
+ "left_line": 1007,
+ "right_line": 1007
+ },
+ {
+ "left_line": 1008,
+ "right_line": 1008
+ },
+ {
+ "left_line": 1009,
+ "right_line": 1009
+ },
+ {
+ "left_line": 1010,
+ "right_line": 1010
+ },
+ {
+ "left_line": 1011,
+ "right_line": 1011
+ },
+ {
+ "left_line": 1012,
+ "right_line": 1012
+ },
+ {
+ "left_line": 1013,
+ "right_line": 1013
+ },
+ {
+ "left_line": 1014,
+ "right_line": 1014
+ },
+ {
+ "left_line": 1015,
+ "right_line": 1015
+ },
+ {
+ "left_line": 1016,
+ "right_line": 1016
+ },
+ {
+ "left_line": 1017,
+ "right_line": 1017
+ },
+ {
+ "left_line": 1018,
+ "right_line": 1018
+ },
+ {
+ "left_line": 1019,
+ "right_line": 1019
+ },
+ {
+ "left_line": 1020,
+ "right_line": 1020
+ },
+ {
+ "left_line": 1021,
+ "right_line": 1021
+ },
+ {
+ "left_line": 1022,
+ "right_line": 1022
+ },
+ {
+ "left_line": 1023,
+ "right_line": 1023
+ },
+ {
+ "left_line": 1024,
+ "right_line": 1024
+ },
+ {
+ "left_line": 1025,
+ "right_line": 1025
+ },
+ {
+ "left_line": 1026,
+ "right_line": 1026
+ },
+ {
+ "left_line": 1027,
+ "right_line": 1027
+ },
+ {
+ "left_line": 1028,
+ "right_line": 1028
+ },
+ {
+ "left_line": 1029,
+ "right_line": 1029
+ },
+ {
+ "left_line": 1030,
+ "right_line": 1030
+ },
+ {
+ "left_line": 1031,
+ "right_line": 1031
+ },
+ {
+ "left_line": 1032,
+ "right_line": 1032
+ },
+ {
+ "left_line": 1033,
+ "right_line": 1033
+ },
+ {
+ "left_line": 1034,
+ "right_line": 1034
+ },
+ {
+ "left_line": 1035,
+ "right_line": 1035
+ },
+ {
+ "left_line": 1036,
+ "right_line": 1036
+ },
+ {
+ "left_line": 1037,
+ "right_line": 1037
+ },
+ {
+ "left_line": 1038,
+ "right_line": 1038
+ },
+ {
+ "left_line": 1039,
+ "right_line": 1039
+ },
+ {
+ "left_line": 1040,
+ "right_line": 1040
+ },
+ {
+ "left_line": 1041,
+ "right_line": 1041
+ },
+ {
+ "left_line": 1042,
+ "right_line": 1042
+ },
+ {
+ "left_line": 1043,
+ "right_line": 1043
+ },
+ {
+ "left_line": 1044,
+ "right_line": 1044
+ },
+ {
+ "left_line": 1045,
+ "right_line": 1045
+ },
+ {
+ "left_line": 1046,
+ "right_line": 1046
+ },
+ {
+ "left_line": 1047,
+ "right_line": 1047
+ },
+ {
+ "left_line": 1048,
+ "right_line": 1048
+ },
+ {
+ "left_line": 1049,
+ "right_line": 1049
+ },
+ {
+ "left_line": 1050,
+ "right_line": 1050
+ },
+ {
+ "left_line": 1051,
+ "right_line": 1051
+ },
+ {
+ "left_line": 1052,
+ "right_line": 1052
+ },
+ {
+ "left_line": 1053,
+ "right_line": 1053
+ },
+ {
+ "left_line": 1054,
+ "right_line": 1054
+ },
+ {
+ "left_line": 1055,
+ "right_line": 1055
+ },
+ {
+ "left_line": 1056,
+ "right_line": 1056
+ },
+ {
+ "left_line": 1057,
+ "right_line": 1057
+ },
+ {
+ "left_line": 1058,
+ "right_line": 1058
+ },
+ {
+ "left_line": 1059,
+ "right_line": 1059
+ },
+ {
+ "left_line": 1060,
+ "right_line": 1060
+ },
+ {
+ "left_line": 1061,
+ "right_line": 1061
+ },
+ {
+ "left_line": 1062,
+ "right_line": 1062
+ },
+ {
+ "left_line": 1063,
+ "right_line": 1063
+ },
+ {
+ "left_line": 1064,
+ "right_line": 1064
+ },
+ {
+ "left_line": 1065,
+ "right_line": 1065
+ },
+ {
+ "left_line": 1066,
+ "right_line": 1066
+ },
+ {
+ "left_line": 1067,
+ "right_line": 1067
+ },
+ {
+ "left_line": 1068,
+ "right_line": 1068
+ },
+ {
+ "left_line": 1069,
+ "right_line": 1069
+ },
+ {
+ "left_line": 1070,
+ "right_line": 1070
+ },
+ {
+ "left_line": 1071,
+ "right_line": 1071
+ },
+ {
+ "left_line": 1072,
+ "right_line": 1072
+ },
+ {
+ "left_line": 1073,
+ "right_line": 1073
+ },
+ {
+ "left_line": 1074,
+ "right_line": 1074
+ },
+ {
+ "left_line": 1075,
+ "right_line": 1075
+ },
+ {
+ "left_line": 1076,
+ "right_line": 1076
+ },
+ {
+ "left_line": 1077,
+ "right_line": 1077
+ },
+ {
+ "left_line": 1078,
+ "right_line": 1078
+ },
+ {
+ "left_line": 1079,
+ "right_line": 1079
+ },
+ {
+ "left_line": 1080,
+ "right_line": 1080
+ },
+ {
+ "left_line": 1081,
+ "right_line": 1081
+ },
+ {
+ "left_line": 1082,
+ "right_line": 1082
+ },
+ {
+ "left_line": 1083,
+ "right_line": 1083
+ },
+ {
+ "left_line": 1084,
+ "right_line": 1084
+ },
+ {
+ "left_line": 1085,
+ "right_line": 1085
+ },
+ {
+ "left_line": 1086,
+ "right_line": 1086
+ },
+ {
+ "left_line": 1087,
+ "right_line": 1087
+ },
+ {
+ "left_line": 1088,
+ "right_line": 1088
+ },
+ {
+ "left_line": 1089,
+ "right_line": 1089
+ },
+ {
+ "left_line": 1090,
+ "right_line": 1090
+ },
+ {
+ "left_line": 1091,
+ "right_line": 1091
+ },
+ {
+ "left_line": 1092,
+ "right_line": 1092
+ },
+ {
+ "left_line": 1093,
+ "right_line": 1093
+ },
+ {
+ "left_line": 1094,
+ "right_line": 1094
+ },
+ {
+ "left_line": 1095,
+ "right_line": 1095
+ },
+ {
+ "left_line": 1096,
+ "right_line": 1096
+ },
+ {
+ "left_line": 1097,
+ "right_line": 1097
+ },
+ {
+ "left_line": 1098,
+ "right_line": 1098
+ },
+ {
+ "left_line": 1099,
+ "right_line": 1099
+ },
+ {
+ "left_line": 1100,
+ "right_line": 1100
+ },
+ {
+ "left_line": 1101,
+ "right_line": 1101
+ },
+ {
+ "left_line": 1102,
+ "right_line": 1102
+ },
+ {
+ "left_line": 1103,
+ "right_line": 1103
+ },
+ {
+ "left_line": 1104,
+ "right_line": 1104
+ },
+ {
+ "left_line": 1105,
+ "right_line": 1105
+ },
+ {
+ "left_line": 1106,
+ "right_line": 1106
+ },
+ {
+ "left_line": 1107,
+ "right_line": 1107
+ },
+ {
+ "left_line": 1108,
+ "right_line": 1108
+ },
+ {
+ "left_line": 1109,
+ "right_line": 1109
+ },
+ {
+ "left_line": 1110,
+ "right_line": 1110
+ },
+ {
+ "left_line": 1111,
+ "right_line": 1111
+ },
+ {
+ "left_line": 1112,
+ "right_line": 1112
+ },
+ {
+ "left_line": 1113,
+ "right_line": 1113
+ },
+ {
+ "left_line": 1114,
+ "right_line": 1114
+ },
+ {
+ "left_line": 1115,
+ "right_line": 1115
+ },
+ {
+ "left_line": 1116,
+ "right_line": 1116
+ },
+ {
+ "left_line": 1117,
+ "right_line": 1117
+ },
+ {
+ "left_line": 1118,
+ "right_line": 1118
+ },
+ {
+ "left_line": 1119,
+ "right_line": 1119
+ },
+ {
+ "left_line": 1120,
+ "right_line": 1120
+ },
+ {
+ "left_line": 1121,
+ "right_line": 1121
+ },
+ {
+ "left_line": 1122,
+ "right_line": 1122
+ },
+ {
+ "left_line": 1123,
+ "right_line": 1123
+ },
+ {
+ "left_line": 1124,
+ "right_line": 1124
+ },
+ {
+ "left_line": 1125,
+ "right_line": 1125
+ },
+ {
+ "left_line": 1126,
+ "right_line": 1126
+ },
+ {
+ "left_line": 1127,
+ "right_line": 1127
+ },
+ {
+ "left_line": 1128,
+ "right_line": 1128
+ },
+ {
+ "left_line": 1129,
+ "right_line": 1129
+ },
+ {
+ "left_line": 1130,
+ "right_line": 1130
+ },
+ {
+ "left_line": 1131,
+ "right_line": 1131
+ },
+ {
+ "left_line": 1132,
+ "right_line": 1132
+ },
+ {
+ "left_line": 1133,
+ "right_line": 1133
+ },
+ {
+ "left_line": 1134,
+ "right_line": 1134
+ },
+ {
+ "left_line": 1135,
+ "right_line": 1135
+ },
+ {
+ "left_line": 1136,
+ "right_line": 1136
+ },
+ {
+ "left_line": 1137,
+ "right_line": 1137
+ },
+ {
+ "left_line": 1138,
+ "right_line": 1138
+ },
+ {
+ "left_line": 1139,
+ "right_line": 1139
+ },
+ {
+ "left_line": 1140,
+ "right_line": 1140
+ },
+ {
+ "left_line": 1141,
+ "right_line": 1141
+ },
+ {
+ "left_line": 1142,
+ "right_line": 1142
+ },
+ {
+ "left_line": 1143,
+ "right_line": 1143
+ },
+ {
+ "left_line": 1144,
+ "right_line": 1144
+ },
+ {
+ "left_line": 1145,
+ "right_line": 1145
+ },
+ {
+ "left_line": 1146,
+ "right_line": 1146
+ },
+ {
+ "left_line": 1147,
+ "right_line": 1147
+ },
+ {
+ "left_line": 1148,
+ "right_line": 1148
+ },
+ {
+ "left_line": 1149,
+ "right_line": 1149
+ },
+ {
+ "left_line": 1150,
+ "right_line": 1150
+ },
+ {
+ "left_line": 1151,
+ "right_line": 1151
+ },
+ {
+ "left_line": 1152,
+ "right_line": 1152
+ },
+ {
+ "left_line": 1153,
+ "right_line": 1153
+ },
+ {
+ "left_line": 1154,
+ "right_line": 1154
+ },
+ {
+ "left_line": 1155,
+ "right_line": 1155
+ },
+ {
+ "left_line": 1156,
+ "right_line": 1156
+ },
+ {
+ "left_line": 1157,
+ "right_line": 1157
+ },
+ {
+ "left_line": 1158,
+ "right_line": 1158
+ },
+ {
+ "left_line": 1159,
+ "right_line": 1159
+ },
+ {
+ "left_line": 1160,
+ "right_line": 1160
+ },
+ {
+ "left_line": 1161,
+ "right_line": 1161
+ },
+ {
+ "left_line": 1162,
+ "right_line": 1162
+ },
+ {
+ "left_line": 1163,
+ "right_line": 1163
+ },
+ {
+ "left_line": 1164,
+ "right_line": 1164
+ },
+ {
+ "left_line": 1165,
+ "right_line": 1165
+ },
+ {
+ "left_line": 1166,
+ "right_line": 1166
+ },
+ {
+ "left_line": 1167,
+ "right_line": 1167
+ },
+ {
+ "left_line": 1168,
+ "right_line": 1168
+ },
+ {
+ "left_line": 1169,
+ "right_line": 1169
+ },
+ {
+ "left_line": 1170,
+ "right_line": 1170
+ },
+ {
+ "left_line": 1171,
+ "right_line": 1171
+ },
+ {
+ "left_line": 1172,
+ "right_line": 1172
+ },
+ {
+ "left_line": 1173,
+ "right_line": 1173
+ },
+ {
+ "left_line": 1174,
+ "right_line": 1174
+ },
+ {
+ "left_line": 1175,
+ "right_line": 1175
+ },
+ {
+ "left_line": 1176,
+ "right_line": 1176
+ },
+ {
+ "left_line": 1177,
+ "right_line": 1177
+ },
+ {
+ "left_line": 1178,
+ "right_line": 1178
+ },
+ {
+ "left_line": 1179,
+ "right_line": 1179
+ },
+ {
+ "left_line": 1180,
+ "right_line": 1180
+ },
+ {
+ "left_line": 1181,
+ "right_line": 1181
+ },
+ {
+ "left_line": 1182,
+ "right_line": 1182
+ },
+ {
+ "left_line": 1183,
+ "right_line": 1183
+ },
+ {
+ "left_line": 1184,
+ "right_line": 1184
+ },
+ {
+ "left_line": 1185,
+ "right_line": 1185
+ },
+ {
+ "left_line": 1186,
+ "right_line": 1186
+ },
+ {
+ "left_line": 1187,
+ "right_line": 1187
+ },
+ {
+ "left_line": 1188,
+ "right_line": 1188
+ },
+ {
+ "left_line": 1189,
+ "right_line": 1189
+ },
+ {
+ "left_line": 1190,
+ "right_line": 1190
+ },
+ {
+ "left_line": 1191,
+ "right_line": 1191
+ },
+ {
+ "left_line": 1192,
+ "right_line": 1192
+ },
+ {
+ "left_line": 1193,
+ "right_line": 1193
+ },
+ {
+ "left_line": 1194,
+ "right_line": 1194
+ },
+ {
+ "left_line": 1195,
+ "right_line": 1195
+ },
+ {
+ "left_line": 1196,
+ "right_line": 1196
+ },
+ {
+ "left_line": 1197,
+ "right_line": 1197
+ },
+ {
+ "left_line": 1198,
+ "right_line": 1198
+ },
+ {
+ "left_line": 1199,
+ "right_line": 1199
+ },
+ {
+ "left_line": 1200,
+ "right_line": 1200
+ },
+ {
+ "left_line": 1201,
+ "right_line": 1201
+ },
+ {
+ "left_line": 1202,
+ "right_line": 1202
+ },
+ {
+ "left_line": 1203,
+ "right_line": 1203
+ },
+ {
+ "left_line": 1204,
+ "right_line": 1204
+ },
+ {
+ "left_line": 1205,
+ "right_line": 1205
+ },
+ {
+ "left_line": 1206,
+ "right_line": 1206
+ },
+ {
+ "left_line": 1207,
+ "right_line": 1207
+ },
+ {
+ "left_line": 1208,
+ "right_line": 1208
+ },
+ {
+ "left_line": 1209,
+ "right_line": 1209
+ },
+ {
+ "left_line": 1210,
+ "right_line": 1210
+ },
+ {
+ "left_line": 1211,
+ "right_line": 1211
+ },
+ {
+ "left_line": 1212,
+ "right_line": 1212
+ },
+ {
+ "left_line": 1213,
+ "right_line": 1213
+ },
+ {
+ "left_line": 1214,
+ "right_line": 1214
+ },
+ {
+ "left_line": 1215,
+ "right_line": 1215
+ },
+ {
+ "left_line": 1216,
+ "right_line": 1216
+ },
+ {
+ "left_line": 1217,
+ "right_line": 1217
+ },
+ {
+ "left_line": 1218,
+ "right_line": 1218
+ },
+ {
+ "left_line": 1219,
+ "right_line": 1219
+ },
+ {
+ "left_line": 1220,
+ "right_line": 1220
+ },
+ {
+ "left_line": 1221,
+ "right_line": 1221
+ },
+ {
+ "left_line": 1222,
+ "right_line": 1222
+ },
+ {
+ "left_line": 1223,
+ "right_line": 1223
+ },
+ {
+ "left_line": 1224,
+ "right_line": 1224
+ },
+ {
+ "left_line": 1225,
+ "right_line": 1225
+ },
+ {
+ "left_line": 1226,
+ "right_line": 1226
+ },
+ {
+ "left_line": 1227,
+ "right_line": 1227
+ },
+ {
+ "left_line": 1228,
+ "right_line": 1228
+ },
+ {
+ "left_line": 1229,
+ "right_line": 1229
+ },
+ {
+ "left_line": 1230,
+ "right_line": 1230
+ },
+ {
+ "left_line": 1231,
+ "right_line": 1231
+ },
+ {
+ "left_line": 1232,
+ "right_line": 1232
+ },
+ {
+ "left_line": 1233,
+ "right_line": 1233
+ },
+ {
+ "left_line": 1234,
+ "right_line": 1234
+ },
+ {
+ "left_line": 1235,
+ "right_line": 1235
+ },
+ {
+ "left_line": 1236,
+ "right_line": 1236
+ },
+ {
+ "left_line": 1237,
+ "right_line": 1237
+ },
+ {
+ "left_line": 1238,
+ "right_line": 1238
+ },
+ {
+ "left_line": 1239,
+ "right_line": 1239
+ },
+ {
+ "left_line": 1240,
+ "right_line": 1240
+ },
+ {
+ "left_line": 1241,
+ "right_line": 1241
+ },
+ {
+ "left_line": 1242,
+ "right_line": 1242
+ },
+ {
+ "left_line": 1243,
+ "right_line": 1243
+ },
+ {
+ "left_line": 1244,
+ "right_line": 1244
+ },
+ {
+ "left_line": 1245,
+ "right_line": 1245
+ },
+ {
+ "left_line": 1246,
+ "right_line": 1246
+ },
+ {
+ "left_line": 1247,
+ "right_line": 1247
+ },
+ {
+ "left_line": 1248,
+ "right_line": 1248
+ },
+ {
+ "left_line": 1249,
+ "right_line": 1249
+ },
+ {
+ "left_line": 1250,
+ "right_line": 1250
+ },
+ {
+ "left_line": 1251,
+ "right_line": 1251
+ },
+ {
+ "left_line": 1252,
+ "right_line": 1252
+ },
+ {
+ "left_line": 1253,
+ "right_line": 1253
+ },
+ {
+ "left_line": 1254,
+ "right_line": 1254
+ },
+ {
+ "left_line": 1255,
+ "right_line": 1255
+ },
+ {
+ "left_line": 1256,
+ "right_line": 1256
+ },
+ {
+ "left_line": 1257,
+ "right_line": 1257
+ },
+ {
+ "left_line": 1258,
+ "right_line": 1258
+ },
+ {
+ "left_line": 1259,
+ "right_line": 1259
+ },
+ {
+ "left_line": 1260,
+ "right_line": 1260
+ },
+ {
+ "left_line": 1261,
+ "right_line": 1261
+ },
+ {
+ "left_line": 1262,
+ "right_line": 1262
+ },
+ {
+ "left_line": 1263,
+ "right_line": 1263
+ },
+ {
+ "left_line": 1264,
+ "right_line": 1264
+ },
+ {
+ "left_line": 1265,
+ "right_line": 1265
+ },
+ {
+ "left_line": 1266,
+ "right_line": 1266
+ },
+ {
+ "left_line": 1267,
+ "right_line": 1267
+ },
+ {
+ "left_line": 1268,
+ "right_line": 1268
+ },
+ {
+ "left_line": 1269,
+ "right_line": 1269
+ },
+ {
+ "left_line": 1270,
+ "right_line": 1270
+ },
+ {
+ "left_line": 1271,
+ "right_line": 1271
+ },
+ {
+ "left_line": 1272,
+ "right_line": 1272
+ },
+ {
+ "left_line": 1273,
+ "right_line": 1273
+ },
+ {
+ "left_line": 1274,
+ "right_line": 1274
+ },
+ {
+ "left_line": 1275,
+ "right_line": 1275
+ },
+ {
+ "left_line": 1276,
+ "right_line": 1276
+ },
+ {
+ "left_line": 1277,
+ "right_line": 1277
+ },
+ {
+ "left_line": 1278,
+ "right_line": 1278
+ },
+ {
+ "left_line": 1279,
+ "right_line": 1279
+ },
+ {
+ "left_line": 1280,
+ "right_line": 1280
+ },
+ {
+ "left_line": 1281,
+ "right_line": 1281
+ },
+ {
+ "left_line": 1282,
+ "right_line": 1282
+ },
+ {
+ "left_line": 1283,
+ "right_line": 1283
+ },
+ {
+ "left_line": 1284,
+ "right_line": 1284
+ },
+ {
+ "left_line": 1285,
+ "right_line": 1285
+ },
+ {
+ "left_line": 1286,
+ "right_line": 1286
+ },
+ {
+ "left_line": 1287,
+ "right_line": 1287
+ },
+ {
+ "left_line": 1288,
+ "right_line": 1288
+ },
+ {
+ "left_line": 1289,
+ "right_line": 1289
+ },
+ {
+ "left_line": 1290,
+ "right_line": 1290
+ },
+ {
+ "left_line": 1291,
+ "right_line": 1291
+ },
+ {
+ "left_line": 1292,
+ "right_line": 1292
+ },
+ {
+ "left_line": 1293,
+ "right_line": 1293
+ },
+ {
+ "left_line": 1294,
+ "right_line": 1294
+ },
+ {
+ "left_line": 1295,
+ "right_line": 1295
+ },
+ {
+ "left_line": 1296,
+ "right_line": 1296
+ },
+ {
+ "left_line": 1297,
+ "right_line": 1297
+ },
+ {
+ "left_line": 1298,
+ "right_line": 1298
+ },
+ {
+ "left_line": 1299,
+ "right_line": 1299
+ },
+ {
+ "left_line": 1300,
+ "right_line": 1300
+ },
+ {
+ "left_line": 1301,
+ "right_line": 1301
+ },
+ {
+ "left_line": 1302,
+ "right_line": 1302
+ },
+ {
+ "left_line": 1303,
+ "right_line": 1303
+ },
+ {
+ "left_line": 1304,
+ "right_line": 1304
+ },
+ {
+ "left_line": 1305,
+ "right_line": 1305
+ },
+ {
+ "left_line": 1306,
+ "right_line": 1306
+ },
+ {
+ "left_line": 1307,
+ "right_line": 1307
+ },
+ {
+ "left_line": 1308,
+ "right_line": 1308
+ },
+ {
+ "left_line": 1309,
+ "right_line": 1309
+ },
+ {
+ "left_line": 1310,
+ "right_line": 1310
+ },
+ {
+ "left_line": 1311,
+ "right_line": 1311
+ },
+ {
+ "left_line": 1312,
+ "right_line": 1312
+ },
+ {
+ "left_line": 1313,
+ "right_line": 1313
+ },
+ {
+ "left_line": 1314,
+ "right_line": 1314
+ },
+ {
+ "left_line": 1315,
+ "right_line": 1315
+ },
+ {
+ "left_line": 1316,
+ "right_line": 1316
+ },
+ {
+ "left_line": 1317,
+ "right_line": 1317
+ },
+ {
+ "left_line": 1318,
+ "right_line": 1318
+ },
+ {
+ "left_line": 1319,
+ "right_line": 1319
+ },
+ {
+ "left_line": 1320,
+ "right_line": 1320
+ },
+ {
+ "left_line": 1321,
+ "right_line": 1321
+ },
+ {
+ "left_line": 1322,
+ "right_line": 1322
+ },
+ {
+ "left_line": 1323,
+ "right_line": 1323
+ },
+ {
+ "left_line": 1324,
+ "right_line": 1324
+ },
+ {
+ "left_line": 1325,
+ "right_line": 1325
+ },
+ {
+ "left_line": 1326,
+ "right_line": 1326
+ },
+ {
+ "left_line": 1327,
+ "right_line": 1327
+ },
+ {
+ "left_line": 1328,
+ "right_line": 1328
+ },
+ {
+ "left_line": 1329,
+ "right_line": 1329
+ },
+ {
+ "left_line": 1330,
+ "right_line": 1330
+ },
+ {
+ "left_line": 1331,
+ "right_line": 1331
+ },
+ {
+ "left_line": 1332,
+ "right_line": 1332
+ },
+ {
+ "left_line": 1333,
+ "right_line": 1333
+ },
+ {
+ "left_line": 1334,
+ "right_line": 1334
+ },
+ {
+ "left_line": 1335,
+ "right_line": 1335
+ },
+ {
+ "left_line": 1336,
+ "right_line": 1336
+ },
+ {
+ "left_line": 1337,
+ "right_line": 1337
+ },
+ {
+ "left_line": 1338,
+ "right_line": 1338
+ },
+ {
+ "left_line": 1339,
+ "right_line": 1339
+ },
+ {
+ "left_line": 1340,
+ "right_line": 1340
+ },
+ {
+ "left_line": 1341,
+ "right_line": 1341
+ },
+ {
+ "left_line": 1342,
+ "right_line": 1342
+ },
+ {
+ "left_line": 1343,
+ "right_line": 1343
+ },
+ {
+ "left_line": 1344,
+ "right_line": 1344
+ },
+ {
+ "left_line": 1345,
+ "right_line": 1345
+ },
+ {
+ "left_line": 1346,
+ "right_line": 1346
+ },
+ {
+ "left_line": 1347,
+ "right_line": 1347
+ },
+ {
+ "left_line": 1348,
+ "right_line": 1348
+ },
+ {
+ "left_line": 1349,
+ "right_line": 1349
+ },
+ {
+ "left_line": 1350,
+ "right_line": 1350
+ },
+ {
+ "left_line": 1351,
+ "right_line": 1351
+ },
+ {
+ "left_line": 1352,
+ "right_line": 1352
+ },
+ {
+ "left_line": 1353,
+ "right_line": 1353
+ },
+ {
+ "left_line": 1354,
+ "right_line": 1354
+ },
+ {
+ "left_line": 1355,
+ "right_line": 1355
+ },
+ {
+ "left_line": 1356,
+ "right_line": 1356
+ },
+ {
+ "left_line": 1357,
+ "right_line": 1357
+ },
+ {
+ "left_line": 1358,
+ "right_line": 1358
+ },
+ {
+ "left_line": 1359,
+ "right_line": 1359
+ },
+ {
+ "left_line": 1360,
+ "right_line": 1360
+ },
+ {
+ "left_line": 1361,
+ "right_line": 1361
+ },
+ {
+ "left_line": 1362,
+ "right_line": 1362
+ },
+ {
+ "left_line": 1363,
+ "right_line": 1363
+ },
+ {
+ "left_line": 1364,
+ "right_line": 1364
+ },
+ {
+ "left_line": 1365,
+ "right_line": 1365
+ },
+ {
+ "left_line": 1366,
+ "right_line": 1366
+ },
+ {
+ "left_line": 1367,
+ "right_line": 1367
+ },
+ {
+ "left_line": 1368,
+ "right_line": 1368
+ },
+ {
+ "left_line": 1369,
+ "right_line": 1369
+ },
+ {
+ "left_line": 1370,
+ "right_line": 1370
+ },
+ {
+ "left_line": 1371,
+ "right_line": 1371
+ },
+ {
+ "left_line": 1372,
+ "right_line": 1372
+ },
+ {
+ "left_line": 1373,
+ "right_line": 1373
+ },
+ {
+ "left_line": 1374,
+ "right_line": 1374
+ },
+ {
+ "left_line": 1375,
+ "right_line": 1375
+ },
+ {
+ "left_line": 1376,
+ "right_line": 1376
+ },
+ {
+ "left_line": 1377,
+ "right_line": 1377
+ },
+ {
+ "left_line": 1378,
+ "right_line": 1378
+ },
+ {
+ "left_line": 1379,
+ "right_line": 1379
+ },
+ {
+ "left_line": 1380,
+ "right_line": 1380
+ },
+ {
+ "left_line": 1381,
+ "right_line": 1381
+ },
+ {
+ "left_line": 1382,
+ "right_line": 1382
+ },
+ {
+ "left_line": 1383,
+ "right_line": 1383
+ },
+ {
+ "left_line": 1384,
+ "right_line": 1384
+ },
+ {
+ "left_line": 1385,
+ "right_line": 1385
+ },
+ {
+ "left_line": 1386,
+ "right_line": 1386
+ },
+ {
+ "left_line": 1387,
+ "right_line": 1387
+ },
+ {
+ "left_line": 1388,
+ "right_line": 1388
+ },
+ {
+ "left_line": 1389,
+ "right_line": 1389
+ },
+ {
+ "left_line": 1390,
+ "right_line": 1390
+ },
+ {
+ "left_line": 1391,
+ "right_line": 1391
+ },
+ {
+ "left_line": 1392,
+ "right_line": 1392
+ },
+ {
+ "left_line": 1393,
+ "right_line": 1393
+ },
+ {
+ "left_line": 1394,
+ "right_line": 1394
+ },
+ {
+ "left_line": 1395,
+ "right_line": 1395
+ },
+ {
+ "left_line": 1396,
+ "right_line": 1396
+ },
+ {
+ "left_line": 1397,
+ "right_line": 1397
+ },
+ {
+ "left_line": 1398,
+ "right_line": 1398
+ },
+ {
+ "left_line": 1399,
+ "right_line": 1399
+ },
+ {
+ "left_line": 1400,
+ "right_line": 1400
+ },
+ {
+ "left_line": 1401,
+ "right_line": 1401
+ },
+ {
+ "left_line": 1402,
+ "right_line": 1402
+ },
+ {
+ "left_line": 1403,
+ "right_line": 1403
+ },
+ {
+ "left_line": 1404,
+ "right_line": 1404
+ },
+ {
+ "left_line": 1405,
+ "right_line": 1405
+ },
+ {
+ "left_line": 1406,
+ "right_line": 1406
+ },
+ {
+ "left_line": 1407,
+ "right_line": 1407
+ },
+ {
+ "left_line": 1408,
+ "right_line": 1408
+ },
+ {
+ "left_line": 1409,
+ "right_line": 1409
+ },
+ {
+ "left_line": 1410,
+ "right_line": 1410
+ },
+ {
+ "left_line": 1411,
+ "right_line": 1411
+ },
+ {
+ "left_line": 1412,
+ "right_line": 1412
+ },
+ {
+ "left_line": 1413,
+ "right_line": 1413
+ },
+ {
+ "left_line": 1414,
+ "right_line": 1414
+ },
+ {
+ "left_line": 1415,
+ "right_line": 1415
+ },
+ {
+ "left_line": 1416,
+ "right_line": 1416
+ },
+ {
+ "left_line": 1417,
+ "right_line": 1417
+ },
+ {
+ "left_line": 1418,
+ "right_line": 1418
+ },
+ {
+ "left_line": 1419,
+ "right_line": 1419
+ },
+ {
+ "left_line": 1420,
+ "right_line": 1420
+ },
+ {
+ "left_line": 1421,
+ "right_line": 1421
+ },
+ {
+ "left_line": 1422,
+ "right_line": 1422
+ },
+ {
+ "left_line": 1423,
+ "right_line": 1423
+ },
+ {
+ "left_line": 1424,
+ "right_line": 1424
+ },
+ {
+ "left_line": 1425,
+ "right_line": 1425
+ },
+ {
+ "left_line": 1426,
+ "right_line": 1426
+ },
+ {
+ "left_line": 1427,
+ "right_line": 1427
+ },
+ {
+ "left_line": 1428,
+ "right_line": 1428
+ },
+ {
+ "left_line": 1429,
+ "right_line": 1429
+ },
+ {
+ "left_line": 1430,
+ "right_line": 1430
+ },
+ {
+ "left_line": 1431,
+ "right_line": 1431
+ },
+ {
+ "left_line": 1432,
+ "right_line": 1432
+ },
+ {
+ "left_line": 1433,
+ "right_line": 1433
+ },
+ {
+ "left_line": 1434,
+ "right_line": 1434
+ },
+ {
+ "left_line": 1435,
+ "right_line": 1435
+ },
+ {
+ "left_line": 1436,
+ "right_line": 1436
+ },
+ {
+ "left_line": 1437,
+ "right_line": 1437
+ },
+ {
+ "left_line": 1438,
+ "right_line": 1438
+ },
+ {
+ "left_line": 1439,
+ "right_line": 1439
+ },
+ {
+ "left_line": 1440,
+ "right_line": 1440
+ },
+ {
+ "left_line": 1441,
+ "right_line": 1441
+ },
+ {
+ "left_line": 1442,
+ "right_line": 1442
+ },
+ {
+ "left_line": 1443,
+ "right_line": 1443
+ },
+ {
+ "left_line": 1444,
+ "right_line": 1444
+ },
+ {
+ "left_line": 1445,
+ "right_line": 1445
+ },
+ {
+ "left_line": 1446,
+ "right_line": 1446
+ },
+ {
+ "left_line": 1447,
+ "right_line": 1447
+ },
+ {
+ "left_line": 1448,
+ "right_line": 1448
+ },
+ {
+ "left_line": 1449,
+ "right_line": 1449
+ },
+ {
+ "left_line": 1450,
+ "right_line": 1450
+ },
+ {
+ "left_line": 1451,
+ "right_line": 1451
+ },
+ {
+ "left_line": 1452,
+ "right_line": 1452
+ },
+ {
+ "left_line": 1453,
+ "right_line": 1453
+ },
+ {
+ "left_line": 1454,
+ "right_line": 1454
+ },
+ {
+ "left_line": 1455,
+ "right_line": 1455
+ },
+ {
+ "left_line": 1456,
+ "right_line": 1456
+ },
+ {
+ "left_line": 1457,
+ "right_line": 1457
+ },
+ {
+ "left_line": 1458,
+ "right_line": 1458
+ },
+ {
+ "left_line": 1459,
+ "right_line": 1459
+ },
+ {
+ "left_line": 1460,
+ "right_line": 1460
+ },
+ {
+ "left_line": 1461,
+ "right_line": 1461
+ },
+ {
+ "left_line": 1462,
+ "right_line": 1462
+ },
+ {
+ "left_line": 1463,
+ "right_line": 1463
+ },
+ {
+ "left_line": 1464,
+ "right_line": 1464
+ },
+ {
+ "left_line": 1465,
+ "right_line": 1465
+ },
+ {
+ "left_line": 1466,
+ "right_line": 1466
+ },
+ {
+ "left_line": 1467,
+ "right_line": 1467
+ },
+ {
+ "left_line": 1468,
+ "right_line": 1468
+ },
+ {
+ "left_line": 1469,
+ "right_line": 1469
+ },
+ {
+ "left_line": 1470,
+ "right_line": 1470
+ },
+ {
+ "left_line": 1471,
+ "right_line": 1471
+ },
+ {
+ "left_line": 1472,
+ "right_line": 1472
+ },
+ {
+ "left_line": 1473,
+ "right_line": 1473
+ },
+ {
+ "left_line": 1474,
+ "right_line": 1474
+ },
+ {
+ "left_line": 1475,
+ "right_line": 1475
+ },
+ {
+ "left_line": 1476,
+ "right_line": 1476
+ },
+ {
+ "left_line": 1477,
+ "right_line": 1477
+ },
+ {
+ "left_line": 1478,
+ "right_line": 1478
+ },
+ {
+ "left_line": 1479,
+ "right_line": 1479
+ },
+ {
+ "left_line": 1480,
+ "right_line": 1480
+ },
+ {
+ "left_line": 1481,
+ "right_line": 1481
+ },
+ {
+ "left_line": 1482,
+ "right_line": 1482
+ },
+ {
+ "left_line": 1483,
+ "right_line": 1483
+ },
+ {
+ "left_line": 1484,
+ "right_line": 1484
+ },
+ {
+ "left_line": 1485,
+ "right_line": 1485
+ },
+ {
+ "left_line": 1486,
+ "right_line": 1486
+ },
+ {
+ "left_line": 1487,
+ "right_line": 1487
+ },
+ {
+ "left_line": 1488,
+ "right_line": 1488
+ },
+ {
+ "left_line": 1489,
+ "right_line": 1489
+ },
+ {
+ "left_line": 1490,
+ "right_line": 1490
+ },
+ {
+ "left_line": 1491,
+ "right_line": 1491
+ },
+ {
+ "left_line": 1492,
+ "right_line": 1492
+ },
+ {
+ "left_line": 1493,
+ "right_line": 1493
+ },
+ {
+ "left_line": 1494,
+ "right_line": 1494
+ },
+ {
+ "left_line": 1495,
+ "right_line": 1495
+ },
+ {
+ "left_line": 1496,
+ "right_line": 1496
+ },
+ {
+ "left_line": 1497,
+ "right_line": 1497
+ },
+ {
+ "left_line": 1498,
+ "right_line": 1498
+ },
+ {
+ "left_line": 1499,
+ "right_line": 1499
+ },
+ {
+ "left_line": 1500,
+ "right_line": 1500
+ },
+ {
+ "left_line": 1501,
+ "right_line": 1501
+ },
+ {
+ "left_line": 1502,
+ "right_line": 1502
+ },
+ {
+ "left_line": 1503,
+ "right_line": 1503
+ },
+ {
+ "left_line": 1504,
+ "right_line": 1504
+ },
+ {
+ "left_line": 1505,
+ "right_line": 1505
+ },
+ {
+ "left_line": 1506,
+ "right_line": 1506
+ },
+ {
+ "left_line": 1507,
+ "right_line": 1507
+ },
+ {
+ "left_line": 1508,
+ "right_line": 1508
+ },
+ {
+ "left_line": 1509,
+ "right_line": 1509
+ },
+ {
+ "left_line": 1510,
+ "right_line": 1510
+ },
+ {
+ "left_line": 1511,
+ "right_line": 1511
+ },
+ {
+ "left_line": 1512,
+ "right_line": 1512
+ },
+ {
+ "left_line": 1513,
+ "right_line": 1513
+ },
+ {
+ "left_line": 1514,
+ "right_line": 1514
+ },
+ {
+ "left_line": 1515,
+ "right_line": 1515
+ },
+ {
+ "left_line": 1516,
+ "right_line": 1516
+ },
+ {
+ "left_line": 1517,
+ "right_line": 1517
+ },
+ {
+ "left_line": 1518,
+ "right_line": 1518
+ },
+ {
+ "left_line": 1519,
+ "right_line": 1519
+ },
+ {
+ "left_line": 1520,
+ "right_line": 1520
+ },
+ {
+ "left_line": 1521,
+ "right_line": 1521
+ },
+ {
+ "left_line": 1522,
+ "right_line": 1522
+ },
+ {
+ "left_line": 1523,
+ "right_line": 1523
+ },
+ {
+ "left_line": 1524,
+ "right_line": 1524
+ },
+ {
+ "left_line": 1525,
+ "right_line": 1525
+ },
+ {
+ "left_line": 1526,
+ "right_line": 1526
+ },
+ {
+ "left_line": 1527,
+ "right_line": 1527
+ },
+ {
+ "left_line": 1528,
+ "right_line": 1528
+ },
+ {
+ "left_line": 1529,
+ "right_line": 1529
+ },
+ {
+ "left_line": 1530,
+ "right_line": 1530
+ },
+ {
+ "left_line": 1531,
+ "right_line": 1531
+ },
+ {
+ "left_line": 1532,
+ "right_line": 1532
+ },
+ {
+ "left_line": 1533,
+ "right_line": 1533
+ },
+ {
+ "left_line": 1534,
+ "right_line": 1534
+ },
+ {
+ "left_line": 1535,
+ "right_line": 1535
+ },
+ {
+ "left_line": 1536,
+ "right_line": 1536
+ },
+ {
+ "left_line": 1537,
+ "right_line": 1537
+ },
+ {
+ "left_line": 1538,
+ "right_line": 1538
+ },
+ {
+ "left_line": 1539,
+ "right_line": 1539
+ },
+ {
+ "left_line": 1540,
+ "right_line": 1540
+ },
+ {
+ "left_line": 1541,
+ "right_line": 1541
+ },
+ {
+ "left_line": 1542,
+ "right_line": 1542
+ },
+ {
+ "left_line": 1543,
+ "right_line": 1543
+ },
+ {
+ "left_line": 1544,
+ "right_line": 1544
+ },
+ {
+ "left_line": 1545,
+ "right_line": 1545
+ },
+ {
+ "left_line": 1546,
+ "right_line": 1546
+ },
+ {
+ "left_line": 1547,
+ "right_line": 1547
+ },
+ {
+ "left_line": 1548,
+ "right_line": 1548
+ },
+ {
+ "left_line": 1549,
+ "right_line": 1549
+ },
+ {
+ "left_line": 1550,
+ "right_line": 1550
+ },
+ {
+ "left_line": 1551,
+ "right_line": 1551
+ },
+ {
+ "left_line": 1552,
+ "right_line": 1552
+ },
+ {
+ "left_line": 1553,
+ "right_line": 1553
+ },
+ {
+ "left_line": 1554,
+ "right_line": 1554
+ },
+ {
+ "left_line": 1555,
+ "right_line": 1555
+ },
+ {
+ "left_line": 1556,
+ "right_line": 1556
+ },
+ {
+ "left_line": 1557,
+ "right_line": 1557
+ },
+ {
+ "left_line": 1558,
+ "right_line": 1558
+ },
+ {
+ "left_line": 1559,
+ "right_line": 1559
+ },
+ {
+ "left_line": 1560,
+ "right_line": 1560
+ },
+ {
+ "left_line": 1561,
+ "right_line": 1561
+ },
+ {
+ "left_line": 1562,
+ "right_line": 1562
+ },
+ {
+ "left_line": 1563,
+ "right_line": 1563
+ },
+ {
+ "left_line": 1564,
+ "right_line": 1564
+ },
+ {
+ "left_line": 1565,
+ "right_line": 1565
+ },
+ {
+ "left_line": 1566,
+ "right_line": 1566
+ },
+ {
+ "left_line": 1567,
+ "right_line": 1567
+ },
+ {
+ "left_line": 1568,
+ "right_line": 1568
+ },
+ {
+ "left_line": 1569,
+ "right_line": 1569
+ },
+ {
+ "left_line": 1570,
+ "right_line": 1570
+ },
+ {
+ "left_line": 1571,
+ "right_line": 1571
+ },
+ {
+ "left_line": 1572,
+ "right_line": 1572
+ },
+ {
+ "left_line": 1573,
+ "right_line": 1573
+ },
+ {
+ "left_line": 1574,
+ "right_line": 1574
+ },
+ {
+ "left_line": 1575,
+ "right_line": 1575
+ },
+ {
+ "left_line": 1576,
+ "right_line": 1576
+ },
+ {
+ "left_line": 1577,
+ "right_line": 1577
+ },
+ {
+ "left_line": 1578,
+ "right_line": 1578
+ },
+ {
+ "left_line": 1579,
+ "right_line": 1579
+ },
+ {
+ "left_line": 1580,
+ "right_line": 1580
+ },
+ {
+ "left_line": 1581,
+ "right_line": 1581
+ },
+ {
+ "left_line": 1582,
+ "right_line": 1582
+ },
+ {
+ "left_line": 1583,
+ "right_line": 1583
+ },
+ {
+ "left_line": 1584,
+ "right_line": 1584
+ },
+ {
+ "left_line": 1585,
+ "right_line": 1585
+ },
+ {
+ "left_line": 1586,
+ "right_line": 1586
+ },
+ {
+ "left_line": 1587,
+ "right_line": 1587
+ },
+ {
+ "left_line": 1588,
+ "right_line": 1588
+ },
+ {
+ "left_line": 1589,
+ "right_line": 1589
+ },
+ {
+ "left_line": 1590,
+ "right_line": 1590
+ },
+ {
+ "left_line": 1591,
+ "right_line": 1591
+ },
+ {
+ "left_line": 1592,
+ "right_line": 1592
+ },
+ {
+ "left_line": 1593,
+ "right_line": 1593
+ },
+ {
+ "left_line": 1594,
+ "right_line": 1594
+ },
+ {
+ "left_line": 1595,
+ "right_line": 1595
+ },
+ {
+ "left_line": 1596,
+ "right_line": 1596
+ },
+ {
+ "left_line": 1597,
+ "right_line": 1597
+ },
+ {
+ "left_line": 1598,
+ "right_line": 1598
+ },
+ {
+ "left_line": 1599,
+ "right_line": 1599
+ },
+ {
+ "left_line": 1600,
+ "right_line": 1600
+ },
+ {
+ "left_line": 1601,
+ "right_line": 1601
+ },
+ {
+ "left_line": 1602,
+ "right_line": 1602
+ },
+ {
+ "left_line": 1603,
+ "right_line": 1603
+ },
+ {
+ "left_line": 1604,
+ "right_line": 1604
+ },
+ {
+ "left_line": 1605,
+ "right_line": 1605
+ },
+ {
+ "left_line": 1606,
+ "right_line": 1606
+ },
+ {
+ "left_line": 1607,
+ "right_line": 1607
+ },
+ {
+ "left_line": 1608,
+ "right_line": 1608
+ },
+ {
+ "left_line": 1609,
+ "right_line": 1609
+ },
+ {
+ "left_line": 1610,
+ "right_line": 1610
+ },
+ {
+ "left_line": 1611,
+ "right_line": 1611
+ },
+ {
+ "left_line": 1612,
+ "right_line": 1612
+ },
+ {
+ "left_line": 1613,
+ "right_line": 1613
+ },
+ {
+ "left_line": 1614,
+ "right_line": 1614
+ },
+ {
+ "left_line": 1615,
+ "right_line": 1615
+ },
+ {
+ "left_line": 1616,
+ "right_line": 1616
+ },
+ {
+ "left_line": 1617,
+ "right_line": 1617
+ },
+ {
+ "left_line": 1618,
+ "right_line": 1618
+ },
+ {
+ "left_line": 1619,
+ "right_line": 1619
+ },
+ {
+ "left_line": 1620,
+ "right_line": 1620
+ },
+ {
+ "left_line": 1621,
+ "right_line": 1621
+ },
+ {
+ "left_line": 1622,
+ "right_line": 1622
+ },
+ {
+ "left_line": 1623,
+ "right_line": 1623
+ },
+ {
+ "left_line": 1624,
+ "right_line": 1624
+ },
+ {
+ "left_line": 1625,
+ "right_line": 1625
+ },
+ {
+ "left_line": 1626,
+ "right_line": 1626
+ },
+ {
+ "left_line": 1627,
+ "right_line": 1627
+ },
+ {
+ "left_line": 1628,
+ "right_line": 1628
+ },
+ {
+ "left_line": 1629,
+ "right_line": 1629
+ },
+ {
+ "left_line": 1630,
+ "right_line": 1630
+ },
+ {
+ "left_line": 1631,
+ "right_line": 1631
+ },
+ {
+ "left_line": 1632,
+ "right_line": 1632
+ },
+ {
+ "left_line": 1633,
+ "right_line": 1633
+ },
+ {
+ "left_line": 1634,
+ "right_line": 1634
+ },
+ {
+ "left_line": 1635,
+ "right_line": 1635
+ },
+ {
+ "left_line": 1636,
+ "right_line": 1636
+ },
+ {
+ "left_line": 1637,
+ "right_line": 1637
+ },
+ {
+ "left_line": 1638,
+ "right_line": 1638
+ },
+ {
+ "left_line": 1639,
+ "right_line": 1639
+ },
+ {
+ "left_line": 1640,
+ "right_line": 1640
+ },
+ {
+ "left_line": 1641,
+ "right_line": 1641
+ },
+ {
+ "left_line": 1642,
+ "right_line": 1642
+ },
+ {
+ "left_line": 1643,
+ "right_line": 1643
+ },
+ {
+ "left_line": 1644,
+ "right_line": 1644
+ },
+ {
+ "left_line": 1645,
+ "right_line": 1645
+ },
+ {
+ "left_line": 1646,
+ "right_line": 1646
+ },
+ {
+ "left_line": 1647,
+ "right_line": 1647
+ },
+ {
+ "left_line": 1648,
+ "right_line": 1648
+ },
+ {
+ "left_line": 1649,
+ "right_line": 1649
+ },
+ {
+ "left_line": 1650,
+ "right_line": 1650
+ },
+ {
+ "left_line": 1651,
+ "right_line": 1651
+ },
+ {
+ "left_line": 1652,
+ "right_line": 1652
+ },
+ {
+ "left_line": 1653,
+ "right_line": 1653
+ },
+ {
+ "left_line": 1654,
+ "right_line": 1654
+ },
+ {
+ "left_line": 1655,
+ "right_line": 1655
+ },
+ {
+ "left_line": 1656,
+ "right_line": 1656
+ },
+ {
+ "left_line": 1657,
+ "right_line": 1657
+ },
+ {
+ "left_line": 1658,
+ "right_line": 1658
+ },
+ {
+ "left_line": 1659,
+ "right_line": 1659
+ },
+ {
+ "left_line": 1660,
+ "right_line": 1660
+ },
+ {
+ "left_line": 1661,
+ "right_line": 1661
+ },
+ {
+ "left_line": 1662,
+ "right_line": 1662
+ },
+ {
+ "left_line": 1663,
+ "right_line": 1663
+ },
+ {
+ "left_line": 1664,
+ "right_line": 1664
+ },
+ {
+ "left_line": 1665,
+ "right_line": 1665
+ },
+ {
+ "left_line": 1666,
+ "right_line": 1666
+ },
+ {
+ "left_line": 1667,
+ "right_line": 1667
+ },
+ {
+ "left_line": 1668,
+ "right_line": 1668
+ },
+ {
+ "left_line": 1669,
+ "right_line": 1669
+ },
+ {
+ "left_line": 1670,
+ "right_line": 1670
+ },
+ {
+ "left_line": 1671,
+ "right_line": 1671
+ },
+ {
+ "left_line": 1672,
+ "right_line": 1672
+ },
+ {
+ "left_line": 1673,
+ "right_line": 1673
+ },
+ {
+ "left_line": 1674,
+ "right_line": 1674
+ },
+ {
+ "left_line": 1675,
+ "right_line": 1675
+ },
+ {
+ "left_line": 1676,
+ "right_line": 1676
+ },
+ {
+ "left_line": 1677,
+ "right_line": 1677
+ },
+ {
+ "left_line": 1678,
+ "right_line": 1678
+ },
+ {
+ "left_line": 1679,
+ "right_line": 1679
+ },
+ {
+ "left_line": 1680,
+ "right_line": 1680
+ },
+ {
+ "left_line": 1681,
+ "right_line": 1681
+ },
+ {
+ "left_line": 1682,
+ "right_line": 1682
+ },
+ {
+ "left_line": 1683,
+ "right_line": 1683
+ },
+ {
+ "left_line": 1684,
+ "right_line": 1684
+ },
+ {
+ "left_line": 1685,
+ "right_line": 1685
+ },
+ {
+ "left_line": 1686,
+ "right_line": 1686
+ },
+ {
+ "left_line": 1687,
+ "right_line": 1687
+ },
+ {
+ "left_line": 1688,
+ "right_line": 1688
+ },
+ {
+ "left_line": 1689,
+ "right_line": 1689
+ },
+ {
+ "left_line": 1690,
+ "right_line": 1690
+ },
+ {
+ "left_line": 1691,
+ "right_line": 1691
+ },
+ {
+ "left_line": 1692,
+ "right_line": 1692
+ },
+ {
+ "left_line": 1693,
+ "right_line": 1693
+ },
+ {
+ "left_line": 1694,
+ "right_line": 1694
+ },
+ {
+ "left_line": 1695,
+ "right_line": 1695
+ },
+ {
+ "left_line": 1696,
+ "right_line": 1696
+ },
+ {
+ "left_line": 1697,
+ "right_line": 1697
+ },
+ {
+ "left_line": 1698,
+ "right_line": 1698
+ },
+ {
+ "left_line": 1699,
+ "right_line": 1699
+ },
+ {
+ "left_line": 1700,
+ "right_line": 1700
+ },
+ {
+ "left_line": 1701,
+ "right_line": 1701
+ },
+ {
+ "left_line": 1702,
+ "right_line": 1702
+ },
+ {
+ "left_line": 1703,
+ "right_line": 1703
+ },
+ {
+ "left_line": 1704,
+ "right_line": 1704
+ },
+ {
+ "left_line": 1705,
+ "right_line": 1705
+ },
+ {
+ "left_line": 1706,
+ "right_line": 1706
+ },
+ {
+ "left_line": 1707,
+ "right_line": 1707
+ },
+ {
+ "left_line": 1708,
+ "right_line": 1708
+ },
+ {
+ "left_line": 1709,
+ "right_line": 1709
+ },
+ {
+ "left_line": 1710,
+ "right_line": 1710
+ },
+ {
+ "left_line": 1711,
+ "right_line": 1711
+ },
+ {
+ "left_line": 1712,
+ "right_line": 1712
+ },
+ {
+ "left_line": 1713,
+ "right_line": 1713
+ },
+ {
+ "left_line": 1714,
+ "right_line": 1714
+ },
+ {
+ "left_line": 1715,
+ "right_line": 1715
+ },
+ {
+ "left_line": 1716,
+ "right_line": 1716
+ },
+ {
+ "left_line": 1717,
+ "right_line": 1717
+ },
+ {
+ "left_line": 1718,
+ "right_line": 1718
+ },
+ {
+ "left_line": 1719,
+ "right_line": 1719
+ },
+ {
+ "left_line": 1720,
+ "right_line": 1720
+ },
+ {
+ "left_line": 1721,
+ "right_line": 1721
+ },
+ {
+ "left_line": 1722,
+ "right_line": 1722
+ },
+ {
+ "left_line": 1723,
+ "right_line": 1723
+ },
+ {
+ "left_line": 1724,
+ "right_line": 1724
+ },
+ {
+ "left_line": 1725,
+ "right_line": 1725
+ },
+ {
+ "left_line": 1726,
+ "right_line": 1726
+ },
+ {
+ "left_line": 1727,
+ "right_line": 1727
+ },
+ {
+ "left_line": 1728,
+ "right_line": 1728
+ },
+ {
+ "left_line": 1729,
+ "right_line": 1729
+ },
+ {
+ "left_line": 1730,
+ "right_line": 1730
+ },
+ {
+ "left_line": 1731,
+ "right_line": 1731
+ },
+ {
+ "left_line": 1732,
+ "right_line": 1732
+ },
+ {
+ "left_line": 1733,
+ "right_line": 1733
+ },
+ {
+ "left_line": 1734,
+ "right_line": 1734
+ },
+ {
+ "left_line": 1735,
+ "right_line": 1735
+ },
+ {
+ "left_line": 1736,
+ "right_line": 1736
+ },
+ {
+ "left_line": 1737,
+ "right_line": 1737
+ },
+ {
+ "left_line": 1738,
+ "right_line": 1738
+ },
+ {
+ "left_line": 1739,
+ "right_line": 1739
+ },
+ {
+ "left_line": 1740,
+ "right_line": 1740
+ },
+ {
+ "left_line": 1741,
+ "right_line": 1741
+ },
+ {
+ "left_line": 1742,
+ "right_line": 1742
+ },
+ {
+ "left_line": 1743,
+ "right_line": 1743
+ },
+ {
+ "left_line": 1744,
+ "right_line": 1744
+ },
+ {
+ "left_line": 1745,
+ "right_line": 1745
+ },
+ {
+ "left_line": 1746,
+ "right_line": 1746
+ },
+ {
+ "left_line": 1747,
+ "right_line": 1747
+ },
+ {
+ "left_line": 1748,
+ "right_line": 1748
+ },
+ {
+ "left_line": 1749,
+ "right_line": 1749
+ },
+ {
+ "left_line": 1750,
+ "right_line": 1750
+ },
+ {
+ "left_line": 1751,
+ "right_line": 1751
+ },
+ {
+ "left_line": 1752,
+ "right_line": 1752
+ },
+ {
+ "left_line": 1753,
+ "right_line": 1753
+ },
+ {
+ "left_line": 1754,
+ "right_line": 1754
+ },
+ {
+ "left_line": 1755,
+ "right_line": 1755
+ },
+ {
+ "left_line": 1756,
+ "right_line": 1756
+ },
+ {
+ "left_line": 1757,
+ "right_line": 1757
+ },
+ {
+ "left_line": 1758,
+ "right_line": 1758
+ },
+ {
+ "left_line": 1759,
+ "right_line": 1759
+ },
+ {
+ "left_line": 1760,
+ "right_line": 1760
+ },
+ {
+ "left_line": 1761,
+ "right_line": 1761
+ },
+ {
+ "left_line": 1762,
+ "right_line": 1762
+ },
+ {
+ "left_line": 1763,
+ "right_line": 1763
+ },
+ {
+ "left_line": 1764,
+ "right_line": 1764
+ },
+ {
+ "left_line": 1765,
+ "right_line": 1765
+ },
+ {
+ "left_line": 1766,
+ "right_line": 1766
+ },
+ {
+ "left_line": 1767,
+ "right_line": 1767
+ },
+ {
+ "left_line": 1768,
+ "right_line": 1768
+ },
+ {
+ "left_line": 1769,
+ "right_line": 1769
+ },
+ {
+ "left_line": 1770,
+ "right_line": 1770
+ },
+ {
+ "left_line": 1771,
+ "right_line": 1771
+ },
+ {
+ "left_line": 1772,
+ "right_line": 1772
+ },
+ {
+ "left_line": 1773,
+ "right_line": 1773
+ },
+ {
+ "left_line": 1774,
+ "right_line": 1774
+ },
+ {
+ "left_line": 1775,
+ "right_line": 1775
+ },
+ {
+ "left_line": 1776,
+ "right_line": 1776
+ },
+ {
+ "left_line": 1777,
+ "right_line": 1777
+ },
+ {
+ "left_line": 1778,
+ "right_line": 1778
+ },
+ {
+ "left_line": 1779,
+ "right_line": 1779
+ },
+ {
+ "left_line": 1780,
+ "right_line": 1780
+ },
+ {
+ "left_line": 1781,
+ "right_line": 1781
+ },
+ {
+ "left_line": 1782,
+ "right_line": 1782
+ },
+ {
+ "left_line": 1783,
+ "right_line": 1783
+ },
+ {
+ "left_line": 1784,
+ "right_line": 1784
+ },
+ {
+ "left_line": 1785,
+ "right_line": 1785
+ },
+ {
+ "left_line": 1786,
+ "right_line": 1786
+ },
+ {
+ "left_line": 1787,
+ "right_line": 1787
+ },
+ {
+ "left_line": 1788,
+ "right_line": 1788
+ },
+ {
+ "left_line": 1789,
+ "right_line": 1789
+ },
+ {
+ "left_line": 1790,
+ "right_line": 1790
+ },
+ {
+ "left_line": 1791,
+ "right_line": 1791
+ },
+ {
+ "left_line": 1792,
+ "right_line": 1792
+ },
+ {
+ "left_line": 1793,
+ "right_line": 1793
+ },
+ {
+ "left_line": 1794,
+ "right_line": 1794
+ },
+ {
+ "left_line": 1795,
+ "right_line": 1795
+ },
+ {
+ "left_line": 1796,
+ "right_line": 1796
+ },
+ {
+ "left_line": 1797,
+ "right_line": 1797
+ },
+ {
+ "left_line": 1798,
+ "right_line": 1798
+ },
+ {
+ "left_line": 1799,
+ "right_line": 1799
+ },
+ {
+ "left_line": 1800,
+ "right_line": 1800
+ },
+ {
+ "left_line": 1801,
+ "right_line": 1801
+ },
+ {
+ "left_line": 1802,
+ "right_line": 1802
+ },
+ {
+ "left_line": 1803,
+ "right_line": 1803
+ },
+ {
+ "left_line": 1804,
+ "right_line": 1804
+ },
+ {
+ "left_line": 1805,
+ "right_line": 1805
+ },
+ {
+ "left_line": 1806,
+ "right_line": 1806
+ },
+ {
+ "left_line": 1807,
+ "right_line": 1807
+ },
+ {
+ "left_line": 1808,
+ "right_line": 1808
+ },
+ {
+ "left_line": 1809,
+ "right_line": 1809
+ },
+ {
+ "left_line": 1810,
+ "right_line": 1810
+ },
+ {
+ "left_line": 1811,
+ "right_line": 1811
+ },
+ {
+ "left_line": 1812,
+ "right_line": 1812
+ },
+ {
+ "left_line": 1813,
+ "right_line": 1813
+ },
+ {
+ "left_line": 1814,
+ "right_line": 1814
+ },
+ {
+ "left_line": 1815,
+ "right_line": 1815
+ },
+ {
+ "left_line": 1816,
+ "right_line": 1816
+ },
+ {
+ "left_line": 1817,
+ "right_line": 1817
+ },
+ {
+ "left_line": 1818,
+ "right_line": 1818
+ },
+ {
+ "left_line": 1819,
+ "right_line": 1819
+ },
+ {
+ "left_line": 1820,
+ "right_line": 1820
+ },
+ {
+ "left_line": 1821,
+ "right_line": 1821
+ },
+ {
+ "left_line": 1822,
+ "right_line": 1822
+ },
+ {
+ "left_line": 1823,
+ "right_line": 1823
+ },
+ {
+ "left_line": 1824,
+ "right_line": 1824
+ },
+ {
+ "left_line": 1825,
+ "right_line": 1825
+ },
+ {
+ "left_line": 1826,
+ "right_line": 1826
+ },
+ {
+ "left_line": 1827,
+ "right_line": 1827
+ },
+ {
+ "left_line": 1828,
+ "right_line": 1828
+ },
+ {
+ "left_line": 1829,
+ "right_line": 1829
+ },
+ {
+ "left_line": 1830,
+ "right_line": 1830
+ },
+ {
+ "left_line": 1831,
+ "right_line": 1831
+ },
+ {
+ "left_line": 1832,
+ "right_line": 1832
+ },
+ {
+ "left_line": 1833,
+ "right_line": 1833
+ },
+ {
+ "left_line": 1834,
+ "right_line": 1834
+ },
+ {
+ "left_line": 1835,
+ "right_line": 1835
+ },
+ {
+ "left_line": 1836,
+ "right_line": 1836
+ },
+ {
+ "left_line": 1837,
+ "right_line": 1837
+ },
+ {
+ "left_line": 1838,
+ "right_line": 1838
+ },
+ {
+ "left_line": 1839,
+ "right_line": 1839
+ },
+ {
+ "left_line": 1840,
+ "right_line": 1840
+ },
+ {
+ "left_line": 1841,
+ "right_line": 1841
+ },
+ {
+ "left_line": 1842,
+ "right_line": 1842
+ },
+ {
+ "left_line": 1843,
+ "right_line": 1843
+ },
+ {
+ "left_line": 1844,
+ "right_line": 1844
+ },
+ {
+ "left_line": 1845,
+ "right_line": 1845
+ },
+ {
+ "left_line": 1846,
+ "right_line": 1846
+ },
+ {
+ "left_line": 1847,
+ "right_line": 1847
+ },
+ {
+ "left_line": 1848,
+ "right_line": 1848
+ },
+ {
+ "left_line": 1849,
+ "right_line": 1849
+ },
+ {
+ "left_line": 1850,
+ "right_line": 1850
+ },
+ {
+ "left_line": 1851,
+ "right_line": 1851
+ },
+ {
+ "left_line": 1852,
+ "right_line": 1852
+ },
+ {
+ "left_line": 1853,
+ "right_line": 1853
+ },
+ {
+ "left_line": 1854,
+ "right_line": 1854
+ },
+ {
+ "left_line": 1855,
+ "right_line": 1855
+ },
+ {
+ "left_line": 1856,
+ "right_line": 1856
+ },
+ {
+ "left_line": 1857,
+ "right_line": 1857
+ },
+ {
+ "left_line": 1858,
+ "right_line": 1858
+ },
+ {
+ "left_line": 1859,
+ "right_line": 1859
+ },
+ {
+ "left_line": 1860,
+ "right_line": 1860
+ },
+ {
+ "left_line": 1861,
+ "right_line": 1861
+ },
+ {
+ "left_line": 1862,
+ "right_line": 1862
+ },
+ {
+ "left_line": 1863,
+ "right_line": 1863
+ },
+ {
+ "left_line": 1864,
+ "right_line": 1864
+ },
+ {
+ "left_line": 1865,
+ "right_line": 1865
+ },
+ {
+ "left_line": 1866,
+ "right_line": 1866
+ },
+ {
+ "left_line": 1867,
+ "right_line": 1867
+ },
+ {
+ "left_line": 1868,
+ "right_line": 1868
+ },
+ {
+ "left_line": 1869,
+ "right_line": 1869
+ },
+ {
+ "left_line": 1870,
+ "right_line": 1870
+ },
+ {
+ "left_line": 1871,
+ "right_line": 1871
+ },
+ {
+ "left_line": 1872,
+ "right_line": 1872
+ },
+ {
+ "left_line": 1873,
+ "right_line": 1873
+ },
+ {
+ "left_line": 1874,
+ "right_line": 1874
+ },
+ {
+ "left_line": 1875,
+ "right_line": 1875
+ },
+ {
+ "left_line": 1876,
+ "right_line": 1876
+ },
+ {
+ "left_line": 1877,
+ "right_line": 1877
+ },
+ {
+ "left_line": 1878,
+ "right_line": 1878
+ },
+ {
+ "left_line": 1879,
+ "right_line": 1879
+ },
+ {
+ "left_line": 1880,
+ "right_line": 1880
+ },
+ {
+ "left_line": 1881,
+ "right_line": 1881
+ },
+ {
+ "left_line": 1882,
+ "right_line": 1882
+ },
+ {
+ "left_line": 1883,
+ "right_line": 1883
+ },
+ {
+ "left_line": 1884,
+ "right_line": 1884
+ },
+ {
+ "left_line": 1885,
+ "right_line": 1885
+ },
+ {
+ "left_line": 1886,
+ "right_line": 1886
+ },
+ {
+ "left_line": 1887,
+ "right_line": 1887
+ },
+ {
+ "left_line": 1888,
+ "right_line": 1888
+ },
+ {
+ "left_line": 1889,
+ "right_line": 1889
+ },
+ {
+ "left_line": 1890,
+ "right_line": 1890
+ },
+ {
+ "left_line": 1891,
+ "right_line": 1891
+ },
+ {
+ "left_line": 1892,
+ "right_line": 1892
+ },
+ {
+ "left_line": 1893,
+ "right_line": 1893
+ },
+ {
+ "left_line": 1894,
+ "right_line": 1894
+ },
+ {
+ "left_line": 1895,
+ "right_line": 1895
+ },
+ {
+ "left_line": 1896,
+ "right_line": 1896
+ },
+ {
+ "left_line": 1897,
+ "right_line": 1897
+ },
+ {
+ "left_line": 1898,
+ "right_line": 1898
+ },
+ {
+ "left_line": 1899,
+ "right_line": 1899
+ },
+ {
+ "left_line": 1900,
+ "right_line": 1900
+ },
+ {
+ "left_line": 1901,
+ "right_line": 1901
+ },
+ {
+ "left_line": 1902,
+ "right_line": 1902
+ },
+ {
+ "left_line": 1903,
+ "right_line": 1903
+ },
+ {
+ "left_line": 1904,
+ "right_line": 1904
+ },
+ {
+ "left_line": 1905,
+ "right_line": 1905
+ },
+ {
+ "left_line": 1906,
+ "right_line": 1906
+ },
+ {
+ "left_line": 1907,
+ "right_line": 1907
+ },
+ {
+ "left_line": 1908,
+ "right_line": 1908
+ },
+ {
+ "left_line": 1909,
+ "right_line": 1909
+ },
+ {
+ "left_line": 1910,
+ "right_line": 1910
+ },
+ {
+ "left_line": 1911,
+ "right_line": 1911
+ },
+ {
+ "left_line": 1912,
+ "right_line": 1912
+ },
+ {
+ "left_line": 1913,
+ "right_line": 1913
+ },
+ {
+ "left_line": 1914,
+ "right_line": 1914
+ },
+ {
+ "left_line": 1915,
+ "right_line": 1915
+ },
+ {
+ "left_line": 1916,
+ "right_line": 1916
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/testdata/ts_zod_improve_mini/metadata.json b/tests/testdata/ts_zod_improve_mini/metadata.json
new file mode 100644
index 0000000..4845110
--- /dev/null
+++ b/tests/testdata/ts_zod_improve_mini/metadata.json
@@ -0,0 +1,12 @@
+{
+ "repo": "https://github.com/colinhacks/zod",
+ "parent": "21afffdb42ccab554036312e33fed0ea3cb8f982",
+ "commit": "e36743e513aadb307b29949a80d6eb0dcc8fc278",
+ "message": "Improve mini treeshaking",
+ "author": "Colin McDonnell",
+ "date": "2026-01-04T00:03:53-08:00",
+ "language": "typescript",
+ "files_changed": 1,
+ "insertions": 0,
+ "deletions": 0
+}
\ No newline at end of file
diff --git a/tests/testdata/ts_zod_improve_mini/new.ts b/tests/testdata/ts_zod_improve_mini/new.ts
new file mode 100644
index 0000000..0e9cedf
--- /dev/null
+++ b/tests/testdata/ts_zod_improve_mini/new.ts
@@ -0,0 +1,1916 @@
+import * as core from "../core/index.js";
+import * as util from "../core/util.js";
+import * as parse from "./parse.js";
+
+type SomeType = core.SomeType;
+
+export interface ZodMiniType<
+ out Output = unknown,
+ out Input = unknown,
+ out Internals extends core.$ZodTypeInternals