Repository navigation
Unified: Add schema checking and corpus-style tests #21848
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+5,445
−4,708
Merged
Changes from 2 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
f668b99
Unified: Add support for tree-sitter-style corpus tests
asgerf 49f1909
Yeast: add reachable_node_ids()
asgerf a049850
Yeast: add type-checking errors in AST dump
asgerf c3a9218
Yeast: Add one-shot phase kind
asgerf bb9e996
Shared: Do not emit ReservedWord class when there are no unnamed tokens
asgerf 5d0cb9e
YEAST: fix one-shot rules for unnamed nodes and self-captures
asgerf 8a2a48d
Unified extractor: add AST schema, swift translation rules, and corpu…
asgerf 72b683d
Unified: Add Swift corpus tests
asgerf 5772ee4
YEAST: add NodeRef type, YeastDisplay trait, and source text storage
asgerf 9283801
Unified: Add some more AST nodes and rules
asgerf 2307839
Yeast: Change how patterns with repetition are parsed
asgerf 6b58482
Yeast: Fix text associated with synthesized nodes
asgerf a966dff
Unified: Add more patterns and some fixes to the AST
asgerf ccc1dd5
Unified: Add tuple_pattern
asgerf 3b7a53f
yeast-macros: merge repeated field declarations and support repetitio…
asgerf cbe4c81
Unified: add tuple_pattern and sequence_condition; refine if-let/guar…
asgerf 55194dd
Unified: Support for calls and member access
asgerf 600a496
Unified: Simplify concatenation of arguments
asgerf 7fa6c4e
Unified: Update test output after rebasing on grammar changes
asgerf b031e5b
Unified: regenerate QL and make tests not crash
asgerf 554bdf1
Yeast: fix warning about unnecessary mutability
asgerf File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| use codeql_extractor::extractor::simple; | ||
|
|
||
| #[path = "swift/swift.rs"] | ||
| mod swift; | ||
|
|
||
| pub fn all_language_specs() -> Vec<simple::LanguageSpec> { | ||
| vec![swift::language_spec()] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| === | ||
| Additive expression is desugared | ||
| === | ||
|
|
||
| 1 + 2 | ||
|
|
||
| --- | ||
|
|
||
| source_file | ||
| simple_identifier "blah" | ||
|
|
||
|
|
||
| === | ||
| Another additive expression is desugared | ||
| === | ||
|
|
||
| foo + bar | ||
|
|
||
| --- | ||
|
|
||
| source_file | ||
| simple_identifier "blah" | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| use std::fs; | ||
| use std::path::Path; | ||
|
|
||
| use codeql_extractor::extractor::simple; | ||
| use yeast::{dump::dump_ast, Runner}; | ||
|
|
||
| #[path = "../src/languages/mod.rs"] | ||
| mod languages; | ||
|
|
||
| #[derive(Debug)] | ||
| struct CorpusCase { | ||
| name: String, | ||
| input: String, | ||
| expected: String, | ||
| } | ||
|
|
||
| fn update_mode_enabled() -> bool { | ||
| std::env::var("UNIFIED_UPDATE_CORPUS") | ||
| .map(|v| matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on")) | ||
| .unwrap_or(false) | ||
| } | ||
|
|
||
| fn is_header_rule(line: &str) -> bool { | ||
| let trimmed = line.trim(); | ||
| trimmed.len() >= 3 && trimmed.chars().all(|c| c == '=') | ||
| } | ||
|
|
||
| fn parse_corpus(content: &str) -> Vec<CorpusCase> { | ||
| let lines: Vec<&str> = content.lines().collect(); | ||
| let mut i = 0; | ||
| let mut cases = Vec::new(); | ||
|
|
||
| while i < lines.len() { | ||
| while i < lines.len() && lines[i].trim().is_empty() { | ||
| i += 1; | ||
| } | ||
| if i >= lines.len() { | ||
| break; | ||
| } | ||
|
|
||
| assert!( | ||
| is_header_rule(lines[i]), | ||
| "Expected header delimiter at line {}", | ||
| i + 1 | ||
| ); | ||
| i += 1; | ||
|
|
||
| assert!(i < lines.len(), "Missing test name at line {}", i + 1); | ||
| let name = lines[i].trim().to_string(); | ||
| i += 1; | ||
|
|
||
| assert!( | ||
| i < lines.len() && is_header_rule(lines[i]), | ||
| "Missing closing header delimiter for case {name}" | ||
| ); | ||
| i += 1; | ||
|
|
||
| let input_start = i; | ||
| while i < lines.len() && lines[i].trim() != "---" { | ||
| i += 1; | ||
| } | ||
| assert!(i < lines.len(), "Missing --- separator for case {name}"); | ||
| let input = lines[input_start..i].join("\n").trim_end().to_string(); | ||
| i += 1; | ||
|
|
||
| let expected_start = i; | ||
| while i < lines.len() { | ||
| if is_header_rule(lines[i]) | ||
| && i + 2 < lines.len() | ||
| && !lines[i + 1].trim().is_empty() | ||
| && is_header_rule(lines[i + 2]) | ||
| { | ||
| break; | ||
| } | ||
| i += 1; | ||
| } | ||
| let expected = lines[expected_start..i].join("\n").trim().to_string(); | ||
|
|
||
| cases.push(CorpusCase { | ||
| name, | ||
| input, | ||
| expected, | ||
| }); | ||
| } | ||
|
|
||
| cases | ||
| } | ||
|
|
||
| fn render_corpus(cases: &[CorpusCase]) -> String { | ||
| let mut out = String::new(); | ||
|
|
||
| for (idx, case) in cases.iter().enumerate() { | ||
| if idx > 0 { | ||
| out.push('\n'); | ||
| } | ||
| out.push_str("===\n"); | ||
| out.push_str(case.name.trim()); | ||
| out.push_str("\n===\n"); | ||
| out.push('\n'); | ||
| out.push_str(case.input.trim()); | ||
| out.push_str("\n\n---\n"); | ||
| out.push('\n'); | ||
| out.push_str(case.expected.trim()); | ||
| out.push_str("\n\n"); | ||
| } | ||
|
|
||
| out | ||
| } | ||
|
|
||
| fn run_desugaring(lang: &simple::LanguageSpec, input: &str) -> String { | ||
| let runner = match lang.desugar.as_ref() { | ||
| Some(config) => Runner::from_config(lang.ts_language.clone(), config) | ||
| .expect("Failed to create yeast runner from desugaring config"), | ||
| None => Runner::new(lang.ts_language.clone(), &[]), | ||
| }; | ||
| let ast = runner | ||
| .run(input) | ||
| .unwrap_or_else(|e| panic!("Failed to parse corpus input: {e}")); | ||
| dump_ast(&ast, ast.get_root(), input) | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_corpus() { | ||
| let update_mode = update_mode_enabled(); | ||
| let all_languages = languages::all_language_specs(); | ||
| let corpus_dir = Path::new("tests/corpus"); | ||
|
|
||
| for lang in all_languages { | ||
| let lang_corpus_dir = corpus_dir.join(&lang.prefix); | ||
| if !lang_corpus_dir.exists() { | ||
| continue; | ||
| } | ||
|
|
||
| let mut corpus_files: Vec<_> = fs::read_dir(&lang_corpus_dir) | ||
| .unwrap_or_else(|e| { | ||
| panic!( | ||
| "Failed to read corpus directory {}: {e}", | ||
| lang_corpus_dir.display() | ||
| ) | ||
| }) | ||
| .map(|entry| entry.expect("Failed to read corpus entry").path()) | ||
| .filter(|path| path.extension().is_some_and(|ext| ext == "txt")) | ||
| .collect(); | ||
| corpus_files.sort(); | ||
|
|
||
| for corpus_path in corpus_files { | ||
| let content = fs::read_to_string(&corpus_path) | ||
| .unwrap_or_else(|e| panic!("Failed to read {}: {e}", corpus_path.display())); | ||
| let mut cases = parse_corpus(&content); | ||
| assert!( | ||
| !cases.is_empty(), | ||
| "No corpus cases found in {}", | ||
| corpus_path.display() | ||
| ); | ||
|
|
||
| for case in &mut cases { | ||
| let actual = run_desugaring(&lang, &case.input); | ||
| if update_mode { | ||
| case.expected = actual.trim().to_string(); | ||
| } else { | ||
| assert_eq!( | ||
| case.expected.trim(), | ||
| actual.trim(), | ||
| "Corpus case failed in {}: {}", | ||
| corpus_path.display(), | ||
| case.name | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| if update_mode { | ||
| let updated = render_corpus(&cases); | ||
| fs::write(&corpus_path, updated).unwrap_or_else(|e| { | ||
| panic!( | ||
| "Failed to update corpus file {}: {e}", | ||
| corpus_path.display() | ||
| ) | ||
| }); | ||
| } | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| #!/bin/bash | ||
| set -euo pipefail | ||
| IFS=$'\n\t' | ||
|
|
||
| cd "$(dirname "$0")/.." | ||
|
|
||
| cd extractor | ||
| UNIFIED_UPDATE_CORPUS=1 cargo test |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how this will be used, but I note that this might return the same id several times if the AST turns out to in fact be a DAG (which I don't think is something we check for and/or prevent currently).