Skip to content

Commit 3a48972

Browse files
authored
Merge pull request #22233 from github/tausbn/swift-syntax-rs-sequenced
unified: Switch over to using `swift-syntax` for parsing
2 parents 4d885a8 + ceffe40 commit 3a48972

135 files changed

Lines changed: 7748 additions & 3967 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

ql/Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ql/extractor/src/extractor.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,28 +29,24 @@ pub fn run(options: Options) -> std::io::Result<()> {
2929
prefix: "ql",
3030
ts_language: tree_sitter_ql::LANGUAGE.into(),
3131
node_types: tree_sitter_ql::NODE_TYPES,
32-
desugar: None,
3332
file_globs: vec!["*.ql".into(), "*.qll".into()],
3433
},
3534
simple::LanguageSpec {
3635
prefix: "dbscheme",
3736
ts_language: tree_sitter_ql_dbscheme::LANGUAGE.into(),
3837
node_types: tree_sitter_ql_dbscheme::NODE_TYPES,
39-
desugar: None,
4038
file_globs: vec!["*.dbscheme".into()],
4139
},
4240
simple::LanguageSpec {
4341
prefix: "json",
4442
ts_language: tree_sitter_json::LANGUAGE.into(),
4543
node_types: tree_sitter_json::NODE_TYPES,
46-
desugar: None,
4744
file_globs: vec!["*.json".into(), "*.jsonl".into(), "*.jsonc".into()],
4845
},
4946
simple::LanguageSpec {
5047
prefix: "blame",
5148
ts_language: tree_sitter_blame::LANGUAGE.into(),
5249
node_types: tree_sitter_blame::NODE_TYPES,
53-
desugar: None,
5450
file_globs: vec!["*.blame".into()],
5551
},
5652
],

