Skip to content

Commit b3f2c80

Browse files
cknittclaude
andauthored
Indent copied declarations in created interfaces at their place (#9144)
* Indent copied declarations in created interfaces at their place Why: createInterface copied a type declaration or external with its source column as indentation, so a type written after `{` on the same line was pushed far right (#9135). Format modified (createInterface output): Example: module Make = (X: {type t}) => { type u = X.t } Before (rescript): module Make: ( X: { type t }, ) => { type u = X.t } After (rescript): module Make: ( X: { type t }, ) => { type u = X.t } Review: What happens to the lines after the first? They keep their indentation relative to the line the declaration starts on, re-based onto the interface's indentation. Fixes #9135 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kbf3gHYkomifc8JyphPGqu Signed-off-by: Christoph Knittel <ck@cca.io> * Copy lines inside multi-line string literals verbatim in created interfaces Answers Codex's finding: reindenting the lines after the first also shifted lines inside a multi-line string or template literal, changing its value, such as an `@as` name. The extractor now scans the copied text and leaves lines that start inside a literal as they are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Kbf3gHYkomifc8JyphPGqu Signed-off-by: Christoph Knittel <ck@cca.io> --------- Signed-off-by: Christoph Knittel <ck@cca.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 8eeb8dc commit b3f2c80

4 files changed

Lines changed: 149 additions & 9 deletions

File tree

‎analysis/src/create_interface.ml‎

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,99 @@
11
module Source_file_extractor = struct
2-
let extract lines ~pos_start ~pos_end =
2+
let leading_spaces line =
3+
let len = String.length line in
4+
let rec go i = if i < len && line.[i] = ' ' then go (i + 1) else i in
5+
go 0
6+
7+
(* For each line from [line_start] to [line_end], whether it begins inside
8+
a string or template literal that starts on an earlier line. Such lines
9+
are part of the literal's value, so reindenting them would change it. *)
10+
let literal_lines lines ~line_start ~col_start ~line_end =
11+
let first = lines.(line_start) in
12+
let col_start = max 0 (min col_start (String.length first)) in
13+
let src =
14+
String.concat "\n"
15+
(String.sub first col_start (String.length first - col_start)
16+
:: Array.to_list
17+
(Array.sub lines (line_start + 1) (line_end - line_start)))
18+
in
19+
let inside = Array.make (line_end - line_start + 1) false in
20+
let mark (start_pos : Lexing.position) (end_pos : Lexing.position) =
21+
for n = start_pos.pos_lnum to end_pos.pos_lnum - 1 do
22+
inside.(n) <- true
23+
done
24+
in
25+
let scanner = Res_scanner.make ~filename:"" src in
26+
(* Scan the rest of a template literal, whose backtick is at [start_pos],
27+
including the expressions in its interpolations. *)
28+
let rec template start_pos =
29+
let _, end_pos, token = Res_scanner.scan_template_literal_token scanner in
30+
match token with
31+
| Res_token.TemplatePart _ ->
32+
if interpolation 0 then template start_pos else mark start_pos end_pos
33+
| _ -> mark start_pos end_pos
34+
(* Scan up to the [}] closing an interpolation; false at the end of the
35+
text. *)
36+
and interpolation depth =
37+
match Res_scanner.scan scanner with
38+
| _, _, Res_token.Eof -> false
39+
| _, _, Rbrace when depth = 0 -> true
40+
| _, _, Rbrace -> interpolation (depth - 1)
41+
| _, _, Lbrace -> interpolation (depth + 1)
42+
| start_pos, _, Backtick ->
43+
template start_pos;
44+
interpolation depth
45+
| _ -> interpolation depth
46+
in
47+
let rec go () =
48+
match Res_scanner.scan scanner with
49+
| _, _, Res_token.Eof -> ()
50+
| start_pos, end_pos, String _ ->
51+
mark start_pos end_pos;
52+
go ()
53+
| start_pos, _, Backtick ->
54+
template start_pos;
55+
go ()
56+
| _ -> go ()
57+
in
58+
go ();
59+
inside
60+
61+
(* The source text from [pos_start] to [pos_end], indented by [indent]:
62+
the lines after the first keep their indentation relative to the line
63+
the text starts on, except lines inside a string or template literal,
64+
which are copied verbatim. *)
65+
let extract lines ~indent ~pos_start ~pos_end =
366
let line_start, col_start = pos_start in
467
let line_end, col_end = pos_end in
568
let res = ref [] in
669
if line_start < 0 || line_start > line_end || line_end >= Array.length lines
770
then []
8-
else (
71+
else
72+
let base = min col_start (leading_spaces lines.(line_start)) in
73+
let in_literal = literal_lines lines ~line_start ~col_start ~line_end in
74+
let reindent n line =
75+
let drop = min base (leading_spaces line) in
76+
if in_literal.(n - line_start) then line
77+
else if drop = String.length line then ""
78+
else indent ^ String.sub line drop (String.length line - drop)
79+
in
980
for n = line_end downto line_start do
1081
let line = lines.(n) in
1182
let len = String.length line in
1283
if n = line_start && n = line_end then (
1384
if col_start >= 0 && col_start < col_end && col_end <= len then
14-
let indent = String.make col_start ' ' in
1585
res :=
1686
(indent ^ String.sub line col_start (col_end - col_start)) :: !res)
1787
else if n = line_start then (
1888
if col_start >= 0 && col_start < len then
19-
let indent = String.make col_start ' ' in
2089
res :=
2190
(indent ^ String.sub line col_start (len - col_start)) :: !res)
2291
else if n = line_end then (
2392
if col_end > 0 && col_end <= len then
24-
res := String.sub line 0 col_end :: !res)
25-
else res := line :: !res
93+
res := reindent n (String.sub line 0 col_end) :: !res)
94+
else res := reindent n line :: !res
2695
done;
27-
!res)
96+
!res
2897
end
2998

3099
module Attributes_utils : sig
@@ -280,7 +349,7 @@ let print_signature ~path ~extractor ~signature =
280349
(* Rescript primitive name, e.g. @val external ... *)
281350
let lines =
282351
let pos_start, pos_end = Loc.range val_loc in
283-
extractor |> Source_file_extractor.extract ~pos_start ~pos_end
352+
extractor |> Source_file_extractor.extract ~indent ~pos_start ~pos_end
284353
in
285354
let attributes = Attributes_utils.make lines in
286355

@@ -304,7 +373,8 @@ let print_signature ~path ~extractor ~signature =
304373
let lines =
305374
let pos_start, pos_end = Loc.range type_decl.type_loc in
306375
let pos_start = attributes_start ~extractor type_decl pos_start in
307-
extractor |> Source_file_extractor.extract ~pos_start ~pos_end
376+
extractor
377+
|> Source_file_extractor.extract ~indent ~pos_start ~pos_end
308378
in
309379
(* Copy the type declaration verbatim to preserve attributes *)
310380
Buffer.add_string buf ((lines |> String.concat "\n") ^ "\n")
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Indent type declarations and externals copied by `createInterface` at their place in the interface instead of by their source column.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// ^int
2+
3+
module Make = (X: {type t}) => {
4+
type u = X.t
5+
}
6+
7+
module M = {type t = int}
8+
9+
module N = {type r = {
10+
a: int,
11+
b: string,
12+
}}
13+
14+
module P = {
15+
@val external now: unit => float = "Date.now"
16+
type v =
17+
| A
18+
| B
19+
}
20+
21+
module Q = {
22+
type w = {
23+
@as("first
24+
second") field: int,
25+
}
26+
@module("a
27+
b") external multi: unit => unit = "default"
28+
type x = {
29+
@as(`one
30+
two`) other: int,
31+
}
32+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
Create Interface src/CreateInterfaceIndent.res
2+
module Make: (
3+
X: {
4+
type t
5+
},
6+
) =>
7+
{
8+
type u = X.t
9+
}
10+
module M: {
11+
type t = int
12+
}
13+
module N: {
14+
type r = {
15+
a: int,
16+
b: string,
17+
}
18+
}
19+
module P: {
20+
@val external now: unit => float = "Date.now"
21+
type v =
22+
| A
23+
| B
24+
}
25+
module Q: {
26+
type w = {
27+
@as("first
28+
second") field: int,
29+
}
30+
@module("a
31+
b") external multi: unit => unit = "default"
32+
type x = {
33+
@as(`one
34+
two`) other: int,
35+
}
36+
}
37+

0 commit comments

Comments
 (0)