From ec550826076deef38033273bb7711839ebdbaa2f Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Thu, 16 Jan 2025 20:52:40 -0800 Subject: [PATCH] Tree-sitter rolling fixes, 1.125 (or 1.124.1) edition (#1172) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [language-python] Fix some indentation corner cases… …like indentation after a comment… if foo: # something …and not indenting on one-liners… if foo: pass …and not indenting after list slice syntax: x[1:2] Also added specs for Python indentation, as it's getting somewhat complex. * (whoops) * Un-focus spec * [language-css] Update `tree-sitter-css` to latest… …and improve highlighting of selectors while typing at end of file. * Bump `parserSource` for CSS grammar * [language-css] Add scope tests `parentOfType` and `childOfType`… …to support scenarios where you need to check parent/child relationship to an `ERROR` node, or to several different kinds of nodes at once. * Fix grammar specs (don't trim the start of the line!) --- .../grammars/modern-tree-sitter-css.cson | 2 +- .../tree-sitter/queries/highlights.scm | 51 ++-- .../grammars/tree-sitter/tree-sitter-css.wasm | Bin 103605 -> 110517 bytes packages/language-css/spec/.eslintrc.js | 14 ++ .../spec/fixtures/ends-in-tag-name.css | 5 + .../language-css/spec/fixtures/sample.css | 10 + .../spec/tree-sitter-grammar-spec.js | 21 ++ .../language-python/grammars/ts/indents.scm | 176 +++++++++++++- packages/language-python/spec/.eslintrc.js | 14 ++ .../language-python/spec/indentation-spec.js | 222 ++++++++++++++++++ src/scope-resolver.js | 34 ++- vendor/jasmine.js | 41 ++-- 12 files changed, 547 insertions(+), 43 deletions(-) create mode 100644 packages/language-css/spec/.eslintrc.js create mode 100644 packages/language-css/spec/fixtures/ends-in-tag-name.css create mode 100644 packages/language-css/spec/fixtures/sample.css create mode 100644 packages/language-css/spec/tree-sitter-grammar-spec.js create mode 100644 packages/language-python/spec/.eslintrc.js create mode 100644 packages/language-python/spec/indentation-spec.js diff --git a/packages/language-css/grammars/modern-tree-sitter-css.cson b/packages/language-css/grammars/modern-tree-sitter-css.cson index 101696e18e..7cf94ea10c 100644 --- a/packages/language-css/grammars/modern-tree-sitter-css.cson +++ b/packages/language-css/grammars/modern-tree-sitter-css.cson @@ -8,7 +8,7 @@ fileTypes: [ ] treeSitter: - parserSource: 'github:tree-sitter/tree-sitter-css#9af0bdd9d225edee12f489cfa8284e248321959b' + parserSource: 'github:tree-sitter/tree-sitter-css#5b24cbe3301a81b00c85d6c38db3bf2561e9ca41' grammar: 'tree-sitter/tree-sitter-css.wasm' highlightsQuery: 'tree-sitter/queries/highlights.scm' foldsQuery: 'tree-sitter/queries/folds.scm' diff --git a/packages/language-css/grammars/tree-sitter/queries/highlights.scm b/packages/language-css/grammars/tree-sitter/queries/highlights.scm index 4caea08958..6380491c5e 100644 --- a/packages/language-css/grammars/tree-sitter/queries/highlights.scm +++ b/packages/language-css/grammars/tree-sitter/queries/highlights.scm @@ -1,23 +1,46 @@ -; NOTE: `tree-sitter-css` recovers poorly from invalidity inside a block when -; you're adding a new property-value pair above others in a list. When the user -; is typing and the file is temporarily invalid, it will make incorrect guesses -; about tokens that occur between the cursor and the end of the block. +; WORKAROUNDS +; =========== + +; Mark `ERROR` nodes that occur inside blocks. We are much more cautious about +; inferences inside blocks because we're often wrong. +( + (ERROR) @_IGNORE_ + (#is? test.childOfType "block") + (#set! isErrorInsideBlock true) +) + +; (stylesheet (ERROR)) can't be queried directly, but it's important that we be +; able to detect it. +( + (ERROR) @_IGNORE_ + (#is? test.childOfType "stylesheet") + (#set! isErrorAtTopLevel true) +) + +; This selector captures an empty file with (e.g.) the word `div` typed. +(ERROR + (identifier) @entity.name.tag.css + (#set! capture.final) + (#is? test.descendantOfNodeWithData isErrorAtTopLevel) +) + +; When there's a parsing error inside a `block` node, too many things get +; incorrectly interpreted as `tag_name`s. Ignore all `tag_name` nodes until the +; error is resolved. ; -; The fix here is for `tree-sitter-css` to get better at recovering from its -; parsing error, but parser authors don't currently have much control over -; that. In the meantime, this query is a decent mitigation: it colors the -; affected tokens like plain text instead of assuming (nearly always -; incorrectly) them to be tag names. +; This should be fixed upstream because it has undesirable effects on nested +; selectors, but in the meantime this workaround is better than doing nothing. ; -; Ideally, this is temporary, and we can remove it soon. Until then, it makes -; syntax highlighting less obnoxious. +; Keep an eye on https://github.com/tree-sitter/tree-sitter-css/issues/65 to +; know when this workaround might no longer be necessary. ((tag_name) @_IGNORE_ - (#is? test.descendantOfType "ERROR") + (#is? test.descendantOfNodeWithData isErrorInsideBlock) (#set! capture.final)) + (ERROR (attribute_name) @_IGNORE_ (#set! capture.final)) @@ -26,8 +49,6 @@ (attribute_name) @invalid.illegal) (#set! capture.final)) -; WORKAROUND: -; ; In `::after`, the "after" has a node type of `tag_name`. Unclear whether this ; is a bug or intended behavior. We want to catch it here so that it doesn't ; get scoped like an HTML tag name in a selector. @@ -38,7 +59,7 @@ (#set! adjust.startAt lastChild.previousSibling.startPosition) (#set! adjust.endAt lastChild.endPosition)) -; Claim this range and block it from being scoped as a tag name. +; Claim the `tag_name` range and block it from being scoped as a tag name. (pseudo_element_selector (tag_name) @_IGNORE_ (#is? test.last true) diff --git a/packages/language-css/grammars/tree-sitter/tree-sitter-css.wasm b/packages/language-css/grammars/tree-sitter/tree-sitter-css.wasm index 6399b8a0b56c8a652030b8b4afa4072b54101386..a958382d2ef86cce0a74371f93b515915e65a0e3 100755 GIT binary patch literal 110517 zcmeIb2YgjU^FF?NZm1y?#RBMS2wenhh=RN(zKFfO>T5Sdh=7zJ2`V;|L>AN9-Ltw%7kNvuF40?maj6oJ%C1@Bi})%-*}RGqcak&hF{wimKXiQsA#$ z^AVHBjvhaz@20Zpl9z&_rb6~=K1@y#!-6R&YMaW++PbRYV=5=qj2>TCJux#yl2l1$<+$o`H4`US z9$r;jotYwt*{ZU#uIliy)i$rG5(@;Yb*QWyJ$}UKiPgjFDvy{net6yJn(<0UP7qBq z#Vw19nutu(W=%4gX4_}lRaPDYS1X5CjU8J#yslb#-O!=sLix99~;1TC`{-MVFwsY0IJ}&6_rBdD9t1Ei*zEf74zzozg{C zta>Im5+BRolSgh92z>j48S4AgXSs3lJ-Ts*Vod!|GiEBra(t8tr8G~m7Cx?J7AVHj zr!-@sVyt{dGpZ?>me3b1{iqNNbYfB%g#(QtojL#I~_4_pAE5$hD9?e*(7~dKj zKZcCkw8SdKc*#iop%~ZSt|g{E$C%!ML|GV<8H#o1Em~%#V%%yNvlZjzJGI1I#kkxs z&Qpxprk{C=asC}zWr1S6W!zh+7%v;fRf_T2T~R(PQmjWzkZu5@G)$Jo*7w`3?{{0@ z@3+1$vA#cs?--6bCbmy0#tMVxImK8-fhtnTvsAHW8KqYh;|~+=Wr{J~NW7yMe;JA8 zigAjO_((Bk8pdae@v~9+N-=&jjFpP9*o@DQim}XqU!@qg8;L&@<2^`}Wx@&iyvhWl zG($1YqWDWCGZU=R@cV4l{D~R!xmM$Os_{&CQPxE1&Qq-ACOH?7eAxLy)%d0H=_E{&Ve8X6(7+)AKUj?HyOqgY=@l_-Jj$$k|jOB`PfpPmIQV%EQXR7fv6N0Z4W0_&B zBfCO826Y#xj`{j8HvS;vC#N(yJ9Riz1*!B zznI*)UooZ|#uCN&!Pt0AF}^a4rxfD@!+1_H?l6p{it&>%`l@0qF^px3@rhx)qZrQ{ z8_N~rW+U;DVq9YwpDD&GhVhkRTmTz--mV18)-7*{FAPi8C^ zDaL#waf4z^GmOQG@wrjCT`?9JiMti!5hHQGVk|a{C1B_XK1Scejm}d_@JF*;Kc^TE z8^%(_xY{sYRgAX`W0_)HbFX mROgFqSLEX@>EUV%%*QpDD&$hVhkR%r(8NRE%$p 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 B@J;{# diff --git a/packages/language-css/spec/.eslintrc.js b/packages/language-css/spec/.eslintrc.js new file mode 100644 index 0000000000..8cc26374a1 --- /dev/null +++ b/packages/language-css/spec/.eslintrc.js @@ -0,0 +1,14 @@ +module.exports = { + env: { jasmine: true }, + globals: { + waitsForPromise: true, + runGrammarTests: true, + runFoldsTests: true + }, + rules: { + "node/no-unpublished-require": "off", + "node/no-extraneous-require": "off", + "no-unused-vars": "off", + "no-empty": "off" + } +}; diff --git a/packages/language-css/spec/fixtures/ends-in-tag-name.css b/packages/language-css/spec/fixtures/ends-in-tag-name.css new file mode 100644 index 0000000000..e7653ac9d2 --- /dev/null +++ b/packages/language-css/spec/fixtures/ends-in-tag-name.css @@ -0,0 +1,5 @@ + +/* Ensure that a file that ends in a bare tag name scopes the tag name properly. */ + +div +/* <- entity.name.tag.css */ diff --git a/packages/language-css/spec/fixtures/sample.css b/packages/language-css/spec/fixtures/sample.css new file mode 100644 index 0000000000..40cfde3f35 --- /dev/null +++ b/packages/language-css/spec/fixtures/sample.css @@ -0,0 +1,10 @@ +div { +/* <- entity.name.tag.css */ +/* ^ punctuation.section.property-list.begin.bracket.curly.css */ +} + +/* Next test verifies that selectors are properly inferred at the end of a file before the `{` is typed. */ + +div.foo +/* <- entity.name.tag.css */ +/* ^ entity.other.attribute-name.class.css */ diff --git a/packages/language-css/spec/tree-sitter-grammar-spec.js b/packages/language-css/spec/tree-sitter-grammar-spec.js new file mode 100644 index 0000000000..971e6f8cda --- /dev/null +++ b/packages/language-css/spec/tree-sitter-grammar-spec.js @@ -0,0 +1,21 @@ +const path = require('path'); + +describe('WASM Tree-sitter CSS grammar', () => { + + beforeEach(async () => { + await atom.packages.activatePackage('language-css'); + }); + + it('passes grammar tests', async () => { + await runGrammarTests( + path.join(__dirname, 'fixtures', 'sample.css'), + /\/\*/, + /\*\// + ); + await runGrammarTests( + path.join(__dirname, 'fixtures', 'ends-in-tag-name.css'), + /\/\*/, + /\*\// + ); + }); +}); diff --git a/packages/language-python/grammars/ts/indents.scm b/packages/language-python/grammars/ts/indents.scm index 9753893079..2a46fe1a38 100644 --- a/packages/language-python/grammars/ts/indents.scm +++ b/packages/language-python/grammars/ts/indents.scm @@ -1,4 +1,8 @@ -; Excluding dictionary key/value separators… + +; IGNORE NON-BLOCK-STARTING COLONS +; ================================ + +; First, exclude dictionary key/value separators… (dictionary (pair ":" @_IGNORE_ (#set! capture.final))) @@ -7,14 +11,164 @@ ((lambda ":" @_IGNORE_) (#set! capture.final)) -; …and type annotations on function parameters/class members… +; …list subscript syntax… +(slice ":" @_IGNORE_ + (#set! capture.final)) + +; …and type annotations on function parameters/class members. (":" @_IGNORE_ . (type) (#set! capture.final)) -; …all other colons we encounter hint at upcoming indents. +; IGNORE BLOCK-STARTING COLONS BEFORE ONE-LINERS +; ============================================== + +; Now that we've done that, all block-starting colons that have their +; consequence block start and end on the same line should be filtered out. +; +; We also test for `lastTextOnRow` to ensure we're not followed by an _empty_ +; consequence block, which is surprisingly common. Probably a bug, but it's got +; to be worked around in the meantime. +; +; We check for adjacency between the `:` and the `block` because otherwise we +; might incorrectly match cases like +; +; if 2 > 1: # some comment +; +; since those comments can also be followed by an empty `block` node on the same +; line. +; +(if_statement + ":" @_IGNORE_ + . + consequence: (block) + (#is-not? test.lastTextOnRow) + (#is? test.startsOnSameRowAs "nextSibling.endPosition") + (#set! capture.final) +) + +(elif_clause + ":" @_IGNORE_ + . + consequence: (block) + (#is-not? test.lastTextOnRow) + (#is? test.startsOnSameRowAs "nextSibling.endPosition") + (#set! capture.final) +) + +(else_clause + ":" @_IGNORE_ + . + body: (block) + (#is-not? test.lastTextOnRow) + (#is? test.startsOnSameRowAs "nextSibling.endPosition") + (#set! capture.final) +) + +(match_statement + ":" @_IGNORE_ + . + body: (block) + (#is-not? test.lastTextOnRow) + (#is? test.startsOnSameRowAs "nextSibling.endPosition") + (#set! capture.final) +) + +(case_clause + ":" @_IGNORE_ + . + consequence: (block) + (#is-not? test.lastTextOnRow) + (#is? test.startsOnSameRowAs "nextSibling.endPosition") + (#set! capture.final) +) + +(while_statement + ":" @_IGNORE_ + . + body: (block) + (#is-not? test.lastTextOnRow) + (#is? test.startsOnSameRowAs "nextSibling.endPosition") + (#set! capture.final) +) + +(for_statement + ":" @_IGNORE_ + . + body: (block) + (#is-not? test.lastTextOnRow) + (#is? test.startsOnSameRowAs "nextSibling.endPosition") + (#set! capture.final) +) + +(try_statement + ":" @_IGNORE_ + . + body: (block) + (#is-not? test.lastTextOnRow) + (#is? test.startsOnSameRowAs "nextSibling.endPosition") + (#set! capture.final) +) + +(except_clause + ":" @_IGNORE_ + . + (block) + (#is-not? test.lastTextOnRow) + (#is? test.startsOnSameRowAs "nextSibling.endPosition") + (#set! capture.final) +) + +; Special case for try/except statements, since they don't seem to be valid +; until they're fully intact. If we don't do this, `except` doesn't dedent. +; +; This is like the `elif`/`else` problem below, but it's trickier because an +; identifier could plausibly begin with the string `except` and we don't want +; to make an across-the-board assumption. +(ERROR + "try" + ":" @indent + (block + (expression_statement + (identifier) @dedent + (#match? @dedent "except") + ) + ) +) + +(function_definition + ":" @_IGNORE_ + . + body: (block) + (#is-not? test.lastTextOnRow) + (#is? test.startsOnSameRowAs "nextSibling.endPosition") + (#set! capture.final) +) + +(class_definition + ":" @_IGNORE_ + . + body: (block) + (#is-not? test.lastTextOnRow) + (#is? test.startsOnSameRowAs "nextSibling.endPosition") + (#set! capture.final) +) + + +; REMAINING COLONS +; ================ + +; Now that we've done this work, all other colons we encounter hint at upcoming +; indents. +; +; TODO: Based on the stuff we're doing above, it's arguable that the +; exclude-all-counterexamples approach is no longer useful and we should +; instead be opting into indentation. Revisit this! ":" @indent +; MISCELLANEOUS +; ============= + ; When typing out "else" after an "if" statement, tree-sitter-python won't -; acknowlege it as an `else` statement until it's indented properly, which is +; acknowledge it as an `else` statement until it's indented properly, which is ; quite the dilemma for us. Before that happens, it's an identifier named ; "else". This has a chance of spuriously dedenting if you're typing out a ; variable called `elsewhere` or something, but I'm OK with that. @@ -22,7 +176,21 @@ ; This also means that we _should not_ mark an actual `else` keyword with ; `@dedent`, because if it's recognized as such, that's a sign that it's ; already indented correctly and we shouldn't touch it. +; +; All this also applies to `elif`. ((identifier) @dedent (#match? @dedent "^(elif|else)$")) +; Likewise, typing `case` at the beginning of a line within a match block — in +; cases where it's interpreted as an identifier — strongly suggests that we +; should dedent one level so it's properly recognized as a new `case` keyword. +( + (identifier) @dedent + (#equals? @dedent "case") + (#is? test.descendantOfType "case_clause") +) + + +; All instances of brackets/braces should be indented if they span multiple +; lines. ["(" "[" "{"] @indent [")" "]" "}"] @dedent diff --git a/packages/language-python/spec/.eslintrc.js b/packages/language-python/spec/.eslintrc.js new file mode 100644 index 0000000000..8cc26374a1 --- /dev/null +++ b/packages/language-python/spec/.eslintrc.js @@ -0,0 +1,14 @@ +module.exports = { + env: { jasmine: true }, + globals: { + waitsForPromise: true, + runGrammarTests: true, + runFoldsTests: true + }, + rules: { + "node/no-unpublished-require": "off", + "node/no-extraneous-require": "off", + "no-unused-vars": "off", + "no-empty": "off" + } +}; diff --git a/packages/language-python/spec/indentation-spec.js b/packages/language-python/spec/indentation-spec.js new file mode 100644 index 0000000000..fa4d058704 --- /dev/null +++ b/packages/language-python/spec/indentation-spec.js @@ -0,0 +1,222 @@ +const dedent = require('dedent'); + +describe('Python indentation (modern Tree-sitter)', () => { + let editor; + let languageMode; + let grammar; + + async function insertNewline() { + editor.getLastSelection().insertText('\n', { autoIndent: true, autoIndentNewline: true }) + await languageMode.atTransactionEnd(); + } + + async function expectToAutoIndentAfter(text, assert = true) { + editor.setText(text); + await languageMode.atTransactionEnd(); + editor.setCursorBufferPosition([Infinity, Infinity]); + let currentRow = editor.getLastCursor().getBufferPosition().row; + insertNewline(); + if (assert) { + expect(editor.lineTextForBufferRow(currentRow + 1)).toEqual(' '); + } else { + expect(editor.lineTextForBufferRow(currentRow + 1)).toEqual(''); + } + } + + beforeEach(async () => { + atom.config.set('core.useTreeSitterParsers', true); + + editor = await atom.workspace.open(); + await atom.packages.activatePackage('language-python'); + grammar = atom.grammars.grammarForScopeName('source.python'); + editor.setGrammar(grammar); + languageMode = editor.languageMode; + await languageMode.ready; + }); + + it('indents blocks properly', async () => { + await expectToAutoIndentAfter(`if 1 > 2:`) + await expectToAutoIndentAfter(`if 1 > 2: # test`) + await expectToAutoIndentAfter(`if 1 > 2: pass`, false) + + await expectToAutoIndentAfter(`def f(x):`) + await expectToAutoIndentAfter(`def f(x): # test`) + await expectToAutoIndentAfter(`def f(x): pass`, false) + + await expectToAutoIndentAfter(`class Fx(object):`) + await expectToAutoIndentAfter(`class Fx(object): # test`) + await expectToAutoIndentAfter(`class Fx(object): pass`, false) + + await expectToAutoIndentAfter(`while True:`) + await expectToAutoIndentAfter(`while True: # test`) + await expectToAutoIndentAfter(`while True: pass`, false) + + await expectToAutoIndentAfter(`for _ in iter(x):`) + await expectToAutoIndentAfter(`for _ in iter(x): # test`) + await expectToAutoIndentAfter(`for _ in iter(x): pass`, false) + + await expectToAutoIndentAfter(dedent` + if 1 > 2: + pass + elif 2 > 3: + `) + + await expectToAutoIndentAfter(dedent` + if 1 > 2: + pass + elif 2 > 3: # test + `) + + await expectToAutoIndentAfter(dedent` + if 1 > 2: + pass + elif 2 > 3: pass + `, false) + + await expectToAutoIndentAfter(dedent` + if 1 > 2: + pass + elif 2 > 3: + pass + else: + `) + + await expectToAutoIndentAfter(dedent` + if 1 > 2: + pass + elif 2 > 3: + pass + else: # test + `) + + await expectToAutoIndentAfter(dedent` + if 1 > 2: + pass + elif 2 > 3: + pass + else: pass + `, false) + + await expectToAutoIndentAfter(`try:`) + + // The assertions below don't work because `tree-sitter-python` doesn't + // parse them correctly unless they occur within an already intact + // `try/except` block. This needs to be fixed upstream. + // await expectToAutoIndentAfter(`try: # test`) + // await expectToAutoIndentAfter(`try: pass`, false) + + await expectToAutoIndentAfter(dedent` + try: + do_something() + except: + `) + await expectToAutoIndentAfter(dedent` + try: + do_something() + except: # test + `) + await expectToAutoIndentAfter(dedent` + try: + do_something() + except: pass + `, false) + }); + + it('indents blocks properly (complex cases)', async () => { + editor.setText(dedent` + try: pass + except: pass + `); + await languageMode.atTransactionEnd(); + editor.setCursorBufferPosition([0, Infinity]); + await insertNewline(); + expect(editor.lineTextForBufferRow(1)).toBe('') + + editor.setText(dedent` + try: #foo + except: pass + `) + await languageMode.atTransactionEnd(); + editor.setCursorBufferPosition([0, Infinity]); + await insertNewline(); + expect(editor.lineTextForBufferRow(1)).toBe(' ') + }) + + it(`does not indent for other usages of colons`, async () => { + await expectToAutoIndentAfter(`x = lambda a : a + 10`, false) + await expectToAutoIndentAfter(`x = list[:2]`, false) + await expectToAutoIndentAfter(`x = { foo: 2 }`, false) + }); + + it('indents braces properly', async () => { + let pairs = [ + ['[', ']'], + ['{', '}'], + ['(', ')'] + ]; + for (let [a, b] of pairs) { + editor.setText(`x = ${a} + + ${b}`) + await languageMode.atTransactionEnd(); + editor.setCursorBufferPosition([0, Infinity]); + await insertNewline(); + expect(editor.lineTextForBufferRow(1)).toBe(' ') + } + + editor.setText(`x = < + + >`) + await languageMode.atTransactionEnd(); + editor.setCursorBufferPosition([0, Infinity]); + await insertNewline(); + expect(editor.lineTextForBufferRow(1)).toBe('') + }); + + it('dedents properly', async () => { + editor.setText(dedent` + if 1 > 2: + pass + eli + `); + editor.setCursorBufferPosition([Infinity, Infinity]); + await languageMode.atTransactionEnd(); + editor.getLastSelection().insertText('f', { + autoIndent: true, + autoDecreaseIndent: true + }); + await languageMode.atTransactionEnd(); + expect(editor.lineTextForBufferRow(2)).toBe('elif'); + + editor.setText(dedent` + if 1 > 2: + pass + els + `); + editor.setCursorBufferPosition([Infinity, Infinity]); + await languageMode.atTransactionEnd(); + editor.getLastSelection().insertText('e', { + autoIndent: true, + autoDecreaseIndent: true + }); + await languageMode.atTransactionEnd(); + expect(editor.lineTextForBufferRow(2)).toBe('else'); + + + editor.setText(dedent` + match x: + case "a": + pass + cas + `); + editor.setCursorBufferPosition([Infinity, Infinity]); + await languageMode.atTransactionEnd(); + editor.getLastSelection().insertText('e', { + autoIndent: true, + autoDecreaseIndent: true + }); + await languageMode.atTransactionEnd(); + expect(editor.lineTextForBufferRow(3)).toBe(' case'); + }); + +}); diff --git a/src/scope-resolver.js b/src/scope-resolver.js index 8f6eb8c379..951bc46793 100644 --- a/src/scope-resolver.js +++ b/src/scope-resolver.js @@ -745,8 +745,20 @@ ScopeResolver.TESTS = { return false; }, - // Passes if there's an ancestor, but fails if the ancestor type matches - // the second,third,etc argument + // Passes if this node's parent is of the given type(s). + // + // Only rarely needed, but may be useful when dealing with ERROR nodes. + childOfType(node, type) { + if (!node.parent) return false; + let multiple = type.includes(' '); + let target = multiple ? type.split(/\s+/) : type; + return multiple ? target.includes(node.parent.type) : node.parent.type === type; + }, + + // Takes at least two node types (separated by spaces) and starts traversing + // up the node's parent chain. Passes if the first node type is encountered + // before any of the rest; fails if any of the rest are reached before the + // first. ancestorTypeNearerThan(node, types) { let [target, ...rejected] = types.split(/\s+/); rejected = new Set(rejected) @@ -766,9 +778,21 @@ ScopeResolver.TESTS = { return descendants.length > 0; }, + // Passes if this node has at least one child of the given type(s). + // + // Only rarely needed, but may be useful when dealing with ERROR nodes. + parentOfType(node, type) { + if (node.childCount === 0) return false; + let multiple = type.includes(' '); + let target = multiple ? type.split(/\s+/) : type; + return node.children.some(c => { + return multiple ? target.includes(c.type) : c.type === target; + }); + }, + // Passes if this range (after adjustments) has previously had data stored at // the given key. - rangeWithData(node, rawValue, existingData) { + rangeWithData(_node, rawValue, existingData) { if (existingData === undefined) { return false; } let [key, value] = interpretPossibleKeyValuePair(rawValue, false); @@ -782,7 +806,7 @@ ScopeResolver.TESTS = { // Passes if one of this node's ancestors has stored data at the given key // for its inherent range (ignoring adjustments). - descendantOfNodeWithData(node, rawValue, existingData, instance) { + descendantOfNodeWithData(node, rawValue, _existingData, instance) { let current = node; let [key, value] = interpretPossibleKeyValuePair(rawValue, false); @@ -818,7 +842,7 @@ ScopeResolver.TESTS = { // Passes only when a given config option is present and truthy. Accepts // either (a) a configuration key or (b) a configuration key and value // separated by a space. - config(node, rawValue, existingData, instance) { + config(_node, rawValue, _existingData, instance) { let [key, value] = interpretPossibleKeyValuePair(rawValue, true); // Invalid predicates should be ignored. diff --git a/vendor/jasmine.js b/vendor/jasmine.js index d69b33d2d8..af1c499adb 100644 --- a/vendor/jasmine.js +++ b/vendor/jasmine.js @@ -46,7 +46,7 @@ jasmine.DEFAULT_UPDATE_INTERVAL = 250; jasmine.MAX_PRETTY_PRINT_DEPTH = 40; /** - * Default timeout interval in milliseconds for waitsFor() blocks. + * Default timeout interval in milliseconds for waitsfor () blocks. */ jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000; @@ -546,7 +546,7 @@ if (isCommonJS) exports.runs = runs; /** * Waits a fixed time period before moving to the next block. * - * @deprecated Use waitsFor() instead + * @deprecated Use waitsfor () instead * @param {Number} timeout milliseconds to wait */ var waits = function(timeout) { @@ -2286,7 +2286,7 @@ jasmine.Spec.prototype.expect = function(actual) { /** * Waits a fixed time period before moving to the next block. * - * @deprecated Use waitsFor() instead + * @deprecated Use waitsfor () instead * @param {Number} timeout milliseconds to wait */ jasmine.Spec.prototype.waits = function(timeout) { @@ -2716,17 +2716,22 @@ jasmine.Matchers.prototype.toSatisfy = function(fn) { // position of the editor `expect` should be satisfied, and `testPosition`, that // is where in file the test actually happened. This makes it easier for us // to construct an error showing where EXACTLY was the assertion that failed -function normalizeTreeSitterTextData(editor, commentRegex) { +function normalizeTreeSitterTextData(editor, commentRegex, trailingCommentRegex) { let allMatches = [], lastNonComment = 0 const checkAssert = new RegExp('^\\s*' + commentRegex.source + '\\s*[\\<\\-|\\^]') editor.getBuffer().getLines().forEach((row, i) => { const m = row.match(commentRegex) - if(m) { + if (m) { + if (trailingCommentRegex) { + row = row.replace(trailingCommentRegex, '') + } + // Strip extra space at the end of the line (but not the beginning!) + row = row.replace(/\s+$/, '') // const scope = editor.scopeDescriptorForBufferPosition([i, m.index]) // FIXME: use editor.scopeDescriptorForBufferPosition when it works const scope = editor.tokensForScreenRow(i) const scopes = scope.flatMap(e => e.scopes) - if(scopes.find(s => s.match(/comment/)) && row.match(checkAssert)) { + if (scopes.find(s => s.match(/comment/)) && row.match(checkAssert)) { allMatches.push({row: lastNonComment, text: row, col: m.index, testRow: i}) return } @@ -2735,7 +2740,7 @@ function normalizeTreeSitterTextData(editor, commentRegex) { }) return allMatches.map(({text, row, col, testRow}) => { const exactPos = text.match(/\^\s+(.*)/) - if(exactPos) { + if (exactPos) { const expected = exactPos[1] return { expected, @@ -2744,7 +2749,7 @@ function normalizeTreeSitterTextData(editor, commentRegex) { } } else { const pos = text.match(/\<-\s+(.*)/) - if(!pos) throw new Error(`Can't match ${text}`) + if (!pos) throw new Error(`Can't match ${text}`) return { expected: pos[1], editorPosition: {row, column: col}, @@ -2761,10 +2766,10 @@ async function openDocument(fullPath) { return editor; } -async function runGrammarTests(fullPath, commentRegex) { +async function runGrammarTests(fullPath, commentRegex, trailingCommentRegex = null) { const editor = await openDocument(fullPath); - const normalized = normalizeTreeSitterTextData(editor, commentRegex) + const normalized = normalizeTreeSitterTextData(editor, commentRegex, trailingCommentRegex) expect(normalized.length).toSatisfy((n, reason) => { reason("Tokenizer didn't run correctly - could not find any comment") return n > 0 @@ -2773,7 +2778,7 @@ async function runGrammarTests(fullPath, commentRegex) { expect(editor.scopeDescriptorForBufferPosition(editorPosition).scopes).toSatisfy((scopes, reason) => { const dontFindScope = expected.startsWith("!"); expected = expected.replace(/^!/, "") - if(dontFindScope) { + if (dontFindScope) { reason(`Expected to NOT find scope "${expected}" but found it\n` + ` at ${fullPath}:${testPosition.row+1}:${testPosition.column+1}` ); @@ -2785,7 +2790,7 @@ async function runGrammarTests(fullPath, commentRegex) { const normalized = expected.replace(/([\.\-])/g, '\\$1'); const scopeRegex = new RegExp('^' + normalized + '(\\..+)?$'); let result = scopes.find(e => e.match(scopeRegex)) !== undefined; - if(dontFindScope) result = !result; + if (dontFindScope) result = !result; return result }) }) @@ -2797,25 +2802,25 @@ async function runFoldsTests(fullPath, commentRegex) { let grouped = {} const normalized = normalizeTreeSitterTextData(editor, commentRegex).forEach(test => { const [kind, id] = test.expected.split('.') - if(!kind || !id) { + if (!kind || !id) { throw new Error(`Folds must be in the format fold_end.some-id\n` + ` at ${test.testPosition.row+1}:${test.testPosition.column+1}`) } grouped[id] ||= {} grouped[id][kind] = test }) - for(const k in grouped) { + for (const k in grouped) { const v = grouped[k] const keys = Object.keys(v) - if(keys.indexOf('fold_begin') === -1) + if (keys.indexOf('fold_begin') === -1) throw new Error(`Fold ${k} must contain fold_begin`) - if(keys.indexOf('fold_end') === -1) + if (keys.indexOf('fold_end') === -1) throw new Error(`Fold ${k} must contain fold_end`) - if(keys.indexOf('fold_new_position') === -1) + if (keys.indexOf('fold_new_position') === -1) throw new Error(`Fold ${k} must contain fold_new_position`) } - for(const k in grouped) { + for (const k in grouped) { const fold = grouped[k] const begin = fold['fold_begin'] const end = fold['fold_end']