ruby/extractor/src/extractor.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ pub fn run(options: Options) -> std::io::Result<()> {
9494
node_types::read_node_types_str("erb", tree_sitter_embedded_template::NODE_TYPES)?;
9595
let lines: std::io::Result<Vec<String>> = std::io::BufReader::new(file_list).lines().collect();
9696
let lines = lines?;
97-
let source_root = std::env::current_dir().ok().and_then(|d| d.canonicalize().ok());
97+
let source_root = std::env::current_dir()
98+
.ok()
99+
.and_then(|d| d.canonicalize().ok());
98100
lines
99101
.par_iter()
100102
.try_for_each(|line| {
@@ -126,7 +128,6 @@ pub fn run(options: Options) -> std::io::Result<()> {
126128
&path,
127129
&source,
128130
&[],
129-
None,
130131
);
131132

132133
let (ranges, line_breaks) = scan_erb(
@@ -215,7 +216,6 @@ pub fn run(options: Options) -> std::io::Result<()> {
215216
&path,
216217
&source,
217218
&code_ranges,
218-
None,
219219
);
220220
std::fs::create_dir_all(src_archive_file.parent().unwrap())?;
221221
if needs_conversion {
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//! Extraction for languages that rewrite their syntax tree before extraction.
2+
//!
3+
//! Unlike [`crate::extractor::simple`] (direct tree-sitter extraction), a
4+
//! desugaring language parses source into a [`ParsedTree`] — a `yeast::Ast`
5+
//! plus side-channel `extra` tokens (comments and similar) — and rewrites the
6+
//! AST through a [`yeast::Desugarer`] before emitting TRAP. The parser is a
7+
//! closure, so both tree-sitter grammars (via
8+
//! [`crate::extractor::tree_sitter_parser`]) and fully custom parsers plug in
9+
//! the same way.
10+
11+
use crate::trap;
12+
use std::path::PathBuf;
13+
14+
use crate::diagnostics;
15+
use crate::extractor::ParsedTree;
16+
use crate::extractor::driver::{self, LanguageExtractor};
17+
use crate::node_types::{self, NodeTypeMap};
18+
19+
/// A parser turns source bytes into a [`ParsedTree`]. Tree-sitter grammars plug
20+
/// in via [`crate::extractor::tree_sitter_parser`]; custom (non-tree-sitter)
21+
/// parsers supply their own closure.
22+
pub type Parser = Box<dyn Fn(&[u8]) -> Result<ParsedTree, String> + Send + Sync>;
23+
24+
pub struct LanguageSpec {
25+
pub prefix: &'static str,
26+
/// The parser: source -> `yeast::Ast` + `extra` tokens (see [`Parser`]).
27+
pub parser: Parser,
28+
/// Fallback TRAP schema, used only when `desugarer` does not supply its own
29+
/// output schema (via [`yeast::Desugarer::output_node_types_yaml`]). May be
30+
/// empty for a custom parser whose desugarer always provides the schema.
31+
pub node_types: &'static str,
32+
/// The desugarer applied to the parsed AST before extraction. Its
33+
/// `output_node_types_yaml()` (when set) provides the TRAP schema.
34+
///
35+
/// `Box<dyn yeast::Desugarer>` so the shared extractor is agnostic to the
36+
/// user-defined context type the desugarer uses internally.
37+
pub desugarer: Box<dyn yeast::Desugarer>,
38+
pub file_globs: Vec<String>,
39+
}
40+
41+
impl LanguageExtractor for LanguageSpec {
42+
fn file_globs(&self) -> &[String] {
43+
&self.file_globs
44+
}
45+
46+
fn build_schema(&self) -> std::io::Result<NodeTypeMap> {
47+
let effective_node_types: String = match self.desugarer.output_node_types_yaml() {
48+
Some(yaml) => yeast::node_types_yaml::convert(yaml).map_err(|e| {
49+
std::io::Error::other(format!(
50+
"Failed to convert YAML node-types to JSON for {}: {e}",
51+
self.prefix
52+
))
53+
})?,
54+
None => self.node_types.to_string(),
55+
};
56+
node_types::read_node_types_str(self.prefix, &effective_node_types)
57+
}
58+
59+
fn extract_file(
60+
&self,
61+
schema: &NodeTypeMap,
62+
diagnostics_writer: &mut diagnostics::LogWriter,
63+
trap_writer: &mut trap::Writer,
64+
path: &std::path::Path,
65+
source: &[u8],
66+
) {
67+
crate::extractor::extract_parsed(
68+
self.parser.as_ref(),
69+
self.prefix,
70+
schema,
71+
diagnostics_writer,
72+
trap_writer,
73+
None,
74+
path,
75+
source,
76+
self.desugarer.as_ref(),
77+
);
78+
}
79+
}
80+
81+
pub struct Extractor {
82+
pub prefix: String,
83+
pub languages: Vec<LanguageSpec>,
84+
pub trap_dir: PathBuf,
85+
pub source_archive_dir: PathBuf,
86+
pub file_lists: Vec<PathBuf>,
87+
// Typically constructed via `trap::Compression::from_env`.
88+
// This allow us to report the error using our diagnostics system
89+
// without exposing it to consumers.
90+
pub trap_compression: Result<trap::Compression, String>,
91+
}
92+
93+
impl Extractor {
94+
pub fn run(&self) -> std::io::Result<()> {
95+
driver::run_extractor(
96+
&self.prefix,
97+
&self.languages,
98+
&self.trap_dir,
99+
&self.source_archive_dir,
100+
&self.file_lists,
101+
&self.trap_compression,
102+
)
103+
}
104+
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
//! Shared multi-file extraction driver.
2+
//!
3+
//! The `simple` (direct tree-sitter) and `desugaring` (parse + desugar)
4+
//! extractors differ only in how a language's schema is built and how a single
5+
//! file is extracted. Everything else — threading, matching files to languages
6+
//! by glob, writing TRAP, and copying into the source archive — is identical
7+
//! and lives here, parameterised over the [`LanguageExtractor`] trait.
8+
9+
use globset::{GlobBuilder, GlobSetBuilder};
10+
use rayon::prelude::*;
11+
use std::fs::File;
12+
use std::io::BufRead;
13+
use std::path::{Path, PathBuf};
14+
15+
use crate::diagnostics;
16+
use crate::file_paths;
17+
use crate::node_types::NodeTypeMap;
18+
use crate::trap;
19+
20+
/// A language that [`run_extractor`] can process: it knows its file globs, its
21+
/// TRAP schema, and how to extract a single file. Implemented by
22+
/// [`super::simple::LanguageSpec`] (direct tree-sitter extraction) and
23+
/// [`super::desugaring::LanguageSpec`] (parse into an AST and desugar it).
24+
pub(crate) trait LanguageExtractor: Sync {
25+
/// The file-name globs that select files for this language.
26+
fn file_globs(&self) -> &[String];
27+
/// Build the TRAP node-type schema used to validate emitted tuples.
28+
fn build_schema(&self) -> std::io::Result<NodeTypeMap>;
29+
/// Extract a single file's `source` into `trap_writer`.
30+
fn extract_file(
31+
&self,
32+
schema: &NodeTypeMap,
33+
diagnostics_writer: &mut diagnostics::LogWriter,
34+
trap_writer: &mut trap::Writer,
35+
path: &Path,
36+
source: &[u8],
37+
);
38+
}
39+
40+
/// Drive extraction over `languages` for every file listed in `file_lists`.
41+
///
42+
/// Sets up the thread pool, builds a combined glob set, and for each input file
43+
/// dispatches to the matching language's [`LanguageExtractor::extract_file`],
44+
/// writing the resulting TRAP and a source-archive copy.
45+
pub(crate) fn run_extractor<L: LanguageExtractor>(
46+
prefix: &str,
47+
languages: &[L],
48+
trap_dir: &Path,
49+
source_archive_dir: &Path,
50+
file_lists: &[PathBuf],
51+
trap_compression: &Result<trap::Compression, String>,
52+
) -> std::io::Result<()> {
53+
tracing::info!("Extraction started");
54+
let diagnostics = diagnostics::DiagnosticLoggers::new(prefix);
55+
let mut main_thread_logger = diagnostics.logger();
56+
let num_threads = match crate::options::num_threads() {
57+
Ok(num) => num,
58+
Err(e) => {
59+
main_thread_logger.write(
60+
main_thread_logger
61+
.new_entry("configuration-error", "Configuration error")
62+
.message(
63+
"{}; defaulting to 1 thread.",
64+
&[diagnostics::MessageArg::Code(&e)],
65+
)
66+
.severity(diagnostics::Severity::Warning),
67+
);
68+
1
69+
}
70+
};
71+
tracing::info!(
72+
"Using {} {}",
73+
num_threads,
74+
if num_threads == 1 {
75+
"thread"
76+
} else {
77+
"threads"
78+
}
79+
);
80+
let trap_compression = match trap_compression {
81+
Ok(x) => *x,
82+
Err(e) => {
83+
main_thread_logger.write(
84+
main_thread_logger
85+
.new_entry("configuration-error", "Configuration error")
86+
.message("{}; using gzip.", &[diagnostics::MessageArg::Code(e)])
87+
.severity(diagnostics::Severity::Warning),
88+
);
89+
trap::Compression::Gzip
90+
}
91+
};
92+
drop(main_thread_logger);
93+
94+
rayon::ThreadPoolBuilder::new()
95+
.num_threads(num_threads)
96+
.build_global()
97+
.unwrap();
98+
99+
let file_lists: Vec<File> = file_lists
100+
.iter()
101+
.map(|file_list| {
102+
File::open(file_list)
103+
.unwrap_or_else(|_| panic!("Unable to open file list at {file_list:?}"))
104+
})
105+
.collect();
106+
107+
let mut schemas = vec![];
108+
for lang in languages {
109+
schemas.push(lang.build_schema()?);
110+
}
111+
112+
// Construct a single globset containing all language globs,
113+
// and a mapping from glob index to language index.
114+
let (globset, glob_language_mapping) = {
115+
let mut builder = GlobSetBuilder::new();
116+
let mut glob_lang_mapping = vec![];
117+
for (i, lang) in languages.iter().enumerate() {
118+
for glob_str in lang.file_globs() {
119+
let glob = GlobBuilder::new(glob_str)
120+
.literal_separator(true)
121+
.build()
122+
.expect("invalid glob");
123+
builder.add(glob);
124+
glob_lang_mapping.push(i);
125+
}
126+
}
127+
(
128+
builder.build().expect("failed to build globset"),
129+
glob_lang_mapping,
130+
)
131+
};
132+
133+
let path_transformer = file_paths::load_path_transformer()?;
134+
135+
let lines: std::io::Result<Vec<String>> = file_lists
136+
.iter()
137+
.flat_map(|file_list| std::io::BufReader::new(file_list).lines())
138+
.collect();
139+
let lines = lines?;
140+
141+
lines
142+
.par_iter()
143+
.try_for_each(|line| {
144+
let mut diagnostics_writer = diagnostics.logger();
145+
let path = PathBuf::from(line).canonicalize()?;
146+
let src_archive_file = crate::file_paths::path_for(
147+
source_archive_dir,
148+
&path,
149+
"",
150+
path_transformer.as_ref(),
151+
);
152+
let source = std::fs::read(&path)?;
153+
let mut trap_writer = trap::Writer::new();
154+
155+
match path.file_name() {
156+
None => {
157+
tracing::error!(?path, "No file name found, skipping file.");
158+
}
159+
Some(filename) => {
160+
let matches = globset.matches(filename);
161+
if matches.is_empty() {
162+
tracing::error!(?path, "No matching language found, skipping file.");
163+
} else {
164+
let mut languages_processed = vec![false; languages.len()];
165+
166+
for m in matches {
167+
let i = glob_language_mapping[m];
168+
if languages_processed[i] {
169+
continue;
170+
}
171+
languages_processed[i] = true;
172+
let lang = &languages[i];
173+
174+
lang.extract_file(
175+
&schemas[i],
176+
&mut diagnostics_writer,
177+
&mut trap_writer,
178+
&path,
179+
&source,
180+
);
181+
std::fs::create_dir_all(src_archive_file.parent().unwrap())?;
182+
std::fs::copy(&path, &src_archive_file)?;
183+
write_trap(trap_dir, &path, &trap_writer, trap_compression)?;
184+
}
185+
}
186+
}
187+
}
188+
Ok(()) as std::io::Result<()>
189+
})
190+
.expect("failed to extract files");
191+
192+
let path = PathBuf::from("extras");
193+
let mut trap_writer = trap::Writer::new();
194+
crate::extractor::populate_empty_location(&mut trap_writer);
195+
196+
let res = write_trap(trap_dir, &path, &trap_writer, trap_compression);
197+
tracing::info!("Extraction complete");
198+
res
199+
}
200+
201+
fn write_trap(
202+
trap_dir: &Path,
203+
path: &Path,
204+
trap_writer: &trap::Writer,
205+
trap_compression: trap::Compression,
206+
) -> std::io::Result<()> {
207+
let trap_file = crate::file_paths::path_for(trap_dir, path, trap_compression.extension(), None);
208+
std::fs::create_dir_all(trap_file.parent().unwrap())?;
209+
trap_writer.write_to_file(&trap_file, trap_compression)
210+
}

0 commit comments

Comments
 (0)