From 2e06a178c15e911f93d30a262b01b0dee35bd23f Mon Sep 17 00:00:00 2001 From: Yusuke Abe Date: Thu, 13 Jun 2024 13:20:51 +0900 Subject: [PATCH 01/11] fix(noUnknownProperty): prevent warning on CSS custom property (#3193) --- CHANGELOG.md | 3 +-- .../src/lint/nursery/no_unknown_property.rs | 5 ++++- .../tests/specs/nursery/noUnknownProperty/valid.css | 13 +++++++++++++ .../specs/nursery/noUnknownProperty/valid.css.snap | 13 +++++++++++++ 4 files changed, 31 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85f29baeb061..3cd7d5fdcf7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,7 +88,6 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b ### Linter - #### New features - Add [nursery/useValidAutocomplete](https://biomejs.dev/linter/rules/use-valid-autocomplete/). Contributed by @unvalley @@ -98,8 +97,8 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b - [useImportExtensions](https://biomejs.dev/linter/rules/use-import-extensions/) now suggests a correct fix for `import '.'` and `import './.'`. Contributed by @minht11 - Fix [useDateNow](https://biomejs.dev/linter/rules/use-date-now/) false positive when new Date object has arguments `new Date(0).getTime()`. Contributed by @minht11. - The [`noUnmatchableAnbSelector`](https://biomejs.dev/linter/rules/no-unmatchable-anb-selector/) rule is now able to catch unmatchable `an+b` selectors like `0n+0` or `-0n+0`. Contributed by @Sec-ant. - - The [`useHookAtTopLevel`](https://biomejs.dev/linter/rules/use-hook-at-top-level/) rule now recognizes properties named as hooks like `foo.useFoo()`. Contributed by @ksnyder9801 +- Fix [#3092](https://github.com/biomejs/biome/issues/3092), prevent warning for `Custom properties (--*)`. Contributed by @chansuke ### Parser diff --git a/crates/biome_css_analyze/src/lint/nursery/no_unknown_property.rs b/crates/biome_css_analyze/src/lint/nursery/no_unknown_property.rs index bf6d0fa10802..7e2aa1891966 100644 --- a/crates/biome_css_analyze/src/lint/nursery/no_unknown_property.rs +++ b/crates/biome_css_analyze/src/lint/nursery/no_unknown_property.rs @@ -70,7 +70,10 @@ impl Rule for NoUnknownProperty { fn run(ctx: &RuleContext) -> Option { let node = ctx.query(); let property_name = node.name().ok()?.text().to_lowercase(); - if !is_known_properties(&property_name) && !vendor_prefixed(&property_name) { + if !property_name.starts_with("--") + && !is_known_properties(&property_name) + && !vendor_prefixed(&property_name) + { return Some(node.name().ok()?.range()); } None diff --git a/crates/biome_css_analyze/tests/specs/nursery/noUnknownProperty/valid.css b/crates/biome_css_analyze/tests/specs/nursery/noUnknownProperty/valid.css index 43341a0d14af..d7ad6d7ec60e 100644 --- a/crates/biome_css_analyze/tests/specs/nursery/noUnknownProperty/valid.css +++ b/crates/biome_css_analyze/tests/specs/nursery/noUnknownProperty/valid.css @@ -42,3 +42,16 @@ a { a { -webkit-mask-image: url(mask.png); } + +/* Custom property */ +a { + --custom-color: #1234560; +} + +a { + --custom-margin: 100px; +} + +a { + --custom-property: 10px; +} diff --git a/crates/biome_css_analyze/tests/specs/nursery/noUnknownProperty/valid.css.snap b/crates/biome_css_analyze/tests/specs/nursery/noUnknownProperty/valid.css.snap index 9e062796a435..1844b7bb7c64 100644 --- a/crates/biome_css_analyze/tests/specs/nursery/noUnknownProperty/valid.css.snap +++ b/crates/biome_css_analyze/tests/specs/nursery/noUnknownProperty/valid.css.snap @@ -49,4 +49,17 @@ a { -webkit-mask-image: url(mask.png); } +/* Custom property */ +a { + --custom-color: #1234560; +} + +a { + --custom-margin: 100px; +} + +a { + --custom-property: 10px; +} + ``` From 9e8addd91535dd8186c1e9fdc5156021fd0f32d3 Mon Sep 17 00:00:00 2001 From: Denis Bezrukov <6227442+denbezrukov@users.noreply.github.com> Date: Thu, 13 Jun 2024 10:37:46 +0200 Subject: [PATCH 02/11] fix(css_formatter): fix CSS extra whitespace added within :has #3192 (#3198) --- CHANGELOG.md | 1 + .../src/css/selectors/relative_selector.rs | 6 +- .../biome_css_formatter/tests/quick_test.rs | 6 +- .../pseudo_class/pseudo_class_has.css | 17 ++ .../pseudo_class/pseudo_class_has.css.snap | 72 +++++ .../css/attribute/insensitive.css.snap | 41 --- .../prettier/css/attribute/quotes.css.snap | 13 +- .../prettier/css/attribute/spaces.css.snap | 284 ------------------ 8 files changed, 101 insertions(+), 339 deletions(-) create mode 100644 crates/biome_css_formatter/tests/specs/css/selectors/pseudo_class/pseudo_class_has.css create mode 100644 crates/biome_css_formatter/tests/specs/css/selectors/pseudo_class/pseudo_class_has.css.snap delete mode 100644 crates/biome_css_formatter/tests/specs/prettier/css/attribute/insensitive.css.snap delete mode 100644 crates/biome_css_formatter/tests/specs/prettier/css/attribute/spaces.css.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index 3cd7d5fdcf7c..cb52cf0f6d67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,7 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b #### Bug fixes - Fix [#3103](https://github.com/biomejs/biome/issues/3103) by correctly resolving CSS formatter options. Contributed by @ah-yu +- Fix [#3192](https://github.com/biomejs/biome/issues/3192) don't add an extra whitespace within :has. Contributed by @denbezrukov ### JavaScript APIs diff --git a/crates/biome_css_formatter/src/css/selectors/relative_selector.rs b/crates/biome_css_formatter/src/css/selectors/relative_selector.rs index 92fb6ea3efd5..82f636e04d72 100644 --- a/crates/biome_css_formatter/src/css/selectors/relative_selector.rs +++ b/crates/biome_css_formatter/src/css/selectors/relative_selector.rs @@ -11,6 +11,10 @@ impl FormatNodeRule for FormatCssRelativeSelector { selector, } = node.as_fields(); - write!(f, [combinator.format(), space(), selector.format()]) + if combinator.is_some() { + write!(f, [combinator.format(), space()])?; + } + + write!(f, [selector.format()]) } } diff --git a/crates/biome_css_formatter/tests/quick_test.rs b/crates/biome_css_formatter/tests/quick_test.rs index 883111e4487f..22bdba725852 100644 --- a/crates/biome_css_formatter/tests/quick_test.rs +++ b/crates/biome_css_formatter/tests/quick_test.rs @@ -13,8 +13,10 @@ mod language { // use this test check if your snippet prints as you wish, without using a snapshot fn quick_test() { let src = r#" -div { -grid-template-columns: 1fr 100px 3em; +.container { + &:has(.child) { + color: blue; + } } "#; let parse = parse_css(src, CssParserOptions::default()); diff --git a/crates/biome_css_formatter/tests/specs/css/selectors/pseudo_class/pseudo_class_has.css b/crates/biome_css_formatter/tests/specs/css/selectors/pseudo_class/pseudo_class_has.css new file mode 100644 index 000000000000..d97312d30eff --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/selectors/pseudo_class/pseudo_class_has.css @@ -0,0 +1,17 @@ +a:has(> img) {} +a:has( > img ) {} +a:has( > img ) {} +a:has( > img , > img ) {} +a:has( img ) {} +dt:has(+ dt) {} +section:not(:has(h1, h2, h3, h4, h5, h6)) {} +section:has(:not(h1, h2, h3, h4, h5, h6)) {} +div:has(p) {} +.header-group:has(h2):has(.subtitle) h2 {} +a:has(> img) .div {} + +.container { + &:has(.child) { + color: blue; + } +} diff --git a/crates/biome_css_formatter/tests/specs/css/selectors/pseudo_class/pseudo_class_has.css.snap b/crates/biome_css_formatter/tests/specs/css/selectors/pseudo_class/pseudo_class_has.css.snap new file mode 100644 index 000000000000..19715b295f29 --- /dev/null +++ b/crates/biome_css_formatter/tests/specs/css/selectors/pseudo_class/pseudo_class_has.css.snap @@ -0,0 +1,72 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: css/selectors/pseudo_class/pseudo_class_has.css +--- +# Input + +```css +a:has(> img) {} +a:has( > img ) {} +a:has( > img ) {} +a:has( > img , > img ) {} +a:has( img ) {} +dt:has(+ dt) {} +section:not(:has(h1, h2, h3, h4, h5, h6)) {} +section:has(:not(h1, h2, h3, h4, h5, h6)) {} +div:has(p) {} +.header-group:has(h2):has(.subtitle) h2 {} +a:has(> img) .div {} + +.container { + &:has(.child) { + color: blue; + } +} + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Quote style: Double Quotes +----- + +```css +a:has(> img) { +} +a:has(> img) { +} +a:has(> img) { +} +a:has(> img, > img) { +} +a:has(img) { +} +dt:has(+ dt) { +} +section:not(:has(h1, h2, h3, h4, h5, h6)) { +} +section:has(:not(h1, h2, h3, h4, h5, h6)) { +} +div:has(p) { +} +.header-group:has(h2):has(.subtitle) h2 { +} +a:has(> img) .div { +} + +.container { + &:has(.child) { + color: blue; + } +} +``` diff --git a/crates/biome_css_formatter/tests/specs/prettier/css/attribute/insensitive.css.snap b/crates/biome_css_formatter/tests/specs/prettier/css/attribute/insensitive.css.snap deleted file mode 100644 index 3eef3a18d7f0..000000000000 --- a/crates/biome_css_formatter/tests/specs/prettier/css/attribute/insensitive.css.snap +++ /dev/null @@ -1,41 +0,0 @@ ---- -source: crates/biome_formatter_test/src/snapshot_builder.rs -info: css/attribute/insensitive.css ---- - -# Input - -```css -input[type="radio" i] {} -img[alt~="person" i][src*="lorem" i] {} -section:has(:not([type="radio" i], [type="checkbox" i])) {} - -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Biome -@@ -2,5 +2,5 @@ - } - img[alt~="person" i][src*="lorem" i] { - } --section:has(:not([type="radio" i], [type="checkbox" i])) { -+section:has( :not([type="radio" i], [type="checkbox" i])) { - } -``` - -# Output - -```css -input[type="radio" i] { -} -img[alt~="person" i][src*="lorem" i] { -} -section:has( :not([type="radio" i], [type="checkbox" i])) { -} -``` - - diff --git a/crates/biome_css_formatter/tests/specs/prettier/css/attribute/quotes.css.snap b/crates/biome_css_formatter/tests/specs/prettier/css/attribute/quotes.css.snap index 750efede426b..d0044568cb5b 100644 --- a/crates/biome_css_formatter/tests/specs/prettier/css/attribute/quotes.css.snap +++ b/crates/biome_css_formatter/tests/specs/prettier/css/attribute/quotes.css.snap @@ -2,7 +2,6 @@ source: crates/biome_formatter_test/src/snapshot_builder.rs info: css/attribute/quotes.css --- - # Input ```css @@ -23,7 +22,7 @@ section:has(:not([type="radio"], [type="checkbox"])) {} ```diff --- Prettier +++ Biome -@@ -4,13 +4,12 @@ +@@ -4,8 +4,7 @@ } a[id="test"] { } @@ -33,12 +32,6 @@ section:has(:not([type="radio"], [type="checkbox"])) {} a[class="(╯°□°)╯︵ ┻━┻"] { } input:not([type="radio"]):not([type="checkbox"]) { - } - input:not([type="radio"], [type="checkbox"]) { - } --section:has(:not([type="radio"], [type="checkbox"])) { -+section:has( :not([type="radio"], [type="checkbox"])) { - } ``` # Output @@ -57,7 +50,7 @@ input:not([type="radio"]):not([type="checkbox"]) { } input:not([type="radio"], [type="checkbox"]) { } -section:has( :not([type="radio"], [type="checkbox"])) { +section:has(:not([type="radio"], [type="checkbox"])) { } ``` @@ -98,5 +91,3 @@ quotes.css:4:16 parse ━━━━━━━━━━━━━━━━━━━ ``` - - diff --git a/crates/biome_css_formatter/tests/specs/prettier/css/attribute/spaces.css.snap b/crates/biome_css_formatter/tests/specs/prettier/css/attribute/spaces.css.snap deleted file mode 100644 index dbacbed62d35..000000000000 --- a/crates/biome_css_formatter/tests/specs/prettier/css/attribute/spaces.css.snap +++ /dev/null @@ -1,284 +0,0 @@ ---- -source: crates/biome_formatter_test/src/snapshot_builder.rs -info: css/attribute/spaces.css ---- - -# Input - -```css -[lang] {} -[ lang] {} -[lang ] {} -[ lang ] {} -[ lang ] {} -[ -lang -] {} -span[lang] {} -span[ lang] {} -span[lang ] {} -span[ lang ] {} -span[ lang ] {} -span[lang='pt'] {} -span[lang ='pt'] {} -span[lang= 'pt'] {} -span[lang = 'pt'] {} -span[lang = 'pt'] {} -span[lang='pt' ] {} -span[lang='pt' ] {} -span[ -lang -= -'pt' -] {} -span[ lang ~= 'en-us' ] {} -span[ lang ~= 'en-us' ] {} -span[ lang |='zh' ] {} -span[ -lang -~= -'en-us' -] {} -a[ href ^= '#' ] {} -a[ href $= '.cn' ] {} -a[ href *= 'example' ] {} -a[ -href -*= -'example' -] {} -input[ type = 'radio' i ] {} -input[ type = 'radio' i ] {} -input[ type ~= 'radio' i ] {} -input[ type ~= 'radio' i ] {} -input[ -type -~= -'radio' -i -] {} -img[ alt = 'person' ][ src = 'lorem' ] {} -img[ alt = 'person' ][ src = 'lorem' ] {} -img[ alt ~= 'person' ][ src *= 'lorem' ] {} -img[ alt ~= 'person' ][ src *= 'lorem' ] {} -img[ -alt -~= -'person' -][ -src -*= -'lorem' -] {} -section:has(:not([type='radio'], [type='checkbox'])) {} -section:has(:not([type='radio' i], [type='checkbox' i])) {} -section:has(:not([ type = 'radio' ], [ type = 'checkbox' ])) {} -section:has(:not([ type = 'radio' i ], [ type = 'checkbox' i ])) {} -section:has(:not([ type = 'radio' ], [ type = 'checkbox' ])) {} -section:has(:not([ type = 'radio' i ], [ type = 'checkbox' i ])) {} -section:has(:not([ -type -= -'radio' -], [ -type -= -'checkbox' -])) {} -section:has(:not([ -type -= -'radio' -i -], [ -type -= -'checkbox' -i -])) {} -[foo|att=val] {} -[ foo | att = val ] {} -[ foo | att = val ] {} -[ -foo -| -att -= -val -] {} -[*|att] {} -[ * | att ] {} -[ * | att ] {} -[ -* -| -att -] {} -[|att] {} -[ | att ] {} -[ | att ] {} -[ -| -att -] {} - -``` - - -# Prettier differences - -```diff ---- Prettier -+++ Biome -@@ -72,21 +72,21 @@ - } - img[alt~="person"][src*="lorem"] { - } --section:has(:not([type="radio"], [type="checkbox"])) { -+section:has( :not([type="radio"], [type="checkbox"])) { - } --section:has(:not([type="radio" i], [type="checkbox" i])) { -+section:has( :not([type="radio" i], [type="checkbox" i])) { - } --section:has(:not([type="radio"], [type="checkbox"])) { -+section:has( :not([type="radio"], [type="checkbox"])) { - } --section:has(:not([type="radio" i], [type="checkbox" i])) { -+section:has( :not([type="radio" i], [type="checkbox" i])) { - } --section:has(:not([type="radio"], [type="checkbox"])) { -+section:has( :not([type="radio"], [type="checkbox"])) { - } --section:has(:not([type="radio" i], [type="checkbox" i])) { -+section:has( :not([type="radio" i], [type="checkbox" i])) { - } --section:has(:not([type="radio"], [type="checkbox"])) { -+section:has( :not([type="radio"], [type="checkbox"])) { - } --section:has(:not([type="radio" i], [type="checkbox" i])) { -+section:has( :not([type="radio" i], [type="checkbox" i])) { - } - [foo|att="val"] { - } -``` - -# Output - -```css -[lang] { -} -[lang] { -} -[lang] { -} -[lang] { -} -[lang] { -} -[lang] { -} -span[lang] { -} -span[lang] { -} -span[lang] { -} -span[lang] { -} -span[lang] { -} -span[lang="pt"] { -} -span[lang="pt"] { -} -span[lang="pt"] { -} -span[lang="pt"] { -} -span[lang="pt"] { -} -span[lang="pt"] { -} -span[lang="pt"] { -} -span[lang="pt"] { -} -span[lang~="en-us"] { -} -span[lang~="en-us"] { -} -span[lang|="zh"] { -} -span[lang~="en-us"] { -} -a[href^="#"] { -} -a[href$=".cn"] { -} -a[href*="example"] { -} -a[href*="example"] { -} -input[type="radio" i] { -} -input[type="radio" i] { -} -input[type~="radio" i] { -} -input[type~="radio" i] { -} -input[type~="radio" i] { -} -img[alt="person"][src="lorem"] { -} -img[alt="person"][src="lorem"] { -} -img[alt~="person"][src*="lorem"] { -} -img[alt~="person"][src*="lorem"] { -} -img[alt~="person"][src*="lorem"] { -} -section:has( :not([type="radio"], [type="checkbox"])) { -} -section:has( :not([type="radio" i], [type="checkbox" i])) { -} -section:has( :not([type="radio"], [type="checkbox"])) { -} -section:has( :not([type="radio" i], [type="checkbox" i])) { -} -section:has( :not([type="radio"], [type="checkbox"])) { -} -section:has( :not([type="radio" i], [type="checkbox" i])) { -} -section:has( :not([type="radio"], [type="checkbox"])) { -} -section:has( :not([type="radio" i], [type="checkbox" i])) { -} -[foo|att="val"] { -} -[foo|att="val"] { -} -[foo|att="val"] { -} -[foo|att="val"] { -} -[*|att] { -} -[*|att] { -} -[*|att] { -} -[*|att] { -} -[|att] { -} -[|att] { -} -[|att] { -} -[|att] { -} -``` - - From cc075b58b0f67f1a36e0548a4fc7a12d15cb2c4c Mon Sep 17 00:00:00 2001 From: Yusuke Abe Date: Fri, 14 Jun 2024 02:14:18 +0900 Subject: [PATCH 03/11] chore: add `chansuke` to maintainers list (#3206) --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c26be96abd7..90e6df339c5d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -431,6 +431,7 @@ Members are listed in alphabetical order. Members are free to use the full name, - [Vasu Singh @vasucp1207](https://github.com/vasucp1207) - [Yagiz Nizipli @anonrig](https://github.com/anonrig) - [Yoshiaki Togami @togami2864](https://github.com/togami2864) +- [Yusuke Abe @chansuke](https://github.com/chansuke) - [Zheyu Zhang @ah-yu](https://github.com/ah-yu) ### Past Maintainers From b70f405b5f7c6f7afeb4bdb9272fcf2ab0b764cd Mon Sep 17 00:00:00 2001 From: unvalley <38400669+unvalley@users.noreply.github.com> Date: Fri, 14 Jun 2024 12:23:31 +0900 Subject: [PATCH 04/11] fix(lint/useJsxKeyInIterable): fix a false positive when key is in parenthesized return statement (#3205) --- CHANGELOG.md | 4 ++++ .../src/lint/correctness/use_jsx_key_in_iterable.rs | 1 + .../tests/specs/correctness/useJsxKeyInIterable/valid.jsx | 7 ++++++- .../specs/correctness/useJsxKeyInIterable/valid.jsx.snap | 7 ++++++- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb52cf0f6d67..3daeb3358628 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b - Add [nursery/noShorthandPropertyOverrides](https://biomejs.dev/linter/rules/no-shorthand-property-overrides). [#2958](https://github.com/biomejs/biome/issues/2958) Contributed by @neokidev +#### Bug fixes + +- Fix [[#3084](https://github.com/biomejs/biome/issues/3084)] false positive by correctly recognize parenthesized return statement. Contributed by @unvalley + ### CLI #### Enhancement diff --git a/crates/biome_js_analyze/src/lint/correctness/use_jsx_key_in_iterable.rs b/crates/biome_js_analyze/src/lint/correctness/use_jsx_key_in_iterable.rs index d59fc091a8ab..d4bba0f3bb1f 100644 --- a/crates/biome_js_analyze/src/lint/correctness/use_jsx_key_in_iterable.rs +++ b/crates/biome_js_analyze/src/lint/correctness/use_jsx_key_in_iterable.rs @@ -193,6 +193,7 @@ fn handle_function_body( .as_ref() .and_then(|ret| { let returned_value = ret.argument()?; + let returned_value = unwrap_parenthesis(returned_value)?; Some(ReactComponentExpression::can_cast( returned_value.syntax().kind(), )) diff --git a/crates/biome_js_analyze/tests/specs/correctness/useJsxKeyInIterable/valid.jsx b/crates/biome_js_analyze/tests/specs/correctness/useJsxKeyInIterable/valid.jsx index e1953f741ba7..5d00b470cdb7 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useJsxKeyInIterable/valid.jsx +++ b/crates/biome_js_analyze/tests/specs/correctness/useJsxKeyInIterable/valid.jsx @@ -95,4 +95,9 @@ React.Children.map(c => React.cloneElement(c, {key: c})); [].map((item) => { const node = ; return {node}; -}) +}); + +[].map((el) => { + const content =

Paragraph

+ return (
{content}
); +}); diff --git a/crates/biome_js_analyze/tests/specs/correctness/useJsxKeyInIterable/valid.jsx.snap b/crates/biome_js_analyze/tests/specs/correctness/useJsxKeyInIterable/valid.jsx.snap index 640a37a6a66a..ceb9f23e5640 100644 --- a/crates/biome_js_analyze/tests/specs/correctness/useJsxKeyInIterable/valid.jsx.snap +++ b/crates/biome_js_analyze/tests/specs/correctness/useJsxKeyInIterable/valid.jsx.snap @@ -101,6 +101,11 @@ React.Children.map(c => React.cloneElement(c, {key: c})); [].map((item) => { const node = ; return {node}; -}) +}); + +[].map((el) => { + const content =

Paragraph

+ return (
{content}
); +}); ``` From 4c4e502322b1491fa33794b648569ec7ea205222 Mon Sep 17 00:00:00 2001 From: Ze-Zheng Wu Date: Fri, 14 Jun 2024 18:01:20 +0800 Subject: [PATCH 05/11] fix(migrate): properly handle rule removal and insertion (#3207) --- CHANGELOG.md | 4 + crates/biome_cli/src/commands/migrate.rs | 2 +- .../src/analyzers/nursery_rules.rs | 202 +++++++++++------- ...son => existingGroupWithExistingRule.json} | 2 +- .../existingGroupWithExistingRule.json.snap | 44 ++++ .../nurseryRules/firstToExistingGroup.json | 11 + .../firstToExistingGroup.json.snap | 47 ++++ .../nurseryRules/lastToExistingGroup.json | 11 + .../lastToExistingGroup.json.snap | 48 +++++ .../nurseryRules/middleToExistingGroup.json | 12 ++ .../middleToExistingGroup.json.snap | 50 +++++ .../nurseryRules/singleToExistingGroup.json | 10 + ...n.snap => singleToExistingGroup.json.snap} | 20 +- 13 files changed, 371 insertions(+), 92 deletions(-) rename crates/biome_migrate/tests/specs/migrations/nurseryRules/{existingGroup.json => existingGroupWithExistingRule.json} (75%) create mode 100644 crates/biome_migrate/tests/specs/migrations/nurseryRules/existingGroupWithExistingRule.json.snap create mode 100644 crates/biome_migrate/tests/specs/migrations/nurseryRules/firstToExistingGroup.json create mode 100644 crates/biome_migrate/tests/specs/migrations/nurseryRules/firstToExistingGroup.json.snap create mode 100644 crates/biome_migrate/tests/specs/migrations/nurseryRules/lastToExistingGroup.json create mode 100644 crates/biome_migrate/tests/specs/migrations/nurseryRules/lastToExistingGroup.json.snap create mode 100644 crates/biome_migrate/tests/specs/migrations/nurseryRules/middleToExistingGroup.json create mode 100644 crates/biome_migrate/tests/specs/migrations/nurseryRules/middleToExistingGroup.json.snap create mode 100644 crates/biome_migrate/tests/specs/migrations/nurseryRules/singleToExistingGroup.json rename crates/biome_migrate/tests/specs/migrations/nurseryRules/{existingGroup.json.snap => singleToExistingGroup.json.snap} (56%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3daeb3358628..fff40e2cd731 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b ### CLI +#### Bug fixes + +- Fix [#3179](https://github.com/biomejs/biome/issues/3179) where comma separators are not correctly removed after running `biome migrate` and thus choke the parser. Contributed by @Sec-ant + #### Enhancement - Reword the reporter message `No fixes needed` to `No fixes applied`. diff --git a/crates/biome_cli/src/commands/migrate.rs b/crates/biome_cli/src/commands/migrate.rs index e4acffd72d83..7f0362f3ab90 100644 --- a/crates/biome_cli/src/commands/migrate.rs +++ b/crates/biome_cli/src/commands/migrate.rs @@ -8,7 +8,7 @@ use biome_service::workspace::RegisterProjectFolderParams; use super::{check_fix_incompatible_arguments, FixFileModeOptions, MigrateSubCommand}; -/// Handler for the "check" command of the Biome CLI +/// Handler for the "migrate" command of the Biome CLI pub(crate) fn migrate( session: CliSession, cli_options: CliOptions, diff --git a/crates/biome_migrate/src/analyzers/nursery_rules.rs b/crates/biome_migrate/src/analyzers/nursery_rules.rs index f471b5c015b8..0ddcb2eaaf14 100644 --- a/crates/biome_migrate/src/analyzers/nursery_rules.rs +++ b/crates/biome_migrate/src/analyzers/nursery_rules.rs @@ -6,11 +6,12 @@ use biome_diagnostics::category; use biome_json_factory::make::{ json_member, json_member_list, json_member_name, json_object_value, json_string_literal, token, }; -use biome_json_syntax::{AnyJsonValue, JsonMember, JsonMemberList, JsonRoot, T}; +use biome_json_syntax::{AnyJsonValue, JsonMember, JsonMemberList, JsonRoot, JsonSyntaxToken, T}; use biome_rowan::{ AstNode, AstNodeExt, AstSeparatedList, BatchMutationExt, TextRange, TriviaPieceKind, WalkEvent, }; use rustc_hash::FxHashMap; +use std::iter::repeat; declare_migration! { pub(crate) NurseryRules { @@ -23,18 +24,20 @@ declare_migration! { #[derive(Debug)] pub(crate) struct MigrateRuleState { /// The member of the new rule - nursery_rule_member: JsonMember, + nursery_rule: JsonMember, /// The member of the group where the new rule should be moved nursery_group: JsonMember, - /// The name of the new rule - new_rule_name: &'static str, + /// The comma separator to be deleted + optional_separator: Option, + /// The name of the target rule + target_rule_name: &'static str, /// The new group name - new_group_name: &'static str, + target_group_name: &'static str, } impl MigrateRuleState { fn as_rule_name_range(&self) -> TextRange { - self.nursery_rule_member.range() + self.nursery_rule.range() } } @@ -69,7 +72,7 @@ fn find_group_by_name(root: &JsonRoot, group_name: &str) -> Option { // used for testing purposes /// - Left: name of the rule in the nursery group -/// - Right: name of the new group and name of the new rule (sometimes we change name) +/// - Right: name of the target group and name of the target rule (sometimes we change name) #[cfg(debug_assertions)] const RULES_TO_MIGRATE: &[(&str, (&str, &str))] = &[ ( @@ -111,7 +114,6 @@ const RULES_TO_MIGRATE: &[(&str, (&str, &str))] = &[ "noSuspiciousSemicolonInJsx", ("suspicious", "noSuspiciousSemicolonInJsx"), ), - ("useImportRestrictions", ("style", "useImportRestrictions")), ( "noConstantMathMinMaxClamp", ("correctness", "noConstantMathMinMaxClamp"), @@ -131,36 +133,54 @@ impl Rule for NurseryRules { let node = ctx.query(); let mut rules_to_migrate = vec![]; - let nursery_group = find_group_by_name(node, "nursery"); - - if let Some(nursery_member) = nursery_group { - let mut rules = FxHashMap::default(); - for (group, (new_group, new_name)) in RULES_TO_MIGRATE { - rules.insert(*group, (*new_group, *new_name)); + if let Some(nursery_group) = find_group_by_name(node, "nursery") { + let mut rules_should_be_migrated = FxHashMap::default(); + for (nursery_rule_name, (target_group_name, target_rule_name)) in RULES_TO_MIGRATE { + rules_should_be_migrated + .insert(*nursery_rule_name, (*target_group_name, *target_rule_name)); } - let object_value = nursery_member + let Some(nursery_group_object) = nursery_group .value() .ok() - .and_then(|node| node.as_json_object_value().cloned()); - - let Some(object_value) = object_value else { + .and_then(|node| node.as_json_object_value().cloned()) + else { return rules_to_migrate; }; - for group_member in object_value.json_member_list().iter().flatten() { - let Ok(member_name_text) = group_member + let mut separator_iterator = nursery_group_object + .json_member_list() + .separators() + .flatten() + .enumerate() + // Repeat the first separator, + // so when the rule to be deleted is the first rule, + // its trailing comma is also deleted: + // { + // "ruleA": "error", + // "ruleB": "error", + // "ruleC": "error" + // } + .flat_map(|(i, s)| repeat(s).take(if i == 0 { 2 } else { 1 })); + + for nursery_rule in nursery_group_object.json_member_list().iter().flatten() { + let optional_separator = separator_iterator.next(); + + let Ok(nursery_rule_name) = nursery_rule .name() .and_then(|node| node.inner_string_text()) else { continue; }; - let new_rule = rules.get(member_name_text.text()).copied(); - if let Some((new_group, new_rule)) = new_rule { + + if let Some((target_group_name, target_rule_name)) = + rules_should_be_migrated.get(nursery_rule_name.text()) + { rules_to_migrate.push(MigrateRuleState { - nursery_rule_member: group_member.clone(), - nursery_group: nursery_member.clone(), - new_rule_name: new_rule, - new_group_name: new_group, + nursery_rule: nursery_rule.clone(), + nursery_group: nursery_group.clone(), + optional_separator, + target_rule_name, + target_group_name, }) } } @@ -175,7 +195,7 @@ impl Rule for NurseryRules { category!("migrate"), state.as_rule_name_range(), markup! { - "This rule has been promoted to "{state.new_group_name}"/"{state.new_rule_name}"." + "This rule has been promoted to "{state.target_group_name}"/"{state.target_rule_name}"." } .to_owned(), ) @@ -186,36 +206,55 @@ impl Rule for NurseryRules { fn action(ctx: &RuleContext, state: &Self::State) -> Option { let node = ctx.query(); let MigrateRuleState { - new_group_name, - new_rule_name, + target_group_name, + target_rule_name, + optional_separator, nursery_group, - nursery_rule_member: nursery_rule, + nursery_rule, } = state; let mut mutation = ctx.root().begin(); + let mut rule_already_exists = false; - let new_group = find_group_by_name(node, new_group_name); + // If the target group exists, then we just need to delete the rule from the nursery group, + // and update the target group by adding a new member with the name of rule we are migrating + if let Some(target_group) = find_group_by_name(node, target_group_name) { + let target_group_value = target_group.value().ok()?; + let target_group_value_object = target_group_value.as_json_object_value()?; - // If the group exists, then we just need to update that group by adding a new member - // with the name of rule we are migrating - if let Some(group) = new_group { - let value = group.value().ok()?; - let value = value.as_json_object_value()?; + let current_rules = target_group_value_object.json_member_list(); + let current_rules_count = current_rules.len(); - let mut separators = vec![]; - let mut new_list = vec![]; + let mut separators = Vec::with_capacity(current_rules_count + 1); + let mut new_rules = Vec::with_capacity(current_rules_count + 1); - let old_list_node = value.json_member_list(); - let new_rule_member = - make_new_rule_name_member(new_rule_name, &nursery_rule.clone().detach())?; - - for member in old_list_node.iter() { - let member = member.ok()?; - new_list.push(member.clone()); + for current_rule in current_rules.iter() { + let current_rule = current_rule.ok()?; + if current_rule + .name() + .and_then(|node| node.inner_string_text()) + .is_ok_and(|text| text.text() == *target_rule_name) + { + rule_already_exists = true; + break; + } + new_rules.push(current_rule.clone()); separators.push(token(T![,])); } - new_list.push(new_rule_member); - mutation.replace_node(old_list_node, json_member_list(new_list, separators)); + + // We only add the rule if the rule doesn't already exist in the target group + // to avoid duplicate rules in the target group + if !rule_already_exists { + let new_rule_member = + make_new_rule_name_member(target_rule_name, &nursery_rule.clone().detach())?; + new_rules.push(new_rule_member); + mutation.replace_node(current_rules, json_member_list(new_rules, separators)); + } + + // Remove the stale nursery rule and the corresponding comma separator mutation.remove_node(nursery_rule.clone()); + if let Some(separator) = optional_separator { + mutation.remove_token(separator.clone()); + } } // If we don't have a group, we have to create one. To avoid possible side effects with our mutation logic // we recreate the "rules" object by removing the `rules.nursery.` member (hence we create a new list), @@ -230,48 +269,49 @@ impl Rule for NurseryRules { .filter_map(|node| { let node = node.ok()?; - if &node == nursery_group { - let object = node.value().ok()?; - let object = object.as_json_object_value()?; - let new_nursery_group: Vec<_> = object - .json_member_list() - .iter() - .filter_map(|node| { - let node = node.ok()?; - if &node == nursery_rule { - None - } else { - Some(node) - } - }) - .collect(); + if &node != nursery_group { + return Some(node); + } - let new_member = json_member( - node.name().ok()?.clone(), - token(T![:]), - AnyJsonValue::JsonObjectValue(json_object_value( - token(T!['{']), - json_member_list(new_nursery_group, vec![]), - token(T!['}']), - )), - ); + let object = node.value().ok()?; + let object = object.as_json_object_value()?; + let new_nursery_group: Vec<_> = object + .json_member_list() + .iter() + .filter_map(|node| { + let node = node.ok()?; + if &node == nursery_rule { + None + } else { + Some(node) + } + }) + .collect(); - return Some(new_member); - } + let new_member = json_member( + node.name().ok()?.clone(), + token(T![:]), + AnyJsonValue::JsonObjectValue(json_object_value( + token(T!['{']), + json_member_list(new_nursery_group, vec![]), + token(T!['}']), + )), + ); - Some(node) + Some(new_member) }) .collect(); + let new_member = json_member( json_member_name( - json_string_literal(new_group_name) + json_string_literal(target_group_name) .with_leading_trivia([(TriviaPieceKind::Whitespace, "\n")]), ), token(T![:]), AnyJsonValue::JsonObjectValue(json_object_value( token(T!['{']), json_member_list( - vec![make_new_rule_name_member(new_rule_name, nursery_rule)?], + vec![make_new_rule_name_member(target_rule_name, nursery_rule)?], vec![], ), token(T!['}']).with_leading_trivia_pieces( @@ -311,8 +351,14 @@ impl Rule for NurseryRules { Some(MigrationAction::new( ActionCategory::QuickFix, ctx.metadata().applicability(), - markup! { - "Move the rule to the new stable group." + if rule_already_exists { + markup! { + "Remove the stale rule from the nursery group." + } + } else { + markup! { + "Move the rule to the new stable group." + } } .to_owned(), mutation, diff --git a/crates/biome_migrate/tests/specs/migrations/nurseryRules/existingGroup.json b/crates/biome_migrate/tests/specs/migrations/nurseryRules/existingGroupWithExistingRule.json similarity index 75% rename from crates/biome_migrate/tests/specs/migrations/nurseryRules/existingGroup.json rename to crates/biome_migrate/tests/specs/migrations/nurseryRules/existingGroupWithExistingRule.json index 92b49d30a5e9..47082f244d1e 100644 --- a/crates/biome_migrate/tests/specs/migrations/nurseryRules/existingGroup.json +++ b/crates/biome_migrate/tests/specs/migrations/nurseryRules/existingGroupWithExistingRule.json @@ -2,7 +2,7 @@ "linter": { "rules": { "nursery": { - "noExcessiveNestedTestSuites": "error" + "noExcessiveNestedTestSuites": "warn" }, "complexity": { "noExcessiveNestedTestSuites": "error" diff --git a/crates/biome_migrate/tests/specs/migrations/nurseryRules/existingGroupWithExistingRule.json.snap b/crates/biome_migrate/tests/specs/migrations/nurseryRules/existingGroupWithExistingRule.json.snap new file mode 100644 index 000000000000..fddf8ee5a9d0 --- /dev/null +++ b/crates/biome_migrate/tests/specs/migrations/nurseryRules/existingGroupWithExistingRule.json.snap @@ -0,0 +1,44 @@ +--- +source: crates/biome_migrate/tests/spec_tests.rs +expression: existingGroupWithExistingRule.json +--- +# Input +```json +{ + "linter": { + "rules": { + "nursery": { + "noExcessiveNestedTestSuites": "warn" + }, + "complexity": { + "noExcessiveNestedTestSuites": "error" + } + } + } +} + +``` + +# Diagnostics +``` +existingGroupWithExistingRule.json:5:9 migrate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This rule has been promoted to complexity/noExcessiveNestedTestSuites. + + 3 │ "rules": { + 4 │ "nursery": { + > 5 │ "noExcessiveNestedTestSuites": "warn" + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 6 │ }, + 7 │ "complexity": { + + i Unsafe fix: Remove the stale rule from the nursery group. + + 3 3 │ "rules": { + 4 4 │ "nursery": { + 5 │ - ········"noExcessiveNestedTestSuites":·"warn" + 6 5 │ }, + 7 6 │ "complexity": { + + +``` diff --git a/crates/biome_migrate/tests/specs/migrations/nurseryRules/firstToExistingGroup.json b/crates/biome_migrate/tests/specs/migrations/nurseryRules/firstToExistingGroup.json new file mode 100644 index 000000000000..4ff1543d6d40 --- /dev/null +++ b/crates/biome_migrate/tests/specs/migrations/nurseryRules/firstToExistingGroup.json @@ -0,0 +1,11 @@ +{ + "linter": { + "rules": { + "nursery": { + "noExcessiveNestedTestSuites": "error", + "nuseryRuleAlways": "error" + }, + "complexity": {} + } + } +} diff --git a/crates/biome_migrate/tests/specs/migrations/nurseryRules/firstToExistingGroup.json.snap b/crates/biome_migrate/tests/specs/migrations/nurseryRules/firstToExistingGroup.json.snap new file mode 100644 index 000000000000..79c40a3c7360 --- /dev/null +++ b/crates/biome_migrate/tests/specs/migrations/nurseryRules/firstToExistingGroup.json.snap @@ -0,0 +1,47 @@ +--- +source: crates/biome_migrate/tests/spec_tests.rs +expression: firstToExistingGroup.json +--- +# Input +```json +{ + "linter": { + "rules": { + "nursery": { + "noExcessiveNestedTestSuites": "error", + "nuseryRuleAlways": "error" + }, + "complexity": {} + } + } +} + +``` + +# Diagnostics +``` +firstToExistingGroup.json:5:9 migrate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This rule has been promoted to complexity/noExcessiveNestedTestSuites. + + 3 │ "rules": { + 4 │ "nursery": { + > 5 │ "noExcessiveNestedTestSuites": "error", + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 6 │ "nuseryRuleAlways": "error" + 7 │ }, + + i Unsafe fix: Move the rule to the new stable group. + + 3 3 │ "rules": { + 4 4 │ "nursery": { + 5 │ - ········"noExcessiveNestedTestSuites":·"error", + 6 5 │ "nuseryRuleAlways": "error" + 7 6 │ }, + 8 │ - ······"complexity":·{} + 7 │ + ······"complexity":·{"noExcessiveNestedTestSuites":·"error"} + 9 8 │ } + 10 9 │ } + + +``` diff --git a/crates/biome_migrate/tests/specs/migrations/nurseryRules/lastToExistingGroup.json b/crates/biome_migrate/tests/specs/migrations/nurseryRules/lastToExistingGroup.json new file mode 100644 index 000000000000..015893c755d0 --- /dev/null +++ b/crates/biome_migrate/tests/specs/migrations/nurseryRules/lastToExistingGroup.json @@ -0,0 +1,11 @@ +{ + "linter": { + "rules": { + "nursery": { + "nuseryRuleAlways": "error", + "noExcessiveNestedTestSuites": "error" + }, + "complexity": {} + } + } +} diff --git a/crates/biome_migrate/tests/specs/migrations/nurseryRules/lastToExistingGroup.json.snap b/crates/biome_migrate/tests/specs/migrations/nurseryRules/lastToExistingGroup.json.snap new file mode 100644 index 000000000000..b54d3726cad4 --- /dev/null +++ b/crates/biome_migrate/tests/specs/migrations/nurseryRules/lastToExistingGroup.json.snap @@ -0,0 +1,48 @@ +--- +source: crates/biome_migrate/tests/spec_tests.rs +expression: lastToExistingGroup.json +--- +# Input +```json +{ + "linter": { + "rules": { + "nursery": { + "nuseryRuleAlways": "error", + "noExcessiveNestedTestSuites": "error" + }, + "complexity": {} + } + } +} + +``` + +# Diagnostics +``` +lastToExistingGroup.json:6:9 migrate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This rule has been promoted to complexity/noExcessiveNestedTestSuites. + + 4 │ "nursery": { + 5 │ "nuseryRuleAlways": "error", + > 6 │ "noExcessiveNestedTestSuites": "error" + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 7 │ }, + 8 │ "complexity": {} + + i Unsafe fix: Move the rule to the new stable group. + + 3 3 │ "rules": { + 4 4 │ "nursery": { + 5 │ - ········"nuseryRuleAlways":·"error", + 6 │ - ········"noExcessiveNestedTestSuites":·"error" + 5 │ + ········"nuseryRuleAlways":·"error" + 7 6 │ }, + 8 │ - ······"complexity":·{} + 7 │ + ······"complexity":·{"noExcessiveNestedTestSuites":·"error"} + 9 8 │ } + 10 9 │ } + + +``` diff --git a/crates/biome_migrate/tests/specs/migrations/nurseryRules/middleToExistingGroup.json b/crates/biome_migrate/tests/specs/migrations/nurseryRules/middleToExistingGroup.json new file mode 100644 index 000000000000..6513070a137b --- /dev/null +++ b/crates/biome_migrate/tests/specs/migrations/nurseryRules/middleToExistingGroup.json @@ -0,0 +1,12 @@ +{ + "linter": { + "rules": { + "nursery": { + "nuseryRuleAlways": "error", + "noExcessiveNestedTestSuites": "error", + "nuseryRuleForever": "error" + }, + "complexity": {} + } + } +} diff --git a/crates/biome_migrate/tests/specs/migrations/nurseryRules/middleToExistingGroup.json.snap b/crates/biome_migrate/tests/specs/migrations/nurseryRules/middleToExistingGroup.json.snap new file mode 100644 index 000000000000..35d2fbb23247 --- /dev/null +++ b/crates/biome_migrate/tests/specs/migrations/nurseryRules/middleToExistingGroup.json.snap @@ -0,0 +1,50 @@ +--- +source: crates/biome_migrate/tests/spec_tests.rs +expression: middleToExistingGroup.json +--- +# Input +```json +{ + "linter": { + "rules": { + "nursery": { + "nuseryRuleAlways": "error", + "noExcessiveNestedTestSuites": "error", + "nuseryRuleForever": "error" + }, + "complexity": {} + } + } +} + +``` + +# Diagnostics +``` +middleToExistingGroup.json:6:9 migrate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This rule has been promoted to complexity/noExcessiveNestedTestSuites. + + 4 │ "nursery": { + 5 │ "nuseryRuleAlways": "error", + > 6 │ "noExcessiveNestedTestSuites": "error", + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + 7 │ "nuseryRuleForever": "error" + 8 │ }, + + i Unsafe fix: Move the rule to the new stable group. + + 3 3 │ "rules": { + 4 4 │ "nursery": { + 5 │ - ········"nuseryRuleAlways":·"error", + 6 │ - ········"noExcessiveNestedTestSuites":·"error", + 5 │ + ········"nuseryRuleAlways":·"error", + 7 6 │ "nuseryRuleForever": "error" + 8 7 │ }, + 9 │ - ······"complexity":·{} + 8 │ + ······"complexity":·{"noExcessiveNestedTestSuites":·"error"} + 10 9 │ } + 11 10 │ } + + +``` diff --git a/crates/biome_migrate/tests/specs/migrations/nurseryRules/singleToExistingGroup.json b/crates/biome_migrate/tests/specs/migrations/nurseryRules/singleToExistingGroup.json new file mode 100644 index 000000000000..8634a951b501 --- /dev/null +++ b/crates/biome_migrate/tests/specs/migrations/nurseryRules/singleToExistingGroup.json @@ -0,0 +1,10 @@ +{ + "linter": { + "rules": { + "nursery": { + "noExcessiveNestedTestSuites": "error" + }, + "complexity": {} + } + } +} diff --git a/crates/biome_migrate/tests/specs/migrations/nurseryRules/existingGroup.json.snap b/crates/biome_migrate/tests/specs/migrations/nurseryRules/singleToExistingGroup.json.snap similarity index 56% rename from crates/biome_migrate/tests/specs/migrations/nurseryRules/existingGroup.json.snap rename to crates/biome_migrate/tests/specs/migrations/nurseryRules/singleToExistingGroup.json.snap index 40081817859e..2ec9746fb393 100644 --- a/crates/biome_migrate/tests/specs/migrations/nurseryRules/existingGroup.json.snap +++ b/crates/biome_migrate/tests/specs/migrations/nurseryRules/singleToExistingGroup.json.snap @@ -1,6 +1,6 @@ --- source: crates/biome_migrate/tests/spec_tests.rs -expression: existingGroup.json +expression: singleToExistingGroup.json --- # Input ```json @@ -10,9 +10,7 @@ expression: existingGroup.json "nursery": { "noExcessiveNestedTestSuites": "error" }, - "complexity": { - "noExcessiveNestedTestSuites": "error" - } + "complexity": {} } } } @@ -21,7 +19,7 @@ expression: existingGroup.json # Diagnostics ``` -existingGroup.json:5:9 migrate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +singleToExistingGroup.json:5:9 migrate FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! This rule has been promoted to complexity/noExcessiveNestedTestSuites. @@ -30,7 +28,7 @@ existingGroup.json:5:9 migrate FIXABLE ━━━━━━━━━━━━━ > 5 │ "noExcessiveNestedTestSuites": "error" │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 │ }, - 7 │ "complexity": { + 7 │ "complexity": {} i Unsafe fix: Move the rule to the new stable group. @@ -38,12 +36,10 @@ existingGroup.json:5:9 migrate FIXABLE ━━━━━━━━━━━━━ 4 4 │ "nursery": { 5 │ - ········"noExcessiveNestedTestSuites":·"error" 6 5 │ }, - 7 6 │ "complexity": { - 8 │ - ········"noExcessiveNestedTestSuites":·"error" - 7 │ + ········"noExcessiveNestedTestSuites":·"error", - 8 │ + ········"noExcessiveNestedTestSuites":·"error" - 9 9 │ } - 10 10 │ } + 7 │ - ······"complexity":·{} + 6 │ + ······"complexity":·{"noExcessiveNestedTestSuites":·"error"} + 8 7 │ } + 9 8 │ } ``` From 743d805345ad963d8c02c5c4c31599b44ff0280c Mon Sep 17 00:00:00 2001 From: Ze-Zheng Wu Date: Fri, 14 Jun 2024 22:10:17 +0800 Subject: [PATCH 06/11] fix(lint/useLiteralKeys): unescaped newline false positive (#3210) --- CHANGELOG.md | 12 ++ .../src/lint/complexity/use_literal_keys.rs | 60 ++++++-- .../complexity/useLiteralKeys/invalid.js | 14 ++ .../complexity/useLiteralKeys/invalid.js.snap | 139 ++++++++++++++++-- .../complexity/useLiteralKeys/invalid.ts.snap | 20 +-- .../specs/complexity/useLiteralKeys/valid.js | 6 +- .../complexity/useLiteralKeys/valid.js.snap | 5 + 7 files changed, 222 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fff40e2cd731..34d23cf98b26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,18 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b - The [`noUnmatchableAnbSelector`](https://biomejs.dev/linter/rules/no-unmatchable-anb-selector/) rule is now able to catch unmatchable `an+b` selectors like `0n+0` or `-0n+0`. Contributed by @Sec-ant. - The [`useHookAtTopLevel`](https://biomejs.dev/linter/rules/use-hook-at-top-level/) rule now recognizes properties named as hooks like `foo.useFoo()`. Contributed by @ksnyder9801 - Fix [#3092](https://github.com/biomejs/biome/issues/3092), prevent warning for `Custom properties (--*)`. Contributed by @chansuke +- Fix a false positive in the [`useLiteralKeys`](https://biomejs.dev/linter/rules/use-literal-keys/) rule. ([#3160](https://github.com/biomejs/biome/issues/3160)) + + This rule now ignores the following kind of computed member name: + + ```js + const a = { + [`line1 + line2`]: true, + }; + ``` + + Contributed by @Sec-ant ### Parser diff --git a/crates/biome_js_analyze/src/lint/complexity/use_literal_keys.rs b/crates/biome_js_analyze/src/lint/complexity/use_literal_keys.rs index fb6467495aaf..d10f99b5678d 100644 --- a/crates/biome_js_analyze/src/lint/complexity/use_literal_keys.rs +++ b/crates/biome_js_analyze/src/lint/complexity/use_literal_keys.rs @@ -62,15 +62,19 @@ declare_rule! { impl Rule for UseLiteralKeys { type Query = Ast; - type State = (TextRange, String); + type State = (TextRange, String, bool); type Signals = Option; type Options = (); fn run(ctx: &RuleContext) -> Self::Signals { let node = ctx.query(); + let mut is_computed_member_name = false; let inner_expression = match node { AnyJsMember::AnyJsComputedMember(computed_member) => computed_member.member().ok()?, - AnyJsMember::JsComputedMemberName(member) => member.expression().ok()?, + AnyJsMember::JsComputedMemberName(member) => { + is_computed_member_name = true; + member.expression().ok()? + } }; let value = inner_expression.as_static_value()?; let value = value.as_string_constant()?; @@ -79,27 +83,47 @@ impl Rule for UseLiteralKeys { // The first is a regular property. // The second is a special property that changes the object prototype. // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/proto - if matches!(node, AnyJsMember::JsComputedMemberName(_)) && value == "__proto__" { + if is_computed_member_name && value == "__proto__" { return None; } - // A computed property `["something"]` can always be simplified to a string literal "something". - if matches!(node, AnyJsMember::JsComputedMemberName(_)) || is_js_ident(value) { - return Some((inner_expression.range(), value.to_string())); + // A computed property `["something"]` can always be simplified to a string literal "something", + // unless it is a template literal inside that contains unescaped new line characters: + // + // const a = { + // `line1 + // line2`: true + // } + // + if (is_computed_member_name && !has_unescaped_new_line(value)) || is_js_ident(value) { + return Some(( + inner_expression.range(), + value.to_string(), + is_computed_member_name, + )); } None } - fn diagnostic(_ctx: &RuleContext, (range, _): &Self::State) -> Option { + fn diagnostic( + _ctx: &RuleContext, + (range, _, is_computed_member_name): &Self::State, + ) -> Option { Some(RuleDiagnostic::new( rule_category!(), range, - markup! { - "The computed expression can be simplified without the use of a string literal." + if *is_computed_member_name { + markup! { + "The computed expression can be simplified to a string literal." + } + } else { + markup! { + "The computed expression can be simplified without the use of a string literal." + } }, )) } - fn action(ctx: &RuleContext, (_, identifier): &Self::State) -> Option { + fn action(ctx: &RuleContext, (_, identifier, _): &Self::State) -> Option { let node = ctx.query(); let mut mutation = ctx.root().begin(); match node { @@ -161,3 +185,19 @@ impl Rule for UseLiteralKeys { declare_node_union! { pub AnyJsMember = AnyJsComputedMember | JsComputedMemberName } + +fn has_unescaped_new_line(text: &str) -> bool { + let mut iter = text.as_bytes().iter(); + while let Some(c) = iter.next() { + match c { + b'\\' => { + iter.next(); + } + b'\n' => { + return true; + } + _ => {} + } + } + false +} diff --git a/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/invalid.js b/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/invalid.js index c16c38296455..98eb4427ac6f 100644 --- a/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/invalid.js +++ b/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/invalid.js @@ -32,3 +32,17 @@ a = { // optional chain a?.["b"]?.['c']?.d?.e?.["f"] +a = { + ["line1\ + line2"]: true, +}; +a = { + [`line1\ + line2`]: true, +}; +a = { + ["line1\nline2"]: true, +}; +a = { + [`line1\nline2`]: true, +}; \ No newline at end of file diff --git a/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/invalid.js.snap b/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/invalid.js.snap index 1f64c90ae13b..c93afb333cc6 100644 --- a/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/invalid.js.snap +++ b/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/invalid.js.snap @@ -38,7 +38,20 @@ a = { // optional chain a?.["b"]?.['c']?.d?.e?.["f"] - +a = { + ["line1\ + line2"]: true, +}; +a = { + [`line1\ + line2`]: true, +}; +a = { + ["line1\nline2"]: true, +}; +a = { + [`line1\nline2`]: true, +}; ``` # Diagnostics @@ -327,7 +340,7 @@ invalid.js:10:7 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ ``` invalid.js:12:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 10 │ a.b[c["d"]] = "something"; 11 │ a = { @@ -351,7 +364,7 @@ invalid.js:12:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ ``` invalid.js:15:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 13 │ }; 14 │ a = { @@ -423,7 +436,7 @@ invalid.js:18:5 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ ``` invalid.js:19:12 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 17 │ a.b[`$c`]; 18 │ a.b["_d"]; @@ -442,7 +455,7 @@ invalid.js:19:12 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ ``` invalid.js:20:12 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 18 │ a.b["_d"]; 19 │ class C { ["a"] = 0 } @@ -461,7 +474,7 @@ invalid.js:20:12 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ ``` invalid.js:21:16 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 19 │ class C { ["a"] = 0 } 20 │ class C { ["a"](){} } @@ -480,7 +493,7 @@ invalid.js:21:16 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ ``` invalid.js:22:16 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 20 │ class C { ["a"](){} } 21 │ class C { get ["a"](){} } @@ -499,7 +512,7 @@ invalid.js:22:16 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ ``` invalid.js:24:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 22 │ class C { set ["a"](x){} } 23 │ a = { @@ -518,7 +531,7 @@ invalid.js:24:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ ``` invalid.js:27:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 25 │ } 26 │ a = { @@ -542,7 +555,7 @@ invalid.js:27:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ ``` invalid.js:30:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 28 │ } 29 │ a = { @@ -566,7 +579,8 @@ invalid.js:34:5 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ 33 │ // optional chain > 34 │ a?.["b"]?.['c']?.d?.e?.["f"] │ ^^^ - 35 │ + 35 │ a = { + 36 │ ["line1\ i Unsafe fix: Use a literal key instead. @@ -583,7 +597,8 @@ invalid.js:34:12 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ 33 │ // optional chain > 34 │ a?.["b"]?.['c']?.d?.e?.["f"] │ ^^^ - 35 │ + 35 │ a = { + 36 │ ["line1\ i Unsafe fix: Use a literal key instead. @@ -600,7 +615,8 @@ invalid.js:34:25 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ 33 │ // optional chain > 34 │ a?.["b"]?.['c']?.d?.e?.["f"] │ ^^^ - 35 │ + 35 │ a = { + 36 │ ["line1\ i Unsafe fix: Use a literal key instead. @@ -608,3 +624,100 @@ invalid.js:34:25 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ │ -- -- ``` + +``` +invalid.js:36:4 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The computed expression can be simplified to a string literal. + + 34 │ a?.["b"]?.['c']?.d?.e?.["f"] + 35 │ a = { + > 36 │ ["line1\ + │ ^^^^^^^ + > 37 │ line2"]: true, + │ ^^^^^^ + 38 │ }; + 39 │ a = { + + i Unsafe fix: Use a literal key instead. + + 34 34 │ a?.["b"]?.['c']?.d?.e?.["f"] + 35 35 │ a = { + 36 │ - ··["line1\ + 37 │ - ··line2"]:·true, + 36 │ + ··"line1\ + 37 │ + ··line2":·true, + 38 38 │ }; + 39 39 │ a = { + + +``` + +``` +invalid.js:40:4 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The computed expression can be simplified to a string literal. + + 38 │ }; + 39 │ a = { + > 40 │ [`line1\ + │ ^^^^^^^ + > 41 │ line2`]: true, + │ ^^^^^^ + 42 │ }; + 43 │ a = { + + i Unsafe fix: Use a literal key instead. + + 38 38 │ }; + 39 39 │ a = { + 40 │ - ··[`line1\ + 41 │ - ··line2`]:·true, + 40 │ + ··"line1\ + 41 │ + ··line2":·true, + 42 42 │ }; + 43 43 │ a = { + + +``` + +``` +invalid.js:44:4 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The computed expression can be simplified to a string literal. + + 42 │ }; + 43 │ a = { + > 44 │ ["line1\nline2"]: true, + │ ^^^^^^^^^^^^^^ + 45 │ }; + 46 │ a = { + + i Unsafe fix: Use a literal key instead. + + 44 │ ··["line1\nline2"]:·true, + │ - - + +``` + +``` +invalid.js:47:4 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! The computed expression can be simplified to a string literal. + + 45 │ }; + 46 │ a = { + > 47 │ [`line1\nline2`]: true, + │ ^^^^^^^^^^^^^^ + 48 │ }; + + i Unsafe fix: Use a literal key instead. + + 45 45 │ }; + 46 46 │ a = { + 47 │ - ··[`line1\nline2`]:·true, + 47 │ + ··"line1\nline2":·true, + 48 48 │ }; + + +``` diff --git a/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/invalid.ts.snap b/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/invalid.ts.snap index 3ab82b583c74..a5989965fb3d 100644 --- a/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/invalid.ts.snap +++ b/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/invalid.ts.snap @@ -34,7 +34,7 @@ export type T = { ``` invalid.ts:2:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 1 │ export interface I { > 2 │ ["p1"]: number @@ -52,7 +52,7 @@ invalid.ts:2:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━ ``` invalid.ts:4:7 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 2 │ ["p1"]: number 3 │ @@ -71,7 +71,7 @@ invalid.ts:4:7 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━ ``` invalid.ts:6:7 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 4 │ get ["p2"](): number 5 │ @@ -90,7 +90,7 @@ invalid.ts:6:7 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━ ``` invalid.ts:8:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 6 │ set ["p2"](x: number) 7 │ @@ -109,7 +109,7 @@ invalid.ts:8:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━ ``` invalid.ts:10:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 8 │ ["m1"](): void 9 │ @@ -128,7 +128,7 @@ invalid.ts:10:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ ``` invalid.ts:14:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 13 │ export type T = { > 14 │ ["p1"]: number @@ -146,7 +146,7 @@ invalid.ts:14:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ ``` invalid.ts:16:7 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 14 │ ["p1"]: number 15 │ @@ -165,7 +165,7 @@ invalid.ts:16:7 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ ``` invalid.ts:18:7 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 16 │ get ["p2"](): number 17 │ @@ -184,7 +184,7 @@ invalid.ts:18:7 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ ``` invalid.ts:20:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 18 │ set ["p2"](x: number) 19 │ @@ -203,7 +203,7 @@ invalid.ts:20:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━ ``` invalid.ts:22:3 lint/complexity/useLiteralKeys FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - ! The computed expression can be simplified without the use of a string literal. + ! The computed expression can be simplified to a string literal. 20 │ ["m1"](): void 21 │ diff --git a/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/valid.js b/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/valid.js index e300b6ed043c..346a3b5d4f56 100644 --- a/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/valid.js +++ b/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/valid.js @@ -31,4 +31,8 @@ a = { // Exception a = { ["__proto__"]: null, -} \ No newline at end of file +} +a = { + [`line1 + line2`]: true, +}; diff --git a/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/valid.js.snap b/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/valid.js.snap index 11b4bd1966b3..2828f91d04ac 100644 --- a/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/valid.js.snap +++ b/crates/biome_js_analyze/tests/specs/complexity/useLiteralKeys/valid.js.snap @@ -38,4 +38,9 @@ a = { a = { ["__proto__"]: null, } +a = { + [`line1 + line2`]: true, +}; + ``` From 219dae0ef579e104c71efd47edbf9ed1545d136e Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Fri, 14 Jun 2024 19:38:59 +0100 Subject: [PATCH 07/11] fix(cli): inject source code in diagnostics printed in `stdin` (#3211) Co-authored-by: Arend van Beelen jr. --- CHANGELOG.md | 1 + crates/biome_cli/src/execute/std_in.rs | 23 ++++-- crates/biome_cli/tests/commands/lint.rs | 32 ++++++++ .../check_stdin_successfully.snap | 32 +++++++- .../check_stdin_write_successfully.snap | 17 +++- .../lint_stdin_successfully.snap | 6 +- .../check_stdin_successfully.snap | 32 +++++++- .../check_stdin_write_successfully.snap | 17 +++- .../lint_stdin_successfully.snap | 12 ++- .../lint_stdin_write_successfully.snap | 6 +- .../check_stdin_successfully.snap | 15 +++- .../check_stdin_write_successfully.snap | 8 +- .../lint_stdin_successfully.snap | 36 ++++++++- .../lint_stdin_write_successfully.snap | 28 ++++++- .../check_stdin_shows_parse_diagnostics.snap | 80 +++++++++++++++++++ 15 files changed, 314 insertions(+), 31 deletions(-) create mode 100644 crates/biome_cli/tests/snapshots/main_commands_lint/check_stdin_shows_parse_diagnostics.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index 34d23cf98b26..477238d75695 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ our [guidelines for writing a good changelog entry](https://github.com/biomejs/b #### Bug fixes +- Fix [#3201](https://github.com/biomejs/biome/issues/3201) by correctly injecting the source code of the file when printing the diagnostics. Contributed by @ematipico - Fix [#3179](https://github.com/biomejs/biome/issues/3179) where comma separators are not correctly removed after running `biome migrate` and thus choke the parser. Contributed by @Sec-ant #### Enhancement diff --git a/crates/biome_cli/src/execute/std_in.rs b/crates/biome_cli/src/execute/std_in.rs index 65503dce7def..ae3d44d4df35 100644 --- a/crates/biome_cli/src/execute/std_in.rs +++ b/crates/biome_cli/src/execute/std_in.rs @@ -4,8 +4,8 @@ use crate::execute::diagnostics::{ContentDiffAdvice, FormatDiffDiagnostic}; use crate::execute::Execution; use crate::{CliDiagnostic, CliSession, TraversalMode}; use biome_console::{markup, ConsoleExt}; -use biome_diagnostics::Diagnostic; use biome_diagnostics::PrintDiagnostic; +use biome_diagnostics::{Diagnostic, DiagnosticExt, Error}; use biome_fs::BiomePath; use biome_service::file_handlers::{AstroFileHandler, SvelteFileHandler, VueFileHandler}; use biome_service::workspace::{ @@ -75,7 +75,7 @@ pub(crate) fn run<'a>( }) } } else if mode.is_check() || mode.is_lint() { - let mut diagnostics = Vec::new(); + let mut diagnostics: Vec = Vec::new(); let mut new_content = Cow::Borrowed(content); workspace.open_file(OpenFileParams { @@ -169,7 +169,20 @@ pub(crate) fn run<'a>( only, skip, })?; - diagnostics.extend(result.diagnostics); + let content = match biome_path.extension_as_str() { + Some("astro") => AstroFileHandler::input(&new_content), + Some("vue") => VueFileHandler::input(&new_content), + Some("svelte") => SvelteFileHandler::input(&new_content), + _ => &new_content, + }; + diagnostics.extend( + result + .diagnostics + .into_iter() + .map(Error::from) + .map(|diag| diag.with_file_source_code(content.to_string())) + .collect::>(), + ); } if file_features.supports_format() && mode.is_check() { @@ -195,7 +208,7 @@ pub(crate) fn run<'a>( old: content.to_string(), }, }; - diagnostics.push(biome_diagnostics::serde::Diagnostic::new(diagnostic)); + diagnostics.push(Error::from(diagnostic)); } } @@ -205,7 +218,7 @@ pub(crate) fn run<'a>( {original_content} }); } - Cow::Owned(new_content) => { + Cow::Owned(ref new_content) => { console.append(markup! { {new_content} }); diff --git a/crates/biome_cli/tests/commands/lint.rs b/crates/biome_cli/tests/commands/lint.rs index a4723ae61bfc..3b641565d003 100644 --- a/crates/biome_cli/tests/commands/lint.rs +++ b/crates/biome_cli/tests/commands/lint.rs @@ -2276,6 +2276,38 @@ fn check_stdin_write_successfully() { )); } +#[test] +fn check_stdin_shows_parse_diagnostics() { + let mut fs = MemoryFileSystem::default(); + let mut console = BufferConsole::default(); + + console.in_buffer.push( + r#" +server.get('/', async () => { + return { hello: 'world' }; +}); + +const = ""; "# + .to_string(), + ); + + let result = run_cli( + DynRef::Borrowed(&mut fs), + &mut console, + Args::from([("lint"), "--write", ("--stdin-file-path"), ("mock.ts")].as_slice()), + ); + + assert!(result.is_ok(), "run_cli returned {result:?}"); + + assert_cli_snapshot(SnapshotPayload::new( + module_path!(), + "check_stdin_shows_parse_diagnostics", + fs, + console, + result, + )); +} + #[test] fn check_stdin_write_unsafe_successfully() { let mut fs = MemoryFileSystem::default(); diff --git a/crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/check_stdin_successfully.snap b/crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/check_stdin_successfully.snap index d1d880e50dad..e3b069feb782 100644 --- a/crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/check_stdin_successfully.snap +++ b/crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/check_stdin_successfully.snap @@ -29,10 +29,15 @@ var foo: string = ""; ``` ```block -file.astro lint/complexity/noUselessRename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.astro:2:9 lint/complexity/noUselessRename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Useless rename. + > 2 │ import {a as a} from 'mod'; + │ ^^^^^^ + 3 │ import { something } from "file.astro"; + 4 │ debugger; + i Safe fix: Remove the renaming. 2 │ import·{a·as·a}·from·'mod'; @@ -41,10 +46,17 @@ file.astro lint/complexity/noUselessRename FIXABLE ━━━━━━━━━ ``` ```block -file.astro lint/suspicious/noDebugger FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.astro:4:1 lint/suspicious/noDebugger FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × This is an unexpected use of the debugger statement. + 2 │ import {a as a} from 'mod'; + 3 │ import { something } from "file.astro"; + > 4 │ debugger; + │ ^^^^^^^^^ + 5 │ statement ( ) ; + 6 │ var foo: string = ""; + i Unsafe fix: Remove debugger statement 2 2 │ import {a as a} from 'mod'; @@ -57,10 +69,16 @@ file.astro lint/suspicious/noDebugger FIXABLE ━━━━━━━━━━ ``` ```block -file.astro lint/style/noInferrableTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.astro:6:8 lint/style/noInferrableTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × This type annotation is trivially inferred from its initialization. + 4 │ debugger; + 5 │ statement ( ) ; + > 6 │ var foo: string = ""; + │ ^^^^^^^^ + 7 │ + i Safe fix: Remove the type annotation. 4 4 │ debugger; @@ -73,10 +91,16 @@ file.astro lint/style/noInferrableTypes FIXABLE ━━━━━━━━━━ ``` ```block -file.astro lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.astro:6:1 lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Use let or const instead of var. + 4 │ debugger; + 5 │ statement ( ) ; + > 6 │ var foo: string = ""; + │ ^^^^^^^^^^^^^^^^^^^^ + 7 │ + i A variable declared with var is accessible in the whole module. Thus, the variable can be accessed before its initialization and outside the block where it is declared. i See MDN web docs for more details. diff --git a/crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/check_stdin_write_successfully.snap b/crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/check_stdin_write_successfully.snap index 6c678832c2fe..6a6c4b582bd4 100644 --- a/crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/check_stdin_write_successfully.snap +++ b/crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/check_stdin_write_successfully.snap @@ -29,10 +29,17 @@ var foo = ""; ``` ```block -file.astro lint/suspicious/noDebugger FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.astro:4:1 lint/suspicious/noDebugger FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × This is an unexpected use of the debugger statement. + 2 │ import { something } from "file.astro"; + 3 │ import { a } from "mod"; + > 4 │ debugger; + │ ^^^^^^^^^ + 5 │ statement(); + 6 │ var foo = ""; + i Unsafe fix: Remove debugger statement 2 2 │ import { something } from "file.astro"; @@ -45,10 +52,16 @@ file.astro lint/suspicious/noDebugger FIXABLE ━━━━━━━━━━ ``` ```block -file.astro lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.astro:6:1 lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Use let or const instead of var. + 4 │ debugger; + 5 │ statement(); + > 6 │ var foo = ""; + │ ^^^^^^^^^^^^ + 7 │ + i A variable declared with var is accessible in the whole module. Thus, the variable can be accessed before its initialization and outside the block where it is declared. i See MDN web docs for more details. diff --git a/crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/lint_stdin_successfully.snap b/crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/lint_stdin_successfully.snap index a1b48c86f48e..e8d37a995706 100644 --- a/crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/lint_stdin_successfully.snap +++ b/crates/biome_cli/tests/snapshots/main_cases_handle_astro_files/lint_stdin_successfully.snap @@ -21,10 +21,14 @@ import {a as a} from 'mod'; ``` ```block -file.astro lint/complexity/noUselessRename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.astro:2:9 lint/complexity/noUselessRename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Useless rename. + > 2 │ import {a as a} from 'mod'; + │ ^^^^^^ + 3 │ + i Safe fix: Remove the renaming. 2 │ import·{a·as·a}·from·'mod'; diff --git a/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/check_stdin_successfully.snap b/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/check_stdin_successfully.snap index 92e6b419ec1b..c14170089201 100644 --- a/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/check_stdin_successfully.snap +++ b/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/check_stdin_successfully.snap @@ -29,10 +29,15 @@ var foo: string = ""; ``` ```block -file.svelte lint/complexity/noUselessRename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:1:10 lint/complexity/noUselessRename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Useless rename. + > 1 │ import { Form as Form } from './components/Form.svelte' ; + │ ^^^^^^^^^^^^^^ + 2 │ import Button from "./components/Button.svelte"; + 3 │ debugger; + i Safe fix: Remove the renaming. 1 │ import·{·Form·as···Form·}·····from·'./components/Form.svelte'·; @@ -41,10 +46,17 @@ file.svelte lint/complexity/noUselessRename FIXABLE ━━━━━━━━ ``` ```block -file.svelte lint/suspicious/noDebugger FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:3:1 lint/suspicious/noDebugger FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × This is an unexpected use of the debugger statement. + 1 │ import { Form as Form } from './components/Form.svelte' ; + 2 │ import Button from "./components/Button.svelte"; + > 3 │ debugger; + │ ^^^^^^^^^ + 4 │ statement ( ) ; + 5 │ var foo: string = ""; + i Unsafe fix: Remove debugger statement 1 1 │ import { Form as Form } from './components/Form.svelte' ; @@ -57,10 +69,16 @@ file.svelte lint/suspicious/noDebugger FIXABLE ━━━━━━━━━━ ``` ```block -file.svelte lint/style/noInferrableTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:5:8 lint/style/noInferrableTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × This type annotation is trivially inferred from its initialization. + 3 │ debugger; + 4 │ statement ( ) ; + > 5 │ var foo: string = ""; + │ ^^^^^^^^ + 6 │ + i Safe fix: Remove the type annotation. 3 3 │ debugger; @@ -73,10 +91,16 @@ file.svelte lint/style/noInferrableTypes FIXABLE ━━━━━━━━━ ``` ```block -file.svelte lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:5:1 lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Use let or const instead of var. + 3 │ debugger; + 4 │ statement ( ) ; + > 5 │ var foo: string = ""; + │ ^^^^^^^^^^^^^^^^^^^^ + 6 │ + i A variable declared with var is accessible in the whole module. Thus, the variable can be accessed before its initialization and outside the block where it is declared. i See MDN web docs for more details. diff --git a/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/check_stdin_write_successfully.snap b/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/check_stdin_write_successfully.snap index 016541813bfb..877fb77a51d3 100644 --- a/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/check_stdin_write_successfully.snap +++ b/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/check_stdin_write_successfully.snap @@ -29,10 +29,17 @@ var foo = ""; ``` ```block -file.svelte lint/suspicious/noDebugger FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:3:1 lint/suspicious/noDebugger FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × This is an unexpected use of the debugger statement. + 1 │ import Button from "./components/Button.svelte"; + 2 │ import { Form } from "./components/Form.svelte"; + > 3 │ debugger; + │ ^^^^^^^^^ + 4 │ statement(); + 5 │ var foo = ""; + i Unsafe fix: Remove debugger statement 1 1 │ import Button from "./components/Button.svelte"; @@ -45,10 +52,16 @@ file.svelte lint/suspicious/noDebugger FIXABLE ━━━━━━━━━━ ``` ```block -file.svelte lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:5:1 lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Use let or const instead of var. + 3 │ debugger; + 4 │ statement(); + > 5 │ var foo = ""; + │ ^^^^^^^^^^^^ + 6 │ + i A variable declared with var is accessible in the whole module. Thus, the variable can be accessed before its initialization and outside the block where it is declared. i See MDN web docs for more details. diff --git a/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/lint_stdin_successfully.snap b/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/lint_stdin_successfully.snap index bbda7d248e3c..c997ebb77a27 100644 --- a/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/lint_stdin_successfully.snap +++ b/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/lint_stdin_successfully.snap @@ -21,10 +21,14 @@ var foo: string = ""; ``` ```block -file.svelte lint/style/noInferrableTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:1:8 lint/style/noInferrableTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × This type annotation is trivially inferred from its initialization. + > 1 │ var foo: string = ""; + │ ^^^^^^^^ + 2 │ + i Safe fix: Remove the type annotation. 1 │ - var·foo:·string·=·""; @@ -35,10 +39,14 @@ file.svelte lint/style/noInferrableTypes FIXABLE ━━━━━━━━━ ``` ```block -file.svelte lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:1:1 lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Use let or const instead of var. + > 1 │ var foo: string = ""; + │ ^^^^^^^^^^^^^^^^^^^^ + 2 │ + i A variable declared with var is accessible in the whole module. Thus, the variable can be accessed before its initialization and outside the block where it is declared. i See MDN web docs for more details. diff --git a/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/lint_stdin_write_successfully.snap b/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/lint_stdin_write_successfully.snap index adb7edf1c642..07cae5b37a65 100644 --- a/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/lint_stdin_write_successfully.snap +++ b/crates/biome_cli/tests/snapshots/main_cases_handle_svelte_files/lint_stdin_write_successfully.snap @@ -21,10 +21,14 @@ var foo = ""; ``` ```block -file.svelte lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:1:1 lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Use let or const instead of var. + > 1 │ var foo = ""; + │ ^^^^^^^^^^^^ + 2 │ + i A variable declared with var is accessible in the whole module. Thus, the variable can be accessed before its initialization and outside the block where it is declared. i See MDN web docs for more details. diff --git a/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/check_stdin_successfully.snap b/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/check_stdin_successfully.snap index 330279a22d52..fb58db58e445 100644 --- a/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/check_stdin_successfully.snap +++ b/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/check_stdin_successfully.snap @@ -27,10 +27,15 @@ delete a.c; ``` ```block -file.vue lint/complexity/noUselessRename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.vue:1:15 lint/complexity/noUselessRename FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Useless rename. + > 1 │ import { Button as Button } from "./components/Button.vue" ; + │ ^^^^^^^^^^^^^^^^^ + 2 │ import * as vueUse from "vue-use" ; + 3 │ + i Safe fix: Remove the renaming. 1 │ import·{······Button··as·Button··}···from··"./components/Button.vue"···; @@ -39,10 +44,16 @@ file.vue lint/complexity/noUselessRename FIXABLE ━━━━━━━━━ ``` ```block -file.vue lint/performance/noDelete FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.vue:4:1 lint/performance/noDelete FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Avoid the delete operator which can impact performance. + 2 │ import * as vueUse from "vue-use" ; + 3 │ + > 4 │ delete a.c; + │ ^^^^^^^^^^ + 5 │ + i Unsafe fix: Use an undefined assignment instead. 2 2 │ import * as vueUse from "vue-use" ; diff --git a/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/check_stdin_write_successfully.snap b/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/check_stdin_write_successfully.snap index d69d07b2ece7..58c8ab051ff6 100644 --- a/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/check_stdin_write_successfully.snap +++ b/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/check_stdin_write_successfully.snap @@ -27,10 +27,16 @@ delete a.c; ``` ```block -file.vue lint/performance/noDelete FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.vue:4:1 lint/performance/noDelete FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Avoid the delete operator which can impact performance. + 2 │ import { Button } from "./components/Button.vue"; + 3 │ + > 4 │ delete a.c; + │ ^^^^^^^^^^ + 5 │ + i Unsafe fix: Use an undefined assignment instead. 2 2 │ import { Button } from "./components/Button.vue"; diff --git a/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/lint_stdin_successfully.snap b/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/lint_stdin_successfully.snap index ff4f470ecf14..2b8f4aa47700 100644 --- a/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/lint_stdin_successfully.snap +++ b/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/lint_stdin_successfully.snap @@ -27,12 +27,22 @@ var foo: string = ""; ``` ```block -file.svelte lint/suspicious/noDoubleEquals FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:1:3 lint/suspicious/noDoubleEquals FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Use === instead of == + > 1 │ a == b; + │ ^^ + 2 │ delete a.c; + 3 │ + i == is only allowed when comparing against null + > 1 │ a == b; + │ ^^ + 2 │ delete a.c; + 3 │ + i Using == may be unsafe if you are relying on type coercion i Unsafe fix: Use === @@ -43,10 +53,16 @@ file.svelte lint/suspicious/noDoubleEquals FIXABLE ━━━━━━━━━ ``` ```block -file.svelte lint/performance/noDelete FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:2:1 lint/performance/noDelete FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Avoid the delete operator which can impact performance. + 1 │ a == b; + > 2 │ delete a.c; + │ ^^^^^^^^^^ + 3 │ + 4 │ var foo: string = ""; + i Unsafe fix: Use an undefined assignment instead. 1 1 │ a == b; @@ -59,10 +75,16 @@ file.svelte lint/performance/noDelete FIXABLE ━━━━━━━━━━ ``` ```block -file.svelte lint/style/noInferrableTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:4:8 lint/style/noInferrableTypes FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × This type annotation is trivially inferred from its initialization. + 2 │ delete a.c; + 3 │ + > 4 │ var foo: string = ""; + │ ^^^^^^^^ + 5 │ + i Safe fix: Remove the type annotation. 2 2 │ delete a.c; @@ -75,10 +97,16 @@ file.svelte lint/style/noInferrableTypes FIXABLE ━━━━━━━━━ ``` ```block -file.svelte lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:4:1 lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Use let or const instead of var. + 2 │ delete a.c; + 3 │ + > 4 │ var foo: string = ""; + │ ^^^^^^^^^^^^^^^^^^^^ + 5 │ + i A variable declared with var is accessible in the whole module. Thus, the variable can be accessed before its initialization and outside the block where it is declared. i See MDN web docs for more details. diff --git a/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/lint_stdin_write_successfully.snap b/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/lint_stdin_write_successfully.snap index ee3e28ddb047..ef14c86fcf1e 100644 --- a/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/lint_stdin_write_successfully.snap +++ b/crates/biome_cli/tests/snapshots/main_cases_handle_vue_files/lint_stdin_write_successfully.snap @@ -27,12 +27,22 @@ var foo = ""; ``` ```block -file.svelte lint/suspicious/noDoubleEquals FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:1:3 lint/suspicious/noDoubleEquals FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Use === instead of == + > 1 │ a == b; + │ ^^ + 2 │ delete a.c; + 3 │ + i == is only allowed when comparing against null + > 1 │ a == b; + │ ^^ + 2 │ delete a.c; + 3 │ + i Using == may be unsafe if you are relying on type coercion i Unsafe fix: Use === @@ -43,10 +53,16 @@ file.svelte lint/suspicious/noDoubleEquals FIXABLE ━━━━━━━━━ ``` ```block -file.svelte lint/performance/noDelete FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:2:1 lint/performance/noDelete FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Avoid the delete operator which can impact performance. + 1 │ a == b; + > 2 │ delete a.c; + │ ^^^^^^^^^^ + 3 │ + 4 │ var foo = ""; + i Unsafe fix: Use an undefined assignment instead. 1 1 │ a == b; @@ -59,10 +75,16 @@ file.svelte lint/performance/noDelete FIXABLE ━━━━━━━━━━ ``` ```block -file.svelte lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +file.svelte:4:1 lint/style/noVar FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ × Use let or const instead of var. + 2 │ delete a.c; + 3 │ + > 4 │ var foo = ""; + │ ^^^^^^^^^^^^ + 5 │ + i A variable declared with var is accessible in the whole module. Thus, the variable can be accessed before its initialization and outside the block where it is declared. i See MDN web docs for more details. diff --git a/crates/biome_cli/tests/snapshots/main_commands_lint/check_stdin_shows_parse_diagnostics.snap b/crates/biome_cli/tests/snapshots/main_commands_lint/check_stdin_shows_parse_diagnostics.snap new file mode 100644 index 000000000000..fa66dedd81c9 --- /dev/null +++ b/crates/biome_cli/tests/snapshots/main_commands_lint/check_stdin_shows_parse_diagnostics.snap @@ -0,0 +1,80 @@ +--- +source: crates/biome_cli/tests/snap_test.rs +expression: content +--- +# Input messages + +```block + +server.get('/', async () => { + return { hello: 'world' }; +}); + +const = ""; +``` + +# Emitted Messages + +```block + +server.get('/', async () => { + return { hello: 'world' }; +}); + +const = ""; +``` + +```block +mock.ts:6:7 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an identifier, an array pattern, or an object pattern but instead found '= ""'. + + 4 │ }); + 5 │ + > 6 │ const = "";· + │ ^^^^ + + i Expected an identifier, an array pattern, or an object pattern here. + + 4 │ }); + 5 │ + > 6 │ const = "";· + │ ^^^^ + + +``` + +```block +mock.ts:6:11 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × expected `,` but instead found `;` + + 4 │ }); + 5 │ + > 6 │ const = "";· + │ ^ + + i Remove ; + + +``` + +```block +mock.ts:6:7 parse ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + × Expected an identifier, an array pattern, or an object pattern but instead found '= ""'. + + 4 │ }); + 5 │ + > 6 │ const = "";· + │ ^^^^ + + i Expected an identifier, an array pattern, or an object pattern here. + + 4 │ }); + 5 │ + > 6 │ const = "";· + │ ^^^^ + + +``` From e8c75e33338992295ead12c002c7bba95c0d5f50 Mon Sep 17 00:00:00 2001 From: Emanuele Stoppa Date: Sun, 16 Jun 2024 12:41:50 +0100 Subject: [PATCH 08/11] chore: rust 1.79.0 (#3222) --- crates/biome_analyze/src/options.rs | 6 ++--- crates/biome_analyze/src/registry.rs | 23 ++++--------------- crates/biome_analyze/src/signals.rs | 3 +-- crates/biome_deserialize/src/impls.rs | 1 - crates/biome_formatter/src/lib.rs | 1 - .../nursery/use_sorted_classes/sort_config.rs | 1 + crates/biome_text_size/src/size.rs | 1 - rust-toolchain.toml | 2 +- xtask/codegen/src/js_kinds_src.rs | 2 ++ 9 files changed, 12 insertions(+), 28 deletions(-) diff --git a/crates/biome_analyze/src/options.rs b/crates/biome_analyze/src/options.rs index f659eda99015..52c13a52978e 100644 --- a/crates/biome_analyze/src/options.rs +++ b/crates/biome_analyze/src/options.rs @@ -94,8 +94,7 @@ impl AnalyzerOptions { pub fn rule_options(&self) -> Option where - R: Rule + 'static, - R::Options: Clone, + R: Rule + 'static, { self.configuration .rules @@ -105,8 +104,7 @@ impl AnalyzerOptions { pub fn rule_fix_kind(&self) -> Option where - R: Rule + 'static, - R::Options: Clone, + R: Rule + 'static, { self.configuration .rules diff --git a/crates/biome_analyze/src/registry.rs b/crates/biome_analyze/src/registry.rs index e6ecd189934c..56f5f1fa0355 100644 --- a/crates/biome_analyze/src/registry.rs +++ b/crates/biome_analyze/src/registry.rs @@ -50,9 +50,7 @@ pub trait RegistryVisitor { /// Record the rule `R` to this visitor fn record_rule(&mut self) where - R: Rule + 'static, - R::Query: Queryable, - ::Output: Clone; + R: Rule> + 'static; } /// Stores metadata information for all the rules in the registry, sorted @@ -85,9 +83,7 @@ impl MetadataRegistry { impl RegistryVisitor for MetadataRegistry { fn record_rule(&mut self) where - R: Rule + 'static, - R::Query: Queryable, - ::Output: Clone, + R: Rule> + 'static, { self.insert_rule(::NAME, R::METADATA.name); } @@ -165,10 +161,7 @@ impl RegistryVisitor for RuleRegistryBuilder /// Add the rule `R` to the list of rules stores in this registry instance fn record_rule(&mut self) where - R: Rule + 'static, - ::Options: Default, - R::Query: Queryable, - ::Output: Clone, + R: Rule> + 'static, { if !self.filter.match_rule::() { return; @@ -384,10 +377,7 @@ type RuleExecutor = fn(&mut MatchQueryParams, &mut RuleState) -> Result impl RegistryRule { fn new(state_index: usize) -> Self where - R: Rule + 'static, - ::Options: Default, - R::Query: Queryable + 'static, - ::Output: Clone, + R: Rule> + 'static, { /// Generic implementation of RuleExecutor for any rule type R fn run( @@ -395,10 +385,7 @@ impl RegistryRule { state: &mut RuleState>, ) -> Result<(), Error> where - R: Rule + 'static, - R::Query: 'static, - ::Output: Clone, - ::Options: Default, + R: Rule> + 'static, { if let Some(node) = params.query.downcast_ref::>>() { if state.suppressions.inner.contains(node) { diff --git a/crates/biome_analyze/src/signals.rs b/crates/biome_analyze/src/signals.rs index d377069134ae..d69e97b77f8b 100644 --- a/crates/biome_analyze/src/signals.rs +++ b/crates/biome_analyze/src/signals.rs @@ -342,8 +342,7 @@ where impl<'bag, R> AnalyzerSignal> for RuleSignal<'bag, R> where - R: Rule + 'static, - ::Options: Default, + R: Rule + 'static, { fn diagnostic(&self) -> Option { let globals = self.options.globals(); diff --git a/crates/biome_deserialize/src/impls.rs b/crates/biome_deserialize/src/impls.rs index c397d5e90948..98d6b95e9f96 100644 --- a/crates/biome_deserialize/src/impls.rs +++ b/crates/biome_deserialize/src/impls.rs @@ -14,7 +14,6 @@ use std::{ num::{NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}, ops::Deref, path::PathBuf, - u8, }; /// Type that allows deserializing a string without heap-allocation. diff --git a/crates/biome_formatter/src/lib.rs b/crates/biome_formatter/src/lib.rs index b4414e3c8cb8..63a8296046c5 100644 --- a/crates/biome_formatter/src/lib.rs +++ b/crates/biome_formatter/src/lib.rs @@ -76,7 +76,6 @@ pub use source_map::{TransformSourceMap, TransformSourceMapBuilder}; use std::marker::PhantomData; use std::num::ParseIntError; use std::str::FromStr; -use std::u8; use token::string::Quote; #[derive(Debug, Eq, PartialEq, Clone, Copy, Hash)] diff --git a/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs b/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs index e9ceefabf9d7..d5edc96f4eb4 100644 --- a/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs +++ b/crates/biome_js_analyze/src/lint/nursery/use_sorted_classes/sort_config.rs @@ -22,6 +22,7 @@ pub type VariantsConfig = Vec; /// The sort config, containing the utility config and the variant config. pub struct SortConfig { pub utilities: &'static [UtilityLayer], + #[allow(dead_code)] pub variants: VariantsConfig, pub layer_index_map: HashMap<&'static str, usize>, } diff --git a/crates/biome_text_size/src/size.rs b/crates/biome_text_size/src/size.rs index f7f27d0a2be2..41948e0b77c0 100644 --- a/crates/biome_text_size/src/size.rs +++ b/crates/biome_text_size/src/size.rs @@ -5,7 +5,6 @@ use { fmt, iter, num::TryFromIntError, ops::{Add, AddAssign, Sub, SubAssign}, - u32, }, }; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 480124d7150e..07e2d0e86063 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -2,4 +2,4 @@ # The default profile includes rustc, rust-std, cargo, rust-docs, rustfmt and clippy. # https://rust-lang.github.io/rustup/concepts/profiles.html profile = "default" -channel = "1.78.0" +channel = "1.79.0" diff --git a/xtask/codegen/src/js_kinds_src.rs b/xtask/codegen/src/js_kinds_src.rs index 1b39ec4a3515..ee98d0b2567c 100644 --- a/xtask/codegen/src/js_kinds_src.rs +++ b/xtask/codegen/src/js_kinds_src.rs @@ -574,6 +574,7 @@ pub struct AstListSeparatorConfiguration { #[derive(Debug)] pub struct AstNodeSrc { + #[allow(dead_code)] pub documentation: Vec, pub name: String, // pub traits: Vec, @@ -607,6 +608,7 @@ pub enum Field { #[derive(Debug, Clone)] pub struct AstEnumSrc { + #[allow(dead_code)] pub documentation: Vec, pub name: String, // pub traits: Vec, From d46c518e0966d4d34befa6b71d19d9708948ae15 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:22:43 +0800 Subject: [PATCH 09/11] chore(deps): update github-actions (#3225) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/bench_cli.yml | 10 +++++----- .github/workflows/benchmark.yml | 4 ++-- .github/workflows/main.yml | 12 ++++++------ .github/workflows/parser_conformance.yml | 6 +++--- .github/workflows/prepare_release.yml | 2 +- .github/workflows/pull_request.yml | 14 +++++++------- .github/workflows/pull_request_js.yml | 2 +- .github/workflows/release_cli.yml | 8 ++++---- .github/workflows/release_js_api.yml | 6 +++--- .github/workflows/release_knope.yml | 12 ++++++------ .github/workflows/repository_dispatch.yml | 2 +- 11 files changed, 39 insertions(+), 39 deletions(-) diff --git a/.github/workflows/bench_cli.yml b/.github/workflows/bench_cli.yml index c3989da42c80..78115b081509 100644 --- a/.github/workflows/bench_cli.yml +++ b/.github/workflows/bench_cli.yml @@ -29,7 +29,7 @@ jobs: return response.data.head.sha; - name: Checkout PR Branch - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: ref: ${{ steps.sha.outputs.result }} @@ -50,7 +50,7 @@ jobs: cp target/release/biome benchmark/target/biome_pr - name: Checkout Main Branch - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: clean: false ref: main @@ -61,17 +61,17 @@ jobs: cp target/release/biome benchmark/target/biome_main - name: Checkout webpack - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: repository: webpack/webpack path: benchmark/target/webpack - name: Checkout prettier - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: repository: prettier/prettier path: benchmark/target/prettier - name: Checkout eslint - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: repository: eslint/eslint path: benchmark/target/eslint diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 866b1d808311..16331e3e9cfe 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -33,7 +33,7 @@ jobs: steps: - name: Checkout PR Branch - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: ref: ${{ steps.sha.outputs.result }} @@ -49,7 +49,7 @@ jobs: run: cargo codspeed build --features codspeed -p xtask_bench - name: Run the benchmarks - uses: CodSpeedHQ/action@0b631f8998f2389eb5144632b6f9f8fabd33a86e # v2.4.1 + uses: CodSpeedHQ/action@f11c406b8c87cda176ff341ed4925bc98086f6d1 # v2.4.2 timeout-minutes: 30 with: run: cargo codspeed run diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6fedc1f572b3..d77262f23c5e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,11 +21,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Support longpaths run: git config core.longpaths true - name: Checkout repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -62,7 +62,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -86,7 +86,7 @@ jobs: - os: macos-latest steps: - name: Checkout repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -112,7 +112,7 @@ jobs: if: matrix.os == 'windows-latest' run: git config --system core.longpaths true - name: Checkout repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive - name: Free Disk Space diff --git a/.github/workflows/parser_conformance.yml b/.github/workflows/parser_conformance.yml index fca307074736..860365a4c4a2 100644 --- a/.github/workflows/parser_conformance.yml +++ b/.github/workflows/parser_conformance.yml @@ -25,13 +25,13 @@ jobs: steps: - name: Checkout PR Branch - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Support longpaths run: git config core.longpaths true - name: Checkout PR Branch - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: submodules: recursive @@ -52,7 +52,7 @@ jobs: run: cargo coverage --json > new_results.json - name: Checkout main Branch - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: clean: false ref: main diff --git a/.github/workflows/prepare_release.yml b/.github/workflows/prepare_release.yml index e62ad23c013e..f475abe8f5d2 100644 --- a/.github/workflows/prepare_release.yml +++ b/.github/workflows/prepare_release.yml @@ -13,7 +13,7 @@ jobs: if: "!contains(github.event.head_commit.message, 'chore: prepare release')" # Skip merges from releases runs-on: ubuntu-latest steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 - name: Configure Git diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index d0c994303804..cd5f2ecef356 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR branch - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -48,7 +48,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR Branch - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -66,7 +66,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR Branch - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -86,7 +86,7 @@ jobs: - os: ubuntu-latest steps: - name: Checkout PR branch - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -104,7 +104,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR branch - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -148,7 +148,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR branch - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain @@ -163,7 +163,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout PR branch - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Install toolchain diff --git a/.github/workflows/pull_request_js.yml b/.github/workflows/pull_request_js.yml index 68c4066dba14..fc9e600e97ca 100644 --- a/.github/workflows/pull_request_js.yml +++ b/.github/workflows/pull_request_js.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Free Disk Space uses: ./.github/actions/free-disk-space - name: Cache pnpm modules diff --git a/.github/workflows/release_cli.yml b/.github/workflows/release_cli.yml index 2bc8ae2ef73d..37feea8e9be9 100644 --- a/.github/workflows/release_cli.yml +++ b/.github/workflows/release_cli.yml @@ -19,7 +19,7 @@ jobs: nightly: ${{ env.nightly }} version_changed: ${{ steps.version.outputs.changed }} steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Check nightly status id: nightly @@ -88,7 +88,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Install Node.js uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -156,7 +156,7 @@ jobs: needs: check steps: - name: Checkout repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh @@ -189,7 +189,7 @@ jobs: contents: write id-token: write steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Download CLI artifacts uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 diff --git a/.github/workflows/release_js_api.yml b/.github/workflows/release_js_api.yml index e18c91f4c3f4..b22127ac5f89 100644 --- a/.github/workflows/release_js_api.yml +++ b/.github/workflows/release_js_api.yml @@ -19,7 +19,7 @@ jobs: nightly: ${{ env.nightly }} version_changed: ${{ steps.version.outputs.changed }} steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Check nightly status id: nightly @@ -58,7 +58,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Install Node.js uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -118,7 +118,7 @@ jobs: contents: write id-token: write steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Download package artifact uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 diff --git a/.github/workflows/release_knope.yml b/.github/workflows/release_knope.yml index 494de509973f..0100d57835c9 100644 --- a/.github/workflows/release_knope.yml +++ b/.github/workflows/release_knope.yml @@ -14,7 +14,7 @@ jobs: version: ${{ env.version }} version_changed: ${{ steps.version.outputs.changed }} steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Check version changes uses: EndBug/version-check@d4be4219408b50d1bbbfd350a47cbcb126878692 # v2.1.4 @@ -68,7 +68,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Install Node.js uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -140,7 +140,7 @@ jobs: needs: retrieve-version steps: - name: Checkout repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Install wasm-pack run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh @@ -173,7 +173,7 @@ jobs: contents: write id-token: write steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Download CLI artifacts uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 @@ -204,7 +204,7 @@ jobs: contents: write id-token: write steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Download CLI artifacts uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 @@ -238,7 +238,7 @@ jobs: needs: release runs-on: ubuntu-latest steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Install toolchain uses: moonrepo/setup-rust@d8048d4fdff0633123678b093726e6d7c8ad6de5 # v1.2.0 with: diff --git a/.github/workflows/repository_dispatch.yml b/.github/workflows/repository_dispatch.yml index dcf57b8a03d6..4ae5c43098f8 100644 --- a/.github/workflows/repository_dispatch.yml +++ b/.github/workflows/repository_dispatch.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Warm up wasm-pack cache id: cache-restore From 048925d84f98096b05a2af7842e84af09101691c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 21:26:32 +0100 Subject: [PATCH 10/11] chore(deps): update @biomejs packages (#3224) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../@biomejs/backend-jsonrpc/package.json | 4 +- packages/@biomejs/js-api/package.json | 2 +- .../tailwindcss-config-analyzer/package.json | 2 +- pnpm-lock.yaml | 206 +++++++++--------- 4 files changed, 107 insertions(+), 107 deletions(-) diff --git a/packages/@biomejs/backend-jsonrpc/package.json b/packages/@biomejs/backend-jsonrpc/package.json index ea4defce3d11..5811e0585aab 100644 --- a/packages/@biomejs/backend-jsonrpc/package.json +++ b/packages/@biomejs/backend-jsonrpc/package.json @@ -36,9 +36,9 @@ }, "license": "MIT OR Apache-2.0", "devDependencies": { - "@types/node": "20.14.2", + "@types/node": "20.14.3", "typescript": "5.4.5", - "vite": "5.2.13", + "vite": "5.3.1", "vitest": "1.6.0" }, "optionalDependencies": { diff --git a/packages/@biomejs/js-api/package.json b/packages/@biomejs/js-api/package.json index 96748ceb9ae7..03b44717ce7f 100644 --- a/packages/@biomejs/js-api/package.json +++ b/packages/@biomejs/js-api/package.json @@ -50,7 +50,7 @@ "@biomejs/wasm-nodejs": "link:../wasm-nodejs", "@biomejs/wasm-web": "link:../wasm-web", "typescript": "5.4.5", - "vite": "5.2.13", + "vite": "5.3.1", "vitest": "1.6.0" }, "peerDependencies": { diff --git a/packages/tailwindcss-config-analyzer/package.json b/packages/tailwindcss-config-analyzer/package.json index f8e1f01e1e4e..c9c8d809e52d 100644 --- a/packages/tailwindcss-config-analyzer/package.json +++ b/packages/tailwindcss-config-analyzer/package.json @@ -10,7 +10,7 @@ "tailwindcss": "^3.4.4" }, "devDependencies": { - "@types/bun": "1.1.3", + "@types/bun": "1.1.4", "read-package-up": "11.0.0", "typescript": "5.4.5" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb39f174633a..88f5498b608f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,17 +36,17 @@ importers: version: 1.8.1 devDependencies: '@types/node': - specifier: 20.14.2 - version: 20.14.2 + specifier: 20.14.3 + version: 20.14.3 typescript: specifier: 5.4.5 version: 5.4.5 vite: - specifier: 5.2.13 - version: 5.2.13(@types/node@20.14.2) + specifier: 5.3.1 + version: 5.3.1(@types/node@20.14.3) vitest: specifier: 1.6.0 - version: 1.6.0(@types/node@20.14.2) + version: 1.6.0(@types/node@20.14.3) packages/@biomejs/biome: optionalDependencies: @@ -106,8 +106,8 @@ importers: specifier: 5.4.5 version: 5.4.5 vite: - specifier: 5.2.13 - version: 5.2.13 + specifier: 5.3.1 + version: 5.3.1 vitest: specifier: 1.6.0 version: 1.6.0 @@ -125,8 +125,8 @@ importers: version: 3.4.4 devDependencies: '@types/bun': - specifier: 1.1.3 - version: 1.1.3 + specifier: 1.1.4 + version: 1.1.4 read-package-up: specifier: 11.0.0 version: 11.0.0 @@ -235,8 +235,8 @@ packages: dev: false optional: true - /@esbuild/aix-ppc64@0.20.2: - resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} + /@esbuild/aix-ppc64@0.21.5: + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] @@ -244,8 +244,8 @@ packages: dev: true optional: true - /@esbuild/android-arm64@0.20.2: - resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} + /@esbuild/android-arm64@0.21.5: + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -253,8 +253,8 @@ packages: dev: true optional: true - /@esbuild/android-arm@0.20.2: - resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} + /@esbuild/android-arm@0.21.5: + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -262,8 +262,8 @@ packages: dev: true optional: true - /@esbuild/android-x64@0.20.2: - resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} + /@esbuild/android-x64@0.21.5: + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -271,8 +271,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64@0.20.2: - resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} + /@esbuild/darwin-arm64@0.21.5: + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -280,8 +280,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64@0.20.2: - resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} + /@esbuild/darwin-x64@0.21.5: + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -289,8 +289,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64@0.20.2: - resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} + /@esbuild/freebsd-arm64@0.21.5: + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -298,8 +298,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64@0.20.2: - resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} + /@esbuild/freebsd-x64@0.21.5: + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -307,8 +307,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64@0.20.2: - resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} + /@esbuild/linux-arm64@0.21.5: + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -316,8 +316,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm@0.20.2: - resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} + /@esbuild/linux-arm@0.21.5: + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -325,8 +325,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32@0.20.2: - resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} + /@esbuild/linux-ia32@0.21.5: + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -334,8 +334,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64@0.20.2: - resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} + /@esbuild/linux-loong64@0.21.5: + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -343,8 +343,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el@0.20.2: - resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} + /@esbuild/linux-mips64el@0.21.5: + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -352,8 +352,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64@0.20.2: - resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} + /@esbuild/linux-ppc64@0.21.5: + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -361,8 +361,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64@0.20.2: - resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} + /@esbuild/linux-riscv64@0.21.5: + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -370,8 +370,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x@0.20.2: - resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} + /@esbuild/linux-s390x@0.21.5: + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -379,8 +379,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64@0.20.2: - resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} + /@esbuild/linux-x64@0.21.5: + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -388,8 +388,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64@0.20.2: - resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} + /@esbuild/netbsd-x64@0.21.5: + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -397,8 +397,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64@0.20.2: - resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} + /@esbuild/openbsd-x64@0.21.5: + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -406,8 +406,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64@0.20.2: - resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} + /@esbuild/sunos-x64@0.21.5: + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -415,8 +415,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64@0.20.2: - resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} + /@esbuild/win32-arm64@0.21.5: + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -424,8 +424,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32@0.20.2: - resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} + /@esbuild/win32-ia32@0.21.5: + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -433,8 +433,8 @@ packages: dev: true optional: true - /@esbuild/win32-x64@0.20.2: - resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} + /@esbuild/win32-x64@0.21.5: + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -626,10 +626,10 @@ packages: resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} dev: true - /@types/bun@1.1.3: - resolution: {integrity: sha512-i+mVz8C/lx+RprDR6Mr402iE1kmajgJPnmSfJ/NvU85sGGXSylYZ/6yc+XhVLr2E/t8o6HmjwV0evtnUOR0CFA==} + /@types/bun@1.1.4: + resolution: {integrity: sha512-ejSuv/3s0hTHj/nkkLzBlHxm4JxOPygbLNi0kzM6ooq8rOiQvIUCv7RRErTaWSfb+QVnKz6x7qlp8N86bGDiIg==} dependencies: - bun-types: 1.1.9 + bun-types: 1.1.13 dev: true /@types/estree@1.0.5: @@ -642,8 +642,8 @@ packages: undici-types: 5.26.5 dev: true - /@types/node@20.14.2: - resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} + /@types/node@20.14.3: + resolution: {integrity: sha512-Nuzqa6WAxeGnve6SXqiPAM9rA++VQs+iLZ1DDd56y0gdvygSZlQvZuvdFPR3yLqkVxPu4WrO02iDEyH1g+wazw==} dependencies: undici-types: 5.26.5 dev: true @@ -655,7 +655,7 @@ packages: /@types/ws@8.5.10: resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} dependencies: - '@types/node': 20.14.2 + '@types/node': 20.14.3 dev: true /@vitest/expect@1.6.0: @@ -784,8 +784,8 @@ packages: fill-range: 7.0.1 dev: false - /bun-types@1.1.9: - resolution: {integrity: sha512-3YuLiH4Ne/ghk7K6mHiaqCqKOMrtB0Z5p1WAskHSVgi0iMZgsARV4yGkbfi565YsStvUq6GXTWB3ga7M8cznkA==} + /bun-types@1.1.13: + resolution: {integrity: sha512-G/TqF0SsMQGLr4g7K3B2BK8BrPEA1EqCNwxZbyRdj5M4t54zvwyaqvRJOW34kuPqc2IvNNalRU3swc8B4oc4FA==} dependencies: '@types/node': 20.12.12 '@types/ws': 8.5.10 @@ -929,35 +929,35 @@ packages: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} dev: false - /esbuild@0.20.2: - resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} + /esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/aix-ppc64': 0.20.2 - '@esbuild/android-arm': 0.20.2 - '@esbuild/android-arm64': 0.20.2 - '@esbuild/android-x64': 0.20.2 - '@esbuild/darwin-arm64': 0.20.2 - '@esbuild/darwin-x64': 0.20.2 - '@esbuild/freebsd-arm64': 0.20.2 - '@esbuild/freebsd-x64': 0.20.2 - '@esbuild/linux-arm': 0.20.2 - '@esbuild/linux-arm64': 0.20.2 - '@esbuild/linux-ia32': 0.20.2 - '@esbuild/linux-loong64': 0.20.2 - '@esbuild/linux-mips64el': 0.20.2 - '@esbuild/linux-ppc64': 0.20.2 - '@esbuild/linux-riscv64': 0.20.2 - '@esbuild/linux-s390x': 0.20.2 - '@esbuild/linux-x64': 0.20.2 - '@esbuild/netbsd-x64': 0.20.2 - '@esbuild/openbsd-x64': 0.20.2 - '@esbuild/sunos-x64': 0.20.2 - '@esbuild/win32-arm64': 0.20.2 - '@esbuild/win32-ia32': 0.20.2 - '@esbuild/win32-x64': 0.20.2 + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 dev: true /escape-string-regexp@1.0.5: @@ -1790,7 +1790,7 @@ packages: debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.2.13 + vite: 5.3.1 transitivePeerDependencies: - '@types/node' - less @@ -1802,7 +1802,7 @@ packages: - terser dev: true - /vite-node@1.6.0(@types/node@20.14.2): + /vite-node@1.6.0(@types/node@20.14.3): resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -1811,7 +1811,7 @@ packages: debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.2.13(@types/node@20.14.2) + vite: 5.3.1(@types/node@20.14.3) transitivePeerDependencies: - '@types/node' - less @@ -1823,8 +1823,8 @@ packages: - terser dev: true - /vite@5.2.13: - resolution: {integrity: sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==} + /vite@5.3.1: + resolution: {integrity: sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -1851,15 +1851,15 @@ packages: terser: optional: true dependencies: - esbuild: 0.20.2 + esbuild: 0.21.5 postcss: 8.4.38 rollup: 4.13.0 optionalDependencies: fsevents: 2.3.3 dev: true - /vite@5.2.13(@types/node@20.14.2): - resolution: {integrity: sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A==} + /vite@5.3.1(@types/node@20.14.3): + resolution: {integrity: sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -1886,8 +1886,8 @@ packages: terser: optional: true dependencies: - '@types/node': 20.14.2 - esbuild: 0.20.2 + '@types/node': 20.14.3 + esbuild: 0.21.5 postcss: 8.4.38 rollup: 4.13.0 optionalDependencies: @@ -1936,7 +1936,7 @@ packages: strip-literal: 2.0.0 tinybench: 2.6.0 tinypool: 0.8.3 - vite: 5.2.13 + vite: 5.3.1 vite-node: 1.6.0 why-is-node-running: 2.2.2 transitivePeerDependencies: @@ -1949,7 +1949,7 @@ packages: - terser dev: true - /vitest@1.6.0(@types/node@20.14.2): + /vitest@1.6.0(@types/node@20.14.3): resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -1974,7 +1974,7 @@ packages: jsdom: optional: true dependencies: - '@types/node': 20.14.2 + '@types/node': 20.14.3 '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 '@vitest/snapshot': 1.6.0 @@ -1992,8 +1992,8 @@ packages: strip-literal: 2.0.0 tinybench: 2.6.0 tinypool: 0.8.3 - vite: 5.2.13(@types/node@20.14.2) - vite-node: 1.6.0(@types/node@20.14.2) + vite: 5.3.1(@types/node@20.14.3) + vite-node: 1.6.0(@types/node@20.14.3) why-is-node-running: 2.2.2 transitivePeerDependencies: - less From f8786aac2b0759d44de1dfbb1c7a549a45f9d360 Mon Sep 17 00:00:00 2001 From: Arend van Beelen jr Date: Mon, 17 Jun 2024 22:48:10 +0200 Subject: [PATCH 11/11] chore(grit): improve snippet compiler (#3223) --- Cargo.lock | 1 + crates/biome_grit_patterns/Cargo.toml | 3 + crates/biome_grit_patterns/src/diagnostics.rs | 2 +- .../biome_grit_patterns/src/grit_binding.rs | 120 ++-- .../src/grit_code_snippet.rs | 2 +- .../biome_grit_patterns/src/grit_context.rs | 12 +- crates/biome_grit_patterns/src/grit_file.rs | 2 +- .../biome_grit_patterns/src/grit_js_parser.rs | 14 +- .../src/grit_node_patterns.rs | 91 ++- crates/biome_grit_patterns/src/grit_query.rs | 21 +- .../src/grit_resolved_pattern.rs | 382 +++++++++-- .../src/grit_target_language.rs | 72 ++- .../js_target_language.rs | 7 +- .../src/grit_target_node.rs | 422 +++++++------ crates/biome_grit_patterns/src/grit_tree.rs | 26 +- crates/biome_grit_patterns/src/lib.rs | 2 +- .../src/pattern_compiler.rs | 274 +------- .../src/pattern_compiler/snippet_compiler.rs | 592 +++++++++++++++++- crates/biome_grit_patterns/src/util.rs | 8 +- .../biome_grit_patterns/tests/quick_test.rs | 19 +- 20 files changed, 1402 insertions(+), 670 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4adcb27bca48..60bfaefa5049 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -548,6 +548,7 @@ dependencies = [ "grit-pattern-matcher", "grit-util", "im", + "insta", "path-absolutize", "regex", "rustc-hash", diff --git a/crates/biome_grit_patterns/Cargo.toml b/crates/biome_grit_patterns/Cargo.toml index 7f24ff1850d4..f96a82d1a2a7 100644 --- a/crates/biome_grit_patterns/Cargo.toml +++ b/crates/biome_grit_patterns/Cargo.toml @@ -29,5 +29,8 @@ regex = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true, features = ["derive"] } +[dev-dependencies] +insta = { workspace = true } + [lints] workspace = true diff --git a/crates/biome_grit_patterns/src/diagnostics.rs b/crates/biome_grit_patterns/src/diagnostics.rs index 9d15b9c80a5c..cd496022316c 100644 --- a/crates/biome_grit_patterns/src/diagnostics.rs +++ b/crates/biome_grit_patterns/src/diagnostics.rs @@ -3,7 +3,7 @@ use biome_rowan::TextRange; #[derive(Debug, Diagnostic)] #[diagnostic(severity = Warning)] -pub(crate) struct CompilerDiagnostic { +pub struct CompilerDiagnostic { #[message] #[description] message: String, diff --git a/crates/biome_grit_patterns/src/grit_binding.rs b/crates/biome_grit_patterns/src/grit_binding.rs index 951efcc34801..13b05a65a3d8 100644 --- a/crates/biome_grit_patterns/src/grit_binding.rs +++ b/crates/biome_grit_patterns/src/grit_binding.rs @@ -1,23 +1,28 @@ use crate::{ grit_context::GritQueryContext, grit_target_language::GritTargetLanguage, - grit_target_node::GritTargetNode, grit_tree::GritTree, source_location_ext::SourceFileExt, + grit_target_node::GritTargetNode, source_location_ext::SourceFileExt, util::TextRangeGritExt, }; use biome_diagnostics::{display::SourceFile, SourceCode}; -use grit_pattern_matcher::{binding::Binding, constant::Constant}; -use grit_util::{Ast, AstNode, ByteRange, CodeRange, Range}; -use std::path::Path; +use biome_rowan::TextRange; +use grit_pattern_matcher::{ + binding::Binding, constant::Constant, effects::Effect, pattern::FileRegistry, +}; +use grit_util::{AnalysisLogs, AstNode, ByteRange, CodeRange, Range}; +use std::{borrow::Cow, collections::HashMap, path::Path}; #[derive(Clone, Debug, PartialEq)] -pub(crate) enum GritBinding<'a> { - Tree(&'a GritTree), - Node(GritTargetNode), - Constant(&'a Constant), -} +pub enum GritBinding<'a> { + /// Binds to a specific node. + Node(GritTargetNode<'a>), -impl<'a> GritBinding<'a> { - pub fn from_tree(tree: &'a GritTree) -> Self { - Self::Tree(tree) - } + /// Binds to a range in the tree's source text. + Range(TextRange, &'a str), + + /// Binds to an empty slot inside a node. + Empty(GritTargetNode<'a>, u32), + + /// Binds to an individual constant. + Constant(&'a Constant), } impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { @@ -25,7 +30,7 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { Self::Constant(constant) } - fn from_node(node: GritTargetNode) -> Self { + fn from_node(node: GritTargetNode<'a>) -> Self { Self::Node(node) } @@ -33,8 +38,11 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { todo!() } - fn from_range(_range: ByteRange, _source: &'a str) -> Self { - todo!() + fn from_range(range: ByteRange, source: &'a str) -> Self { + Self::Range( + TextRange::new((range.start as u32).into(), (range.end as u32).into()), + source, + ) } /// Returns the only node bound by this binding. @@ -43,10 +51,10 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { /// /// Returns `None` if the binding has no associated node, or if there is /// more than one associated node. - fn singleton(&self) -> Option { + fn singleton(&self) -> Option> { match self { Self::Node(node) => Some(node.clone()), - Self::Tree(..) | Self::Constant(..) => None, + Self::Range(..) | Self::Empty(..) | Self::Constant(..) => None, } } @@ -56,41 +64,37 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { fn position(&self, _language: &GritTargetLanguage) -> Option { match self { - GritBinding::Tree(tree) => { - let source = tree.source(); + GritBinding::Node(node) => { let source = SourceFile::new(SourceCode { - text: &source, + text: node.text(), line_starts: None, }); - source.to_grit_range(tree.root_node().text_range()) + source.to_grit_range(node.text_trimmed_range()) } - GritBinding::Node(node) => { - // TODO: This is probably very inefficient. - let root = node.ancestors().last()?; - let source = root.text().to_string(); + GritBinding::Range(range, source) => { let source = SourceFile::new(SourceCode { - text: &source, + text: source, line_starts: None, }); - source.to_grit_range(root.text_trimmed_range()) + source.to_grit_range(*range) } - GritBinding::Constant(_) => None, + GritBinding::Empty(..) | GritBinding::Constant(_) => None, } } fn range(&self, _language: &GritTargetLanguage) -> Option { match self { - GritBinding::Tree(tree) => Some(tree.root_node().byte_range()), GritBinding::Node(node) => Some(node.byte_range()), - GritBinding::Constant(_) => None, + GritBinding::Range(range, _) => Some(range.to_byte_range()), + GritBinding::Empty(..) | GritBinding::Constant(_) => None, } } fn code_range(&self, _language: &GritTargetLanguage) -> Option { match self { - GritBinding::Tree(tree) => Some(tree.root_node().code_range()), GritBinding::Node(node) => Some(node.code_range()), - GritBinding::Constant(_) => None, + GritBinding::Range(range, source) => Some(range.to_code_range(source)), + GritBinding::Empty(..) | GritBinding::Constant(_) => None, } } @@ -114,17 +118,24 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { fn linearized_text( &self, _language: &GritTargetLanguage, - _effects: &[grit_pattern_matcher::effects::Effect<'a, GritQueryContext>], - _files: &grit_pattern_matcher::pattern::FileRegistry<'a, GritQueryContext>, - _memo: &mut std::collections::HashMap>, + _effects: &[Effect<'a, GritQueryContext>], + _files: &FileRegistry<'a, GritQueryContext>, + _memo: &mut HashMap>, _distributed_indent: Option, - _logs: &mut grit_util::AnalysisLogs, - ) -> anyhow::Result> { + _logs: &mut AnalysisLogs, + ) -> anyhow::Result> { todo!() } - fn text(&self, _language: &GritTargetLanguage) -> anyhow::Result> { - todo!() + fn text(&self, _language: &GritTargetLanguage) -> anyhow::Result> { + match self { + GritBinding::Node(node) => Ok(node.text().into()), + GritBinding::Range(range, source) => { + Ok((&source[range.start().into()..range.end().into()]).into()) + } + GritBinding::Empty(_, _) => Ok("".into()), + GritBinding::Constant(constant) => Ok(constant.to_string().into()), + } } fn source(&self) -> Option<&'a str> { @@ -139,19 +150,25 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { todo!() } - fn as_node(&self) -> Option { - todo!() + fn as_node(&self) -> Option> { + match self { + GritBinding::Node(node) => Some(node.clone()), + GritBinding::Range(..) | GritBinding::Empty(..) | GritBinding::Constant(_) => None, + } } fn is_list(&self) -> bool { - todo!() + self.as_node().map_or(false, |node| node.is_list()) } - fn list_items(&self) -> Option + Clone> { - None:: + fn list_items(&self) -> Option> + Clone> { + match self { + GritBinding::Node(node) if node.is_list() => Some(node.children()), + _ => None, + } } - fn parent_node(&self) -> Option { + fn parent_node(&self) -> Option> { todo!() } @@ -167,14 +184,3 @@ impl<'a> Binding<'a, GritQueryContext> for GritBinding<'a> { todo!() } } - -#[derive(Clone)] -struct TodoIterator; - -impl Iterator for TodoIterator { - type Item = GritTargetNode; - - fn next(&mut self) -> Option { - todo!() - } -} diff --git a/crates/biome_grit_patterns/src/grit_code_snippet.rs b/crates/biome_grit_patterns/src/grit_code_snippet.rs index f032b5ebbd3b..9780e782367a 100644 --- a/crates/biome_grit_patterns/src/grit_code_snippet.rs +++ b/crates/biome_grit_patterns/src/grit_code_snippet.rs @@ -10,7 +10,7 @@ use grit_pattern_matcher::pattern::{ use grit_util::AnalysisLogs; #[derive(Clone, Debug)] -pub(crate) struct GritCodeSnippet { +pub struct GritCodeSnippet { pub(crate) patterns: Vec<(GritTargetSyntaxKind, Pattern)>, pub(crate) source: String, pub(crate) dynamic_snippet: Option>, diff --git a/crates/biome_grit_patterns/src/grit_context.rs b/crates/biome_grit_patterns/src/grit_context.rs index 5022ac9d002e..39f966a149d3 100644 --- a/crates/biome_grit_patterns/src/grit_context.rs +++ b/crates/biome_grit_patterns/src/grit_context.rs @@ -5,7 +5,7 @@ use crate::grit_node_patterns::{GritLeafNodePattern, GritNodePattern}; use crate::grit_resolved_pattern::GritResolvedPattern; use crate::grit_target_language::GritTargetLanguage; use crate::grit_target_node::GritTargetNode; -use crate::grit_tree::GritTree; +use crate::grit_tree::GritTargetTree; use anyhow::Result; use grit_pattern_matcher::context::{ExecContext, QueryContext}; use grit_pattern_matcher::file_owners::FileOwners; @@ -15,10 +15,10 @@ use grit_pattern_matcher::pattern::{ use grit_util::AnalysisLogs; #[derive(Clone, Debug, PartialEq)] -pub(crate) struct GritQueryContext; +pub struct GritQueryContext; impl QueryContext for GritQueryContext { - type Node<'a> = GritTargetNode; + type Node<'a> = GritTargetNode<'a>; type NodePattern = GritNodePattern; type LeafNodePattern = GritLeafNodePattern; type ExecContext<'a> = GritExecContext; @@ -27,11 +27,11 @@ impl QueryContext for GritQueryContext { type ResolvedPattern<'a> = GritResolvedPattern<'a>; type Language<'a> = GritTargetLanguage; type File<'a> = GritFile<'a>; - type Tree<'a> = GritTree; + type Tree<'a> = GritTargetTree; } #[derive(Debug)] -pub(crate) struct GritExecContext { +pub struct GritExecContext { lang: GritTargetLanguage, } @@ -68,7 +68,7 @@ impl<'a> ExecContext<'a, GritQueryContext> for GritExecContext { todo!() } - fn files(&self) -> &FileOwners { + fn files(&self) -> &FileOwners { todo!() } diff --git a/crates/biome_grit_patterns/src/grit_file.rs b/crates/biome_grit_patterns/src/grit_file.rs index f744e7fbd09e..a22ab7617ba0 100644 --- a/crates/biome_grit_patterns/src/grit_file.rs +++ b/crates/biome_grit_patterns/src/grit_file.rs @@ -11,7 +11,7 @@ use grit_util::Ast; use path_absolutize::Absolutize; #[derive(Clone, Debug, PartialEq)] -pub(crate) enum GritFile<'a> { +pub enum GritFile<'a> { Resolved(Box>), Ptr(FilePtr), } diff --git a/crates/biome_grit_patterns/src/grit_js_parser.rs b/crates/biome_grit_patterns/src/grit_js_parser.rs index 7a3ff958f6db..92fbfc5fcb5d 100644 --- a/crates/biome_grit_patterns/src/grit_js_parser.rs +++ b/crates/biome_grit_patterns/src/grit_js_parser.rs @@ -1,4 +1,4 @@ -use crate::{grit_analysis_ext::GritAnalysisExt, grit_tree::GritTree}; +use crate::{grit_analysis_ext::GritAnalysisExt, grit_tree::GritTargetTree}; use biome_js_parser::{parse, JsParserOptions}; use biome_js_syntax::JsFileSource; use grit_util::{AnalysisLogs, FileOrigin, Parser, SnippetTree}; @@ -7,22 +7,22 @@ use std::path::Path; pub struct GritJsParser; impl Parser for GritJsParser { - type Tree = GritTree; + type Tree = GritTargetTree; fn parse_file( &mut self, body: &str, path: Option<&Path>, logs: &mut AnalysisLogs, - _old_tree: FileOrigin<'_, GritTree>, - ) -> Option { + _old_tree: FileOrigin<'_, GritTargetTree>, + ) -> Option { let parse_result = parse(body, JsFileSource::tsx(), JsParserOptions::default()); for diagnostic in parse_result.diagnostics() { logs.push(diagnostic.to_log(path)); } - Some(GritTree::new(parse_result.syntax().into())) + Some(GritTargetTree::new(parse_result.syntax().into())) } fn parse_snippet( @@ -30,7 +30,7 @@ impl Parser for GritJsParser { prefix: &'static str, source: &str, postfix: &'static str, - ) -> SnippetTree { + ) -> SnippetTree { let context = format!("{prefix}{source}{postfix}"); let len = if cfg!(target_arch = "wasm32") { @@ -42,7 +42,7 @@ impl Parser for GritJsParser { let parse_result = parse(&context, JsFileSource::tsx(), JsParserOptions::default()); SnippetTree { - tree: GritTree::new(parse_result.syntax().into()), + tree: GritTargetTree::new(parse_result.syntax().into()), source: source.to_owned(), prefix, postfix, diff --git a/crates/biome_grit_patterns/src/grit_node_patterns.rs b/crates/biome_grit_patterns/src/grit_node_patterns.rs index f24e884f132b..fb85a33ffeec 100644 --- a/crates/biome_grit_patterns/src/grit_node_patterns.rs +++ b/crates/biome_grit_patterns/src/grit_node_patterns.rs @@ -2,15 +2,18 @@ use crate::grit_context::{GritExecContext, GritQueryContext}; use crate::grit_resolved_pattern::GritResolvedPattern; use crate::grit_target_node::{GritTargetNode, GritTargetSyntaxKind}; use anyhow::Result; +use grit_pattern_matcher::binding::Binding; +use grit_pattern_matcher::context::ExecContext; use grit_pattern_matcher::pattern::{ - AstLeafNodePattern, AstNodePattern, Matcher, Pattern, PatternName, PatternOrPredicate, State, + AstLeafNodePattern, AstNodePattern, Matcher, Pattern, PatternName, PatternOrPredicate, + ResolvedPattern, State, }; -use grit_util::AnalysisLogs; +use grit_util::{AnalysisLogs, Language}; #[derive(Clone, Debug)] -pub(crate) struct GritNodePattern { +pub struct GritNodePattern { pub kind: GritTargetSyntaxKind, - pub args: Vec, + pub args: Vec, } impl AstNodePattern for GritNodePattern { @@ -28,12 +31,72 @@ impl AstNodePattern for GritNodePattern { impl Matcher for GritNodePattern { fn execute<'a>( &'a self, - _binding: &GritResolvedPattern, - _state: &mut State<'a, GritQueryContext>, - _context: &'a GritExecContext, - _logs: &mut AnalysisLogs, + binding: &GritResolvedPattern<'a>, + init_state: &mut State<'a, GritQueryContext>, + context: &'a GritExecContext, + logs: &mut AnalysisLogs, ) -> Result { - todo!() + let Some(binding) = binding.get_last_binding() else { + return Ok(false); + }; + let Some(node) = binding.singleton() else { + return Ok(false); + }; + if binding.is_list() { + return self.execute( + &ResolvedPattern::from_node_binding(node), + init_state, + context, + logs, + ); + } + + if node.kind() != self.kind { + return Ok(false); + } + if self.args.is_empty() { + return Ok(true); + } + + if context.language().is_comment_kind(self.kind) { + let Some(range) = context.language().comment_text_range(&node) else { + return Ok(false); + }; + + return self.args[0].pattern.execute( + &ResolvedPattern::from_range_binding(range, node.text()), + init_state, + context, + logs, + ); + } + + let mut running_state = init_state.clone(); + for GritNodePatternArg { + pattern, + slot_index, + } in &self.args + { + let mut cur_state = running_state.clone(); + + let res = pattern.execute( + &if let Some(child) = node.child_by_slot_index(*slot_index) { + GritResolvedPattern::from_node_binding(child) + } else { + GritResolvedPattern::from_empty_binding(node.clone(), *slot_index) + }, + &mut cur_state, + context, + logs, + ); + if res? { + running_state = cur_state; + } else { + return Ok(false); + } + } + *init_state = running_state; + Ok(true) } } @@ -44,13 +107,13 @@ impl PatternName for GritNodePattern { } #[derive(Clone, Debug)] -pub(crate) struct GritNodeArg { - slot_index: usize, +pub struct GritNodePatternArg { + slot_index: u32, pattern: Pattern, } -impl GritNodeArg { - pub fn new(slot_index: usize, pattern: Pattern) -> Self { +impl GritNodePatternArg { + pub fn new(slot_index: u32, pattern: Pattern) -> Self { Self { slot_index, pattern, @@ -59,7 +122,7 @@ impl GritNodeArg { } #[derive(Clone, Debug)] -pub(crate) struct GritLeafNodePattern { +pub struct GritLeafNodePattern { kind: GritTargetSyntaxKind, text: String, } diff --git a/crates/biome_grit_patterns/src/grit_query.rs b/crates/biome_grit_patterns/src/grit_query.rs index 65398d855c4c..16e1890aa32e 100644 --- a/crates/biome_grit_patterns/src/grit_query.rs +++ b/crates/biome_grit_patterns/src/grit_query.rs @@ -2,7 +2,7 @@ use crate::diagnostics::CompilerDiagnostic; use crate::grit_context::{GritExecContext, GritQueryContext}; use crate::grit_resolved_pattern::GritResolvedPattern; use crate::grit_target_language::GritTargetLanguage; -use crate::grit_tree::GritTree; +use crate::grit_tree::GritTargetTree; use crate::pattern_compiler::PatternCompiler; use crate::pattern_compiler::{ compilation_context::CompilationContext, compilation_context::NodeCompilationContext, @@ -11,27 +11,32 @@ use crate::variables::{VarRegistry, VariableLocations}; use crate::CompileError; use anyhow::Result; use biome_grit_syntax::{GritRoot, GritRootExt}; +use grit_pattern_matcher::effects::Effect; use grit_pattern_matcher::pattern::{FileRegistry, Matcher, Pattern, State}; +use im::Vector; use std::collections::BTreeMap; /// Represents a top-level Grit query. /// /// Grit queries provide the pub struct GritQuery { - pub(crate) pattern: Pattern, + pub pattern: Pattern, + + /// Diagnostics discovered during compilation of the query. + pub diagnostics: Vec, /// Context for executing the query. context: GritExecContext, - /// Diagnostics discovered during compilation of the query. - diagnostics: Vec, - /// All variables discovered during query compilation. variables: VariableLocations, } impl GritQuery { - pub fn execute(&self, tree: &GritTree) -> Result { + pub fn execute<'a>( + &'a self, + tree: &'a GritTargetTree, + ) -> Result>> { let var_registry = VarRegistry::from_locations(&self.variables); let binding = GritResolvedPattern::from_tree(tree); @@ -42,7 +47,9 @@ impl GritQuery { let mut logs = Vec::new().into(); self.pattern - .execute(&binding, &mut state, &self.context, &mut logs) + .execute(&binding, &mut state, &self.context, &mut logs)?; + + Ok(state.effects) } pub fn from_node(root: GritRoot, lang: GritTargetLanguage) -> Result { diff --git a/crates/biome_grit_patterns/src/grit_resolved_pattern.rs b/crates/biome_grit_patterns/src/grit_resolved_pattern.rs index cc28546e9447..cd8fccd57d13 100644 --- a/crates/biome_grit_patterns/src/grit_resolved_pattern.rs +++ b/crates/biome_grit_patterns/src/grit_resolved_pattern.rs @@ -1,23 +1,27 @@ +use crate::grit_code_snippet::GritCodeSnippet; use crate::grit_context::GritExecContext; use crate::grit_file::GritFile; -use crate::grit_tree::GritTree; +use crate::grit_target_node::GritTargetNode; +use crate::grit_tree::GritTargetTree; +use crate::GritTargetLanguage; use crate::{grit_binding::GritBinding, grit_context::GritQueryContext}; -use anyhow::Result; +use anyhow::{anyhow, Error, Result}; use grit_pattern_matcher::binding::Binding; use grit_pattern_matcher::constant::Constant; -use grit_pattern_matcher::context::QueryContext; +use grit_pattern_matcher::context::{ExecContext, QueryContext}; use grit_pattern_matcher::effects::Effect; use grit_pattern_matcher::pattern::{ - Accessor, DynamicPattern, DynamicSnippet, FilePtr, FileRegistry, ListIndex, Pattern, - ResolvedPattern, ResolvedSnippet, State, + Accessor, DynamicPattern, DynamicSnippet, DynamicSnippetPart, FilePtr, FileRegistry, GritCall, + ListIndex, Pattern, PatternName, PatternOrResolved, ResolvedFile, ResolvedPattern, + ResolvedSnippet, State, }; -use grit_util::{AnalysisLogs, CodeRange, Range}; +use grit_util::{AnalysisLogs, Ast, CodeRange, Range}; use im::{vector, Vector}; use std::borrow::Cow; use std::collections::{BTreeMap, HashMap}; #[derive(Clone, Debug, PartialEq)] -pub(crate) enum GritResolvedPattern<'a> { +pub enum GritResolvedPattern<'a> { Binding(Vector>), Snippets(Vector>), List(Vector>), @@ -25,12 +29,60 @@ pub(crate) enum GritResolvedPattern<'a> { File(GritFile<'a>), Files(Box>), Constant(Constant), - Tree(GritTree), } impl<'a> GritResolvedPattern<'a> { - pub fn from_tree(tree: &'a GritTree) -> Self { - Self::from_binding(GritBinding::from_tree(tree)) + pub fn from_empty_binding(node: GritTargetNode<'a>, slot_index: u32) -> Self { + Self::from_binding(GritBinding::Empty(node, slot_index)) + } + + pub fn from_tree(tree: &'a GritTargetTree) -> Self { + Self::from_binding(GritBinding::from_node(tree.root_node())) + } + + fn to_snippets(&self) -> Result>> { + match self { + Self::Snippets(snippets) => Ok(snippets.clone()), + Self::Binding(bindings) => Ok(vector![ResolvedSnippet::from_binding( + bindings + .last() + .ok_or_else(|| { + anyhow::anyhow!("cannot create resolved snippet from unresolved binding") + })? + .to_owned(), + )]), + Self::List(elements) => { + // merge separated by space + let mut snippets = Vec::new(); + for pattern in elements { + snippets.extend(pattern.to_snippets()?); + snippets.push(ResolvedSnippet::Text(" ".into())); + } + snippets.pop(); + Ok(snippets.into()) + } + Self::Map(map) => { + let mut snippets = Vec::new(); + snippets.push(ResolvedSnippet::Text("{".into())); + for (key, value) in map { + snippets.push(ResolvedSnippet::Text(format!("\"{}\": ", key).into())); + snippets.extend(value.to_snippets()?); + snippets.push(ResolvedSnippet::Text(", ".into())); + } + snippets.pop(); + snippets.push(ResolvedSnippet::Text("}".into())); + Ok(snippets.into()) + } + Self::File(_) => Err(anyhow::anyhow!( + "cannot convert ResolvedPattern::File to ResolvedSnippet" + )), + Self::Files(_) => Err(anyhow::anyhow!( + "cannot convert ResolvedPattern::Files to ResolvedSnippet" + )), + Self::Constant(constant) => { + Ok(vector![ResolvedSnippet::Text(constant.to_string().into())]) + } + } } } @@ -39,73 +91,241 @@ impl<'a> ResolvedPattern<'a, GritQueryContext> for GritResolvedPattern<'a> { Self::Binding(vector![binding]) } - fn from_constant(_constant: Constant) -> Self { - todo!() + fn from_constant(constant: Constant) -> Self { + Self::Constant(constant) } - fn from_file_pointer(_file: FilePtr) -> Self { - todo!() + fn from_file_pointer(file: FilePtr) -> Self { + Self::File(GritFile::Ptr(file)) } - fn from_files(_files: Self) -> Self { - todo!() + fn from_files(files: Self) -> Self { + Self::Files(Box::new(files)) } - fn from_list_parts(_parts: impl Iterator) -> Self { - todo!() + fn from_list_parts(parts: impl Iterator) -> Self { + Self::List(parts.collect()) } - fn from_string(_string: String) -> Self { - todo!() + fn from_string(string: String) -> Self { + Self::Snippets(vector![ResolvedSnippet::Text(string.into())]) } - fn from_resolved_snippet(_snippet: ResolvedSnippet<'a, GritQueryContext>) -> Self { - todo!() + fn from_resolved_snippet(snippet: ResolvedSnippet<'a, GritQueryContext>) -> Self { + Self::Snippets(vector![snippet]) } fn from_dynamic_snippet( - _snippet: &'a DynamicSnippet, - _state: &mut State<'a, GritQueryContext>, - _context: &'a GritExecContext, - _logs: &mut grit_util::AnalysisLogs, + snippet: &'a DynamicSnippet, + state: &mut State<'a, GritQueryContext>, + context: &'a GritExecContext, + logs: &mut grit_util::AnalysisLogs, ) -> anyhow::Result { - todo!() + let mut parts = Vec::new(); + for part in &snippet.parts { + match part { + DynamicSnippetPart::String(string) => { + parts.push(ResolvedSnippet::Text(string.into())); + } + DynamicSnippetPart::Variable(var) => { + let content = &state.bindings[var.scope].last().unwrap()[var.index]; + let name = &content.name; + // feels weird not sure if clone is correct + let value = if let Some(value) = &content.value { + value.clone() + } else if let Some(pattern) = content.pattern { + Self::from_pattern(pattern, state, context, logs)? + } else { + anyhow::bail!( + "cannot create resolved snippet from unresolved variable {name}" + ) + }; + let value = value.to_snippets()?; + parts.extend(value); + } + } + } + Ok(Self::Snippets(parts.into())) } fn from_dynamic_pattern( - _pattern: &'a DynamicPattern, - _state: &mut State<'a, GritQueryContext>, - _context: &'a GritExecContext, - _logs: &mut grit_util::AnalysisLogs, - ) -> anyhow::Result { - todo!() + pattern: &'a DynamicPattern, + state: &mut State<'a, GritQueryContext>, + context: &'a GritExecContext, + logs: &mut AnalysisLogs, + ) -> Result { + match pattern { + DynamicPattern::Variable(var) => { + let content = &state.bindings[var.scope].last().unwrap()[var.index]; + let name = &content.name; + // feels weird not sure if clone is correct + if let Some(value) = &content.value { + Ok(value.clone()) + } else if let Some(pattern) = content.pattern { + Self::from_pattern(pattern, state, context, logs) + } else { + anyhow::bail!("cannot create resolved snippet from unresolved variable {name}") + } + } + DynamicPattern::Accessor(accessor) => { + Self::from_accessor(accessor, state, context, logs) + } + DynamicPattern::ListIndex(index) => Self::from_list_index(index, state, context, logs), + DynamicPattern::List(list) => { + let mut elements = Vec::new(); + for element in &list.elements { + elements.push(Self::from_dynamic_pattern(element, state, context, logs)?); + } + Ok(Self::List(elements.into())) + } + DynamicPattern::Snippet(snippet) => { + Self::from_dynamic_snippet(snippet, state, context, logs) + } + DynamicPattern::CallBuiltIn(built_in) => built_in.call(state, context, logs), + DynamicPattern::CallFunction(func) => func.call(state, context, logs), + DynamicPattern::CallForeignFunction(_) => unimplemented!(), + } } fn from_accessor( - _accessor: &'a Accessor, - _state: &mut State<'a, GritQueryContext>, - _context: &'a GritExecContext, - _logs: &mut grit_util::AnalysisLogs, - ) -> anyhow::Result { - todo!() + accessor: &'a Accessor, + state: &mut State<'a, GritQueryContext>, + context: &'a GritExecContext, + logs: &mut AnalysisLogs, + ) -> Result { + match accessor.get(state, context.language())? { + Some(PatternOrResolved::Pattern(pattern)) => { + Self::from_pattern(pattern, state, context, logs) + } + Some(PatternOrResolved::ResolvedBinding(resolved)) => Ok(resolved), + Some(PatternOrResolved::Resolved(resolved)) => Ok(resolved.clone()), + None => Ok(Self::from_constant_binding(&Constant::Undefined)), + } } fn from_list_index( - _index: &'a ListIndex, - _state: &mut State<'a, GritQueryContext>, - _context: &'a GritExecContext, - _logs: &mut grit_util::AnalysisLogs, - ) -> anyhow::Result { - todo!() + index: &'a ListIndex, + state: &mut State<'a, GritQueryContext>, + context: &'a GritExecContext, + logs: &mut AnalysisLogs, + ) -> Result { + match index.get(state, context.language())? { + Some(PatternOrResolved::Pattern(pattern)) => { + Self::from_pattern(pattern, state, context, logs) + } + Some(PatternOrResolved::ResolvedBinding(resolved)) => Ok(resolved), + Some(PatternOrResolved::Resolved(resolved)) => Ok(resolved.clone()), + None => Ok(Self::from_constant_binding(&Constant::Undefined)), + } } fn from_pattern( - _pattern: &'a Pattern, - _state: &mut State<'a, GritQueryContext>, - _context: &'a GritExecContext, - _logs: &mut grit_util::AnalysisLogs, - ) -> anyhow::Result { - todo!() + pattern: &'a Pattern, + state: &mut State<'a, GritQueryContext>, + context: &'a GritExecContext, + logs: &mut AnalysisLogs, + ) -> Result { + match pattern { + Pattern::Dynamic(pattern) => Self::from_dynamic_pattern(pattern, state, context, logs), + Pattern::CodeSnippet(GritCodeSnippet { + dynamic_snippet: Some(pattern), + .. + }) => Self::from_dynamic_pattern(pattern, state, context, logs), + Pattern::CallBuiltIn(built_in) => built_in.call(state, context, logs), + Pattern::CallFunction(func) => func.call(state, context, logs), + Pattern::CallForeignFunction(_) => unimplemented!(), + Pattern::StringConstant(string) => Ok(Self::Snippets(vector![ResolvedSnippet::Text( + (&string.text).into(), + )])), + Pattern::IntConstant(int) => Ok(Self::Constant(Constant::Integer(int.value))), + Pattern::FloatConstant(double) => Ok(Self::Constant(Constant::Float(double.value))), + Pattern::BooleanConstant(bool) => Ok(Self::Constant(Constant::Boolean(bool.value))), + Pattern::Variable(var) => { + let content = &state.bindings[var.scope].last().unwrap()[var.index]; + let name = &content.name; + // feels weird not sure if clone is correct + if let Some(value) = &content.value { + Ok(value.clone()) + } else if let Some(pattern) = content.pattern { + Self::from_pattern(pattern, state, context, logs) + } else { + anyhow::bail!("cannot create resolved snippet from unresolved variable {name}") + } + } + Pattern::List(list) => list + .patterns + .iter() + .map(|pattern| Self::from_pattern(pattern, state, context, logs)) + .collect::>>() + .map(Self::List), + Pattern::ListIndex(index) => Self::from_list_index(index, state, context, logs), + Pattern::Map(map) => map + .elements + .iter() + .map(|(key, value)| { + Ok(( + key.clone(), + Self::from_pattern(value, state, context, logs)?, + )) + }) + .collect::>>() + .map(Self::Map), + Pattern::Accessor(accessor) => Self::from_accessor(accessor, state, context, logs), + Pattern::File(file_pattern) => { + let name = &file_pattern.name; + let body = &file_pattern.body; + let name = Self::from_pattern(name, state, context, logs)?; + let name = name.text(&state.files, context.language())?; + let name = Self::Constant(Constant::String(name.to_string())); + let body = Self::from_pattern(body, state, context, logs)?; + Ok(Self::File(GritFile::Resolved(Box::new(ResolvedFile { + name, + body, + })))) + } + Pattern::Add(add_pattern) => add_pattern.call(state, context, logs), + Pattern::Subtract(subtract_pattern) => subtract_pattern.call(state, context, logs), + Pattern::Multiply(multiply_pattern) => multiply_pattern.call(state, context, logs), + Pattern::Divide(divide_pattern) => divide_pattern.call(state, context, logs), + Pattern::Modulo(modulo_pattern) => modulo_pattern.call(state, context, logs), + Pattern::Before(before) => before.prev_pattern(state, context, logs), + Pattern::After(after) => after.next_pattern(state, context, logs), + Pattern::AstNode(_) + | Pattern::CodeSnippet(_) + | Pattern::Call(_) + | Pattern::Regex(_) + | Pattern::Files(_) + | Pattern::Bubble(_) + | Pattern::Limit(_) + | Pattern::Assignment(_) + | Pattern::Accumulate(_) + | Pattern::And(_) + | Pattern::Or(_) + | Pattern::Maybe(_) + | Pattern::Any(_) + | Pattern::Not(_) + | Pattern::If(_) + | Pattern::Undefined + | Pattern::Top + | Pattern::Bottom + | Pattern::Underscore + | Pattern::AstLeafNode(_) + | Pattern::Rewrite(_) + | Pattern::Log(_) + | Pattern::Range(_) + | Pattern::Contains(_) + | Pattern::Includes(_) + | Pattern::Within(_) + | Pattern::Where(_) + | Pattern::Some(_) + | Pattern::Every(_) + | Pattern::Dots + | Pattern::Like(_) + | Pattern::Sequential(_) => Err(anyhow::anyhow!(format!( + "cannot make resolved pattern from arbitrary pattern {}", + pattern.name() + ))), + } } fn extend( @@ -119,10 +339,37 @@ impl<'a> ResolvedPattern<'a, GritQueryContext> for GritResolvedPattern<'a> { fn float( &self, - _state: &FileRegistry<'a, GritQueryContext>, - _language: &::Language<'a>, - ) -> anyhow::Result { - todo!() + state: &FileRegistry<'a, GritQueryContext>, + language: &GritTargetLanguage, + ) -> Result { + match self { + Self::Constant(c) => match c { + Constant::Float(d) => Ok(*d), + Constant::Integer(i) => Ok(*i as f64), + Constant::String(s) => Ok(s.parse::()?), + Constant::Boolean(_) | Constant::Undefined => Err(anyhow::anyhow!("Cannot convert constant to double. Ensure that you are only attempting arithmetic operations on numeric-parsable types.")), + }, + Self::Snippets(s) => { + let text = s + .iter() + .map(|snippet| snippet.text(state, language)) + .collect::>>()? + .join(""); + text.parse::().map_err(|_| { + anyhow::anyhow!("Failed to convert snippet to double. Ensure that you are only attempting arithmetic operations on numeric-parsable types.") + }) + } + Self::Binding(binding) => { + let text = binding + .last() + .ok_or_else(|| anyhow::anyhow!("cannot grab text of resolved_pattern with no binding"))? + .text(language)?; + text.parse::().map_err(|_| { + anyhow::anyhow!("Failed to convert binding to double. Ensure that you are only attempting arithmetic operations on numeric-parsable types.") + }) + } + Self::List(_) | Self::Map(_) | Self::File(_) | Self::Files(_) => Err(anyhow::anyhow!("Cannot convert type to double. Ensure that you are only attempting arithmetic operations on numeric-parsable types.")), + } } fn get_bindings(&self) -> Option>> { @@ -265,10 +512,29 @@ impl<'a> ResolvedPattern<'a, GritQueryContext> for GritResolvedPattern<'a> { fn text( &self, - _state: &grit_pattern_matcher::pattern::FileRegistry<'a, GritQueryContext>, - _language: &::Language<'a>, + state: &FileRegistry<'a, GritQueryContext>, + language: &GritTargetLanguage, ) -> Result> { - todo!() + match self { + GritResolvedPattern::Binding(bindings) => Ok(bindings + .last() + .ok_or_else(|| anyhow!("cannot grab text of resolved_pattern with no binding"))? + .text(language)? + .into_owned() + .into()), + GritResolvedPattern::Snippets(snippets) => Ok(snippets + .iter() + .try_fold(String::new(), |mut text, snippet| { + text.push_str(&snippet.text(state, language)?); + Ok::(text) + })? + .into()), + GritResolvedPattern::List(_) => todo!(), + GritResolvedPattern::Map(_) => todo!(), + GritResolvedPattern::File(_) => todo!(), + GritResolvedPattern::Files(_) => todo!(), + GritResolvedPattern::Constant(_) => todo!(), + } } } diff --git a/crates/biome_grit_patterns/src/grit_target_language.rs b/crates/biome_grit_patterns/src/grit_target_language.rs index 27553cd03f0a..08c9cc9891f7 100644 --- a/crates/biome_grit_patterns/src/grit_target_language.rs +++ b/crates/biome_grit_patterns/src/grit_target_language.rs @@ -4,7 +4,7 @@ pub use js_target_language::JsTargetLanguage; use crate::grit_js_parser::GritJsParser; use crate::grit_target_node::{GritTargetNode, GritTargetSyntaxKind}; -use crate::grit_tree::GritTree; +use crate::grit_tree::GritTargetTree; use biome_rowan::SyntaxKind; use grit_util::{Ast, CodeRange, EffectRange, Language, Parser, SnippetTree}; use std::borrow::Cow; @@ -35,7 +35,7 @@ macro_rules! generate_target_language { } } - fn get_parser(&self) -> Box> { + fn get_parser(&self) -> Box> { match self { $(Self::$language(_) => Box::new($parser)),+ } @@ -47,18 +47,15 @@ macro_rules! generate_target_language { } } - pub fn parse_snippet_contexts(&self, source: &str) -> Vec> { - let source = self.substitute_metavariable_prefix(source); - self.snippet_context_strings() - .iter() - .map(|(pre, post)| self.get_parser().parse_snippet(pre, &source, post)) - .filter(|result| !result.tree.root_node().kind().is_bogus()) - .collect() + pub fn is_comment_kind(&self, kind: GritTargetSyntaxKind) -> bool { + match self { + $(Self::$language(_) => $language::is_comment_kind(kind)),+ + } } } impl Language for GritTargetLanguage { - type Node<'a> = GritTargetNode; + type Node<'a> = GritTargetNode<'a>; fn language_name(&self) -> &'static str { match self { @@ -81,7 +78,7 @@ macro_rules! generate_target_language { fn is_metavariable(&self, node: &GritTargetNode) -> bool { node.kind() == self.metavariable_kind() || (self.is_alternative_metavariable_kind(node.kind()) - && self.exact_replaced_variable_regex().is_match(&node.text_trimmed().to_string())) + && self.exact_replaced_variable_regex().is_match(node.text())) } fn align_padding<'a>( @@ -111,6 +108,49 @@ generate_target_language! { [JsTargetLanguage, GritJsParser] } +impl GritTargetLanguage { + /// Returns `true` when the text `content` contains an identifier for a + /// metavariable using bracket syntax. + /// + /// The metavariable may occur anywhere inside `content`. + pub fn matches_bracket_metavariable(&self, content: &str) -> bool { + self.metavariable_bracket_regex().is_match(content) + } + + /// Returns `true` when the text `content` is a metavariable identifier. + /// + /// No other text is allowed inside `content`. + pub fn matches_exact_metavariable(&self, content: &str) -> bool { + self.exact_variable_regex().is_match(content) + } + + /// Returns `true` when the text `content` contains a metavariable + /// identifier with its prefix replaced with the + /// `[Self::metavariable_prefix_substitute()]. + /// + /// The metavariable may occur anywhere inside `content`. + pub fn matches_replaced_metavariable(&self, content: &str) -> bool { + self.replaced_metavariable_regex().is_match(content) + } + + pub fn parse_snippet_contexts(&self, source: &str) -> Vec> { + let source = self.substitute_metavariable_prefix(source); + self.snippet_context_strings() + .iter() + .map(|(pre, post)| self.get_parser().parse_snippet(pre, &source, post)) + .filter(|result| { + result + .tree + .root_node() + .descendants() + .map_or(false, |mut descendants| { + !descendants.any(|descendant| descendant.kind().is_bogus()) + }) + }) + .collect() + } +} + /// Trait to be implemented by the language-specific implementations. /// /// This is used to make language implementations a little easier, by not @@ -134,7 +174,15 @@ trait GritTargetLanguageImpl { fn snippet_context_strings(&self) -> &[(&'static str, &'static str)]; /// Determines whether the given target node is a comment. - fn is_comment(&self, node: &GritTargetNode) -> bool; + /// + /// This is allowed to return `true` for nodes whose kind would not return + /// `true` when passed directly to [is_comment_kind()]. + fn is_comment(&self, node: &GritTargetNode) -> bool { + Self::is_comment_kind(node.kind()) + } + + /// Determines whether the given kind is a comment kind. + fn is_comment_kind(kind: GritTargetSyntaxKind) -> bool; /// Returns the syntax kind for metavariables. fn metavariable_kind() -> Self::Kind; diff --git a/crates/biome_grit_patterns/src/grit_target_language/js_target_language.rs b/crates/biome_grit_patterns/src/grit_target_language/js_target_language.rs index e5b390d6cb47..93e1ee696b5b 100644 --- a/crates/biome_grit_patterns/src/grit_target_language/js_target_language.rs +++ b/crates/biome_grit_patterns/src/grit_target_language/js_target_language.rs @@ -1,5 +1,5 @@ use super::GritTargetLanguageImpl; -use crate::grit_target_node::{GritTargetNode, GritTargetSyntaxKind}; +use crate::grit_target_node::GritTargetSyntaxKind; use biome_js_syntax::JsSyntaxKind; use biome_parser::{token_set, TokenSet}; @@ -39,9 +39,8 @@ impl GritTargetLanguageImpl for JsTargetLanguage { ] } - fn is_comment(&self, node: &GritTargetNode) -> bool { - node.kind() - .as_js_kind() + fn is_comment_kind(kind: GritTargetSyntaxKind) -> bool { + kind.as_js_kind() .map_or(false, |kind| COMMENT_KINDS.contains(kind)) } diff --git a/crates/biome_grit_patterns/src/grit_target_node.rs b/crates/biome_grit_patterns/src/grit_target_node.rs index 2c120fa76d25..c8855b32a8d1 100644 --- a/crates/biome_grit_patterns/src/grit_target_node.rs +++ b/crates/biome_grit_patterns/src/grit_target_node.rs @@ -1,17 +1,23 @@ -use crate::util::TextRangeGritExt; -use biome_js_syntax::{JsLanguage, JsSyntaxKind, JsSyntaxNode, JsSyntaxToken}; -use biome_rowan::{SyntaxKind, SyntaxNodeText, SyntaxSlot, TextRange}; +use crate::{util::TextRangeGritExt, GritTargetTree}; +use biome_js_syntax::{JsSyntaxKind, JsSyntaxNode, JsSyntaxToken}; +use biome_rowan::{NodeOrToken, SyntaxKind, SyntaxSlot, TextRange}; use grit_util::{AstCursor, AstNode as GritAstNode, ByteRange, CodeRange}; -use std::{borrow::Cow, str::Utf8Error}; +use std::{borrow::Cow, ops::Deref, str::Utf8Error}; -/// Generates the `GritTargetNode`, `GritTargetToken`, and -/// `GritTargetSyntaxKind` enums. +use NodeOrToken::*; + +/// Generates the `GritTargetNode` and `GritTargetSyntaxKind` enums. +/// +/// Note that `GritTargetNode` can represent both nodes and tokens. While Biome +/// uses different terminology for the two, Grit treats them one and the +/// same. It does sometimes refer to "named nodes" when specifically talking +/// about nodes and not tokens, since tokens are not named in the grammar. /// /// These enums can represent nodes, tokens and kinds for all the languages we /// support running Grit queries on. /// /// We intentionally use enums for these types, rather than using generics for -/// specifying specific types: +/// specifying specific language-specific-types: /// - If we used generics instead, those would infest all code using these /// types, and we would end up with an explosion of generics all over the Grit /// runtime. @@ -27,192 +33,129 @@ use std::{borrow::Cow, str::Utf8Error}; macro_rules! generate_target_node { ($([$lang:ident, $lang_node:ident, $lang_token:ident, $lang_kind:ident]),+) => { #[derive(Clone, Debug, PartialEq)] - pub enum GritTargetNode { - $($lang_node($lang_node)),+ + pub enum GritTargetLanguageNode { + $($lang(NodeOrToken<$lang_node, $lang_token>)),+ } - $(impl From<$lang_node> for GritTargetNode { + $(impl From<$lang_node> for GritTargetLanguageNode { fn from(value: $lang_node) -> Self { - Self::$lang_node(value) + Self::$lang(Node(value)) } })+ - impl GritTargetNode { - pub fn first_child(&self) -> Option { - match self { - $(Self::$lang_node(node) => node.first_child().map(Into::into)),+ - } + $(impl From<$lang_token> for GritTargetLanguageNode { + fn from(value: $lang_token) -> Self { + Self::$lang(Token(value)) } + })+ - pub fn first_token(&self) -> Option { - match self { - $(Self::$lang_node(node) => node.first_token().map(Into::into)),+ - } + $(impl From> for GritTargetLanguageNode { + fn from(value: NodeOrToken<$lang_node, $lang_token>) -> Self { + Self::$lang(value) } + })+ - pub fn has_children(&self) -> bool { + impl GritTargetLanguageNode { + pub fn descendants(&self) -> Option> { match self { - $(Self::$lang_node(node) => node.first_child().is_some()),+ + $(Self::$lang(Node(node)) => Some(node.descendants().map(Into::into))),+, + _ => None } } - pub fn index(&self) -> usize { + pub fn first_child(&self) -> Option { match self { - $(Self::$lang_node(node) => node.index()),+ + $(Self::$lang(Node(node)) => node.first_child().map(Into::into)),+, + _ => None } } - pub fn kind(&self) -> GritTargetSyntaxKind { + pub fn has_children(&self) -> bool { match self { - $(Self::$lang_node(node) => node.kind().into()),+ + $(Self::$lang(Node(node)) => node.first_child().is_some()),+, + _ => false } } - pub fn slots(&self) -> impl Iterator { + pub fn index(&self) -> u32 { match self { - $(Self::$lang_node(node) => node.slots().map(Into::into)),+ + $(Self::$lang(Node(node)) => node.index() as u32),+, + $(Self::$lang(Token(token)) => token.index() as u32),+ } } - pub fn text(&self) -> SyntaxNodeText { + #[inline] + pub fn is_token(&self) -> bool { match self { - $(Self::$lang_node(node) => node.text()),+ + $(Self::$lang(Node(_)) => false),+, + $(Self::$lang(Token(_)) => true),+ } } - pub fn text_range(&self) -> TextRange { + #[inline] + pub fn kind(&self) -> GritTargetSyntaxKind { match self { - $(Self::$lang_node(node) => node.text_range()),+ + $(Self::$lang(Node(node)) => node.kind().into()),+, + $(Self::$lang(Token(token)) => token.kind().into()),+ } } - pub fn text_trimmed(&self) -> SyntaxNodeText { + pub fn next_sibling(&self) -> Option { match self { - $(Self::$lang_node(node) => node.text_trimmed()),+ + $(Self::$lang(Node(node)) => node.next_sibling_or_token().map(Into::into)),+, + $(Self::$lang(Token(token)) => token.next_sibling_or_token().map(Into::into)),+ } } - pub fn text_trimmed_range(&self) -> TextRange { + pub fn owned_text(&self) -> Cow { match self { - $(Self::$lang_node(node) => node.text_trimmed_range()),+ + $(Self::$lang(Node(node)) => Cow::Owned(node.text().to_string())),+, + $(Self::$lang(Token(token)) => Cow::Borrowed(token.text())),+ } } - pub fn start_byte(&self) -> u32 { - self.text_trimmed_range().start().into() - } - - pub fn end_byte(&self) -> u32 { - self.text_trimmed_range().end().into() - } - } - - impl GritAstNode for GritTargetNode { - fn ancestors(&self) -> impl Iterator { - AncestorIterator::new(self) - } - - fn children(&self) -> impl Iterator { - ChildrenIterator::new(self) - } - - fn parent(&self) -> Option { + pub fn parent(&self) -> Option { match self { - $(Self::$lang_node(node) => node.parent().map(Into::into)),+ - } - } - - fn next_named_node(&self) -> Option { - let mut current_node = Cow::Borrowed(self); - loop { - if let Some(sibling) = current_node.next_sibling() { - return Some(sibling); - } - current_node = Cow::Owned(current_node.parent()?); + $(Self::$lang(Node(node)) => node.parent().map(Into::into)),+, + $(Self::$lang(Token(token)) => token.parent().map(Into::into)),+ } } - fn previous_named_node(&self) -> Option { - let mut current_node = Cow::Borrowed(self); - loop { - if let Some(sibling) = current_node.previous_sibling() { - return Some(sibling); - } - current_node = Cow::Owned(current_node.parent()?); - } - } - - fn next_sibling(&self) -> Option { + pub fn previous_sibling(&self) -> Option { match self { - $(Self::$lang_node(node) => node.next_sibling().map(Into::into)),+ + $(Self::$lang(Node(node)) => node.prev_sibling_or_token().map(Into::into)),+, + $(Self::$lang(Token(token)) => token.prev_sibling_or_token().map(Into::into)),+ } } - fn previous_sibling(&self) -> Option { + pub fn slots<'a>(&self, tree: &'a GritTargetTree) -> Option>> { match self { - $(Self::$lang_node(node) => node.prev_sibling().map(Into::into)),+ - } - } - - fn text(&self) -> Result, Utf8Error> { - Ok(Cow::Owned(self.text_trimmed().to_string())) - } - - fn byte_range(&self) -> ByteRange { - self.text_trimmed_range().to_byte_range() - } - - fn code_range(&self) -> CodeRange { - let range = self.text_trimmed_range(); - CodeRange { - start: range.start().into(), - end: range.end().into(), - // Code ranges contain an address so they can quickly check whether - // a particular binding belongs to a given range or not. - address: self - .first_token() - .map(|token| token.text().as_ptr() as usize) - .unwrap_or_default(), + $(Self::$lang(Node(node)) => Some(node.slots().map(|slot| match slot { + SyntaxSlot::Node(node) => GritSyntaxSlot::Node(GritTargetNode::new(node.into(), tree)), + SyntaxSlot::Token(token) => GritSyntaxSlot::Node(GritTargetNode::new(token.into(), tree)), + SyntaxSlot::Empty { index } => GritSyntaxSlot::Empty { index } + }))),+, + $(Self::$lang(Token(_token)) => None),+ } } - fn walk(&self) -> impl AstCursor { - GritTargetNodeCursor::new(self) - } - } - - #[derive(Clone, Debug, PartialEq)] - pub enum GritTargetToken { - $($lang_token($lang_token)),+ - } - - $(impl From<$lang_token> for GritTargetToken { - fn from(value: $lang_token) -> Self { - Self::$lang_token(value) - } - })+ - - impl GritTargetToken { - pub fn index(&self) -> usize { - match self { - $(Self::$lang_token(token) => token.index()),+ - } - } - - pub fn kind(&self) -> GritTargetSyntaxKind { + pub fn text_range(&self) -> TextRange { match self { - $(Self::$lang_token(token) => token.kind().into()),+ + $(Self::$lang(Node(node)) => node.text_range()),+, + $(Self::$lang(Token(token)) => token.text_range()),+ } } - pub fn text(&self) -> &str { + #[inline] + pub fn text_trimmed_range(&self) -> TextRange { match self { - $(Self::$lang_token(token) => token.text()),+ + $(Self::$lang(Node(node)) => node.text_trimmed_range()),+, + $(Self::$lang(Token(token)) => token.text_trimmed_range()),+ } } } - #[derive(Clone, Debug, PartialEq)] + #[derive(Clone, Copy, Debug, PartialEq)] pub enum GritTargetSyntaxKind { $($lang_kind($lang_kind)),+ } @@ -235,23 +178,7 @@ macro_rules! generate_target_node { $(Self::$lang_kind(kind) => kind.is_list()),+ } } - - pub fn is_token(&self) -> bool { - match self { - $(Self::$lang_kind(kind) => kind.is_punct() || kind.is_literal()),+ - } - } } - - $(impl From> for GritSyntaxSlot { - fn from(slot: SyntaxSlot<$lang>) -> Self { - match slot { - SyntaxSlot::Node(node) => Self::Node(node.into()), - SyntaxSlot::Token(token) => Self::Token(token.into()), - SyntaxSlot::Empty { index } => Self::Empty { index }, - } - } - })+ }; } @@ -259,6 +186,149 @@ generate_target_node! { [JsLanguage, JsSyntaxNode, JsSyntaxToken, JsSyntaxKind] } +#[derive(Clone, Debug, PartialEq)] +pub struct GritTargetNode<'a> { + node: GritTargetLanguageNode, + tree: &'a GritTargetTree, +} + +impl<'a> GritTargetNode<'a> { + pub fn new(node: GritTargetLanguageNode, tree: &'a GritTargetTree) -> Self { + Self { node, tree } + } + + pub fn child_by_slot_index(&self, index: u32) -> Option { + self.slots() + .and_then(|mut slots| slots.nth(index as usize)) + .and_then(|slot| match slot { + GritSyntaxSlot::Node(node) => Some(node), + GritSyntaxSlot::Empty { .. } => None, + }) + } + + pub fn descendants(&'a self) -> Option> { + self.node.descendants().map(|descendants| { + descendants.map(|node| Self { + node, + tree: self.tree, + }) + }) + } + + #[inline] + pub fn end_byte(&self) -> u32 { + self.text_trimmed_range().end().into() + } + + pub fn first_child(&self) -> Option { + self.node.first_child().map(|node| Self { + node, + tree: self.tree, + }) + } + + #[inline] + pub fn is_bogus(&self) -> bool { + self.kind().is_bogus() + } + + #[inline] + pub fn is_list(&self) -> bool { + self.kind().is_list() + } + + #[inline] + pub fn slots(&self) -> Option>> { + self.node.slots(self.tree) + } + + #[inline] + pub fn start_byte(&self) -> u32 { + self.text_trimmed_range().start().into() + } + + pub fn text(&self) -> &'a str { + let trimmed_range = self.text_trimmed_range(); + &self.tree.text()[trimmed_range.start().into()..trimmed_range.end().into()] + } +} + +impl<'a> Deref for GritTargetNode<'a> { + type Target = GritTargetLanguageNode; + + fn deref(&self) -> &Self::Target { + &self.node + } +} + +impl<'a> GritAstNode for GritTargetNode<'a> { + fn ancestors(&self) -> impl Iterator { + AncestorIterator::new(self.clone()) + } + + fn byte_range(&self) -> ByteRange { + self.text_trimmed_range().to_byte_range() + } + + fn code_range(&self) -> CodeRange { + self.text_trimmed_range().to_code_range(self.text()) + } + + #[allow(refining_impl_trait)] + fn children(&self) -> impl Iterator + Clone { + ChildrenIterator::new(self) + } + + fn parent(&self) -> Option { + self.node.parent().map(|node| Self { + node, + tree: self.tree, + }) + } + + fn next_sibling(&self) -> Option { + self.node.next_sibling().map(|node| Self { + node, + tree: self.tree, + }) + } + + fn previous_sibling(&self) -> Option { + self.node.previous_sibling().map(|node| Self { + node, + tree: self.tree, + }) + } + + fn next_named_node(&self) -> Option { + let mut current_node = Cow::Borrowed(self); + loop { + if let Some(sibling) = current_node.next_sibling() { + return Some(sibling); + } + current_node = Cow::Owned(current_node.parent()?); + } + } + + fn previous_named_node(&self) -> Option { + let mut current_node = Cow::Borrowed(self); + loop { + if let Some(sibling) = current_node.previous_sibling() { + return Some(sibling); + } + current_node = Cow::Owned(current_node.parent()?); + } + } + + fn text(&self) -> Result, Utf8Error> { + Ok(Cow::Borrowed(self.text())) + } + + fn walk(&self) -> impl AstCursor { + GritTargetNodeCursor::new(self) + } +} + impl GritTargetSyntaxKind { pub fn as_js_kind(&self) -> Option { match self { @@ -268,47 +338,42 @@ impl GritTargetSyntaxKind { } #[derive(Clone, Debug, PartialEq)] -pub enum GritSyntaxSlot { +pub enum GritSyntaxSlot<'a> { /// Slot that stores a node child - Node(GritTargetNode), - /// Slot that stores a token child - Token(GritTargetToken), + Node(GritTargetNode<'a>), /// Slot that marks that the child in this position isn't present in the source code. Empty { index: u32 }, } -impl GritSyntaxSlot { +impl<'a> GritSyntaxSlot<'a> { pub fn contains_list(&self) -> bool { match self { GritSyntaxSlot::Node(node) => node.kind().is_list(), - GritSyntaxSlot::Token(_) | GritSyntaxSlot::Empty { .. } => false, + GritSyntaxSlot::Empty { .. } => false, } } - pub fn index(&self) -> usize { + pub fn index(&self) -> u32 { match self { GritSyntaxSlot::Node(node) => node.index(), - GritSyntaxSlot::Token(token) => token.index(), - GritSyntaxSlot::Empty { index } => *index as usize, + GritSyntaxSlot::Empty { index } => *index, } } } #[derive(Clone)] -pub struct AncestorIterator { - node: Option, +pub struct AncestorIterator<'a> { + node: Option>, } -impl AncestorIterator { - fn new(node: &GritTargetNode) -> Self { - Self { - node: Some(node.clone()), - } +impl<'a> AncestorIterator<'a> { + fn new(node: GritTargetNode<'a>) -> Self { + Self { node: Some(node) } } } -impl Iterator for AncestorIterator { - type Item = GritTargetNode; +impl<'a> Iterator for AncestorIterator<'a> { + type Item = GritTargetNode<'a>; fn next(&mut self) -> Option { let node = self.node.as_ref().cloned()?; @@ -317,12 +382,13 @@ impl Iterator for AncestorIterator { } } -pub struct ChildrenIterator { - cursor: Option, +#[derive(Clone, Debug)] +pub struct ChildrenIterator<'a> { + cursor: Option>, } -impl ChildrenIterator { - fn new(node: &GritTargetNode) -> Self { +impl<'a> ChildrenIterator<'a> { + fn new(node: &GritTargetNode<'a>) -> Self { let mut cursor = GritTargetNodeCursor::new(node); Self { cursor: cursor.goto_first_child().then_some(cursor), @@ -330,8 +396,8 @@ impl ChildrenIterator { } } -impl Iterator for ChildrenIterator { - type Item = GritTargetNode; +impl<'a> Iterator for ChildrenIterator<'a> { + type Item = GritTargetNode<'a>; fn next(&mut self) -> Option { let c = self.cursor.as_mut()?; @@ -343,14 +409,14 @@ impl Iterator for ChildrenIterator { } } -#[derive(Clone)] -struct GritTargetNodeCursor { - node: GritTargetNode, - root: GritTargetNode, +#[derive(Clone, Debug)] +struct GritTargetNodeCursor<'a> { + node: GritTargetNode<'a>, + root: GritTargetNode<'a>, } -impl GritTargetNodeCursor { - fn new(node: &GritTargetNode) -> Self { +impl<'a> GritTargetNodeCursor<'a> { + fn new(node: &GritTargetNode<'a>) -> Self { Self { node: node.clone(), root: node.clone(), @@ -358,8 +424,8 @@ impl GritTargetNodeCursor { } } -impl AstCursor for GritTargetNodeCursor { - type Node = GritTargetNode; +impl<'a> AstCursor for GritTargetNodeCursor<'a> { + type Node = GritTargetNode<'a>; fn goto_first_child(&mut self) -> bool { match self.node.first_child() { diff --git a/crates/biome_grit_patterns/src/grit_tree.rs b/crates/biome_grit_patterns/src/grit_tree.rs index 368909d74cbf..c4a2d2800a84 100644 --- a/crates/biome_grit_patterns/src/grit_tree.rs +++ b/crates/biome_grit_patterns/src/grit_tree.rs @@ -1,28 +1,34 @@ -use crate::grit_target_node::GritTargetNode; +use crate::grit_target_node::{GritTargetLanguageNode, GritTargetNode}; use grit_util::Ast; use std::borrow::Cow; #[derive(Clone, Debug, PartialEq)] -pub struct GritTree { - root: GritTargetNode, +pub struct GritTargetTree { + root: GritTargetLanguageNode, + source: String, } -impl GritTree { - pub fn new(root: GritTargetNode) -> Self { - Self { root } +impl GritTargetTree { + pub fn new(root: GritTargetLanguageNode) -> Self { + let source = root.owned_text().into_owned(); + Self { root, source } + } + + pub fn text(&self) -> &str { + &self.source } } -impl Ast for GritTree { - type Node<'a> = GritTargetNode +impl Ast for GritTargetTree { + type Node<'a> = GritTargetNode<'a> where Self: 'a; fn root_node(&self) -> GritTargetNode { - self.root.clone() + GritTargetNode::new(self.root.clone(), self) } fn source(&self) -> Cow { - self.root.text().to_string().into() + Cow::Borrowed(&self.source) } } diff --git a/crates/biome_grit_patterns/src/lib.rs b/crates/biome_grit_patterns/src/lib.rs index 21e165c75754..5bd23aa4f0af 100644 --- a/crates/biome_grit_patterns/src/lib.rs +++ b/crates/biome_grit_patterns/src/lib.rs @@ -22,7 +22,7 @@ mod variables; pub use errors::*; pub use grit_query::GritQuery; pub use grit_target_language::{GritTargetLanguage, JsTargetLanguage}; -pub use grit_tree::GritTree; +pub use grit_tree::GritTargetTree; use biome_grit_parser::parse_grit; diff --git a/crates/biome_grit_patterns/src/pattern_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler.rs index 53ce2534e598..f5f8f230d025 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler.rs @@ -63,8 +63,6 @@ mod variable_compiler; mod where_compiler; mod within_compiler; -use std::collections::BTreeMap; - use self::{ accumulate_compiler::AccumulateCompiler, add_compiler::AddCompiler, after_compiler::AfterCompiler, and_compiler::AndCompiler, any_compiler::AnyCompiler, @@ -81,19 +79,10 @@ use self::{ subtract_compiler::SubtractCompiler, variable_compiler::VariableCompiler, where_compiler::WhereCompiler, within_compiler::WithinCompiler, }; -use crate::{ - grit_context::GritQueryContext, - grit_node_patterns::{GritLeafNodePattern, GritNodeArg, GritNodePattern}, - grit_target_node::{GritSyntaxSlot, GritTargetNode, GritTargetToken}, - CompileError, GritTargetLanguage, -}; +use crate::{grit_context::GritQueryContext, CompileError}; use biome_grit_syntax::{AnyGritMaybeCurlyPattern, AnyGritPattern, GritSyntaxKind}; use biome_rowan::AstNode as _; -use grit_pattern_matcher::pattern::{ - is_reserved_metavariable, DynamicPattern, DynamicSnippet, DynamicSnippetPart, List, Pattern, - RegexLike, RegexPattern, Variable, -}; -use grit_util::{traverse, AstNode, ByteRange, GritMetaValue, Language, Order}; +use grit_pattern_matcher::pattern::{DynamicPattern, DynamicSnippet, DynamicSnippetPart, Pattern}; pub(crate) struct PatternCompiler; @@ -237,262 +226,3 @@ impl PatternCompiler { } } } - -impl PatternCompiler { - pub(crate) fn from_snippet_node( - node: GritTargetNode, - context_range: ByteRange, - context: &mut NodeCompilationContext, - is_rhs: bool, - ) -> Result, CompileError> { - let snippet_start = node.text().char_at(0.into()).unwrap_or_default() as usize; - let ranges = metavariable_ranges(&node, &context.compilation.lang); - let range_map = metavariable_range_mapping(ranges, snippet_start); - - fn node_to_pattern( - node: GritTargetNode, - context_range: ByteRange, - range_map: &BTreeMap, - context: &mut NodeCompilationContext, - is_rhs: bool, - ) -> anyhow::Result, CompileError> { - let metavariable = - metavariable_descendent(&node, context_range, range_map, context, is_rhs)?; - if let Some(metavariable) = metavariable { - return Ok(metavariable); - } - - let kind = node.kind(); - if !node.has_children() { - if let Some(token) = node.first_token() { - let content = token.text(); - if context - .compilation - .lang - .replaced_metavariable_regex() - .is_match(content) - { - let regex = - implicit_metavariable_regex(&token, context_range, range_map, context)?; - if let Some(regex) = regex { - return Ok(Pattern::Regex(Box::new(regex))); - } - } - - return Ok(Pattern::AstLeafNode(GritLeafNodePattern::new( - kind, content, - ))); - } - } - - let args: Vec = node - .slots() - // TODO: Implement filtering for disregarded snippet fields. - // Implementing this will make it more convenient to match - // CST nodes without needing to match all the trivia in the - // snippet. - .map(|slot| { - let mut nodes_list: Vec> = match &slot { - GritSyntaxSlot::Node(node) => node - .children() - .map(|n| node_to_pattern(n, context_range, range_map, context, is_rhs)) - .collect::>()?, - _ => Vec::new(), - }; - if !slot.contains_list() { - Ok(GritNodeArg::new( - slot.index(), - nodes_list - .pop() - .unwrap_or(Pattern::Dynamic(DynamicPattern::Snippet( - DynamicSnippet { - parts: vec![DynamicSnippetPart::String(String::new())], - }, - ))), - )) - } else if nodes_list.len() == 1 - && matches!( - nodes_list.first(), - Some(Pattern::Variable(_) | Pattern::Underscore) - ) - { - Ok(GritNodeArg::new(slot.index(), nodes_list.pop().unwrap())) - } else { - Ok(GritNodeArg::new( - slot.index(), - Pattern::List(Box::new(List::new(nodes_list))), - )) - } - }) - .collect::>()?; - Ok(Pattern::AstNode(Box::new(GritNodePattern { kind, args }))) - } - node_to_pattern(node, context_range, &range_map, context, is_rhs) - } -} - -fn implicit_metavariable_regex( - token: &GritTargetToken, - context_range: ByteRange, - range_map: &BTreeMap, - context: &mut NodeCompilationContext, -) -> Result>, CompileError> { - let source = token.text(); - let capture_string = "(.*)"; - let uncapture_string = ".*"; - let variable_regex = context.compilation.lang.replaced_metavariable_regex(); - let mut last = 0; - let mut regex_string = String::new(); - let mut variables: Vec = vec![]; - for m in variable_regex.find_iter(source) { - regex_string.push_str(®ex::escape(&source[last..m.start()])); - let range = ByteRange::new(m.start(), m.end()); - last = range.end; - let name = m.as_str(); - let variable = text_to_var(name, range, context_range, range_map, context)?; - match variable { - SnippetValues::Dots => return Ok(None), - SnippetValues::Underscore => regex_string.push_str(uncapture_string), - SnippetValues::Variable(var) => { - regex_string.push_str(capture_string); - variables.push(var); - } - } - } - - if last < source.len() { - regex_string.push_str(®ex::escape(&source[last..])); - } - let regex = regex_string.to_string(); - let regex = RegexLike::Regex(regex); - Ok(Some(RegexPattern::new(regex, variables))) -} - -fn metavariable_descendent( - node: &GritTargetNode, - context_range: ByteRange, - range_map: &BTreeMap, - context: &mut NodeCompilationContext, - is_rhs: bool, -) -> Result>, CompileError> { - let Some(token) = node.first_token() else { - return Ok(None); - }; - if !context.compilation.lang.is_metavariable(node) { - return Ok(None); - } - - let name = token.text(); - if is_reserved_metavariable(name, Some(&context.compilation.lang)) && !is_rhs { - return Err(CompileError::ReservedMetavariable( - name.trim_start_matches(context.compilation.lang.metavariable_prefix_substitute()) - .to_string(), - )); - } - - let range = node.byte_range(); - text_to_var(name, range, context_range, range_map, context).map(|s| Some(s.into())) -} - -fn metavariable_ranges(node: &GritTargetNode, lang: &GritTargetLanguage) -> Vec { - let cursor = node.walk(); - traverse(cursor, Order::Pre) - .flat_map(|child| { - if lang.is_metavariable(&child) { - vec![child.byte_range()] - } else { - node_sub_variables(&child, lang) - } - }) - .collect() -} - -// assumes that metavariable substitute is 1 byte larger than the original. eg. -// len(µ) = 2 bytes, len($) = 1 byte -fn metavariable_range_mapping( - mut ranges: Vec, - snippet_offset: usize, -) -> BTreeMap { - // assumes metavariable ranges do not enclose one another - ranges.sort_by_key(|r| r.start); - - let mut byte_offset = snippet_offset; - let mut map = BTreeMap::new(); - for range in ranges { - let start_byte = range.start - byte_offset; - if !cfg!(target_arch = "wasm32") { - byte_offset += 1; - } - - let end_byte = range.end - byte_offset; - let new_range = ByteRange::new(start_byte, end_byte); - map.insert(range, new_range); - } - - map -} - -fn node_sub_variables(node: &GritTargetNode, lang: &impl Language) -> Vec { - let mut ranges = vec![]; - if node.has_children() { - return ranges; - } - - let Some(token) = node.first_token() else { - return ranges; - }; - - let source = token.text(); - let variable_regex = lang.replaced_metavariable_regex(); - for m in variable_regex.find_iter(source) { - let var_range = ByteRange::new(m.start(), m.end()); - let start_byte = node.start_byte() as usize; - let end_byte = node.end_byte() as usize; - if var_range.start >= start_byte && var_range.end <= end_byte { - ranges.push(var_range); - } - } - - ranges -} - -enum SnippetValues { - Dots, - Underscore, - Variable(Variable), -} - -impl From for Pattern { - fn from(value: SnippetValues) -> Self { - match value { - SnippetValues::Dots => Pattern::Dots, - SnippetValues::Underscore => Pattern::Underscore, - SnippetValues::Variable(v) => Pattern::Variable(v), - } - } -} - -fn text_to_var( - name: &str, - range: ByteRange, - context_range: ByteRange, - range_map: &BTreeMap, - context: &mut NodeCompilationContext, -) -> Result { - let name = context - .compilation - .lang - .snippet_metavariable_to_grit_metavariable(name) - .ok_or_else(|| CompileError::MetavariableNotFound(name.to_string()))?; - match name { - GritMetaValue::Dots => Ok(SnippetValues::Dots), - GritMetaValue::Underscore => Ok(SnippetValues::Underscore), - GritMetaValue::Variable(name) => { - let range = *range_map - .get(&range) - .ok_or_else(|| CompileError::InvalidMetavariableRange(range))?; - let var = context.register_variable(name, range + context_range.start)?; - Ok(SnippetValues::Variable(var)) - } - } -} diff --git a/crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs b/crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs index a2f4e084eb29..a37e3b7fba8f 100644 --- a/crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs +++ b/crates/biome_grit_patterns/src/pattern_compiler/snippet_compiler.rs @@ -1,17 +1,21 @@ -use super::{compilation_context::NodeCompilationContext, PatternCompiler}; +use super::compilation_context::NodeCompilationContext; use crate::{ grit_code_snippet::GritCodeSnippet, grit_context::GritQueryContext, - grit_target_node::{GritTargetNode, GritTargetSyntaxKind}, - grit_tree::GritTree, - CompileError, + grit_node_patterns::{GritLeafNodePattern, GritNodePattern, GritNodePatternArg}, + grit_target_node::{GritSyntaxSlot, GritTargetNode, GritTargetSyntaxKind}, + grit_tree::GritTargetTree, + CompileError, GritTargetLanguage, }; use grit_pattern_matcher::{ constants::GLOBAL_VARS_SCOPE_INDEX, - pattern::{DynamicPattern, DynamicSnippet, DynamicSnippetPart, Pattern, Variable}, + pattern::{ + is_reserved_metavariable, DynamicPattern, DynamicSnippet, DynamicSnippetPart, List, + Pattern, RegexLike, RegexPattern, Variable, + }, }; -use grit_util::{Ast, AstNode, ByteRange, Language, SnippetTree}; -use std::borrow::Cow; +use grit_util::{traverse, Ast, AstNode, ByteRange, GritMetaValue, Language, Order, SnippetTree}; +use std::{borrow::Cow, collections::BTreeMap}; pub(crate) fn parse_snippet_content( source: &str, @@ -31,8 +35,7 @@ pub(crate) fn parse_snippet_content( if context .compilation .lang - .metavariable_bracket_regex() - .is_match(source) + .matches_bracket_metavariable(source) { return if is_rhs { Ok(Pattern::Dynamic( @@ -46,12 +49,10 @@ pub(crate) fn parse_snippet_content( if context .compilation .lang - .exact_variable_regex() - .is_match(source.trim()) + .matches_exact_metavariable(source.trim()) { return match source.trim() { - "$_" => Ok(Pattern::Underscore), - "^_" => Ok(Pattern::Underscore), + "$_" | "^_" => Ok(Pattern::Underscore), name => { let var = context.register_variable(name.to_owned(), range)?; Ok(Pattern::Variable(var)) @@ -74,10 +75,9 @@ pub(crate) fn parse_snippet_content( let patterns: Vec<(GritTargetSyntaxKind, Pattern)> = snippet_nodes .into_iter() .map(|node| { - Ok(( - node.kind(), - PatternCompiler::from_snippet_node(node, range, context, is_rhs)?, - )) + let range_map = metavariable_range_mapping(&node, &context.compilation.lang); + let pattern = pattern_from_node(&node, range, &range_map, context, is_rhs)?; + Ok((node.kind(), pattern)) }) .collect::>()?; let dynamic_snippet = dynamic_snippet_from_source(source, range, context) @@ -94,15 +94,8 @@ pub(crate) fn dynamic_snippet_from_source( source_range: ByteRange, context: &mut NodeCompilationContext, ) -> Result { - let source_string = raw_source - .replace("\\n", "\n") - .replace("\\$", "$") - .replace("\\^", "^") - .replace("\\`", "`") - .replace("\\\"", "\"") - .replace("\\\\", "\\"); - let source = source_string.as_str(); - let metavariables = split_snippet(source, &context.compilation.lang); + let source = unescape(raw_source); + let metavariables = split_snippet(&source, &context.compilation.lang); let mut parts = Vec::with_capacity(2 * metavariables.len() + 1); let mut last = 0; // Reverse the iterator so we go over the variables in ascending order. @@ -140,16 +133,25 @@ pub(crate) fn dynamic_snippet_from_source( Ok(DynamicSnippet { parts }) } -pub fn nodes_from_trees(indices: &[SnippetTree]) -> Vec { - indices.iter().filter_map(snippet_node_from_tree).collect() +fn nodes_from_trees(snippets: &[SnippetTree]) -> Vec { + snippets.iter().filter_map(node_from_tree).collect() } -fn snippet_node_from_tree(snippet: &SnippetTree) -> Option { +/// Finds the outermost node containing the parsed snippet, but not any snippet +/// context. +/// +/// Snippets get parsed with surrounding _context_ strings. Because of this, the +/// root node of the snippet tree isn't necessarily the root node of the source +/// snippet. Instead, it's the root node of the snippet with surrounding +/// context. This function descends from the root node into the tree, to find +/// the outermost node containing the parsed snippet, while stripping off the +/// part of the tree that resulted from the given context. +fn node_from_tree(snippet: &SnippetTree) -> Option { let mut snippet_root = snippet.tree.root_node(); // find the outermost node with the same index as the snippet - 'outer: while snippet_root.start_byte() < snippet.snippet_start - || snippet_root.end_byte() > snippet.snippet_end + 'outer: while snippet_root.start_byte() <= snippet.snippet_start + || snippet_root.end_byte() >= snippet.snippet_end { let mut has_children = false; for child in snippet_root.clone().children() { @@ -178,7 +180,7 @@ fn snippet_node_from_tree(snippet: &SnippetTree) -> Option snippet.snippet_start || root_end < snippet.snippet_end { @@ -186,14 +188,225 @@ fn snippet_node_from_tree(snippet: &SnippetTree) -> Option, + context: &mut NodeCompilationContext, + is_rhs: bool, +) -> anyhow::Result, CompileError> { + let metavariable = metavariable_descendent(node, context_range, range_map, context, is_rhs)?; + if let Some(metavariable) = metavariable { + return Ok(metavariable); + } + + if !node.has_children() { + let content = node.text(); + let pattern = if let Some(regex_pattern) = context + .compilation + .lang + .matches_replaced_metavariable(content) + .then(|| implicit_metavariable_regex(node, context_range, range_map, context)) + .transpose()? + .flatten() + { + Pattern::Regex(Box::new(regex_pattern)) + } else { + Pattern::AstLeafNode(GritLeafNodePattern::new(node.kind(), content)) + }; + + return Ok(pattern); + } + + let kind = node.kind(); + let args = node + .slots() + .map(|slots| { + // TODO: Implement filtering for disregarded snippet fields. + // Implementing this will make it more convenient to match + // CST nodes without needing to match all the trivia in the + // snippet (if I understand correctly). + slots + .map(|slot| pattern_arg_from_slot(slot, context_range, range_map, context, is_rhs)) + .collect::, CompileError>>() + }) + .transpose()? + .unwrap_or_default(); + + Ok(Pattern::AstNode(Box::new(GritNodePattern { kind, args }))) +} + +fn pattern_arg_from_slot( + slot: GritSyntaxSlot, + context_range: ByteRange, + range_map: &BTreeMap, + context: &mut NodeCompilationContext, + is_rhs: bool, +) -> Result { + if slot.contains_list() { + let mut nodes_list: Vec> = match &slot { + GritSyntaxSlot::Node(node) => node + .children() + .map(|n| pattern_from_node(&n, context_range, range_map, context, is_rhs)) + .collect::>()?, + _ => Vec::new(), + }; + Ok(GritNodePatternArg::new( + slot.index(), + if nodes_list.len() == 1 + && matches!( + nodes_list.first(), + Some(Pattern::Variable(_) | Pattern::Underscore) + ) + { + nodes_list.pop().unwrap() + } else { + Pattern::List(Box::new(List::new(nodes_list))) + }, + )) + } else if let GritSyntaxSlot::Node(node) = slot { + let pattern = pattern_from_node(&node, context_range, range_map, context, is_rhs)?; + Ok(GritNodePatternArg::new(node.index(), pattern)) + } else { + let pattern = Pattern::Dynamic(DynamicPattern::Snippet(DynamicSnippet { + parts: vec![DynamicSnippetPart::String(String::new())], + })); + Ok(GritNodePatternArg::new(slot.index(), pattern)) + } +} + +fn implicit_metavariable_regex( + node: &GritTargetNode, + context_range: ByteRange, + range_map: &BTreeMap, + context: &mut NodeCompilationContext, +) -> Result>, CompileError> { + let source = node.text(); + let capture_string = "(.*)"; + let uncapture_string = ".*"; + let variable_regex = context.compilation.lang.replaced_metavariable_regex(); + let mut last = 0; + let mut regex_string = String::new(); + let mut variables: Vec = vec![]; + for m in variable_regex.find_iter(source) { + regex_string.push_str(®ex::escape(&source[last..m.start()])); + let range = ByteRange::new(m.start(), m.end()); + last = range.end; + let name = m.as_str(); + let variable = text_to_var(name, range, context_range, range_map, context)?; + match variable { + SnippetValues::Dots => return Ok(None), + SnippetValues::Underscore => regex_string.push_str(uncapture_string), + SnippetValues::Variable(var) => { + regex_string.push_str(capture_string); + variables.push(var); + } + } + } + + if last < source.len() { + regex_string.push_str(®ex::escape(&source[last..])); + } + let regex = regex_string.to_string(); + let regex = RegexLike::Regex(regex); + Ok(Some(RegexPattern::new(regex, variables))) +} + +fn metavariable_descendent( + node: &GritTargetNode, + context_range: ByteRange, + range_map: &BTreeMap, + context: &mut NodeCompilationContext, + is_rhs: bool, +) -> Result>, CompileError> { + if !context.compilation.lang.is_metavariable(node) { + return Ok(None); + } + + let name = node.text(); + if is_reserved_metavariable(name, Some(&context.compilation.lang)) && !is_rhs { + return Err(CompileError::ReservedMetavariable( + name.trim_start_matches(context.compilation.lang.metavariable_prefix_substitute()) + .to_string(), + )); + } + + let range = node.byte_range(); + text_to_var(name, range, context_range, range_map, context).map(|s| Some(s.into())) +} + +// assumes that metavariable substitute is 1 byte larger than the original. eg. +// len(µ) = 2 bytes, len($) = 1 byte +fn metavariable_range_mapping( + node: &GritTargetNode, + lang: &GritTargetLanguage, +) -> BTreeMap { + let mut ranges = metavariable_ranges(node, lang); + let snippet_start = node.text().chars().next().unwrap_or_default() as usize; + + // assumes metavariable ranges do not enclose one another + ranges.sort_by_key(|r| r.start); + + let mut byte_offset = snippet_start; + let mut map = BTreeMap::new(); + for range in ranges { + let start_byte = range.start - byte_offset; + if !cfg!(target_arch = "wasm32") { + byte_offset += 1; + } + + let end_byte = range.end - byte_offset; + let new_range = ByteRange::new(start_byte, end_byte); + map.insert(range, new_range); + } + + map +} + +fn metavariable_ranges(node: &GritTargetNode, lang: &GritTargetLanguage) -> Vec { + let cursor = node.walk(); + traverse(cursor, Order::Pre) + .flat_map(|child| { + if lang.is_metavariable(&child) { + vec![child.byte_range()] + } else { + node_sub_variables(&child, lang) + } + }) + .collect() +} + +fn node_sub_variables(node: &GritTargetNode, lang: &GritTargetLanguage) -> Vec { + let mut ranges = Vec::new(); + if node.has_children() { + return ranges; + } + + let source = node.text(); + let variable_regex = lang.replaced_metavariable_regex(); + for m in variable_regex.find_iter(source) { + let var_range = ByteRange::new(m.start(), m.end()); + let start_byte = node.start_byte() as usize; + let end_byte = node.end_byte() as usize; + if var_range.start >= start_byte && var_range.end <= end_byte { + ranges.push(var_range); + } + } + + ranges } /// Takes a snippet with metavariables and returns a list of ranges and the @@ -220,3 +433,314 @@ pub fn split_snippet<'a>(snippet: &'a str, lang: &impl Language) -> Vec<(ByteRan ranges_and_metavars } + +enum SnippetValues { + Dots, + Underscore, + Variable(Variable), +} + +impl From for Pattern { + fn from(value: SnippetValues) -> Self { + match value { + SnippetValues::Dots => Pattern::Dots, + SnippetValues::Underscore => Pattern::Underscore, + SnippetValues::Variable(v) => Pattern::Variable(v), + } + } +} + +fn text_to_var( + name: &str, + range: ByteRange, + context_range: ByteRange, + range_map: &BTreeMap, + context: &mut NodeCompilationContext, +) -> Result { + let name = context + .compilation + .lang + .snippet_metavariable_to_grit_metavariable(name) + .ok_or_else(|| CompileError::MetavariableNotFound(name.to_string()))?; + match name { + GritMetaValue::Dots => Ok(SnippetValues::Dots), + GritMetaValue::Underscore => Ok(SnippetValues::Underscore), + GritMetaValue::Variable(name) => { + let range = *range_map + .get(&range) + .ok_or_else(|| CompileError::InvalidMetavariableRange(range))?; + let var = context.register_variable(name, range + context_range.start)?; + Ok(SnippetValues::Variable(var)) + } + } +} + +fn unescape(raw_string: &str) -> String { + let mut result = String::with_capacity(raw_string.len()); + let mut is_escape = false; + for c in raw_string.chars() { + if is_escape { + result.push(match c { + 'n' => '\n', + c => c, + }); + is_escape = false; + } else if c == '\\' { + is_escape = true; + } else { + result.push(c); + } + } + result +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use super::*; + use crate::{ + grit_js_parser::GritJsParser, pattern_compiler::compilation_context::CompilationContext, + JsTargetLanguage, + }; + use grit_util::Parser; + + #[test] + fn test_node_from_tree() { + let snippet = GritJsParser.parse_snippet("", "console.log('hello')", ""); + let node = node_from_tree(&snippet).expect("no node found"); + let formatted = format!("{node:#?}"); + insta::assert_snapshot!(&formatted, @r###" + GritTargetNode { + node: JsLanguage( + Node( + 0: JS_CALL_EXPRESSION@0..20 + 0: JS_STATIC_MEMBER_EXPRESSION@0..11 + 0: JS_IDENTIFIER_EXPRESSION@0..7 + 0: JS_REFERENCE_IDENTIFIER@0..7 + 0: IDENT@0..7 "console" [] [] + 1: DOT@7..8 "." [] [] + 2: JS_NAME@8..11 + 0: IDENT@8..11 "log" [] [] + 1: (empty) + 2: (empty) + 3: JS_CALL_ARGUMENTS@11..20 + 0: L_PAREN@11..12 "(" [] [] + 1: JS_CALL_ARGUMENT_LIST@12..19 + 0: JS_STRING_LITERAL_EXPRESSION@12..19 + 0: JS_STRING_LITERAL@12..19 "'hello'" [] [] + 2: R_PAREN@19..20 ")" [] [] + , + ), + ), + tree: GritTargetTree { + root: JsLanguage( + Node( + 0: JS_MODULE@0..20 + 0: (empty) + 1: (empty) + 2: JS_DIRECTIVE_LIST@0..0 + 3: JS_MODULE_ITEM_LIST@0..20 + 0: JS_EXPRESSION_STATEMENT@0..20 + 0: JS_CALL_EXPRESSION@0..20 + 0: JS_STATIC_MEMBER_EXPRESSION@0..11 + 0: JS_IDENTIFIER_EXPRESSION@0..7 + 0: JS_REFERENCE_IDENTIFIER@0..7 + 0: IDENT@0..7 "console" [] [] + 1: DOT@7..8 "." [] [] + 2: JS_NAME@8..11 + 0: IDENT@8..11 "log" [] [] + 1: (empty) + 2: (empty) + 3: JS_CALL_ARGUMENTS@11..20 + 0: L_PAREN@11..12 "(" [] [] + 1: JS_CALL_ARGUMENT_LIST@12..19 + 0: JS_STRING_LITERAL_EXPRESSION@12..19 + 0: JS_STRING_LITERAL@12..19 "'hello'" [] [] + 2: R_PAREN@19..20 ")" [] [] + 1: (empty) + 4: EOF@20..20 "" [] [] + , + ), + ), + source: "console.log('hello')", + }, + } + "###); + } + + #[test] + fn test_pattern_from_node() { + let compilation_context = CompilationContext::new( + Path::new("test.js"), + GritTargetLanguage::JsTargetLanguage(JsTargetLanguage), + ); + let mut vars = BTreeMap::new(); + let mut vars_array = Vec::new(); + let mut global_vars = BTreeMap::new(); + let mut diagnostics = Vec::new(); + let mut context = NodeCompilationContext::new( + &compilation_context, + &mut vars, + &mut vars_array, + &mut global_vars, + &mut diagnostics, + ); + + let snippet_source = "console.log('hello')"; + let snippet = GritJsParser.parse_snippet("", snippet_source, ""); + let node = node_from_tree(&snippet).expect("no node found"); + let range = ByteRange::new(0, snippet_source.len()); + let range_map = metavariable_range_mapping(&node, &context.compilation.lang); + let pattern = pattern_from_node(&node, range, &range_map, &mut context, false) + .expect("cannot compile pattern from node"); + let formatted = format!("{pattern:#?}"); + insta::assert_snapshot!(&formatted, @r###" + AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JS_CALL_EXPRESSION, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JS_STATIC_MEMBER_EXPRESSION, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JS_IDENTIFIER_EXPRESSION, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + JS_REFERENCE_IDENTIFIER, + ), + text: "console", + }, + ), + }, + ], + }, + ), + }, + GritNodePatternArg { + slot_index: 1, + pattern: AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + DOT, + ), + text: ".", + }, + ), + }, + GritNodePatternArg { + slot_index: 2, + pattern: AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + JS_NAME, + ), + text: "log", + }, + ), + }, + ], + }, + ), + }, + GritNodePatternArg { + slot_index: 1, + pattern: Dynamic( + Snippet( + DynamicSnippet { + parts: [ + String( + "", + ), + ], + }, + ), + ), + }, + GritNodePatternArg { + slot_index: 2, + pattern: Dynamic( + Snippet( + DynamicSnippet { + parts: [ + String( + "", + ), + ], + }, + ), + ), + }, + GritNodePatternArg { + slot_index: 3, + pattern: AstNode( + GritNodePattern { + kind: JsSyntaxKind( + JS_CALL_ARGUMENTS, + ), + args: [ + GritNodePatternArg { + slot_index: 0, + pattern: AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + L_PAREN, + ), + text: "(", + }, + ), + }, + GritNodePatternArg { + slot_index: 1, + pattern: List( + List { + patterns: [ + AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + JS_STRING_LITERAL_EXPRESSION, + ), + text: "'hello'", + }, + ), + ], + }, + ), + }, + GritNodePatternArg { + slot_index: 2, + pattern: AstLeafNode( + GritLeafNodePattern { + kind: JsSyntaxKind( + R_PAREN, + ), + text: ")", + }, + ), + }, + ], + }, + ), + }, + ], + }, + ) + "###); + } +} diff --git a/crates/biome_grit_patterns/src/util.rs b/crates/biome_grit_patterns/src/util.rs index 0733bbd4bcb1..ff86ffa7ca2c 100644 --- a/crates/biome_grit_patterns/src/util.rs +++ b/crates/biome_grit_patterns/src/util.rs @@ -1,12 +1,18 @@ use biome_rowan::TextRange; -use grit_util::ByteRange; +use grit_util::{ByteRange, CodeRange}; pub trait TextRangeGritExt { fn to_byte_range(&self) -> ByteRange; + + fn to_code_range(&self, source: &str) -> CodeRange; } impl TextRangeGritExt for TextRange { fn to_byte_range(&self) -> ByteRange { ByteRange::new(self.start().into(), self.end().into()) } + + fn to_code_range(&self, source: &str) -> CodeRange { + CodeRange::new(self.start().into(), self.end().into(), source) + } } diff --git a/crates/biome_grit_patterns/tests/quick_test.rs b/crates/biome_grit_patterns/tests/quick_test.rs index d6c1a7740626..a1fce4af2210 100644 --- a/crates/biome_grit_patterns/tests/quick_test.rs +++ b/crates/biome_grit_patterns/tests/quick_test.rs @@ -1,12 +1,12 @@ use biome_grit_parser::parse_grit; -use biome_grit_patterns::{GritQuery, GritTargetLanguage, GritTree, JsTargetLanguage}; +use biome_grit_patterns::{GritQuery, GritTargetLanguage, GritTargetTree, JsTargetLanguage}; use biome_js_parser::{parse_module, JsParserOptions}; -// Use this test to quickly execute a Grit query against an source snippet. +// Use this test to quickly execute a Grit query against a source snippet. #[ignore] #[test] fn test_query() { - let parse_grit_result = parse_grit("`console.log('hello')`"); + let parse_grit_result = parse_grit("`console.log('hello')` => `doei()`"); if !parse_grit_result.diagnostics().is_empty() { println!( "Diagnostics from parsing query:\n{:?}", @@ -20,6 +20,12 @@ fn test_query() { ) .expect("could not construct query"); + if !query.diagnostics.is_empty() { + println!("Diagnostics from compiling query:\n{:?}", query.diagnostics); + } + + println!("Query pattern: {:#?}", query.pattern); + let parse_js_result = parse_module( r#" function hello() { @@ -36,7 +42,8 @@ function hello() { ); } - query - .execute(&GritTree::new(parse_js_result.syntax().into())) - .expect("could not execute query"); + let tree = GritTargetTree::new(parse_js_result.syntax().into()); + let effects = query.execute(&tree).expect("could not execute query"); + + println!("Effects: {effects:?}"); }