From f691cfce2856a2f3f067ef3f87495827529445d8 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sat, 27 Jan 2024 23:11:41 -0800 Subject: [PATCH] =?UTF-8?q?Allow=20for=20any=20number=20of=20@=5FIGNORE=5F?= =?UTF-8?q?=20capture=20names=20in=20a=20query=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …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. --- spec/scope-resolver-spec.js | 61 +++++++++++++++++++++++++++++++++++++ src/scope-resolver.js | 5 ++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/spec/scope-resolver-spec.js b/spec/scope-resolver-spec.js index 9d8ec18c6..51bced5b7 100644 --- a/spec/scope-resolver-spec.js +++ b/spec/scope-resolver-spec.js @@ -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', ` diff --git a/src/scope-resolver.js b/src/scope-resolver.js index 46864c2d2..049b08e0d 100644 --- a/src/scope-resolver.js +++ b/src/scope-resolver.js @@ -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; }