Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions crates/plotnik-lib/src/analyze/link_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,15 @@ fn field_not_on_node_type_with_suggestion() {
#[test]
fn negated_field_unknown() {
let input = indoc! {r#"
Q = (function_declaration !nme) @fn
Q = (function_declaration -nme) @fn
"#};

let res = Query::expect_invalid_linking(input);

insta::assert_snapshot!(res, @r"
error: `nme` is not a valid field
|
1 | Q = (function_declaration !nme) @fn
1 | Q = (function_declaration -nme) @fn
| ^^^
|
help: did you mean `name`?
Expand All @@ -145,15 +145,15 @@ fn negated_field_unknown() {
#[test]
fn negated_field_not_on_node_type() {
let input = indoc! {r#"
Q = (function_declaration !condition) @fn
Q = (function_declaration -condition) @fn
"#};

let res = Query::expect_invalid_linking(input);

insta::assert_snapshot!(res, @r"
error: field `condition` is not valid on this node type
|
1 | Q = (function_declaration !condition) @fn
1 | Q = (function_declaration -condition) @fn
| -------------------- ^^^^^^^^^
| |
| on `function_declaration`
Expand All @@ -165,15 +165,15 @@ fn negated_field_not_on_node_type() {
#[test]
fn negated_field_not_on_node_type_with_suggestion() {
let input = indoc! {r#"
Q = (function_declaration !parameter) @fn
Q = (function_declaration -parameter) @fn
"#};

let res = Query::expect_invalid_linking(input);

insta::assert_snapshot!(res, @r"
error: field `parameter` is not valid on this node type
|
1 | Q = (function_declaration !parameter) @fn
1 | Q = (function_declaration -parameter) @fn
| -------------------- ^^^^^^^^^
| |
| on `function_declaration`
Expand All @@ -186,7 +186,7 @@ fn negated_field_not_on_node_type_with_suggestion() {
#[test]
fn negated_field_valid() {
let input = indoc! {r#"
Q = (function_declaration !name) @fn
Q = (function_declaration -name) @fn
"#};

Query::expect_valid_linking(input);
Expand Down
3 changes: 0 additions & 3 deletions crates/plotnik-lib/src/bytecode/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ pub const MAGIC: [u8; 4] = *b"PTKQ";
/// Current bytecode format version.
pub const VERSION: u32 = 1;

/// Terminal step - accept state.
pub const STEP_ACCEPT: u16 = 0;

/// Section alignment in bytes.
pub const SECTION_ALIGN: usize = 64;

Expand Down
2 changes: 1 addition & 1 deletion crates/plotnik-lib/src/bytecode/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ mod nav;
mod sections;
mod type_meta;

pub use constants::{MAGIC, SECTION_ALIGN, STEP_ACCEPT, STEP_SIZE, VERSION};
pub use constants::{MAGIC, SECTION_ALIGN, STEP_SIZE, VERSION};

pub use ids::{QTypeId, StepId, StringId};

Expand Down
9 changes: 7 additions & 2 deletions crates/plotnik-lib/src/diagnostics/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ pub enum DiagnosticKind {
FieldNameUppercase,
TypeNameInvalidChars,
TreeSitterSequenceSyntax,
NegationSyntaxDeprecated,

// Valid syntax, invalid semantics
DuplicateDefinition,
Expand Down Expand Up @@ -90,7 +91,9 @@ impl DiagnosticKind {
/// Default severity for this kind. Can be overridden by policy.
pub fn default_severity(&self) -> Severity {
match self {
Self::UnusedBranchLabels | Self::TreeSitterSequenceSyntax => Severity::Warning,
Self::UnusedBranchLabels
| Self::TreeSitterSequenceSyntax
| Self::NegationSyntaxDeprecated => Severity::Warning,
_ => Severity::Error,
}
}
Expand Down Expand Up @@ -137,9 +140,10 @@ impl DiagnosticKind {
match self {
Self::ExpectedSubtype => Some("e.g., `expression/binary_expression`"),
Self::ExpectedTypeName => Some("e.g., `::MyType` or `::string`"),
Self::ExpectedFieldName => Some("e.g., `!value`"),
Self::ExpectedFieldName => Some("e.g., `-value`"),
Self::EmptyTree => Some("use `(_)` to match any named node, or `_` for any node"),
Self::TreeSitterSequenceSyntax => Some("use `{...}` for sequences"),
Self::NegationSyntaxDeprecated => Some("use `-field` instead of `!field`"),
Self::MixedAltBranches => {
Some("use all labels for a tagged union, or none for a merged struct")
}
Expand Down Expand Up @@ -202,6 +206,7 @@ impl DiagnosticKind {
Self::FieldNameUppercase => "field names must be lowercase",
Self::TypeNameInvalidChars => "type names cannot contain `.` or `-`",
Self::TreeSitterSequenceSyntax => "tree-sitter sequence syntax",
Self::NegationSyntaxDeprecated => "deprecated negation syntax",

// Semantic errors
Self::DuplicateDefinition => "duplicate definition",
Expand Down
2 changes: 1 addition & 1 deletion crates/plotnik-lib/src/emit/codegen_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ fn fields_multiple() {
#[test]
fn fields_negated() {
snap!(indoc! {r#"
Test = (function_declaration name: (identifier) @name !type_parameters)
Test = (function_declaration name: (identifier) @name -type_parameters)
"#});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
source: crates/plotnik-lib/src/emit/codegen_tests.rs
---
Test = (function_declaration name: (identifier) @name !type_parameters)
Test = (function_declaration name: (identifier) @name -type_parameters)
---
[flags]
linked = false
Expand Down
4 changes: 2 additions & 2 deletions crates/plotnik-lib/src/parser/ast_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,12 @@ fn anchor() {

#[test]
fn negated_field() {
let res = Query::expect_valid_ast("Q = (function !async)");
let res = Query::expect_valid_ast("Q = (function -async)");
insta::assert_snapshot!(res, @r"
Root
Def Q
NamedNode function
NegatedField !async
NegatedField -async
");
}

Expand Down
4 changes: 4 additions & 0 deletions crates/plotnik-lib/src/parser/cst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ pub enum SyntaxKind {
#[token("!")]
Negation,

#[token("-")]
Minus,

#[token("~")]
Tilde,

Expand Down Expand Up @@ -274,6 +277,7 @@ pub mod token_sets {
SingleQuote,
Dot,
Negation,
Minus,
KwError,
KwMissing,
]);
Expand Down
2 changes: 1 addition & 1 deletion crates/plotnik-lib/src/parser/grammar/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl Parser<'_, '_> {
SyntaxKind::Underscore => self.parse_wildcard(),
SyntaxKind::SingleQuote | SyntaxKind::DoubleQuote => self.parse_str(),
SyntaxKind::Dot => self.parse_anchor(),
SyntaxKind::Negation => self.parse_negated_field(),
SyntaxKind::Negation | SyntaxKind::Minus => self.parse_negated_field(),
SyntaxKind::Id => self.parse_tree_or_field(),
SyntaxKind::KwError | SyntaxKind::KwMissing => {
self.error_and_bump(DiagnosticKind::ErrorMissingOutsideParens);
Expand Down
20 changes: 18 additions & 2 deletions crates/plotnik-lib/src/parser/grammar/fields.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,26 @@ impl Parser<'_, '_> {
self.finish_node();
}

/// Negated field assertion: `!field` (field must be absent)
/// Negated field assertion: `-field` (field must be absent)
///
/// Also accepts deprecated `!field` syntax with a warning.
pub(crate) fn parse_negated_field(&mut self) {
self.start_node(SyntaxKind::NegatedField);
self.expect(SyntaxKind::Negation, "'!' for negated field");

// Accept both `-` (preferred) and `!` (deprecated)
if self.currently_is(SyntaxKind::Negation) {
let span = self.current_span();
self.diagnostics
.report(
self.source_id,
DiagnosticKind::NegationSyntaxDeprecated,
span,
)
.emit();
self.bump();
} else {
self.expect(SyntaxKind::Minus, "'-' for negated field");
}

if !self.currently_is(SyntaxKind::Id) {
self.error(DiagnosticKind::ExpectedFieldName);
Expand Down
8 changes: 4 additions & 4 deletions crates/plotnik-lib/src/parser/tests/grammar/fields_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ fn multiple_fields() {
#[test]
fn negated_field() {
let input = indoc! {r#"
Q = (function !async)
Q = (function -async)
"#};

let res = Query::expect_valid_cst(input);
Expand All @@ -81,7 +81,7 @@ fn negated_field() {
ParenOpen "("
Id "function"
NegatedField
Negation "!"
Minus "-"
Id "async"
ParenClose ")"
"#);
Expand All @@ -91,7 +91,7 @@ fn negated_field() {
fn negated_and_regular_fields() {
let input = indoc! {r#"
Q = (function
!async
-async
name: (identifier))
"#};

Expand All @@ -106,7 +106,7 @@ fn negated_and_regular_fields() {
ParenOpen "("
Id "function"
NegatedField
Negation "!"
Minus "-"
Id "async"
Field
Id "name"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,18 @@ fn missing_type_name() {
#[test]
fn missing_negated_field_name() {
let input = indoc! {r#"
(call !)
(call -)
"#};

let res = Query::expect_invalid(input);

insta::assert_snapshot!(res, @r"
error: expected field name
|
1 | (call !)
1 | (call -)
| ^
|
help: e.g., `!value`
help: e.g., `-value`
");
}

Expand Down
25 changes: 21 additions & 4 deletions crates/plotnik-lib/src/parser/tests/recovery/validation_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -730,21 +730,21 @@ fn field_name_with_hyphens_error() {
#[test]
fn negated_field_with_upper_ident_parses() {
let input = indoc! {r#"
(call !Arguments)
(call -Arguments)
"#};

let res = Query::expect_invalid(input);

insta::assert_snapshot!(res, @r"
error: field names must be lowercase: field names become struct fields
|
1 | (call !Arguments)
1 | (call -Arguments)
| ^^^^^^^^^
|
help: use `arguments:`
|
1 - (call !Arguments)
1 + (call !arguments:)
1 - (call -Arguments)
1 + (call -arguments:)
|
");
}
Expand Down Expand Up @@ -1396,3 +1396,20 @@ fn named_node_with_children_no_warning() {
// Normal node with children - NOT a tree-sitter sequence
Query::expect_valid("Test = (identifier (child))");
}

#[test]
fn negation_syntax_deprecated_warning() {
// Old syntax `!field` is deprecated in favor of `-field`
let input = "Test = (call !name)";

let res = Query::expect_warning(input);

insta::assert_snapshot!(res, @r"
warning: deprecated negation syntax
|
1 | Test = (call !name)
| ^
|
help: use `-field` instead of `!field`
");
}
2 changes: 1 addition & 1 deletion crates/plotnik-lib/src/query/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ impl<'q> QueryPrinter<'q> {
let prefix = indent(depth);
let span = self.span_str(nf.text_range());
let name = nf.name().map(|t| t.text().to_string()).unwrap_or_default();
writeln!(w, "{}NegatedField{} !{}", prefix, span, name)
writeln!(w, "{}NegatedField{} -{}", prefix, span, name)
}

fn format_branch(
Expand Down
8 changes: 4 additions & 4 deletions crates/plotnik-lib/src/query/printer_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ fn printer_field() {

#[test]
fn printer_negated_field() {
let input = "Q = (call !name)";
let input = "Q = (call -name)";
let q = Query::expect(input);

let res = q.printer().dump();
Expand All @@ -140,7 +140,7 @@ fn printer_negated_field() {
Root
Def Q
NamedNode call
NegatedField !name
NegatedField -name
");
}

Expand Down Expand Up @@ -291,7 +291,7 @@ fn printer_symbols_broken_ref() {
#[test]
fn printer_spans_comprehensive() {
let input = indoc! {r#"
Foo = (call name: (id) !bar)
Foo = (call name: (id) -bar)
Q = [(a) (b)]
"#};
let q = Query::expect(input);
Expand All @@ -304,7 +304,7 @@ fn printer_spans_comprehensive() {
NamedNode [6..28] call
FieldExpr [12..22] name:
NamedNode [18..22] id
NegatedField [23..27] !bar
NegatedField [23..27] -bar
Def [29..42] Q
Alt [33..42]
Branch [34..37]
Expand Down