Allow for any number of @_IGNORE_ capture names in a query…

…by namespacing them as `@_IGNORE_.foo`, `@_IGNORE_.bar`, etc.

It's sometimes necessary to define a capture in a query not because you want to apply a scope name, but because you need to use it as the argument to a predicate. `@_IGNORE_` was intended for that purpose, but it was the _only_ capture name with that special effect.

Now, you can specify any number of captures that don't apply scope names. As long as it equals `@_IGNORE_` or _starts with_ `@_IGNORE_.`, it'll have the same effect. This lets you target two or more nodes and use them all in predicates in the same query without any of them applying a scope name.
This commit is contained in:
Andrew Dupont 2024-01-27 23:11:41 -08:00
parent e547ace81c
commit f691cfce28
2 changed files with 65 additions and 1 deletions

View File

@ -159,6 +159,67 @@ describe('ScopeResolver', () => {
]);
});
it('does not apply any scopes when @_IGNORE_ is used', async () => {
await grammar.setQueryForTest('highlightsQuery', `
(lexical_declaration kind: _ @_IGNORE_
(#match? @_IGNORE_ "const"))
(lexical_declaration kind: _ @let
(#match? @let "let"))
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
buffer.setLanguageMode(languageMode);
buffer.setText(dedent`
// this is a comment
const foo = "ahaha";
let bar = 'troz'
`);
await languageMode.ready;
let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
for (let capture of captures) {
let { node, name } = capture;
let result = scopeResolver.store(capture);
if (name === '_IGNORE_') {
expect(!!result).toBe(false);
} else {
expect(!!result).toBe(true);
}
}
});
it('does not apply any scopes when multiple @_IGNORE_s are used', async () => {
await grammar.setQueryForTest('highlightsQuery', `
(variable_declarator
(identifier) @_IGNORE_.identifier
(string) @_IGNORE_.string
)
`);
const languageMode = new WASMTreeSitterLanguageMode({ grammar, buffer });
buffer.setLanguageMode(languageMode);
buffer.setText(dedent`
// this is a comment
const foo = "ahaha";
let bar = false
`);
await languageMode.ready;
let { scopeResolver, captures } = await getAllCaptures(grammar, languageMode);
for (let capture of captures) {
let { node, name } = capture;
let result = scopeResolver.store(capture);
if (name.startsWith('_IGNORE_')) {
expect(!!result).toBe(false);
} else {
expect(!!result).toBe(true);
}
}
});
describe('adjustments', () => {
it('adjusts ranges with (#set! adjust.startAt)', async () => {
await grammar.setQueryForTest('highlightsQuery', `

View File

@ -487,12 +487,15 @@ class ScopeResolver {
this.setDataForRange(range, props);
}
if (name === '_IGNORE_') {
if (name === '_IGNORE_' || name.startsWith('_IGNORE_.')) {
// "@_IGNORE_" is a magical variable in an SCM file that will not be
// applied in the grammar, but which allows us to prevent other kinds of
// scopes from matching. We purposefully allowed this syntax node to set
// data for a given range, but not to apply its scope ID to any
// boundaries.
//
// A query can also use multiple different @_IGNORE_-style variables by
// adding segments after the @_IGNORE_, such as @_IGNORE_.foo.bar.
return false;
}