Skip to content

Commit 94b78bf

Browse files
Copilotaschackmull
andauthored
Keep import member-chain locations precise
Co-authored-by: aschackmull <28296824+aschackmull@users.noreply.github.com>
1 parent 496cd17 commit 94b78bf

3 files changed

Lines changed: 77 additions & 5 deletions

File tree

‎unified/extractor/src/languages/swift/swift.rs‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,21 @@ fn member_chain(
115115
ctx: &mut yeast::build::BuildCtx<'_, SwiftContext>,
116116
parts: Vec<yeast::Id>,
117117
) -> yeast::Id {
118+
// `member_chain` builds the imported expression inside the larger import
119+
// declaration rule. The imported expression should span the import path,
120+
// not the whole declaration including the `import` keyword.
121+
let source_range = ctx.source_range.take();
118122
let mut iter = parts.into_iter();
119123
let first = iter
120124
.next()
121125
.expect("identifier with `part:` must have at least one part");
122126
let init = tree!((name_expr identifier: (identifier #{first})));
123-
iter.fold(
127+
let result = iter.fold(
124128
init,
125129
|acc, elem| tree!((member_access_expr base: {acc} member: (identifier #{elem}))),
126-
)
130+
);
131+
ctx.source_range = source_range;
132+
result
127133
}
128134

129135
/// Compound-assignment operator spellings (`+=`, `<<=`, ...). Used to tell a

‎unified/extractor/tests/corpus_tests.rs‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,8 @@ fn collect_corpus_stems(dir: &Path, out: &mut Vec<std::path::PathBuf>) {
9898

9999
#[cfg(bazel)]
100100
fn corpus_dir() -> std::path::PathBuf {
101-
let base = std::path::PathBuf::from(
102-
std::env::var("RUNFILES_DIR").expect("RUNFILES_DIR not set"),
103-
);
101+
let base =
102+
std::path::PathBuf::from(std::env::var("RUNFILES_DIR").expect("RUNFILES_DIR not set"));
104103
std::fs::read_dir(&base)
105104
.expect("failed to read RUNFILES_DIR")
106105
.filter_map(Result::ok)

‎unified/extractor/tests/swift_syntax_pipeline.rs‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,39 @@ mod languages;
1616
/// A real `swift-syntax-rs` JSON dump of the Swift source `let x = 1`.
1717
const LET_X_JSON: &str = include_str!("fixtures/let_x.swiftsyntax.json");
1818

19+
const IMPORT_FOUNDATION_JSON: &str = r#"{
20+
"kind": "sourceFile",
21+
"range": {"start":{"offset":0,"line":1,"column":1},"end":{"offset":17,"line":1,"column":18}},
22+
"statements": [
23+
{
24+
"kind": "codeBlockItem",
25+
"range": {"start":{"offset":0,"line":1,"column":1},"end":{"offset":17,"line":1,"column":18}},
26+
"item": {
27+
"kind": "importDecl",
28+
"range": {"start":{"offset":0,"line":1,"column":1},"end":{"offset":17,"line":1,"column":18}},
29+
"importKeyword": {
30+
"kind": "token",
31+
"tokenKind": "keyword(SwiftSyntax.Keyword.import)",
32+
"text": "import",
33+
"range": {"start":{"offset":0,"line":1,"column":1},"end":{"offset":6,"line":1,"column":7}}
34+
},
35+
"path": [
36+
{
37+
"kind": "importPathComponent",
38+
"range": {"start":{"offset":7,"line":1,"column":8},"end":{"offset":17,"line":1,"column":18}},
39+
"name": {
40+
"kind": "token",
41+
"tokenKind": "identifier(\"Foundation\")",
42+
"text": "Foundation",
43+
"range": {"start":{"offset":7,"line":1,"column":8},"end":{"offset":17,"line":1,"column":18}}
44+
}
45+
}
46+
]
47+
}
48+
}
49+
]
50+
}"#;
51+
1952
#[test]
2053
fn swift_syntax_json_runs_through_the_desugarer() {
2154
let lang = languages::all_language_specs()
@@ -47,3 +80,37 @@ fn swift_syntax_json_runs_through_the_desugarer() {
4780
assert!(dump.contains("top_level"), "unexpected dump: {dump}");
4881
assert!(dump.contains("block"), "unexpected dump: {dump}");
4982
}
83+
84+
#[test]
85+
fn import_name_expr_location_excludes_import_keyword() {
86+
let lang = languages::all_language_specs()
87+
.into_iter()
88+
.find(|l| l.file_globs.iter().any(|g| g.contains("swift")))
89+
.expect("swift language spec");
90+
let desugarer = lang.desugarer.as_ref();
91+
let adapted = languages::swift_adapter::json_to_ast(IMPORT_FOUNDATION_JSON)
92+
.expect("adapter should succeed");
93+
94+
let desugared = desugarer
95+
.run_from_ast(adapted.ast)
96+
.expect("desugaring an import should not error");
97+
98+
let name_expr_ids: Vec<yeast::Id> = desugared
99+
.reachable_node_ids()
100+
.into_iter()
101+
.filter(|&id| {
102+
desugared
103+
.get_node(id)
104+
.is_some_and(|node| node.kind_name() == "name_expr")
105+
})
106+
.collect();
107+
assert_eq!(
108+
name_expr_ids.len(),
109+
1,
110+
"expected exactly one reachable name_expr"
111+
);
112+
113+
let name_expr = desugared.get_node(name_expr_ids[0]).unwrap();
114+
assert_eq!(name_expr.start_byte(), 7);
115+
assert_eq!(name_expr.end_byte(), 17);
116+
}

0 commit comments

Comments
 (0)