From da0ca59e10b024839c2a80c43b1c70f9bfb95c11 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Mon, 13 May 2024 20:46:45 +0200 Subject: [PATCH 001/138] refactor: split? --- packages/language-facts/README.md | 3 + packages/language-facts/package.json | 46 + .../src}/builtinData.ts | 121 +- packages/language-facts/src/colors.ts | 311 +++ packages/language-facts/src/entry.ts | 97 + .../src}/facts.ts | 0 packages/language-facts/tsconfig.json | 15 + packages/language-facts/vitest.config.mts | 10 + packages/parser/README.md | 3 + packages/parser/package.json | 47 + packages/parser/src/cssErrors.ts | 142 ++ .../src/parser => parser/src}/cssNodes.ts | 75 +- .../src/cssParser.test.ts} | 1232 +++++++-- .../src/parser => parser/src}/cssParser.ts | 407 ++- .../src/parser => parser/src}/cssScanner.ts | 68 +- .../parser => parser/src}/cssSymbolScope.ts | 160 +- packages/parser/src/parser.ts | 4 + .../src/parser => parser/src}/scssErrors.ts | 13 +- packages/parser/src/scssParser.test.ts | 2243 +++++++++++++++++ .../src/parser => parser/src}/scssParser.ts | 240 +- .../src/parser => parser/src}/scssScanner.ts | 3 - packages/parser/src/utils/arrays.ts | 43 + packages/parser/src/utils/objects.ts | 12 + packages/parser/src/utils/resources.ts | 14 + packages/parser/src/utils/strings.ts | 111 + packages/parser/tsconfig.json | 15 + packages/parser/vitest.config.mts | 10 + .../src/cssLanguageTypes.ts | 1 - .../src/languageFacts/colors.ts | 645 ----- .../src/languageFacts/dataManager.ts | 119 - .../src/languageFacts/dataProvider.ts | 90 - .../src/languageFacts/entry.ts | 213 -- .../src/parser/cssErrors.ts | 58 - .../src/services/cssCompletion.ts | 2 +- .../src/test/scss/parser.test.ts | 1156 --------- vitest.workspace.js | 8 + 36 files changed, 4988 insertions(+), 2749 deletions(-) create mode 100644 packages/language-facts/README.md create mode 100644 packages/language-facts/package.json rename packages/{vscode-css-languageservice/src/languageFacts => language-facts/src}/builtinData.ts (69%) create mode 100644 packages/language-facts/src/colors.ts create mode 100644 packages/language-facts/src/entry.ts rename packages/{vscode-css-languageservice/src/languageFacts => language-facts/src}/facts.ts (100%) create mode 100644 packages/language-facts/tsconfig.json create mode 100644 packages/language-facts/vitest.config.mts create mode 100644 packages/parser/README.md create mode 100644 packages/parser/package.json create mode 100644 packages/parser/src/cssErrors.ts rename packages/{vscode-css-languageservice/src/parser => parser/src}/cssNodes.ts (96%) rename packages/{vscode-css-languageservice/src/test/css/parser.test.ts => parser/src/cssParser.test.ts} (54%) rename packages/{vscode-css-languageservice/src/parser => parser/src}/cssParser.ts (87%) rename packages/{vscode-css-languageservice/src/parser => parser/src}/cssScanner.ts (92%) rename packages/{vscode-css-languageservice/src/parser => parser/src}/cssSymbolScope.ts (73%) create mode 100644 packages/parser/src/parser.ts rename packages/{vscode-css-languageservice/src/parser => parser/src}/scssErrors.ts (78%) create mode 100644 packages/parser/src/scssParser.test.ts rename packages/{vscode-css-languageservice/src/parser => parser/src}/scssParser.ts (85%) rename packages/{vscode-css-languageservice/src/parser => parser/src}/scssScanner.ts (98%) create mode 100644 packages/parser/src/utils/arrays.ts create mode 100644 packages/parser/src/utils/objects.ts create mode 100644 packages/parser/src/utils/resources.ts create mode 100644 packages/parser/src/utils/strings.ts create mode 100644 packages/parser/tsconfig.json create mode 100644 packages/parser/vitest.config.mts delete mode 100644 packages/vscode-css-languageservice/src/languageFacts/colors.ts delete mode 100644 packages/vscode-css-languageservice/src/languageFacts/dataManager.ts delete mode 100644 packages/vscode-css-languageservice/src/languageFacts/dataProvider.ts delete mode 100644 packages/vscode-css-languageservice/src/languageFacts/entry.ts delete mode 100644 packages/vscode-css-languageservice/src/parser/cssErrors.ts delete mode 100644 packages/vscode-css-languageservice/src/test/scss/parser.test.ts create mode 100644 vitest.workspace.js diff --git a/packages/language-facts/README.md b/packages/language-facts/README.md new file mode 100644 index 00000000..b3953486 --- /dev/null +++ b/packages/language-facts/README.md @@ -0,0 +1,3 @@ +# @somesass/parser + +See [Server architecture](https://wkillerud.github.io/some-sass/contributing/architecture.html#server-architecture) for information. diff --git a/packages/language-facts/package.json b/packages/language-facts/package.json new file mode 100644 index 00000000..33b636e3 --- /dev/null +++ b/packages/language-facts/package.json @@ -0,0 +1,46 @@ +{ + "name": "@somesass/language-facts", + "version": "1.0.0", + "private": true, + "keywords": [ + "scss", + "sass" + ], + "engines": { + "node": ">=20" + }, + "homepage": "https://github.com/wkillerud/some-sass/blob/main/packages/language-facts#readme", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/wkillerud/some-sass.git" + }, + "bugs": { + "url": "https://github.com/wkillerud/some-sass/issues" + }, + "files": [ + "dist/", + "!dist/test/", + "!dist/**/*.test.js" + ], + "main": "dist/facts.js", + "types": "dist/facts.d.ts", + "author": "William Killerud (https://www.williamkillerud.com/)", + "license": "MIT", + "scripts": { + "build": "tsc", + "clean": "shx rm -rf dist", + "test": "vitest", + "coverage": "vitest run --coverage" + }, + "dependencies": { + "@vscode/l10n": "0.0.18", + "vscode-languageserver-textdocument": "1.0.11", + "vscode-uri": "3.0.8" + }, + "devDependencies": { + "@vitest/coverage-v8": "1.5.3", + "shx": "0.3.4", + "typescript": "5.4.5", + "vitest": "1.5.3" + } +} diff --git a/packages/vscode-css-languageservice/src/languageFacts/builtinData.ts b/packages/language-facts/src/builtinData.ts similarity index 69% rename from packages/vscode-css-languageservice/src/languageFacts/builtinData.ts rename to packages/language-facts/src/builtinData.ts index 9a54916c..99b7ccaa 100644 --- a/packages/vscode-css-languageservice/src/languageFacts/builtinData.ts +++ b/packages/language-facts/src/builtinData.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -"use strict"; export const positionKeywords: { [name: string]: string } = { bottom: @@ -17,7 +16,8 @@ export const positionKeywords: { [name: string]: string } = { export const repeatStyleKeywords: { [name: string]: string } = { "no-repeat": "Placed once and not repeated in this direction.", - repeat: "Repeated in this direction as often as needed to cover the background painting area.", + repeat: + "Repeated in this direction as often as needed to cover the background painting area.", "repeat-x": "Computes to ‘repeat no-repeat’.", "repeat-y": "Computes to ‘no-repeat repeat’.", round: @@ -33,9 +33,11 @@ export const lineStyleKeywords: { [name: string]: string } = { groove: "Looks as if it were carved in the canvas.", hidden: "Same as ‘none’, but has different behavior in the border conflict resolution rules for border-collapsed tables.", - inset: "Looks as if the content on the inside of the border is sunken into the canvas.", + inset: + "Looks as if the content on the inside of the border is sunken into the canvas.", none: "No border. Color and width are ignored.", - outset: "Looks as if the content on the inside of the border is coming out of the canvas.", + outset: + "Looks as if the content on the inside of the border is coming out of the canvas.", ridge: "Looks as if it were coming out of the canvas.", solid: "A single line segment.", }; @@ -44,8 +46,10 @@ export const lineWidthKeywords = ["medium", "thick", "thin"]; export const boxKeywords: { [name: string]: string } = { "border-box": "The background is painted within (clipped to) the border box.", - "content-box": "The background is painted within (clipped to) the content box.", - "padding-box": "The background is painted within (clipped to) the padding box.", + "content-box": + "The background is painted within (clipped to) the content box.", + "padding-box": + "The background is painted within (clipped to) the padding box.", }; export const geometryBoxKeywords: { [name: string]: string } = { @@ -57,41 +61,58 @@ export const geometryBoxKeywords: { [name: string]: string } = { export const cssWideKeywords: { [name: string]: string } = { initial: "Represents the value specified as the property’s initial value.", - inherit: "Represents the computed value of the property on the element’s parent.", - unset: "Acts as either `inherit` or `initial`, depending on whether the property is inherited or not.", + inherit: + "Represents the computed value of the property on the element’s parent.", + unset: + "Acts as either `inherit` or `initial`, depending on whether the property is inherited or not.", }; export const cssWideFunctions: { [name: string]: string } = { "var()": "Evaluates the value of a custom variable.", - "calc()": "Evaluates an mathematical expression. The following operators can be used: + - * /.", + "calc()": + "Evaluates an mathematical expression. The following operators can be used: + - * /.", }; export const imageFunctions: { [name: string]: string } = { "url()": "Reference an image file by URL", "image()": "Provide image fallbacks and annotations.", - "-webkit-image-set()": "Provide multiple resolutions. Remember to use unprefixed image-set() in addition.", + "-webkit-image-set()": + "Provide multiple resolutions. Remember to use unprefixed image-set() in addition.", "image-set()": "Provide multiple resolutions of an image and const the UA decide which is most appropriate in a given situation.", - "-moz-element()": "Use an element in the document as an image. Remember to use unprefixed element() in addition.", + "-moz-element()": + "Use an element in the document as an image. Remember to use unprefixed element() in addition.", "element()": "Use an element in the document as an image.", - "cross-fade()": "Indicates the two images to be combined and how far along in the transition the combination is.", - "-webkit-gradient()": "Deprecated. Use modern linear-gradient() or radial-gradient() instead.", - "-webkit-linear-gradient()": "Linear gradient. Remember to use unprefixed version in addition.", - "-moz-linear-gradient()": "Linear gradient. Remember to use unprefixed version in addition.", - "-o-linear-gradient()": "Linear gradient. Remember to use unprefixed version in addition.", + "cross-fade()": + "Indicates the two images to be combined and how far along in the transition the combination is.", + "-webkit-gradient()": + "Deprecated. Use modern linear-gradient() or radial-gradient() instead.", + "-webkit-linear-gradient()": + "Linear gradient. Remember to use unprefixed version in addition.", + "-moz-linear-gradient()": + "Linear gradient. Remember to use unprefixed version in addition.", + "-o-linear-gradient()": + "Linear gradient. Remember to use unprefixed version in addition.", "linear-gradient()": "A linear gradient is created by specifying a straight gradient line, and then several colors placed along that line.", - "-webkit-repeating-linear-gradient()": "Repeating Linear gradient. Remember to use unprefixed version in addition.", - "-moz-repeating-linear-gradient()": "Repeating Linear gradient. Remember to use unprefixed version in addition.", - "-o-repeating-linear-gradient()": "Repeating Linear gradient. Remember to use unprefixed version in addition.", + "-webkit-repeating-linear-gradient()": + "Repeating Linear gradient. Remember to use unprefixed version in addition.", + "-moz-repeating-linear-gradient()": + "Repeating Linear gradient. Remember to use unprefixed version in addition.", + "-o-repeating-linear-gradient()": + "Repeating Linear gradient. Remember to use unprefixed version in addition.", "repeating-linear-gradient()": "Same as linear-gradient, except the color-stops are repeated infinitely in both directions, with their positions shifted by multiples of the difference between the last specified color-stop’s position and the first specified color-stop’s position.", - "-webkit-radial-gradient()": "Radial gradient. Remember to use unprefixed version in addition.", - "-moz-radial-gradient()": "Radial gradient. Remember to use unprefixed version in addition.", + "-webkit-radial-gradient()": + "Radial gradient. Remember to use unprefixed version in addition.", + "-moz-radial-gradient()": + "Radial gradient. Remember to use unprefixed version in addition.", "radial-gradient()": "Colors emerge from a single point and smoothly spread outward in a circular or elliptical shape.", - "-webkit-repeating-radial-gradient()": "Repeating radial gradient. Remember to use unprefixed version in addition.", - "-moz-repeating-radial-gradient()": "Repeating radial gradient. Remember to use unprefixed version in addition.", + "-webkit-repeating-radial-gradient()": + "Repeating radial gradient. Remember to use unprefixed version in addition.", + "-moz-repeating-radial-gradient()": + "Repeating radial gradient. Remember to use unprefixed version in addition.", "repeating-radial-gradient()": "Same as radial-gradient, except the color-stops are repeated infinitely in both directions, with their positions shifted by multiples of the difference between the last specified color-stop’s position and the first specified color-stop’s position.", }; @@ -111,27 +132,45 @@ export const transitionTimingFunctions: { [name: string]: string } = { "cubic-bezier(0.6, -0.28, 0.735, 0.045)": "Ease-in Back. Overshoots.", "cubic-bezier(0.68, -0.55, 0.265, 1.55)": "Ease-in-out Back. Overshoots.", "cubic-bezier(0.175, 0.885, 0.32, 1.275)": "Ease-out Back. Overshoots.", - "cubic-bezier(0.6, 0.04, 0.98, 0.335)": "Ease-in Circular. Based on half circle.", - "cubic-bezier(0.785, 0.135, 0.15, 0.86)": "Ease-in-out Circular. Based on half circle.", - "cubic-bezier(0.075, 0.82, 0.165, 1)": "Ease-out Circular. Based on half circle.", - "cubic-bezier(0.55, 0.055, 0.675, 0.19)": "Ease-in Cubic. Based on power of three.", - "cubic-bezier(0.645, 0.045, 0.355, 1)": "Ease-in-out Cubic. Based on power of three.", - "cubic-bezier(0.215, 0.610, 0.355, 1)": "Ease-out Cubic. Based on power of three.", - "cubic-bezier(0.95, 0.05, 0.795, 0.035)": "Ease-in Exponential. Based on two to the power ten.", - "cubic-bezier(1, 0, 0, 1)": "Ease-in-out Exponential. Based on two to the power ten.", - "cubic-bezier(0.19, 1, 0.22, 1)": "Ease-out Exponential. Based on two to the power ten.", + "cubic-bezier(0.6, 0.04, 0.98, 0.335)": + "Ease-in Circular. Based on half circle.", + "cubic-bezier(0.785, 0.135, 0.15, 0.86)": + "Ease-in-out Circular. Based on half circle.", + "cubic-bezier(0.075, 0.82, 0.165, 1)": + "Ease-out Circular. Based on half circle.", + "cubic-bezier(0.55, 0.055, 0.675, 0.19)": + "Ease-in Cubic. Based on power of three.", + "cubic-bezier(0.645, 0.045, 0.355, 1)": + "Ease-in-out Cubic. Based on power of three.", + "cubic-bezier(0.215, 0.610, 0.355, 1)": + "Ease-out Cubic. Based on power of three.", + "cubic-bezier(0.95, 0.05, 0.795, 0.035)": + "Ease-in Exponential. Based on two to the power ten.", + "cubic-bezier(1, 0, 0, 1)": + "Ease-in-out Exponential. Based on two to the power ten.", + "cubic-bezier(0.19, 1, 0.22, 1)": + "Ease-out Exponential. Based on two to the power ten.", "cubic-bezier(0.47, 0, 0.745, 0.715)": "Ease-in Sine.", "cubic-bezier(0.445, 0.05, 0.55, 0.95)": "Ease-in-out Sine.", "cubic-bezier(0.39, 0.575, 0.565, 1)": "Ease-out Sine.", - "cubic-bezier(0.55, 0.085, 0.68, 0.53)": "Ease-in Quadratic. Based on power of two.", - "cubic-bezier(0.455, 0.03, 0.515, 0.955)": "Ease-in-out Quadratic. Based on power of two.", - "cubic-bezier(0.25, 0.46, 0.45, 0.94)": "Ease-out Quadratic. Based on power of two.", - "cubic-bezier(0.895, 0.03, 0.685, 0.22)": "Ease-in Quartic. Based on power of four.", - "cubic-bezier(0.77, 0, 0.175, 1)": "Ease-in-out Quartic. Based on power of four.", - "cubic-bezier(0.165, 0.84, 0.44, 1)": "Ease-out Quartic. Based on power of four.", - "cubic-bezier(0.755, 0.05, 0.855, 0.06)": "Ease-in Quintic. Based on power of five.", - "cubic-bezier(0.86, 0, 0.07, 1)": "Ease-in-out Quintic. Based on power of five.", - "cubic-bezier(0.23, 1, 0.320, 1)": "Ease-out Quintic. Based on power of five.", + "cubic-bezier(0.55, 0.085, 0.68, 0.53)": + "Ease-in Quadratic. Based on power of two.", + "cubic-bezier(0.455, 0.03, 0.515, 0.955)": + "Ease-in-out Quadratic. Based on power of two.", + "cubic-bezier(0.25, 0.46, 0.45, 0.94)": + "Ease-out Quadratic. Based on power of two.", + "cubic-bezier(0.895, 0.03, 0.685, 0.22)": + "Ease-in Quartic. Based on power of four.", + "cubic-bezier(0.77, 0, 0.175, 1)": + "Ease-in-out Quartic. Based on power of four.", + "cubic-bezier(0.165, 0.84, 0.44, 1)": + "Ease-out Quartic. Based on power of four.", + "cubic-bezier(0.755, 0.05, 0.855, 0.06)": + "Ease-in Quintic. Based on power of five.", + "cubic-bezier(0.86, 0, 0.07, 1)": + "Ease-in-out Quintic. Based on power of five.", + "cubic-bezier(0.23, 1, 0.320, 1)": + "Ease-out Quintic. Based on power of five.", }; export const basicShapeFunctions: { [name: string]: string } = { diff --git a/packages/language-facts/src/colors.ts b/packages/language-facts/src/colors.ts new file mode 100644 index 00000000..649c3fcf --- /dev/null +++ b/packages/language-facts/src/colors.ts @@ -0,0 +1,311 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as l10n from "@vscode/l10n"; + +export const colorFunctions = [ + { + label: "rgb", + func: "rgb($red, $green, $blue)", + insertText: "rgb(${1:red}, ${2:green}, ${3:blue})", + desc: l10n.t("Creates a Color from red, green, and blue values."), + }, + { + label: "rgba", + func: "rgba($red, $green, $blue, $alpha)", + insertText: "rgba(${1:red}, ${2:green}, ${3:blue}, ${4:alpha})", + desc: l10n.t("Creates a Color from red, green, blue, and alpha values."), + }, + { + label: "rgb relative", + func: "rgb(from $color $red $green $blue)", + insertText: "rgb(from ${1:color} ${2:r} ${3:g} ${4:b})", + desc: l10n.t( + "Creates a Color from the red, green, and blue values of another Color.", + ), + }, + { + label: "hsl", + func: "hsl($hue, $saturation, $lightness)", + insertText: "hsl(${1:hue}, ${2:saturation}, ${3:lightness})", + desc: l10n.t("Creates a Color from hue, saturation, and lightness values."), + }, + { + label: "hsla", + func: "hsla($hue, $saturation, $lightness, $alpha)", + insertText: "hsla(${1:hue}, ${2:saturation}, ${3:lightness}, ${4:alpha})", + desc: l10n.t( + "Creates a Color from hue, saturation, lightness, and alpha values.", + ), + }, + { + label: "hsl relative", + func: "hsl(from $color $hue $saturation $lightness)", + insertText: "hsl(from ${1:color} ${2:h} ${3:s} ${4:l})", + desc: l10n.t( + "Creates a Color from the hue, saturation, and lightness values of another Color.", + ), + }, + { + label: "hwb", + func: "hwb($hue $white $black)", + insertText: "hwb(${1:hue} ${2:white} ${3:black})", + desc: l10n.t("Creates a Color from hue, white, and black values."), + }, + { + label: "hwb relative", + func: "hwb(from $color $hue $white $black)", + insertText: "hwb(from ${1:color} ${2:h} ${3:w} ${4:b})", + desc: l10n.t( + "Creates a Color from the hue, white, and black values of another Color.", + ), + }, + { + label: "lab", + func: "lab($lightness $a $b)", + insertText: "lab(${1:lightness} ${2:a} ${3:b})", + desc: l10n.t("Creates a Color from lightness, a, and b values."), + }, + { + label: "lab relative", + func: "lab(from $color $lightness $a $b)", + insertText: "lab(from ${1:color} ${2:l} ${3:a} ${4:b})", + desc: l10n.t( + "Creates a Color from the lightness, a, and b values of another Color.", + ), + }, + { + label: "oklab", + func: "oklab($lightness $a $b)", + insertText: "oklab(${1:lightness} ${2:a} ${3:b})", + desc: l10n.t("Creates a Color from lightness, a, and b values."), + }, + { + label: "oklab relative", + func: "oklab(from $color $lightness $a $b)", + insertText: "oklab(from ${1:color} ${2:l} ${3:a} ${4:b})", + desc: l10n.t( + "Creates a Color from the lightness, a, and b values of another Color.", + ), + }, + { + label: "lch", + func: "lch($lightness $chroma $hue)", + insertText: "lch(${1:lightness} ${2:chroma} ${3:hue})", + desc: l10n.t("Creates a Color from lightness, chroma, and hue values."), + }, + { + label: "lch relative", + func: "lch(from $color $lightness $chroma $hue)", + insertText: "lch(from ${1:color} ${2:l} ${3:c} ${4:h})", + desc: l10n.t( + "Creates a Color from the lightness, chroma, and hue values of another Color.", + ), + }, + { + label: "oklch", + func: "oklch($lightness $chroma $hue)", + insertText: "oklch(${1:lightness} ${2:chroma} ${3:hue})", + desc: l10n.t("Creates a Color from lightness, chroma, and hue values."), + }, + { + label: "oklch relative", + func: "oklch(from $color $lightness $chroma $hue)", + insertText: "oklch(from ${1:color} ${2:l} ${3:c} ${4:h})", + desc: l10n.t( + "Creates a Color from the lightness, chroma, and hue values of another Color.", + ), + }, + { + label: "color", + func: "color($color-space $red $green $blue)", + insertText: + "color(${1|srgb,srgb-linear,display-p3,a98-rgb,prophoto-rgb,rec2020,xyx,xyz-d50,xyz-d65|} ${2:red} ${3:green} ${4:blue})", + desc: l10n.t( + "Creates a Color in a specific color space from red, green, and blue values.", + ), + }, + { + label: "color relative", + func: "color(from $color $color-space $red $green $blue)", + insertText: + "color(from ${1:color} ${2|srgb,srgb-linear,display-p3,a98-rgb,prophoto-rgb,rec2020,xyx,xyz-d50,xyz-d65|} ${3:r} ${4:g} ${5:b})", + desc: l10n.t( + "Creates a Color in a specific color space from the red, green, and blue values of another Color.", + ), + }, + { + label: "color-mix", + func: "color-mix(in $color-space, $color $percentage, $color $percentage)", + insertText: + "color-mix(in ${1|srgb,srgb-linear,lab,oklab,xyz,xyz-d50,xyz-d65|}, ${3:color} ${4:percentage}, ${5:color} ${6:percentage})", + desc: l10n.t("Mix two colors together in a rectangular color space."), + }, + { + label: "color-mix hue", + func: "color-mix(in $color-space $interpolation-method hue, $color $percentage, $color $percentage)", + insertText: + "color-mix(in ${1|hsl,hwb,lch,oklch|} ${2|shorter hue,longer hue,increasing hue,decreasing hue|}, ${3:color} ${4:percentage}, ${5:color} ${6:percentage})", + desc: l10n.t("Mix two colors together in a polar color space."), + }, +]; + +export const colors: { [name: string]: string } = { + aliceblue: "#f0f8ff", + antiquewhite: "#faebd7", + aqua: "#00ffff", + aquamarine: "#7fffd4", + azure: "#f0ffff", + beige: "#f5f5dc", + bisque: "#ffe4c4", + black: "#000000", + blanchedalmond: "#ffebcd", + blue: "#0000ff", + blueviolet: "#8a2be2", + brown: "#a52a2a", + burlywood: "#deb887", + cadetblue: "#5f9ea0", + chartreuse: "#7fff00", + chocolate: "#d2691e", + coral: "#ff7f50", + cornflowerblue: "#6495ed", + cornsilk: "#fff8dc", + crimson: "#dc143c", + cyan: "#00ffff", + darkblue: "#00008b", + darkcyan: "#008b8b", + darkgoldenrod: "#b8860b", + darkgray: "#a9a9a9", + darkgrey: "#a9a9a9", + darkgreen: "#006400", + darkkhaki: "#bdb76b", + darkmagenta: "#8b008b", + darkolivegreen: "#556b2f", + darkorange: "#ff8c00", + darkorchid: "#9932cc", + darkred: "#8b0000", + darksalmon: "#e9967a", + darkseagreen: "#8fbc8f", + darkslateblue: "#483d8b", + darkslategray: "#2f4f4f", + darkslategrey: "#2f4f4f", + darkturquoise: "#00ced1", + darkviolet: "#9400d3", + deeppink: "#ff1493", + deepskyblue: "#00bfff", + dimgray: "#696969", + dimgrey: "#696969", + dodgerblue: "#1e90ff", + firebrick: "#b22222", + floralwhite: "#fffaf0", + forestgreen: "#228b22", + fuchsia: "#ff00ff", + gainsboro: "#dcdcdc", + ghostwhite: "#f8f8ff", + gold: "#ffd700", + goldenrod: "#daa520", + gray: "#808080", + grey: "#808080", + green: "#008000", + greenyellow: "#adff2f", + honeydew: "#f0fff0", + hotpink: "#ff69b4", + indianred: "#cd5c5c", + indigo: "#4b0082", + ivory: "#fffff0", + khaki: "#f0e68c", + lavender: "#e6e6fa", + lavenderblush: "#fff0f5", + lawngreen: "#7cfc00", + lemonchiffon: "#fffacd", + lightblue: "#add8e6", + lightcoral: "#f08080", + lightcyan: "#e0ffff", + lightgoldenrodyellow: "#fafad2", + lightgray: "#d3d3d3", + lightgrey: "#d3d3d3", + lightgreen: "#90ee90", + lightpink: "#ffb6c1", + lightsalmon: "#ffa07a", + lightseagreen: "#20b2aa", + lightskyblue: "#87cefa", + lightslategray: "#778899", + lightslategrey: "#778899", + lightsteelblue: "#b0c4de", + lightyellow: "#ffffe0", + lime: "#00ff00", + limegreen: "#32cd32", + linen: "#faf0e6", + magenta: "#ff00ff", + maroon: "#800000", + mediumaquamarine: "#66cdaa", + mediumblue: "#0000cd", + mediumorchid: "#ba55d3", + mediumpurple: "#9370d8", + mediumseagreen: "#3cb371", + mediumslateblue: "#7b68ee", + mediumspringgreen: "#00fa9a", + mediumturquoise: "#48d1cc", + mediumvioletred: "#c71585", + midnightblue: "#191970", + mintcream: "#f5fffa", + mistyrose: "#ffe4e1", + moccasin: "#ffe4b5", + navajowhite: "#ffdead", + navy: "#000080", + oldlace: "#fdf5e6", + olive: "#808000", + olivedrab: "#6b8e23", + orange: "#ffa500", + orangered: "#ff4500", + orchid: "#da70d6", + palegoldenrod: "#eee8aa", + palegreen: "#98fb98", + paleturquoise: "#afeeee", + palevioletred: "#d87093", + papayawhip: "#ffefd5", + peachpuff: "#ffdab9", + peru: "#cd853f", + pink: "#ffc0cb", + plum: "#dda0dd", + powderblue: "#b0e0e6", + purple: "#800080", + red: "#ff0000", + rebeccapurple: "#663399", + rosybrown: "#bc8f8f", + royalblue: "#4169e1", + saddlebrown: "#8b4513", + salmon: "#fa8072", + sandybrown: "#f4a460", + seagreen: "#2e8b57", + seashell: "#fff5ee", + sienna: "#a0522d", + silver: "#c0c0c0", + skyblue: "#87ceeb", + slateblue: "#6a5acd", + slategray: "#708090", + slategrey: "#708090", + snow: "#fffafa", + springgreen: "#00ff7f", + steelblue: "#4682b4", + tan: "#d2b48c", + teal: "#008080", + thistle: "#d8bfd8", + tomato: "#ff6347", + turquoise: "#40e0d0", + violet: "#ee82ee", + wheat: "#f5deb3", + white: "#ffffff", + whitesmoke: "#f5f5f5", + yellow: "#ffff00", + yellowgreen: "#9acd32", +}; + +export const colorKeywords: { [name: string]: string } = { + currentColor: + "The value of the 'color' property. The computed value of the 'currentColor' keyword is the computed value of the 'color' property. If the 'currentColor' keyword is set on the 'color' property itself, it is treated as 'color:inherit' at parse time.", + transparent: + "Fully transparent. This keyword can be considered a shorthand for rgba(0,0,0,0) which is its computed value.", +}; diff --git a/packages/language-facts/src/entry.ts b/packages/language-facts/src/entry.ts new file mode 100644 index 00000000..c27efb30 --- /dev/null +++ b/packages/language-facts/src/entry.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { + IPropertyData, + IAtDirectiveData, + IPseudoClassData, + IPseudoElementData, + IValueData, + MarkupContent, + MarkupKind, + MarkedString, + HoverSettings, +} from "vscode-languageserver-types"; + +export type EntryStatus = + | "standard" + | "experimental" + | "nonstandard" + | "obsolete"; + +export interface Browsers { + E?: string; + FF?: string; + IE?: string; + O?: string; + C?: string; + S?: string; + count: number; + all: boolean; + onCodeComplete: boolean; +} + +export const browserNames = { + E: "Edge", + FF: "Firefox", + S: "Safari", + C: "Chrome", + IE: "IE", + O: "Opera", +}; + +function getEntryStatus(status: EntryStatus) { + switch (status) { + case "experimental": + return "⚠️ Property is experimental. Be cautious when using it.️\n\n"; + case "nonstandard": + return "🚨️ Property is nonstandard. Avoid using it.\n\n"; + case "obsolete": + return "🚨️️️ Property is obsolete. Avoid using it.\n\n"; + default: + return ""; + } +} + +/** + * Input is like `["E12","FF49","C47","IE","O"]` + * Output is like `Edge 12, Firefox 49, Chrome 47, IE, Opera` + */ +export function getBrowserLabel(browsers: string[] = []): string | null { + if (browsers.length === 0) { + return null; + } + + return browsers + .map((b) => { + let result = ""; + const matches = b.match(/([A-Z]+)(\d+)?/)!; + + const name = matches[1]; + const version = matches[2]; + + if (name in browserNames) { + result += browserNames[name as keyof typeof browserNames]; + } + if (version) { + result += " " + version; + } + return result; + }) + .join(", "); +} + +export type IEntry = + | IPropertyData + | IAtDirectiveData + | IPseudoClassData + | IPseudoElementData + | IValueData; + +export interface IValue { + name: string; + description?: string | MarkupContent; + browsers?: string[]; +} diff --git a/packages/vscode-css-languageservice/src/languageFacts/facts.ts b/packages/language-facts/src/facts.ts similarity index 100% rename from packages/vscode-css-languageservice/src/languageFacts/facts.ts rename to packages/language-facts/src/facts.ts diff --git a/packages/language-facts/tsconfig.json b/packages/language-facts/tsconfig.json new file mode 100644 index 00000000..2b0655a9 --- /dev/null +++ b/packages/language-facts/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es2020", + "lib": ["ES2020", "WebWorker"], + "sourceMap": true, + "module": "commonjs", + "moduleResolution": "node", + "declaration": true, + "rootDir": "src", + "outDir": "dist", + "strict": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/language-facts/vitest.config.mts b/packages/language-facts/vitest.config.mts new file mode 100644 index 00000000..e2df9da5 --- /dev/null +++ b/packages/language-facts/vitest.config.mts @@ -0,0 +1,10 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + coverage: { + provider: "v8", + reporter: ["text", "json", "html"], + }, + }, +}); diff --git a/packages/parser/README.md b/packages/parser/README.md new file mode 100644 index 00000000..b3953486 --- /dev/null +++ b/packages/parser/README.md @@ -0,0 +1,3 @@ +# @somesass/parser + +See [Server architecture](https://wkillerud.github.io/some-sass/contributing/architecture.html#server-architecture) for information. diff --git a/packages/parser/package.json b/packages/parser/package.json new file mode 100644 index 00000000..2e42fa17 --- /dev/null +++ b/packages/parser/package.json @@ -0,0 +1,47 @@ +{ + "name": "@somesass/parser", + "version": "1.0.0", + "private": true, + "description": "The parser powering some-sass-language-server", + "keywords": [ + "scss", + "sass" + ], + "engines": { + "node": ">=20" + }, + "homepage": "https://github.com/wkillerud/some-sass/blob/main/packages/parser#readme", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/wkillerud/some-sass.git" + }, + "bugs": { + "url": "https://github.com/wkillerud/some-sass/issues" + }, + "files": [ + "dist/", + "!dist/test/", + "!dist/**/*.test.js" + ], + "main": "dist/parser.js", + "types": "dist/parser.d.ts", + "author": "William Killerud (https://www.williamkillerud.com/)", + "license": "MIT", + "scripts": { + "build": "tsc", + "clean": "shx rm -rf dist", + "test": "vitest", + "coverage": "vitest run --coverage" + }, + "dependencies": { + "@vscode/l10n": "0.0.18", + "vscode-languageserver-textdocument": "1.0.11", + "vscode-uri": "3.0.8" + }, + "devDependencies": { + "@vitest/coverage-v8": "1.5.3", + "shx": "0.3.4", + "typescript": "5.4.5", + "vitest": "1.5.3" + } +} diff --git a/packages/parser/src/cssErrors.ts b/packages/parser/src/cssErrors.ts new file mode 100644 index 00000000..a9cb26be --- /dev/null +++ b/packages/parser/src/cssErrors.ts @@ -0,0 +1,142 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as l10n from "@vscode/l10n"; +import * as nodes from "./cssNodes"; + +export class CSSIssueType implements nodes.IRule { + id: string; + message: string; + + public constructor(id: string, message: string) { + this.id = id; + this.message = message; + } +} + +export const ParseError = { + NumberExpected: new CSSIssueType( + "css-numberexpected", + l10n.t("number expected"), + ), + ConditionExpected: new CSSIssueType( + "css-conditionexpected", + l10n.t("condition expected"), + ), + RuleOrSelectorExpected: new CSSIssueType( + "css-ruleorselectorexpected", + l10n.t("at-rule or selector expected"), + ), + DotExpected: new CSSIssueType("css-dotexpected", l10n.t("dot expected")), + ColonExpected: new CSSIssueType( + "css-colonexpected", + l10n.t("colon expected"), + ), + SemiColonExpected: new CSSIssueType( + "css-semicolonexpected", + l10n.t("semi-colon expected"), + ), + TermExpected: new CSSIssueType("css-termexpected", l10n.t("term expected")), + ExpressionExpected: new CSSIssueType( + "css-expressionexpected", + l10n.t("expression expected"), + ), + OperatorExpected: new CSSIssueType( + "css-operatorexpected", + l10n.t("operator expected"), + ), + IdentifierExpected: new CSSIssueType( + "css-identifierexpected", + l10n.t("identifier expected"), + ), + PercentageExpected: new CSSIssueType( + "css-percentageexpected", + l10n.t("percentage expected"), + ), + URIOrStringExpected: new CSSIssueType( + "css-uriorstringexpected", + l10n.t("uri or string expected"), + ), + URIExpected: new CSSIssueType("css-uriexpected", l10n.t("URI expected")), + VariableNameExpected: new CSSIssueType( + "css-varnameexpected", + l10n.t("variable name expected"), + ), + VariableValueExpected: new CSSIssueType( + "css-varvalueexpected", + l10n.t("variable value expected"), + ), + PropertyValueExpected: new CSSIssueType( + "css-propertyvalueexpected", + l10n.t("property value expected"), + ), + LeftCurlyExpected: new CSSIssueType( + "css-lcurlyexpected", + l10n.t("{ expected"), + ), + RightCurlyExpected: new CSSIssueType( + "css-rcurlyexpected", + l10n.t("} expected"), + ), + LeftSquareBracketExpected: new CSSIssueType( + "css-rbracketexpected", + l10n.t("[ expected"), + ), + RightSquareBracketExpected: new CSSIssueType( + "css-lbracketexpected", + l10n.t("] expected"), + ), + LeftParenthesisExpected: new CSSIssueType( + "css-lparentexpected", + l10n.t("( expected"), + ), + RightParenthesisExpected: new CSSIssueType( + "css-rparentexpected", + l10n.t(") expected"), + ), + CommaExpected: new CSSIssueType( + "css-commaexpected", + l10n.t("comma expected"), + ), + PageDirectiveOrDeclarationExpected: new CSSIssueType( + "css-pagedirordeclexpected", + l10n.t("page directive or declaraton expected"), + ), + UnknownAtRule: new CSSIssueType( + "css-unknownatrule", + l10n.t("at-rule unknown"), + ), + UnknownKeyword: new CSSIssueType( + "css-unknownkeyword", + l10n.t("unknown keyword"), + ), + SelectorExpected: new CSSIssueType( + "css-selectorexpected", + l10n.t("selector expected"), + ), + StringLiteralExpected: new CSSIssueType( + "css-stringliteralexpected", + l10n.t("string literal expected"), + ), + WhitespaceExpected: new CSSIssueType( + "css-whitespaceexpected", + l10n.t("whitespace expected"), + ), + MediaQueryExpected: new CSSIssueType( + "css-mediaqueryexpected", + l10n.t("media query expected"), + ), + IdentifierOrWildcardExpected: new CSSIssueType( + "css-idorwildcardexpected", + l10n.t("identifier or wildcard expected"), + ), + WildcardExpected: new CSSIssueType( + "css-wildcardexpected", + l10n.t("wildcard expected"), + ), + IdentifierOrVariableExpected: new CSSIssueType( + "css-idorvarexpected", + l10n.t("identifier or variable expected"), + ), +}; diff --git a/packages/vscode-css-languageservice/src/parser/cssNodes.ts b/packages/parser/src/cssNodes.ts similarity index 96% rename from packages/vscode-css-languageservice/src/parser/cssNodes.ts rename to packages/parser/src/cssNodes.ts index c57de927..aa7e5c99 100644 --- a/packages/vscode-css-languageservice/src/parser/cssNodes.ts +++ b/packages/parser/src/cssNodes.ts @@ -1,10 +1,11 @@ +/* eslint-disable no-prototype-builtins */ +/* eslint-disable prefer-spread */ +/* eslint-disable @typescript-eslint/no-this-alias */ /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -"use strict"; - -import { trim } from "../utils/strings"; +import { trim } from "./utils/strings"; /// /// Nodes for the css 2.1 specification. See for reference: @@ -217,15 +218,24 @@ export class Node { } public matches(str: string): boolean { - return this.length === str.length && this.getTextProvider()(this.offset, this.length) === str; + return ( + this.length === str.length && + this.getTextProvider()(this.offset, this.length) === str + ); } public startsWith(str: string): boolean { - return this.length >= str.length && this.getTextProvider()(this.offset, str.length) === str; + return ( + this.length >= str.length && + this.getTextProvider()(this.offset, str.length) === str + ); } public endsWith(str: string): boolean { - return this.length >= str.length && this.getTextProvider()(this.end - str.length, str.length) === str; + return ( + this.length >= str.length && + this.getTextProvider()(this.end - str.length, str.length) === str + ); } public accept(visitor: IVisitorFunction): void { @@ -281,17 +291,28 @@ export class Node { } public hasIssue(rule: IRule): boolean { - return Array.isArray(this.issues) && this.issues.some((i) => i.getRule() === rule); + return ( + Array.isArray(this.issues) && + this.issues.some((i) => i.getRule() === rule) + ); } public isErroneous(recursive: boolean = false): boolean { if (this.issues && this.issues.length > 0) { return true; } - return recursive && Array.isArray(this.children) && this.children.some((c) => c.isErroneous(true)); - } - - public setNode(field: keyof this, node: Node | null, index: number = -1): boolean { + return ( + recursive && + Array.isArray(this.children) && + this.children.some((c) => c.isErroneous(true)) + ); + } + + public setNode( + field: keyof this, + node: Node | null, + index: number = -1, + ): boolean { if (node) { node.attachTo(this, index); (this)[field] = node; @@ -369,7 +390,10 @@ export class Node { } public encloses(candidate: Node): boolean { - return this.offset <= candidate.offset && this.offset + this.length >= candidate.offset + candidate.length; + return ( + this.offset <= candidate.offset && + this.offset + this.length >= candidate.offset + candidate.length + ); } public getParent(): Node | null { @@ -529,7 +553,9 @@ export class RuleSet extends BodyDeclaration { } public isNested(): boolean { - return !!this.parent && this.parent.findParent(NodeType.Declarations) !== null; + return ( + !!this.parent && this.parent.findParent(NodeType.Declarations) !== null + ); } } @@ -624,7 +650,10 @@ export class Declaration extends AbstractDeclaration { public getFullPropertyName(): string { const propertyName = this.property ? this.property.getName() : "unknown"; - if (this.parent instanceof Declarations && this.parent.getParent() instanceof NestedProperties) { + if ( + this.parent instanceof Declarations && + this.parent.getParent() instanceof NestedProperties + ) { const parentDecl = this.parent.getParent()!.getParent(); if (parentDecl instanceof Declaration) { return (parentDecl).getFullPropertyName() + propertyName; @@ -652,7 +681,9 @@ export class Declaration extends AbstractDeclaration { return this.value; } - public setNestedProperties(value: NestedProperties | null): value is NestedProperties { + public setNestedProperties( + value: NestedProperties | null, + ): value is NestedProperties { return this.setNode("nestedProperties", value); } @@ -672,7 +703,9 @@ export class CustomPropertyDeclaration extends Declaration { return NodeType.CustomPropertyDeclaration; } - public setPropertySet(value: CustomPropertySet | null): value is CustomPropertySet { + public setPropertySet( + value: CustomPropertySet | null, + ): value is CustomPropertySet { return this.setNode("propertySet", value); } @@ -700,7 +733,7 @@ export class Property extends Node { } public getName(): string { - return trim(this.getText(), /[_\+]+$/); /* +_: less merge */ + return trim(this.getText(), /[_+]+$/); /* +_: less merge */ } public isCustomProperty(): boolean { @@ -833,7 +866,9 @@ export class IfStatement extends BodyDeclaration { return this.setNode("expression", node, 0); } - public setElseClause(elseClause: BodyDeclaration | null): elseClause is BodyDeclaration { + public setElseClause( + elseClause: BodyDeclaration | null, + ): elseClause is BodyDeclaration { return this.setNode("elseClause", elseClause); } } @@ -1639,7 +1674,9 @@ export class MixinReference extends Node { return this.arguments; } - public setContent(node: MixinContentDeclaration | null): node is MixinContentDeclaration { + public setContent( + node: MixinContentDeclaration | null, + ): node is MixinContentDeclaration { return this.setNode("content", node); } diff --git a/packages/vscode-css-languageservice/src/test/css/parser.test.ts b/packages/parser/src/cssParser.test.ts similarity index 54% rename from packages/vscode-css-languageservice/src/test/css/parser.test.ts rename to packages/parser/src/cssParser.test.ts index c87943f7..a6e4b090 100644 --- a/packages/vscode-css-languageservice/src/test/css/parser.test.ts +++ b/packages/parser/src/cssParser.test.ts @@ -2,15 +2,18 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -"use strict"; +import * as assert from "node:assert"; +import { suite, test } from "vitest"; +import { ParseError } from "./cssErrors.js"; +import * as nodes from "./cssNodes.js"; +import { Parser } from "./cssParser.js"; +import { TokenType } from "./cssScanner.js"; -import * as assert from "assert"; -import { Parser } from "../../parser/cssParser"; -import { TokenType } from "../../parser/cssScanner"; -import * as nodes from "../../parser/cssNodes"; -import { ParseError } from "../../parser/cssErrors"; - -export function assertNode(text: string, parser: Parser, f: (...args: any[]) => nodes.Node | null): nodes.Node { +export function assertNode( + text: string, + parser: Parser, + f: (...args: any[]) => nodes.Node | null, +): nodes.Node { const node = parser.internalParse(text, f)!; assert.ok(node !== null, "no node returned"); const markers = nodes.ParseErrorCollector.entries(node); @@ -29,16 +32,29 @@ export function assertNode(text: string, parser: Parser, f: (...args: any[]) => return node; } -export function assertFunction(text: string, parser: Parser, f: () => nodes.Node | null): void { +export function assertFunction( + text: string, + parser: Parser, + f: () => nodes.Node | null, +): void { assertNode(text, parser, f); } -export function assertNoNode(text: string, parser: Parser, f: () => nodes.Node | null): void { +export function assertNoNode( + text: string, + parser: Parser, + f: () => nodes.Node | null, +): void { const node = parser.internalParse(text, f)!; assert.ok(node === null || !parser.accept(TokenType.EOF)); } -export function assertError(text: string, parser: Parser, f: () => nodes.Node | null, error: nodes.IRule): void { +export function assertError( + text: string, + parser: Parser, + f: () => nodes.Node | null, + error: nodes.IRule, +): void { const node = parser.internalParse(text, f)!; assert.ok(node !== null, "no node returned"); let markers = nodes.ParseErrorCollector.entries(node); @@ -48,36 +64,84 @@ export function assertError(text: string, parser: Parser, f: () => nodes.Node | markers = markers.sort((a, b) => { return a.getOffset() - b.getOffset(); }); - assert.equal(markers[0].getRule().id, error.id, "incorrect error returned from parsing: " + text); + assert.equal( + markers[0].getRule().id, + error.id, + "incorrect error returned from parsing: " + text, + ); } } suite("CSS - Parser", () => { test("stylesheet", function () { const parser = new Parser(); - assertNode('@charset "demo" ;', parser, parser._parseStylesheet.bind(parser)); - assertNode("body { margin: 0px; padding: 3em, 6em; }", parser, parser._parseStylesheet.bind(parser)); + assertNode( + '@charset "demo" ;', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "body { margin: 0px; padding: 3em, 6em; }", + parser, + parser._parseStylesheet.bind(parser), + ); assertNode("--> @import "string"; ', parser, parser._parseStylesheet.bind(parser)); - assertNode("@media asdsa { } ", parser, parser._parseStylesheet.bind(parser)); - assertNode("@media screen, projection { }", parser, parser._parseStylesheet.bind(parser)); + assertNode( + ' @import "string"; ', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media asdsa { } ", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media screen, projection { }", + parser, + parser._parseStylesheet.bind(parser), + ); assertNode( "@media screen and (max-width: 400px) { @-ms-viewport { width: 320px; }}", parser, parser._parseStylesheet.bind(parser), ); - assertNode("@-ms-viewport { width: 320px; height: 768px; }", parser, parser._parseStylesheet.bind(parser)); - assertNode("#boo, far {} \n.far boo {}", parser, parser._parseStylesheet.bind(parser)); + assertNode( + "@-ms-viewport { width: 320px; height: 768px; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "#boo, far {} \n.far boo {}", + parser, + parser._parseStylesheet.bind(parser), + ); assertNode( "@-moz-keyframes darkWordHighlight { from { background-color: inherit; } to { background-color: rgba(83, 83, 83, 0.7); } }", parser, parser._parseStylesheet.bind(parser), ); - assertNode("@page { margin: 2.5cm; }", parser, parser._parseStylesheet.bind(parser)); - assertNode('@font-face { font-family: "Example Font"; }', parser, parser._parseStylesheet.bind(parser)); - assertNode('@namespace "http://www.w3.org/1999/xhtml";', parser, parser._parseStylesheet.bind(parser)); - assertNode("@namespace pref url(http://test);", parser, parser._parseStylesheet.bind(parser)); + assertNode( + "@page { margin: 2.5cm; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@font-face { font-family: "Example Font"; }', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@namespace "http://www.w3.org/1999/xhtml";', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@namespace pref url(http://test);", + parser, + parser._parseStylesheet.bind(parser), + ); assertNode( "@-moz-document url(http://test), url-prefix(http://www.w3.org/Style/) { body { color: purple; background: yellow; } }", parser, @@ -88,7 +152,11 @@ suite("CSS - Parser", () => { parser, parser._parseStylesheet.bind(parser), ); - assertNode('input[type="submit"] {}', parser, parser._parseStylesheet.bind(parser)); + assertNode( + 'input[type="submit"] {}', + parser, + parser._parseStylesheet.bind(parser), + ); assertNode( "E:root E:nth-child(n) E:nth-last-child(n) E:nth-of-type(n) E:nth-last-of-type(n) E:first-child E:last-child {}", parser, @@ -99,33 +167,77 @@ suite("CSS - Parser", () => { parser, parser._parseStylesheet.bind(parser), ); - assertNode("E::first-line E::first-letter E::before E::after {}", parser, parser._parseStylesheet.bind(parser)); - assertNode("E.warning E#myid E:not(s) {}", parser, parser._parseStylesheet.bind(parser)); - assertError("@namespace;", parser, parser._parseStylesheet.bind(parser), ParseError.URIExpected); + assertNode( + "E::first-line E::first-letter E::before E::after {}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "E.warning E#myid E:not(s) {}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertError( + "@namespace;", + parser, + parser._parseStylesheet.bind(parser), + ParseError.URIExpected, + ); assertError( "@namespace url(http://test)", parser, parser._parseStylesheet.bind(parser), ParseError.SemiColonExpected, ); - assertError("@charset;", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); - assertError("@charset 'utf8'", parser, parser._parseStylesheet.bind(parser), ParseError.SemiColonExpected); + assertError( + "@charset;", + parser, + parser._parseStylesheet.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + "@charset 'utf8'", + parser, + parser._parseStylesheet.bind(parser), + ParseError.SemiColonExpected, + ); }); test("stylesheet - graceful handling of unknown rules", function () { const parser = new Parser(); assertNode("@unknown-rule;", parser, parser._parseStylesheet.bind(parser)); - assertNode(`@unknown-rule 'foo';`, parser, parser._parseStylesheet.bind(parser)); - assertNode("@unknown-rule (foo) {}", parser, parser._parseStylesheet.bind(parser)); - assertNode("@unknown-rule (foo) { .bar {} }", parser, parser._parseStylesheet.bind(parser)); + assertNode( + `@unknown-rule 'foo';`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@unknown-rule (foo) {}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@unknown-rule (foo) { .bar {} }", + parser, + parser._parseStylesheet.bind(parser), + ); assertNode( "@mskeyframes darkWordHighlight { from { background-color: inherit; } to { background-color: rgba(83, 83, 83, 0.7); } }", parser, parser._parseStylesheet.bind(parser), ); - assertNode("foo { @unknown-rule; }", parser, parser._parseStylesheet.bind(parser)); + assertNode( + "foo { @unknown-rule; }", + parser, + parser._parseStylesheet.bind(parser), + ); - assertError("@unknown-rule (;", parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected); + assertError( + "@unknown-rule (;", + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); assertError( "@unknown-rule [foo", parser, @@ -138,7 +250,12 @@ suite("CSS - Parser", () => { parser._parseStylesheet.bind(parser), ParseError.RightSquareBracketExpected, ); - assertError("@unknown-rule (foo) {", parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected); + assertError( + "@unknown-rule (foo) {", + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightCurlyExpected, + ); assertError( "@unknown-rule (foo) { .bar {}", parser, @@ -149,7 +266,11 @@ suite("CSS - Parser", () => { test("stylesheet - unknown rules node ends properly. Microsoft/vscode#53159", function () { const parser = new Parser(); - const node = assertNode("@unknown-rule (foo) {} .foo {}", parser, parser._parseStylesheet.bind(parser)); + const node = assertNode( + "@unknown-rule (foo) {} .foo {}", + parser, + parser._parseStylesheet.bind(parser), + ); const unknownAtRule = node.getChild(0)!; assert.equal(unknownAtRule.type, nodes.NodeType.UnknownAtRule); @@ -178,15 +299,32 @@ suite("CSS - Parser", () => { parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected, ); - assertError('- @import "foo";', parser, parser._parseStylesheet.bind(parser), ParseError.RuleOrSelectorExpected); + assertError( + '- @import "foo";', + parser, + parser._parseStylesheet.bind(parser), + ParseError.RuleOrSelectorExpected, + ); }); test("@font-face", function () { const parser = new Parser(); assertNode("@font-face {}", parser, parser._parseFontFace.bind(parser)); - assertNode("@font-face { src: url(http://test) }", parser, parser._parseFontFace.bind(parser)); - assertNode("@font-face { font-style: normal; font-stretch: normal; }", parser, parser._parseFontFace.bind(parser)); - assertNode("@font-face { unicode-range: U+0021-007F }", parser, parser._parseFontFace.bind(parser)); + assertNode( + "@font-face { src: url(http://test) }", + parser, + parser._parseFontFace.bind(parser), + ); + assertNode( + "@font-face { font-style: normal; font-stretch: normal; }", + parser, + parser._parseFontFace.bind(parser), + ); + assertNode( + "@font-face { unicode-range: U+0021-007F }", + parser, + parser._parseFontFace.bind(parser), + ); assertError( "@font-face { font-style: normal font-stretch: normal; }", parser, @@ -201,30 +339,110 @@ suite("CSS - Parser", () => { assertNode("to {}", parser, parser._parseKeyframeSelector.bind(parser)); assertNode("0% {}", parser, parser._parseKeyframeSelector.bind(parser)); assertNode("10% {}", parser, parser._parseKeyframeSelector.bind(parser)); - assertNode("cover 10% {}", parser, parser._parseKeyframeSelector.bind(parser)); - assertNode("100000% {}", parser, parser._parseKeyframeSelector.bind(parser)); - assertNode("from { width: 100% }", parser, parser._parseKeyframeSelector.bind(parser)); - assertNode("from { width: 100%; to: 10px; }", parser, parser._parseKeyframeSelector.bind(parser)); - assertNode("from, to { width: 10px; }", parser, parser._parseKeyframeSelector.bind(parser)); - assertNode("10%, to { width: 10px; }", parser, parser._parseKeyframeSelector.bind(parser)); - assertNode("from, 20% { width: 10px; }", parser, parser._parseKeyframeSelector.bind(parser)); - assertNode("10%, 20% { width: 10px; }", parser, parser._parseKeyframeSelector.bind(parser)); - assertNode("cover 10% {}", parser, parser._parseKeyframeSelector.bind(parser)); - assertNode("cover 10%, exit 20% {}", parser, parser._parseKeyframeSelector.bind(parser)); - assertNode("10%, exit 20% {}", parser, parser._parseKeyframeSelector.bind(parser)); - assertNode("from, exit 20% {}", parser, parser._parseKeyframeSelector.bind(parser)); - assertNode("cover 10%, to {}", parser, parser._parseKeyframeSelector.bind(parser)); - assertNode("cover 10%, 20% {}", parser, parser._parseKeyframeSelector.bind(parser)); + assertNode( + "cover 10% {}", + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + "100000% {}", + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + "from { width: 100% }", + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + "from { width: 100%; to: 10px; }", + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + "from, to { width: 10px; }", + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + "10%, to { width: 10px; }", + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + "from, 20% { width: 10px; }", + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + "10%, 20% { width: 10px; }", + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + "cover 10% {}", + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + "cover 10%, exit 20% {}", + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + "10%, exit 20% {}", + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + "from, exit 20% {}", + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + "cover 10%, to {}", + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + "cover 10%, 20% {}", + parser, + parser._parseKeyframeSelector.bind(parser), + ); }); test("@keyframe", function () { const parser = new Parser(); - assertNode("@keyframes name {}", parser, parser._parseKeyframe.bind(parser)); - assertNode("@-webkit-keyframes name {}", parser, parser._parseKeyframe.bind(parser)); - assertNode("@-o-keyframes name {}", parser, parser._parseKeyframe.bind(parser)); - assertNode("@-moz-keyframes name {}", parser, parser._parseKeyframe.bind(parser)); - assertNode("@keyframes name { from {} to {}}", parser, parser._parseKeyframe.bind(parser)); - assertNode("@keyframes name { from {} 80% {} 100% {}}", parser, parser._parseKeyframe.bind(parser)); + assertNode( + "@keyframes name {}", + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + "@-webkit-keyframes name {}", + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + "@-o-keyframes name {}", + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + "@-moz-keyframes name {}", + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + "@keyframes name { from {} to {}}", + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + "@keyframes name { from {} 80% {} 100% {}}", + parser, + parser._parseKeyframe.bind(parser), + ); assertNode( "@keyframes name { from { top: 0px; } 80% { top: 100px; } 100% { top: 50px; }}", parser, @@ -251,7 +469,12 @@ suite("CSS - Parser", () => { parser._parseKeyframe.bind(parser), ParseError.SemiColonExpected, ); - assertError("@keyframes )", parser, parser._parseKeyframe.bind(parser), ParseError.IdentifierExpected); + assertError( + "@keyframes )", + parser, + parser._parseKeyframe.bind(parser), + ParseError.IdentifierExpected, + ); assertError( "@keyframes name { { top: 0px; } }", parser, @@ -291,7 +514,12 @@ suite("CSS - Parser", () => { parser, parser._parseStylesheet.bind(parser), ); - assertError(`@property { }`, parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); + assertError( + `@property { }`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.IdentifierExpected, + ); }); test("@container", function () { @@ -326,26 +554,54 @@ suite("CSS - Parser", () => { const parser = new Parser(); assertNode('@import "asdasdsa"', parser, parser._parseImport.bind(parser)); assertNode('@ImPort "asdsadsa"', parser, parser._parseImport.bind(parser)); - assertNode('@import "asdasd" dsfsdf', parser, parser._parseImport.bind(parser)); + assertNode( + '@import "asdasd" dsfsdf', + parser, + parser._parseImport.bind(parser), + ); assertNode('@import "foo";', parser, parser._parseStylesheet.bind(parser)); - assertNode("@import url(/css/screen.css) screen, projection;", parser, parser._parseStylesheet.bind(parser)); + assertNode( + "@import url(/css/screen.css) screen, projection;", + parser, + parser._parseStylesheet.bind(parser), + ); assertNode( "@import url('landscape.css') screen and (orientation:landscape);", parser, parser._parseStylesheet.bind(parser), ); - assertNode('@import url("/inc/Styles/full.css") (min-width: 940px);', parser, parser._parseStylesheet.bind(parser)); - assertNode("@import url(style.css) screen and (min-width:600px);", parser, parser._parseStylesheet.bind(parser)); + assertNode( + '@import url("/inc/Styles/full.css") (min-width: 940px);', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@import url(style.css) screen and (min-width:600px);", + parser, + parser._parseStylesheet.bind(parser), + ); assertNode( '@import url("./700.css") only screen and (max-width: 700px);', parser, parser._parseStylesheet.bind(parser), ); - assertNode('@import url("override.css") layer;', parser, parser._parseStylesheet.bind(parser)); - assertNode('@import url("tabs.css") layer(framework.component);', parser, parser._parseStylesheet.bind(parser)); + assertNode( + '@import url("override.css") layer;', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@import url("tabs.css") layer(framework.component);', + parser, + parser._parseStylesheet.bind(parser), + ); - assertNode('@import "mystyle.css" supports(display: flex);', parser, parser._parseStylesheet.bind(parser)); + assertNode( + '@import "mystyle.css" supports(display: flex);', + parser, + parser._parseStylesheet.bind(parser), + ); assertNode( '@import url("narrow.css") supports(display: flex) handheld and (max-width: 400px);', @@ -358,7 +614,12 @@ suite("CSS - Parser", () => { parser._parseStylesheet.bind(parser), ); - assertError("@import", parser, parser._parseStylesheet.bind(parser), ParseError.URIOrStringExpected); + assertError( + "@import", + parser, + parser._parseStylesheet.bind(parser), + ParseError.URIOrStringExpected, + ); }); test("@supports", function () { @@ -383,8 +644,16 @@ suite("CSS - Parser", () => { parser, parser._parseSupports.bind(parser), ); - assertNode("@supports ((display: flexbox)) { }", parser, parser._parseSupports.bind(parser)); - assertNode("@supports (display: flexbox !important) { }", parser, parser._parseSupports.bind(parser)); + assertNode( + "@supports ((display: flexbox)) { }", + parser, + parser._parseSupports.bind(parser), + ); + assertNode( + "@supports (display: flexbox !important) { }", + parser, + parser._parseSupports.bind(parser), + ); assertNode( "@supports (grid-area: auto) { @media screen and (min-width: 768px) { .me { } } }", parser, @@ -395,7 +664,11 @@ suite("CSS - Parser", () => { parser, parser._parseSupports.bind(parser), ); // #49288 - assertNode("@supports not (--validValue: , 0 ) {}", parser, parser._parseSupports.bind(parser)); // #82178 + assertNode( + "@supports not (--validValue: , 0 ) {}", + parser, + parser._parseSupports.bind(parser), + ); // #82178 assertError( "@supports (transition-property: color) or (animation-name: foo) and (transform: rotate(10deg)) { }", parser, @@ -414,59 +687,150 @@ suite("CSS - Parser", () => { const parser = new Parser(); assertNode("@media asdsa { }", parser, parser._parseMedia.bind(parser)); assertNode("@meDia sadd{} ", parser, parser._parseMedia.bind(parser)); - assertNode("@media somename, othername2 { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media only screen and (max-width:850px) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media only screen and (max-width:850px) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media all and (min-width:500px) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media screen and (color), projection and (color) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media not screen and (device-aspect-ratio: 16/9) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media print and (min-resolution: 300dpi) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media print and (min-resolution: 118dpcm) { }", parser, parser._parseMedia.bind(parser)); assertNode( - "@media print { @page { margin: 10% } blockquote, pre { page-break-inside: avoid } }", + "@media somename, othername2 { }", parser, parser._parseMedia.bind(parser), ); - assertNode("@media print { body:before { } }", parser, parser._parseMedia.bind(parser)); - assertNode("@media not (-moz-os-version: windows-win7) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media not (not (-moz-os-version: windows-win7)) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media (height > 600px) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media (height < 600px) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media (height <= 600px) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media (400px <= width <= 700px) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media (400px >= width >= 700px) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media screen and (750px <= width < 900px) { }", parser, parser._parseMedia.bind(parser)); - assertError( - "@media somename othername2 { }", + assertNode( + "@media only screen and (max-width:850px) { }", parser, parser._parseMedia.bind(parser), - ParseError.LeftCurlyExpected, ); - assertError("@media not, screen { }", parser, parser._parseMedia.bind(parser), ParseError.MediaQueryExpected); - assertError( - "@media not screen and foo { }", + assertNode( + "@media only screen and (max-width:850px) { }", parser, parser._parseMedia.bind(parser), - ParseError.LeftParenthesisExpected, ); - assertError("@media not screen and () { }", parser, parser._parseMedia.bind(parser), ParseError.IdentifierExpected); - assertError("@media not screen and (color:) { }", parser, parser._parseMedia.bind(parser), ParseError.TermExpected); - assertError( - "@media not screen and (color:#234567 { }", + assertNode( + "@media all and (min-width:500px) { }", parser, parser._parseMedia.bind(parser), - ParseError.RightParenthesisExpected, ); - }); - - test("media_list", function () { - const parser = new Parser(); - assertNode("somename", parser, parser._parseMediaQueryList.bind(parser)); - assertNode("somename, othername", parser, parser._parseMediaQueryList.bind(parser)); - assertNode("not all and (monochrome)", parser, parser._parseMediaQueryList.bind(parser)); - }); - - test("medium", function () { + assertNode( + "@media screen and (color), projection and (color) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media not screen and (device-aspect-ratio: 16/9) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media print and (min-resolution: 300dpi) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media print and (min-resolution: 118dpcm) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media print { @page { margin: 10% } blockquote, pre { page-break-inside: avoid } }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media print { body:before { } }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media not (-moz-os-version: windows-win7) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media not (not (-moz-os-version: windows-win7)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media (height > 600px) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media (height < 600px) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media (height <= 600px) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media (400px <= width <= 700px) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media (400px >= width >= 700px) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (750px <= width < 900px) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertError( + "@media somename othername2 { }", + parser, + parser._parseMedia.bind(parser), + ParseError.LeftCurlyExpected, + ); + assertError( + "@media not, screen { }", + parser, + parser._parseMedia.bind(parser), + ParseError.MediaQueryExpected, + ); + assertError( + "@media not screen and foo { }", + parser, + parser._parseMedia.bind(parser), + ParseError.LeftParenthesisExpected, + ); + assertError( + "@media not screen and () { }", + parser, + parser._parseMedia.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + "@media not screen and (color:) { }", + parser, + parser._parseMedia.bind(parser), + ParseError.TermExpected, + ); + assertError( + "@media not screen and (color:#234567 { }", + parser, + parser._parseMedia.bind(parser), + ParseError.RightParenthesisExpected, + ); + }); + + test("media_list", function () { + const parser = new Parser(); + assertNode("somename", parser, parser._parseMediaQueryList.bind(parser)); + assertNode( + "somename, othername", + parser, + parser._parseMediaQueryList.bind(parser), + ); + assertNode( + "not all and (monochrome)", + parser, + parser._parseMediaQueryList.bind(parser), + ); + }); + + test("medium", function () { const parser = new Parser(); assertNode("somename", parser, parser._parseMedium.bind(parser)); assertNode("-asdas", parser, parser._parseMedium.bind(parser)); @@ -476,16 +840,36 @@ suite("CSS - Parser", () => { test("@page", function () { const parser = new Parser(); assertNode("@page : name{ }", parser, parser._parsePage.bind(parser)); - assertNode("@page :left, :right { }", parser, parser._parsePage.bind(parser)); - assertNode('@page : name{ some : "asdas" }', parser, parser._parsePage.bind(parser)); - assertNode('@page : name{ some : "asdas" !important }', parser, parser._parsePage.bind(parser)); + assertNode( + "@page :left, :right { }", + parser, + parser._parsePage.bind(parser), + ); + assertNode( + '@page : name{ some : "asdas" }', + parser, + parser._parsePage.bind(parser), + ); + assertNode( + '@page : name{ some : "asdas" !important }', + parser, + parser._parsePage.bind(parser), + ); assertNode( '@page : name{ some : "asdas" !important; some : "asdas" !important }', parser, parser._parsePage.bind(parser), ); - assertNode("@page rotated { size : landscape }", parser, parser._parsePage.bind(parser)); - assertNode("@page :left { margin-left: 4cm; margin-right: 3cm; }", parser, parser._parsePage.bind(parser)); + assertNode( + "@page rotated { size : landscape }", + parser, + parser._parsePage.bind(parser), + ); + assertNode( + "@page :left { margin-left: 4cm; margin-right: 3cm; }", + parser, + parser._parsePage.bind(parser), + ); assertNode( "@page { @top-right-corner { content: url(foo.png); border: solid green; } }", parser, @@ -509,24 +893,73 @@ suite("CSS - Parser", () => { parser._parsePage.bind(parser), ParseError.SemiColonExpected, ); - assertError("@page : { }", parser, parser._parsePage.bind(parser), ParseError.IdentifierExpected); - assertError("@page :left, { }", parser, parser._parsePage.bind(parser), ParseError.IdentifierExpected); + assertError( + "@page : { }", + parser, + parser._parsePage.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + "@page :left, { }", + parser, + parser._parsePage.bind(parser), + ParseError.IdentifierExpected, + ); }); test("@layer", function () { const parser = new Parser(); - assertNode("@layer utilities { .padding-sm { padding: .5rem; } }", parser, parser._parseLayer.bind(parser)); + assertNode( + "@layer utilities { .padding-sm { padding: .5rem; } }", + parser, + parser._parseLayer.bind(parser), + ); assertNode("@layer utilities;", parser, parser._parseLayer.bind(parser)); - assertNode("@layer theme, layout, utilities;", parser, parser._parseLayer.bind(parser)); - assertNode("@layer utilities { p { margin-block: 1rem; } }", parser, parser._parseLayer.bind(parser)); - assertNode("@layer framework { @layer layout { } }", parser, parser._parseLayer.bind(parser)); - assertNode("@layer framework.layout { @keyframes slide-left {} }", parser, parser._parseLayer.bind(parser)); + assertNode( + "@layer theme, layout, utilities;", + parser, + parser._parseLayer.bind(parser), + ); + assertNode( + "@layer utilities { p { margin-block: 1rem; } }", + parser, + parser._parseLayer.bind(parser), + ); + assertNode( + "@layer framework { @layer layout { } }", + parser, + parser._parseLayer.bind(parser), + ); + assertNode( + "@layer framework.layout { @keyframes slide-left {} }", + parser, + parser._parseLayer.bind(parser), + ); - assertNode("@media (min-width: 30em) { @layer layout { } }", parser, parser._parseStylesheet.bind(parser)); + assertNode( + "@media (min-width: 30em) { @layer layout { } }", + parser, + parser._parseStylesheet.bind(parser), + ); - assertError("@layer theme layout { }", parser, parser._parseLayer.bind(parser), ParseError.SemiColonExpected); - assertError("@layer theme, layout { }", parser, parser._parseLayer.bind(parser), ParseError.SemiColonExpected); - assertError("@layer framework .layout { }", parser, parser._parseLayer.bind(parser), ParseError.SemiColonExpected); + assertError( + "@layer theme layout { }", + parser, + parser._parseLayer.bind(parser), + ParseError.SemiColonExpected, + ); + assertError( + "@layer theme, layout { }", + parser, + parser._parseLayer.bind(parser), + ParseError.SemiColonExpected, + ); + assertError( + "@layer framework .layout { }", + parser, + parser._parseLayer.bind(parser), + ParseError.SemiColonExpected, + ); assertError( "@layer framework. layout { }", parser, @@ -551,7 +984,11 @@ suite("CSS - Parser", () => { assertNode(">", parser, parser._parseCombinator.bind(parser)); assertNode(">>>", parser, parser._parseCombinator.bind(parser)); assertNode("/deep/", parser, parser._parseCombinator.bind(parser)); - assertNode(":host >>> .data-table { width: 100%; }", parser, parser._parseStylesheet.bind(parser)); + assertNode( + ":host >>> .data-table { width: 100%; }", + parser, + parser._parseStylesheet.bind(parser), + ); assertError( ":host >> .data-table { width: 100%; }", parser, @@ -582,41 +1019,120 @@ suite("CSS - Parser", () => { test("ruleset", function () { const parser = new Parser(); assertNode("name{ }", parser, parser._parseRuleset.bind(parser)); - assertNode(' name\n{ some : "asdas" }', parser, parser._parseRuleset.bind(parser)); - assertNode(' name{ some : "asdas" !important }', parser, parser._parseRuleset.bind(parser)); - assertNode('name{ \n some : "asdas" !important; some : "asdas" }', parser, parser._parseRuleset.bind(parser)); + assertNode( + ' name\n{ some : "asdas" }', + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ' name{ some : "asdas" !important }', + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + 'name{ \n some : "asdas" !important; some : "asdas" }', + parser, + parser._parseRuleset.bind(parser), + ); assertNode("* {}", parser, parser._parseRuleset.bind(parser)); assertNode(".far{}", parser, parser._parseRuleset.bind(parser)); assertNode("boo {}", parser, parser._parseRuleset.bind(parser)); assertNode(".far #boo {}", parser, parser._parseRuleset.bind(parser)); - assertNode("boo { prop: value }", parser, parser._parseRuleset.bind(parser)); - assertNode("boo { prop: value; }", parser, parser._parseRuleset.bind(parser)); - assertNode("boo { prop: value; prop: value }", parser, parser._parseRuleset.bind(parser)); - assertNode("boo { prop: value; prop: value; }", parser, parser._parseRuleset.bind(parser)); + assertNode( + "boo { prop: value }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "boo { prop: value; }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "boo { prop: value; prop: value }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "boo { prop: value; prop: value; }", + parser, + parser._parseRuleset.bind(parser), + ); assertNode("boo {--minimal: }", parser, parser._parseRuleset.bind(parser)); assertNode("boo {--minimal: ;}", parser, parser._parseRuleset.bind(parser)); - assertNode("boo {--normal-text: red yellow green}", parser, parser._parseRuleset.bind(parser)); - assertNode("boo {--normal-text: red yellow green;}", parser, parser._parseRuleset.bind(parser)); - assertNode("boo {--important: red !important;}", parser, parser._parseRuleset.bind(parser)); - assertNode("boo {--nested: {color: green;}}", parser, parser._parseRuleset.bind(parser)); - assertNode("boo {--parens: this()is()ok()}", parser, parser._parseRuleset.bind(parser)); - assertNode("boo {--squares: this[]is[]ok[]too[]}", parser, parser._parseRuleset.bind(parser)); - assertNode("boo {--combined: ([{{[]()()}[]{}}])()}", parser, parser._parseRuleset.bind(parser)); + assertNode( + "boo {--normal-text: red yellow green}", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "boo {--normal-text: red yellow green;}", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "boo {--important: red !important;}", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "boo {--nested: {color: green;}}", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "boo {--parens: this()is()ok()}", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "boo {--squares: this[]is[]ok[]too[]}", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "boo {--combined: ([{{[]()()}[]{}}])()}", + parser, + parser._parseRuleset.bind(parser), + ); assertNode( "boo {--weird-inside-delims: {color: green;;;;;;!important;;}}", parser, parser._parseRuleset.bind(parser), ); - assertNode(`boo {--validValue: , 0 0}`, parser, parser._parseRuleset.bind(parser)); - assertNode(`boo {--validValue: , 0 0;}`, parser, parser._parseRuleset.bind(parser)); - assertError("boo, { }", parser, parser._parseRuleset.bind(parser), ParseError.SelectorExpected); + assertNode( + `boo {--validValue: , 0 0}`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `boo {--validValue: , 0 0;}`, + parser, + parser._parseRuleset.bind(parser), + ); + assertError( + "boo, { }", + parser, + parser._parseRuleset.bind(parser), + ParseError.SelectorExpected, + ); }); test("ruleset /Panic/", function () { const parser = new Parser(); // assertNode('boo { : value }', parser, parser._parseRuleset.bind(parser)); - assertError("boo { prop: ; }", parser, parser._parseRuleset.bind(parser), ParseError.PropertyValueExpected); - assertError("boo { prop }", parser, parser._parseRuleset.bind(parser), ParseError.ColonExpected); + assertError( + "boo { prop: ; }", + parser, + parser._parseRuleset.bind(parser), + ParseError.PropertyValueExpected, + ); + assertError( + "boo { prop }", + parser, + parser._parseRuleset.bind(parser), + ParseError.ColonExpected, + ); assertError( "boo { prop: ; far: 12em; }", parser, @@ -625,8 +1141,18 @@ suite("CSS - Parser", () => { ); // assertNode('boo { prop: ; 1ar: 12em; }', parser, parser._parseRuleset.bind(parser)); - assertError("boo { --too-minimal:}", parser, parser._parseRuleset.bind(parser), ParseError.PropertyValueExpected); - assertError("boo { --unterminated: ", parser, parser._parseRuleset.bind(parser), ParseError.RightCurlyExpected); + assertError( + "boo { --too-minimal:}", + parser, + parser._parseRuleset.bind(parser), + ParseError.PropertyValueExpected, + ); + assertError( + "boo { --unterminated: ", + parser, + parser._parseRuleset.bind(parser), + ParseError.RightCurlyExpected, + ); assertError( "boo { --double-important: red !important !important;}", parser, @@ -666,26 +1192,75 @@ suite("CSS - Parser", () => { }); test("nested ruleset", function () { - let parser = new Parser(); - assertNode(".foo { color: red; input { color: blue; } }", parser, parser._parseRuleset.bind(parser)); - assertNode(".foo { color: red; :focus { color: blue; } }", parser, parser._parseRuleset.bind(parser)); - assertNode(".foo { color: red; .bar { color: blue; } }", parser, parser._parseRuleset.bind(parser)); - assertNode(".foo { color: red; &:hover { color: blue; } }", parser, parser._parseRuleset.bind(parser)); - assertNode(".foo { color: red; + .bar { color: blue; } }", parser, parser._parseRuleset.bind(parser)); - assertNode(".foo { color: red; foo:hover { color: blue }; }", parser, parser._parseRuleset.bind(parser)); - assertNode(".foo { color: red; @media screen { color: blue }; }", parser, parser._parseRuleset.bind(parser)); + const parser = new Parser(); + assertNode( + ".foo { color: red; input { color: blue; } }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".foo { color: red; :focus { color: blue; } }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".foo { color: red; .bar { color: blue; } }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".foo { color: red; &:hover { color: blue; } }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".foo { color: red; + .bar { color: blue; } }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".foo { color: red; foo:hover { color: blue }; }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".foo { color: red; @media screen { color: blue }; }", + parser, + parser._parseRuleset.bind(parser), + ); // Top level curly braces are allowed in declaration values if they are for a custom property. - assertNode(".foo { --foo: {}; }", parser, parser._parseRuleset.bind(parser)); + assertNode( + ".foo { --foo: {}; }", + parser, + parser._parseRuleset.bind(parser), + ); // Top level curly braces are not allowed in declaration values. - assertError(".foo { foo: {}; }", parser, parser._parseRuleset.bind(parser), ParseError.PropertyValueExpected); + assertError( + ".foo { foo: {}; }", + parser, + parser._parseRuleset.bind(parser), + ParseError.PropertyValueExpected, + ); }); test("nested ruleset 2", function () { - let parser = new Parser(); - assertNode(".foo { .parent & { color: blue; } }", parser, parser._parseRuleset.bind(parser)); - assertNode(".foo { color: red; & > .bar, > .baz { color: blue; } }", parser, parser._parseRuleset.bind(parser)); - assertNode(".foo { & .bar & .baz & .qux { color: blue; } }", parser, parser._parseRuleset.bind(parser)); + const parser = new Parser(); + assertNode( + ".foo { .parent & { color: blue; } }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".foo { color: red; & > .bar, > .baz { color: blue; } }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".foo { & .bar & .baz & .qux { color: blue; } }", + parser, + parser._parseRuleset.bind(parser), + ); assertNode( ".foo { color: red; :not(&) { color: blue; }; + .bar + & { color: green; } }", parser, @@ -696,7 +1271,11 @@ suite("CSS - Parser", () => { parser, parser._parseRuleset.bind(parser), ); - assertNode(".foo { & :is(.bar, &.baz) { color: red; } }", parser, parser._parseRuleset.bind(parser)); + assertNode( + ".foo { & :is(.bar, &.baz) { color: red; } }", + parser, + parser._parseRuleset.bind(parser), + ); assertNode( "figure { > figcaption { background: hsl(0 0% 0% / 50%); > p { font-size: .9rem; } } }", parser, @@ -713,23 +1292,51 @@ suite("CSS - Parser", () => { const parser = new Parser(); assertNode("asdsa", parser, parser._parseSelector.bind(parser)); assertNode("asdsa + asdas", parser, parser._parseSelector.bind(parser)); - assertNode("asdsa + asdas + name", parser, parser._parseSelector.bind(parser)); - assertNode("asdsa + asdas + name", parser, parser._parseSelector.bind(parser)); - assertNode("name #id#anotherid", parser, parser._parseSelector.bind(parser)); + assertNode( + "asdsa + asdas + name", + parser, + parser._parseSelector.bind(parser), + ); + assertNode( + "asdsa + asdas + name", + parser, + parser._parseSelector.bind(parser), + ); + assertNode( + "name #id#anotherid", + parser, + parser._parseSelector.bind(parser), + ); assertNode("name.far .boo", parser, parser._parseSelector.bind(parser)); - assertNode("name .name .zweitername", parser, parser._parseSelector.bind(parser)); + assertNode( + "name .name .zweitername", + parser, + parser._parseSelector.bind(parser), + ); assertNode("*", parser, parser._parseSelector.bind(parser)); assertNode("#id", parser, parser._parseSelector.bind(parser)); assertNode("far.boo", parser, parser._parseSelector.bind(parser)); - assertNode("::slotted(div)::after", parser, parser._parseSelector.bind(parser)); // 35076 + assertNode( + "::slotted(div)::after", + parser, + parser._parseSelector.bind(parser), + ); // 35076 }); test("simple selector", function () { const parser = new Parser(); assertNode("name", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("#id#anotherid", parser, parser._parseSimpleSelector.bind(parser)); + assertNode( + "#id#anotherid", + parser, + parser._parseSimpleSelector.bind(parser), + ); assertNode("name.far", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("name.erstername.zweitername", parser, parser._parseSimpleSelector.bind(parser)); + assertNode( + "name.erstername.zweitername", + parser, + parser._parseSimpleSelector.bind(parser), + ); }); test("element name", function () { @@ -749,17 +1356,49 @@ suite("CSS - Parser", () => { assertNode("[name ~= name3]", parser, parser._parseAttrib.bind(parser)); assertNode("[name~=name3]", parser, parser._parseAttrib.bind(parser)); assertNode("[name |= name3]", parser, parser._parseAttrib.bind(parser)); - assertNode('[name |= "this is a striiiing"]', parser, parser._parseAttrib.bind(parser)); - assertNode('[href*="insensitive" i]', parser, parser._parseAttrib.bind(parser)); - assertNode('[href*="sensitive" S]', parser, parser._parseAttrib.bind(parser)); + assertNode( + '[name |= "this is a striiiing"]', + parser, + parser._parseAttrib.bind(parser), + ); + assertNode( + '[href*="insensitive" i]', + parser, + parser._parseAttrib.bind(parser), + ); + assertNode( + '[href*="sensitive" S]', + parser, + parser._parseAttrib.bind(parser), + ); // Single namespace assertNode("[namespace|name]", parser, parser._parseAttrib.bind(parser)); - assertNode("[name-space|name = name2]", parser, parser._parseAttrib.bind(parser)); - assertNode("[name_space|name ~= name3]", parser, parser._parseAttrib.bind(parser)); - assertNode("[name0spae|name~=name3]", parser, parser._parseAttrib.bind(parser)); - assertNode('[NameSpace|name |= "this is a striiiing"]', parser, parser._parseAttrib.bind(parser)); - assertNode("[name\\*space|name |= name3]", parser, parser._parseAttrib.bind(parser)); + assertNode( + "[name-space|name = name2]", + parser, + parser._parseAttrib.bind(parser), + ); + assertNode( + "[name_space|name ~= name3]", + parser, + parser._parseAttrib.bind(parser), + ); + assertNode( + "[name0spae|name~=name3]", + parser, + parser._parseAttrib.bind(parser), + ); + assertNode( + '[NameSpace|name |= "this is a striiiing"]', + parser, + parser._parseAttrib.bind(parser), + ); + assertNode( + "[name\\*space|name |= name3]", + parser, + parser._parseAttrib.bind(parser), + ); assertNode("[*|name]", parser, parser._parseAttrib.bind(parser)); }); @@ -771,34 +1410,93 @@ suite("CSS - Parser", () => { assertNode(":nth-child(1n)", parser, parser._parsePseudo.bind(parser)); assertNode(":nth-child(-n+3)", parser, parser._parsePseudo.bind(parser)); assertNode(":nth-child(2n+1)", parser, parser._parsePseudo.bind(parser)); - assertNode(":nth-child(2n+1 of .foo)", parser, parser._parsePseudo.bind(parser)); - assertNode(':nth-child(2n+1 of .foo > bar, :not(*) ~ [other="value"])', parser, parser._parsePseudo.bind(parser)); + assertNode( + ":nth-child(2n+1 of .foo)", + parser, + parser._parsePseudo.bind(parser), + ); + assertNode( + ':nth-child(2n+1 of .foo > bar, :not(*) ~ [other="value"])', + parser, + parser._parsePseudo.bind(parser), + ); assertNode(":lang(it)", parser, parser._parsePseudo.bind(parser)); assertNode(":not(.class)", parser, parser._parsePseudo.bind(parser)); assertNode(":not(:disabled)", parser, parser._parsePseudo.bind(parser)); assertNode(":not(#foo)", parser, parser._parsePseudo.bind(parser)); assertNode("::slotted(*)", parser, parser._parsePseudo.bind(parser)); // #35076 - assertNode("::slotted(div:hover)", parser, parser._parsePseudo.bind(parser)); // #35076 - assertNode(":global(.output ::selection)", parser, parser._parsePseudo.bind(parser)); // #49010 - assertNode(":matches(:hover, :focus)", parser, parser._parsePseudo.bind(parser)); // #49010 - assertNode(":host([foo=bar][bar=foo])", parser, parser._parsePseudo.bind(parser)); // #49589 + assertNode( + "::slotted(div:hover)", + parser, + parser._parsePseudo.bind(parser), + ); // #35076 + assertNode( + ":global(.output ::selection)", + parser, + parser._parsePseudo.bind(parser), + ); // #49010 + assertNode( + ":matches(:hover, :focus)", + parser, + parser._parsePseudo.bind(parser), + ); // #49010 + assertNode( + ":host([foo=bar][bar=foo])", + parser, + parser._parsePseudo.bind(parser), + ); // #49589 assertNode(":has(> .test)", parser, parser._parsePseudo.bind(parser)); // #250 assertNode(":has(~ .test)", parser, parser._parsePseudo.bind(parser)); // #250 assertNode(":has(+ .test)", parser, parser._parsePseudo.bind(parser)); // #250 assertNode(":has(~ div .test)", parser, parser._parsePseudo.bind(parser)); // #250 - assertError("::", parser, parser._parsePseudo.bind(parser), ParseError.IdentifierExpected); - assertError(":: foo", parser, parser._parsePseudo.bind(parser), ParseError.IdentifierExpected); - assertError(":nth-child(1n of)", parser, parser._parsePseudo.bind(parser), ParseError.SelectorExpected); + assertError( + "::", + parser, + parser._parsePseudo.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + ":: foo", + parser, + parser._parsePseudo.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + ":nth-child(1n of)", + parser, + parser._parsePseudo.bind(parser), + ParseError.SelectorExpected, + ); }); test("declaration", function () { const parser = new Parser(); - assertNode('name : "this is a string" !important', parser, parser._parseDeclaration.bind(parser)); - assertNode('name : "this is a string"', parser, parser._parseDeclaration.bind(parser)); + assertNode( + 'name : "this is a string" !important', + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + 'name : "this is a string"', + parser, + parser._parseDeclaration.bind(parser), + ); assertNode("property:12", parser, parser._parseDeclaration.bind(parser)); - assertNode("-vendor-property: 12", parser, parser._parseDeclaration.bind(parser)); - assertNode("font-size: 12px", parser, parser._parseDeclaration.bind(parser)); - assertNode("color : #888 /4", parser, parser._parseDeclaration.bind(parser)); + assertNode( + "-vendor-property: 12", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "font-size: 12px", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "color : #888 /4", + parser, + parser._parseDeclaration.bind(parser), + ); assertNode( "filter : progid:DXImageTransform.Microsoft.Shadow(color=#000000,direction=45)", parser, @@ -809,9 +1507,21 @@ suite("CSS - Parser", () => { parser, parser._parseDeclaration.bind(parser), ); - assertNode("font-size: 12px", parser, parser._parseDeclaration.bind(parser)); - assertNode("*background: #f00 /* IE 7 and below */", parser, parser._parseDeclaration.bind(parser)); - assertNode("_background: #f60 /* IE 6 and below */", parser, parser._parseDeclaration.bind(parser)); + assertNode( + "font-size: 12px", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "*background: #f00 /* IE 7 and below */", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "_background: #f60 /* IE 6 and below */", + parser, + parser._parseDeclaration.bind(parser), + ); assertNode( "background-image: linear-gradient(to right, silver, white 50px, white calc(100% - 50px), silver)", parser, @@ -832,14 +1542,26 @@ suite("CSS - Parser", () => { parser, parser._parseDeclaration.bind(parser), ); - assertNode("grid-template: [foo] 10px / [bar] 10px", parser, parser._parseDeclaration.bind(parser)); + assertNode( + "grid-template: [foo] 10px / [bar] 10px", + parser, + parser._parseDeclaration.bind(parser), + ); assertNode( `grid-template: 'left1 footer footer' 1fr [end] / [ini] 1fr [info-start] 2fr 1fr [end]`, parser, parser._parseDeclaration.bind(parser), ); - assertNode(`content: "("counter(foo) ")"`, parser, parser._parseDeclaration.bind(parser)); - assertNode(`content: 'Hello\\0A''world'`, parser, parser._parseDeclaration.bind(parser)); + assertNode( + `content: "("counter(foo) ")"`, + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + `content: 'Hello\\0A''world'`, + parser, + parser._parseDeclaration.bind(parser), + ); }); test("term", function () { @@ -856,17 +1578,29 @@ suite("CSS - Parser", () => { assertNode("-45em", parser, parser._parseTerm.bind(parser)); assertNode('"asdsa"', parser, parser._parseTerm.bind(parser)); assertNode("faa", parser, parser._parseTerm.bind(parser)); - assertNode('url("this is a striiiiing")', parser, parser._parseTerm.bind(parser)); + assertNode( + 'url("this is a striiiiing")', + parser, + parser._parseTerm.bind(parser), + ); assertNode("#FFFFFF", parser, parser._parseTerm.bind(parser)); assertNode("name(asd)", parser, parser._parseTerm.bind(parser)); assertNode("calc(50% + 20px)", parser, parser._parseTerm.bind(parser)); - assertNode("calc(50% + (100%/3 - 2*1em - 2*1px))", parser, parser._parseTerm.bind(parser)); + assertNode( + "calc(50% + (100%/3 - 2*1em - 2*1px))", + parser, + parser._parseTerm.bind(parser), + ); assertNoNode( "%('repetitions: %S file: %S', 1 + 2, \"directory/file.less\")", parser, parser._parseTerm.bind(parser), ); // less syntax - assertNoNode('~"ms:alwaysHasItsOwnSyntax.For.Stuff()"', parser, parser._parseTerm.bind(parser)); // less syntax + assertNoNode( + '~"ms:alwaysHasItsOwnSyntax.For.Stuff()"', + parser, + parser._parseTerm.bind(parser), + ); // less syntax assertNode("U+002?-0199", parser, parser._parseTerm.bind(parser)); assertNoNode("U+002?-01??", parser, parser._parseTerm.bind(parser)); assertNoNode("U+00?0;", parser, parser._parseTerm.bind(parser)); @@ -886,10 +1620,26 @@ suite("CSS - Parser", () => { assertNoNode("% ()", parser, parser._parseFunction.bind(parser)); assertFunction("let(--color)", parser, parser._parseFunction.bind(parser)); - assertFunction("let(--color, somevalue)", parser, parser._parseFunction.bind(parser)); - assertFunction("let(--variable1, --variable2)", parser, parser._parseFunction.bind(parser)); - assertFunction("let(--variable1, let(--variable2))", parser, parser._parseFunction.bind(parser)); - assertFunction("fun(value1, value2)", parser, parser._parseFunction.bind(parser)); + assertFunction( + "let(--color, somevalue)", + parser, + parser._parseFunction.bind(parser), + ); + assertFunction( + "let(--variable1, --variable2)", + parser, + parser._parseFunction.bind(parser), + ); + assertFunction( + "let(--variable1, let(--variable2))", + parser, + parser._parseFunction.bind(parser), + ); + assertFunction( + "fun(value1, value2)", + parser, + parser._parseFunction.bind(parser), + ); assertFunction("fun(value1,)", parser, parser._parseFunction.bind(parser)); }); @@ -898,7 +1648,11 @@ suite("CSS - Parser", () => { assertNode("!important", parser, parser._parsePrio.bind(parser)); assertNode("!/*demo*/important", parser, parser._parsePrio.bind(parser)); assertNode("! /*demo*/ important", parser, parser._parsePrio.bind(parser)); - assertNode("! /*dem o*/ important", parser, parser._parsePrio.bind(parser)); + assertNode( + "! /*dem o*/ important", + parser, + parser._parsePrio.bind(parser), + ); }); test("hexcolor", function () { @@ -927,24 +1681,64 @@ suite("CSS - Parser", () => { assertNode("45,5px", parser, parser._parseExpr.bind(parser)); assertNode(" 45 , 5px ", parser, parser._parseExpr.bind(parser)); assertNode("5/6", parser, parser._parseExpr.bind(parser)); - assertNode("36mm, -webkit-calc(100%-10px)", parser, parser._parseExpr.bind(parser)); + assertNode( + "36mm, -webkit-calc(100%-10px)", + parser, + parser._parseExpr.bind(parser), + ); }); test("url", function () { const parser = new Parser(); - assertNode("url(//yourdomain/yourpath.png)", parser, parser._parseURILiteral.bind(parser)); - assertNode("url('http://msft.com')", parser, parser._parseURILiteral.bind(parser)); - assertNode('url("http://msft.com")', parser, parser._parseURILiteral.bind(parser)); - assertNode('url( "http://msft.com")', parser, parser._parseURILiteral.bind(parser)); - assertNode('url(\t"http://msft.com")', parser, parser._parseURILiteral.bind(parser)); - assertNode('url(\n"http://msft.com")', parser, parser._parseURILiteral.bind(parser)); - assertNode('url("http://msft.com"\n)', parser, parser._parseURILiteral.bind(parser)); + assertNode( + "url(//yourdomain/yourpath.png)", + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode( + "url('http://msft.com')", + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode( + 'url("http://msft.com")', + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode( + 'url( "http://msft.com")', + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode( + 'url(\t"http://msft.com")', + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode( + 'url(\n"http://msft.com")', + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode( + 'url("http://msft.com"\n)', + parser, + parser._parseURILiteral.bind(parser), + ); assertNode('url("")', parser, parser._parseURILiteral.bind(parser)); assertNode('uRL("")', parser, parser._parseURILiteral.bind(parser)); assertNode('URL("")', parser, parser._parseURILiteral.bind(parser)); - assertNode("url(http://msft.com)", parser, parser._parseURILiteral.bind(parser)); + assertNode( + "url(http://msft.com)", + parser, + parser._parseURILiteral.bind(parser), + ); assertNode("url()", parser, parser._parseURILiteral.bind(parser)); - assertNode("url('http://msft.com\n)", parser, parser._parseURILiteral.bind(parser)); + assertNode( + "url('http://msft.com\n)", + parser, + parser._parseURILiteral.bind(parser), + ); assertError( 'url("http://msft.com"', parser, diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/parser/src/cssParser.ts similarity index 87% rename from packages/vscode-css-languageservice/src/parser/cssParser.ts rename to packages/parser/src/cssParser.ts index 299a4bcc..9b0ec3a8 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/parser/src/cssParser.ts @@ -2,13 +2,13 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -"use strict"; -import { TokenType, Scanner, IToken } from "./cssScanner"; -import * as nodes from "./cssNodes"; + +import { TextDocument } from "vscode-languageserver-textdocument"; import { ParseError, CSSIssueType } from "./cssErrors"; +import * as nodes from "./cssNodes"; +import { TokenType, Scanner, IToken } from "./cssScanner"; import * as languageFacts from "../languageFacts/facts"; -import { TextDocument } from "../cssLanguageTypes"; -import { isDefined } from "../utils/objects"; +import { isDefined } from "./utils/objects"; export interface IMark { prev?: IToken; @@ -70,7 +70,10 @@ export class Parser { } public hasWhitespace(): boolean { - return !!this.prevToken && this.prevToken.offset + this.prevToken.len !== this.token.offset; + return ( + !!this.prevToken && + this.prevToken.offset + this.prevToken.len !== this.token.offset + ); } public consumeToken(): void { @@ -115,7 +118,10 @@ export class Parser { public acceptOneKeyword(keywords: string[]): boolean { if (TokenType.AtKeyword === this.token.type) { for (const keyword of keywords) { - if (keyword.length === this.token.text.length && keyword === this.token.text.toLowerCase()) { + if ( + keyword.length === this.token.text.length && + keyword === this.token.text.toLowerCase() + ) { this.consumeToken(); return true; } @@ -183,12 +189,18 @@ export class Parser { return false; } - public resync(resyncTokens: TokenType[] | undefined, resyncStopTokens: TokenType[] | undefined): boolean { + public resync( + resyncTokens: TokenType[] | undefined, + resyncStopTokens: TokenType[] | undefined, + ): boolean { while (true) { if (resyncTokens && resyncTokens.indexOf(this.token.type) !== -1) { this.consumeToken(); return true; - } else if (resyncStopTokens && resyncStopTokens.indexOf(this.token.type) !== -1) { + } else if ( + resyncStopTokens && + resyncStopTokens.indexOf(this.token.type) !== -1 + ) { return true; } else { if (this.token.type === TokenType.EOF) { @@ -237,7 +249,16 @@ export class Parser { ): void { if (this.token !== this.lastErrorToken) { // do not report twice on the same token - node.addIssue(new nodes.Marker(node, error, nodes.Level.Error, undefined, this.token.offset, this.token.len)); + node.addIssue( + new nodes.Marker( + node, + error, + nodes.Level.Error, + undefined, + this.token.offset, + this.token.len, + ), + ); this.lastErrorToken = this.token; } if (resyncTokens || resyncStopTokens) { @@ -300,11 +321,19 @@ export class Parser { node.addChild(statement); hasMatch = true; inRecovery = false; - if (!this.peek(TokenType.EOF) && this._needsSemicolonAfter(statement) && !this.accept(TokenType.SemiColon)) { + if ( + !this.peek(TokenType.EOF) && + this._needsSemicolonAfter(statement) && + !this.accept(TokenType.SemiColon) + ) { this.markError(node, ParseError.SemiColonExpected); } } - while (this.accept(TokenType.SemiColon) || this.accept(TokenType.CDO) || this.accept(TokenType.CDC)) { + while ( + this.accept(TokenType.SemiColon) || + this.accept(TokenType.CDO) || + this.accept(TokenType.CDC) + ) { // accept empty statements hasMatch = true; inRecovery = false; @@ -333,14 +362,18 @@ export class Parser { return this._parseCharset(); } - public _parseStylesheetStatement(isNested: boolean = false): nodes.Node | null { + public _parseStylesheetStatement( + isNested: boolean = false, + ): nodes.Node | null { if (this.peek(TokenType.AtKeyword)) { return this._parseStylesheetAtStatement(isNested); } return this._parseRuleset(isNested); } - public _parseStylesheetAtStatement(isNested: boolean = false): nodes.Node | null { + public _parseStylesheetAtStatement( + isNested: boolean = false, + ): nodes.Node | null { return ( this._parseImport() || this._parseMedia(isNested) || @@ -447,7 +480,9 @@ export class Parser { return false; } - public _parseDeclarations(parseDeclaration: () => nodes.Node | null): nodes.Declarations | null { + public _parseDeclarations( + parseDeclaration: () => nodes.Node | null, + ): nodes.Declarations | null { const node = this.create(nodes.Declarations); if (!this.accept(TokenType.CurlyL)) { return null; @@ -458,11 +493,21 @@ export class Parser { if (this.peek(TokenType.CurlyR)) { break; } - if (this._needsSemicolonAfter(decl) && !this.accept(TokenType.SemiColon)) { - return this.finish(node, ParseError.SemiColonExpected, [TokenType.SemiColon, TokenType.CurlyR]); + if ( + this._needsSemicolonAfter(decl) && + !this.accept(TokenType.SemiColon) + ) { + return this.finish(node, ParseError.SemiColonExpected, [ + TokenType.SemiColon, + TokenType.CurlyR, + ]); } // We accepted semicolon token. Link it to declaration. - if (decl && this.prevToken && this.prevToken.type === TokenType.SemiColon) { + if ( + decl && + this.prevToken && + this.prevToken.type === TokenType.SemiColon + ) { (decl as nodes.Declaration).semicolonPosition = this.prevToken.offset; } while (this.accept(TokenType.SemiColon)) { @@ -472,14 +517,23 @@ export class Parser { } if (!this.accept(TokenType.CurlyR)) { - return this.finish(node, ParseError.RightCurlyExpected, [TokenType.CurlyR, TokenType.SemiColon]); + return this.finish(node, ParseError.RightCurlyExpected, [ + TokenType.CurlyR, + TokenType.SemiColon, + ]); } return this.finish(node); } - public _parseBody(node: T, parseDeclaration: () => nodes.Node | null): T { + public _parseBody( + node: T, + parseDeclaration: () => nodes.Node | null, + ): T { if (!node.setDeclarations(this._parseDeclarations(parseDeclaration))) { - return this.finish(node, ParseError.LeftCurlyExpected, [TokenType.CurlyR, TokenType.SemiColon]); + return this.finish(node, ParseError.LeftCurlyExpected, [ + TokenType.CurlyR, + TokenType.SemiColon, + ]); } return this.finish(node); } @@ -512,7 +566,12 @@ export class Parser { if (!this.accept(TokenType.Colon)) { return ( - this.finish(node, ParseError.ColonExpected, [TokenType.Colon], stopTokens || [TokenType.SemiColon]) + this.finish( + node, + ParseError.ColonExpected, + [TokenType.Colon], + stopTokens || [TokenType.SemiColon], + ) ); } if (this.prevToken) { @@ -530,7 +589,9 @@ export class Parser { return this.finish(node); } - public _tryParseCustomPropertyDeclaration(stopTokens?: TokenType[]): nodes.CustomPropertyDeclaration | null { + public _tryParseCustomPropertyDeclaration( + stopTokens?: TokenType[], + ): nodes.CustomPropertyDeclaration | null { if (!this.peekRegExp(TokenType.Ident, /^--/)) { return null; } @@ -550,8 +611,13 @@ export class Parser { if (this.peek(TokenType.CurlyL)) { // try to parse it as nested declaration const propertySet = this.create(nodes.CustomPropertySet); - const declarations = this._parseDeclarations(this._parseRuleSetDeclaration.bind(this)); - if (propertySet.setDeclarations(declarations) && !declarations.isErroneous(true)) { + const declarations = this._parseDeclarations( + this._parseRuleSetDeclaration.bind(this), + ); + if ( + propertySet.setDeclarations(declarations) && + !declarations.isErroneous(true) + ) { propertySet.addChild(this._parsePrio()); if (this.peek(TokenType.SemiColon)) { this.finish(propertySet); @@ -567,7 +633,9 @@ export class Parser { const expression = this._parseExpr(); if (expression && !expression.isErroneous(true)) { this._parsePrio(); - if (this.peekOne(...(stopTokens || []), TokenType.SemiColon, TokenType.EOF)) { + if ( + this.peekOne(...(stopTokens || []), TokenType.SemiColon, TokenType.EOF) + ) { node.setValue(expression); if (this.peek(TokenType.SemiColon)) { node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist @@ -578,7 +646,10 @@ export class Parser { this.restoreAtMark(mark); node.addChild(this._parseCustomPropertyValue(stopTokens)); node.addChild(this._parsePrio()); - if (isDefined(node.colonPosition) && this.token.offset === node.colonPosition + 1) { + if ( + isDefined(node.colonPosition) && + this.token.offset === node.colonPosition + 1 + ) { return this.finish(node, ParseError.PropertyValueExpected); } return this.finish(node); @@ -595,9 +666,12 @@ export class Parser { * terminators like semicolons and !important directives (when not inside * of delimitors). */ - public _parseCustomPropertyValue(stopTokens: TokenType[] = [TokenType.CurlyR]): nodes.Node { + public _parseCustomPropertyValue( + stopTokens: TokenType[] = [TokenType.CurlyR], + ): nodes.Node { const node = this.create(nodes.Node); - const isTopLevel = () => curlyDepth === 0 && parensDepth === 0 && bracketsDepth === 0; + const isTopLevel = () => + curlyDepth === 0 && parensDepth === 0 && bracketsDepth === 0; const onStopToken = () => stopTokens.indexOf(this.token.type) !== -1; let curlyDepth = 0; let parensDepth = 0; @@ -669,7 +743,9 @@ export class Parser { return this.finish(node); } - public _tryToParseDeclaration(stopTokens?: TokenType[]): nodes.Declaration | null { + public _tryToParseDeclaration( + stopTokens?: TokenType[], + ): nodes.Declaration | null { const mark = this.mark(); if (this._parseProperty() && this.accept(TokenType.Colon)) { // looks like a declaration, go ahead @@ -732,7 +808,10 @@ export class Parser { const node = this.create(nodes.Import); this.consumeToken(); // @import - if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) { + if ( + !node.addChild(this._parseURILiteral()) && + !node.addChild(this._parseStringLiteral()) + ) { return this.finish(node, ParseError.URIOrStringExpected); } @@ -743,18 +822,32 @@ export class Parser { if (this.acceptIdent("layer")) { if (this.accept(TokenType.ParenthesisL)) { if (!node.addChild(this._parseLayerName())) { - return this.finish(node, ParseError.IdentifierExpected, [TokenType.SemiColon]); + return this.finish(node, ParseError.IdentifierExpected, [ + TokenType.SemiColon, + ]); } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.ParenthesisR], []); + return this.finish( + node, + ParseError.RightParenthesisExpected, + [TokenType.ParenthesisR], + [], + ); } } } if (this.acceptIdent("supports")) { if (this.accept(TokenType.ParenthesisL)) { - node.addChild(this._tryToParseDeclaration() || this._parseSupportsCondition()); + node.addChild( + this._tryToParseDeclaration() || this._parseSupportsCondition(), + ); if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.ParenthesisR], []); + return this.finish( + node, + ParseError.RightParenthesisExpected, + [TokenType.ParenthesisR], + [], + ); } } } @@ -779,7 +872,10 @@ export class Parser { // url literal also starts with ident node.addChild(this._parseIdent()); // optional prefix - if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) { + if ( + !node.addChild(this._parseURILiteral()) && + !node.addChild(this._parseStringLiteral()) + ) { return this.finish(node, ParseError.URIExpected, [TokenType.SemiColon]); } } @@ -802,7 +898,11 @@ export class Parser { } public _parseViewPort(): nodes.Node | null { - if (!this.peekKeyword("@-ms-viewport") && !this.peekKeyword("@-o-viewport") && !this.peekKeyword("@viewport")) { + if ( + !this.peekKeyword("@-ms-viewport") && + !this.peekKeyword("@-o-viewport") && + !this.peekKeyword("@viewport") + ) { return null; } const node = this.create(nodes.ViewPort); @@ -828,7 +928,9 @@ export class Parser { } if (!node.setIdentifier(this._parseKeyframeIdent())) { - return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.IdentifierExpected, [ + TokenType.CurlyR, + ]); } return this._parseBody(node, this._parseKeyframeSelector.bind(this)); @@ -923,7 +1025,10 @@ export class Parser { const node = this.create(nodes.PropertyAtRule); this.consumeToken(); // @layer - if (!this.peekRegExp(TokenType.Ident, /^--/) || !node.setName(this._parseIdent([nodes.ReferenceType.Property]))) { + if ( + !this.peekRegExp(TokenType.Ident, /^--/) || + !node.setName(this._parseIdent([nodes.ReferenceType.Property])) + ) { return this.finish(node, ParseError.IdentifierExpected); } @@ -946,8 +1051,14 @@ export class Parser { if (names) { node.setNames(names); } - if ((!names || names.getChildren().length === 1) && this.peek(TokenType.CurlyL)) { - return this._parseBody(node, this._parseLayerDeclaration.bind(this, isNested)); + if ( + (!names || names.getChildren().length === 1) && + this.peek(TokenType.CurlyL) + ) { + return this._parseBody( + node, + this._parseLayerDeclaration.bind(this, isNested), + ); } if (!this.accept(TokenType.SemiColon)) { return this.finish(node, ParseError.SemiColonExpected); @@ -958,7 +1069,11 @@ export class Parser { public _parseLayerDeclaration(isNested = false): nodes.Node | null { if (isNested) { // if nested, the body can contain rulesets, but also declarations - return this._tryParseRuleset(true) || this._tryToParseDeclaration() || this._parseStylesheetStatement(true); + return ( + this._tryParseRuleset(true) || + this._tryToParseDeclaration() || + this._parseStylesheetStatement(true) + ); } return this._parseStylesheetStatement(false); } @@ -1000,13 +1115,20 @@ export class Parser { this.consumeToken(); // @supports node.addChild(this._parseSupportsCondition()); - return this._parseBody(node, this._parseSupportsDeclaration.bind(this, isNested)); + return this._parseBody( + node, + this._parseSupportsDeclaration.bind(this, isNested), + ); } public _parseSupportsDeclaration(isNested = false): nodes.Node | null { if (isNested) { // if nested, the body can contain rulesets, but also declarations - return this._tryParseRuleset(true) || this._tryToParseDeclaration() || this._parseStylesheetStatement(true); + return ( + this._tryParseRuleset(true) || + this._tryToParseDeclaration() || + this._parseStylesheetStatement(true) + ); } return this._parseStylesheetStatement(false); } @@ -1041,13 +1163,20 @@ export class Parser { if (this.prevToken) { node.lParent = this.prevToken.offset; } - if (!node.addChild(this._tryToParseDeclaration([TokenType.ParenthesisR]))) { + if ( + !node.addChild(this._tryToParseDeclaration([TokenType.ParenthesisR])) + ) { if (!this._parseSupportsCondition()) { return this.finish(node, ParseError.ConditionExpected); } } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.ParenthesisR], []); + return this.finish( + node, + ParseError.RightParenthesisExpected, + [TokenType.ParenthesisR], + [], + ); } if (this.prevToken) { node.rParent = this.prevToken.offset; @@ -1071,13 +1200,22 @@ export class Parser { this.restoreAtMark(pos); } } - return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.ParenthesisL]); + return this.finish( + node, + ParseError.LeftParenthesisExpected, + [], + [TokenType.ParenthesisL], + ); } public _parseMediaDeclaration(isNested = false): nodes.Node | null { if (isNested) { // if nested, the body can contain rulesets, but also declarations - return this._tryParseRuleset(true) || this._tryToParseDeclaration() || this._parseStylesheetStatement(true); + return ( + this._tryParseRuleset(true) || + this._tryToParseDeclaration() || + this._parseStylesheetStatement(true) + ); } return this._parseStylesheetStatement(false); } @@ -1094,7 +1232,10 @@ export class Parser { if (!node.addChild(this._parseMediaQueryList())) { return this.finish(node, ParseError.MediaQueryExpected); } - return this._parseBody(node, this._parseMediaDeclaration.bind(this, isNested)); + return this._parseBody( + node, + this._parseMediaDeclaration.bind(this, isNested), + ); } public _parseMediaQueryList(): nodes.Medialist { @@ -1162,7 +1303,12 @@ export class Parser { while (parseExpression) { if (!this.accept(TokenType.ParenthesisL)) { - return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish( + node, + ParseError.LeftParenthesisExpected, + [], + [TokenType.CurlyL], + ); } if (this.peek(TokenType.ParenthesisL) || this.peekIdent("not")) { // @@ -1172,7 +1318,12 @@ export class Parser { } // not yet implemented: general enclosed if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish( + node, + ParseError.RightParenthesisExpected, + [], + [TokenType.CurlyL], + ); } parseExpression = this.acceptIdent("and") || this.acceptIdent("or"); } @@ -1191,15 +1342,30 @@ export class Parser { if (node.addChild(this._parseMediaFeatureName())) { if (this.accept(TokenType.Colon)) { if (!node.addChild(this._parseMediaFeatureValue())) { - return this.finish(node, ParseError.TermExpected, [], resyncStopToken); + return this.finish( + node, + ParseError.TermExpected, + [], + resyncStopToken, + ); } } else if (this._parseMediaFeatureRangeOperator()) { if (!node.addChild(this._parseMediaFeatureValue())) { - return this.finish(node, ParseError.TermExpected, [], resyncStopToken); + return this.finish( + node, + ParseError.TermExpected, + [], + resyncStopToken, + ); } if (this._parseMediaFeatureRangeOperator()) { if (!node.addChild(this._parseMediaFeatureValue())) { - return this.finish(node, ParseError.TermExpected, [], resyncStopToken); + return this.finish( + node, + ParseError.TermExpected, + [], + resyncStopToken, + ); } } } else { @@ -1207,18 +1373,38 @@ export class Parser { } } else if (node.addChild(this._parseMediaFeatureValue())) { if (!this._parseMediaFeatureRangeOperator()) { - return this.finish(node, ParseError.OperatorExpected, [], resyncStopToken); + return this.finish( + node, + ParseError.OperatorExpected, + [], + resyncStopToken, + ); } if (!node.addChild(this._parseMediaFeatureName())) { - return this.finish(node, ParseError.IdentifierExpected, [], resyncStopToken); + return this.finish( + node, + ParseError.IdentifierExpected, + [], + resyncStopToken, + ); } if (this._parseMediaFeatureRangeOperator()) { if (!node.addChild(this._parseMediaFeatureValue())) { - return this.finish(node, ParseError.TermExpected, [], resyncStopToken); + return this.finish( + node, + ParseError.TermExpected, + [], + resyncStopToken, + ); } } } else { - return this.finish(node, ParseError.IdentifierExpected, [], resyncStopToken); + return this.finish( + node, + ParseError.IdentifierExpected, + [], + resyncStopToken, + ); } return this.finish(node); } @@ -1324,7 +1510,11 @@ export class Parser { public _parseContainerDeclaration(isNested = false): nodes.Node | null { if (isNested) { // if nested, the body can contain rulesets, but also declarations - return this._tryParseRuleset(true) || this._tryToParseDeclaration() || this._parseStylesheetStatement(true); + return ( + this._tryParseRuleset(true) || + this._tryToParseDeclaration() || + this._parseStylesheetStatement(true) + ); } return this._parseStylesheetStatement(false); } @@ -1339,7 +1529,10 @@ export class Parser { node.addChild(this._parseIdent()); // optional container name node.addChild(this._parseContainerQuery()); - return this._parseBody(node, this._parseContainerDeclaration.bind(this, isNested)); + return this._parseBody( + node, + this._parseContainerDeclaration.bind(this, isNested), + ); } public _parseContainerQuery(): nodes.Node | null { @@ -1376,18 +1569,38 @@ export class Parser { node.addChild(this._parseMediaFeature()); } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish( + node, + ParseError.RightParenthesisExpected, + [], + [TokenType.CurlyL], + ); } } else if (this.acceptIdent("style")) { if (this.hasWhitespace() || !this.accept(TokenType.ParenthesisL)) { - return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish( + node, + ParseError.LeftParenthesisExpected, + [], + [TokenType.CurlyL], + ); } node.addChild(this._parseStyleQuery()); if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish( + node, + ParseError.RightParenthesisExpected, + [], + [TokenType.CurlyL], + ); } } else { - return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish( + node, + ParseError.LeftParenthesisExpected, + [], + [TokenType.CurlyL], + ); } return this.finish(node); } @@ -1425,10 +1638,20 @@ export class Parser { if (this.accept(TokenType.ParenthesisL)) { node.addChild(this._parseStyleQuery()); if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish( + node, + ParseError.RightParenthesisExpected, + [], + [TokenType.CurlyL], + ); } } else { - return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish( + node, + ParseError.LeftParenthesisExpected, + [], + [TokenType.CurlyL], + ); } return this.finish(node); } @@ -1442,7 +1665,8 @@ export class Parser { const node = this.create(nodes.UnknownAtRule); node.addChild(this._parseUnknownAtRuleName()); - const isTopLevel = () => curlyDepth === 0 && parensDepth === 0 && bracketsDepth === 0; + const isTopLevel = () => + curlyDepth === 0 && parensDepth === 0 && bracketsDepth === 0; let curlyLCount = 0; let curlyDepth = 0; let parensDepth = 0; @@ -1586,7 +1810,12 @@ export class Parser { const node = this.create(nodes.Node); this.consumeToken(); const mark = this.mark(); - if (!this.hasWhitespace() && this.acceptIdent("deep") && !this.hasWhitespace() && this.acceptDelim("/")) { + if ( + !this.hasWhitespace() && + this.acceptIdent("deep") && + !this.hasWhitespace() && + this.acceptDelim("/") + ) { node.type = nodes.NodeType.SelectorCombinatorShadowPiercingDescendant; return this.finish(node); } @@ -1602,10 +1831,15 @@ export class Parser { const node = this.create(nodes.SimpleSelector); let c = 0; - if (node.addChild(this._parseElementName() || this._parseNestingSelector())) { + if ( + node.addChild(this._parseElementName() || this._parseNestingSelector()) + ) { c++; } - while ((c === 0 || !this.hasWhitespace()) && node.addChild(this._parseSimpleSelectorBody())) { + while ( + (c === 0 || !this.hasWhitespace()) && + node.addChild(this._parseSimpleSelectorBody()) + ) { c++; } return c > 0 ? this.finish(node) : null; @@ -1621,7 +1855,12 @@ export class Parser { } public _parseSimpleSelectorBody(): nodes.Node | null { - return this._parsePseudo() || this._parseHash() || this._parseClass() || this._parseAttrib(); + return ( + this._parsePseudo() || + this._parseHash() || + this._parseClass() || + this._parseAttrib() + ); } public _parseSelectorIdent(): nodes.Node | null { @@ -1718,7 +1957,10 @@ export class Parser { if (!selectors.addChild(this._parseSelector(true))) { return null; } - while (this.accept(TokenType.Comma) && selectors.addChild(this._parseSelector(true))) { + while ( + this.accept(TokenType.Comma) && + selectors.addChild(this._parseSelector(true)) + ) { // loop } if (this.peek(TokenType.ParenthesisR)) { @@ -1960,13 +2202,19 @@ export class Parser { public _parseURLArgument(): nodes.Node | null { const node = this.create(nodes.Node); - if (!this.accept(TokenType.String) && !this.accept(TokenType.BadString) && !this.acceptUnquotedString()) { + if ( + !this.accept(TokenType.String) && + !this.accept(TokenType.BadString) && + !this.acceptUnquotedString() + ) { return null; } return this.finish(node); } - public _parseIdent(referenceTypes?: nodes.ReferenceType[]): nodes.Identifier | null { + public _parseIdent( + referenceTypes?: nodes.ReferenceType[], + ): nodes.Identifier | null { if (!this.peek(TokenType.Ident)) { return null; } @@ -2004,7 +2252,9 @@ export class Parser { } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected); + return ( + this.finish(node, ParseError.RightParenthesisExpected) + ); } return this.finish(node); } @@ -2039,7 +2289,12 @@ export class Parser { } public _parseHexColor(): nodes.Node | null { - if (this.peekRegExp(TokenType.Hash, /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/g)) { + if ( + this.peekRegExp( + TokenType.Hash, + /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/g, + ) + ) { const node = this.create(nodes.HexColorValue); this.consumeToken(); return this.finish(node); diff --git a/packages/vscode-css-languageservice/src/parser/cssScanner.ts b/packages/parser/src/cssScanner.ts similarity index 92% rename from packages/vscode-css-languageservice/src/parser/cssScanner.ts rename to packages/parser/src/cssScanner.ts index fa521baf..579eee3d 100644 --- a/packages/vscode-css-languageservice/src/parser/cssScanner.ts +++ b/packages/parser/src/cssScanner.ts @@ -1,8 +1,9 @@ +/* eslint-disable prefer-const */ +/* eslint-disable no-constant-condition */ /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -"use strict"; export enum TokenType { Ident, @@ -129,7 +130,10 @@ export class MultiLineStream { public advanceWhileChar(condition: (ch: number) => boolean): number { const posNow = this.position; - while (this.position < this.len && condition(this.source.charCodeAt(this.position))) { + while ( + this.position < this.len && + condition(this.source.charCodeAt(this.position)) + ) { this.position++; } return this.position - posNow; @@ -139,7 +143,6 @@ export class MultiLineStream { const _a = "a".charCodeAt(0); const _f = "f".charCodeAt(0); const _z = "z".charCodeAt(0); -const _u = "u".charCodeAt(0); const _A = "A".charCodeAt(0); const _F = "F".charCodeAt(0); const _Z = "Z".charCodeAt(0); @@ -254,7 +257,11 @@ export class Scanner { const offset = this.stream.pos(); const content: string[] = []; if (this._unquotedString(content)) { - return this.finishToken(offset, TokenType.UnquotedString, content.join("")); + return this.finishToken( + offset, + TokenType.UnquotedString, + content.join(""), + ); } return null; } @@ -349,7 +356,11 @@ export class Scanner { return this.finishToken(offset, tokenType, content.join("")); } else { // Unknown dimension 43ft - return this.finishToken(offset, TokenType.Dimension, content.join("")); + return this.finishToken( + offset, + TokenType.Dimension, + content.join(""), + ); } } @@ -483,14 +494,22 @@ export class Scanner { this.stream.advance(1); ch = this.stream.peekChar(); let hexNumCount = 0; - while (hexNumCount < 6 && ((ch >= _0 && ch <= _9) || (ch >= _a && ch <= _f) || (ch >= _A && ch <= _F))) { + while ( + hexNumCount < 6 && + ((ch >= _0 && ch <= _9) || + (ch >= _a && ch <= _f) || + (ch >= _A && ch <= _F)) + ) { this.stream.advance(1); ch = this.stream.peekChar(); hexNumCount++; } if (hexNumCount > 0) { try { - const hexVal = parseInt(this.stream.substring(this.stream.pos() - hexNumCount), 16); + const hexVal = parseInt( + this.stream.substring(this.stream.pos() - hexNumCount), + 16, + ); if (hexVal) { result.push(String.fromCharCode(hexVal)); } @@ -520,7 +539,14 @@ export class Scanner { private _stringChar(closeQuote: number, result: string[]) { // not closeQuote, not backslash, not newline const ch = this.stream.peekChar(); - if (ch !== 0 && ch !== closeQuote && ch !== _BSL && ch !== _CAR && ch !== _LFD && ch !== _NWL) { + if ( + ch !== 0 && + ch !== closeQuote && + ch !== _BSL && + ch !== _CAR && + ch !== _LFD && + ch !== _NWL + ) { this.stream.advance(1); result.push(String.fromCharCode(ch)); return true; @@ -533,7 +559,10 @@ export class Scanner { const closeQuote = this.stream.nextChar(); result.push(String.fromCharCode(closeQuote)); - while (this._stringChar(closeQuote, result) || this._escape(result, true)) { + while ( + this._stringChar(closeQuote, result) || + this._escape(result, true) + ) { // loop } @@ -581,7 +610,9 @@ export class Scanner { private _whitespace(): boolean { const n = this.stream.advanceWhileChar((ch) => { - return ch === _WSP || ch === _TAB || ch === _NWL || ch === _LFD || ch === _CAR; + return ( + ch === _WSP || ch === _TAB || ch === _NWL || ch === _LFD || ch === _CAR + ); }); return n > 0; } @@ -598,7 +629,11 @@ export class Scanner { const pos = this.stream.pos(); const hasMinus = this._minus(result); if (hasMinus) { - if (this._minus(result) /* -- */ || this._identFirstChar(result) || this._escape(result)) { + if ( + this._minus(result) /* -- */ || + this._identFirstChar(result) || + this._escape(result) + ) { while (this._identChar(result) || this._escape(result)) { // loop } @@ -663,9 +698,14 @@ export class Scanner { // assume u has already been parsed if (this.stream.advanceIfChar(_PLS)) { - const isHexDigit = (ch: number) => (ch >= _0 && ch <= _9) || (ch >= _a && ch <= _f) || (ch >= _A && ch <= _F); - - const codePoints = this.stream.advanceWhileChar(isHexDigit) + this.stream.advanceWhileChar((ch) => ch === _QSM); + const isHexDigit = (ch: number) => + (ch >= _0 && ch <= _9) || + (ch >= _a && ch <= _f) || + (ch >= _A && ch <= _F); + + const codePoints = + this.stream.advanceWhileChar(isHexDigit) + + this.stream.advanceWhileChar((ch) => ch === _QSM); if (codePoints >= 1 && codePoints <= 6) { if (this.stream.advanceIfChar(_MIN)) { const digits = this.stream.advanceWhileChar(isHexDigit); diff --git a/packages/vscode-css-languageservice/src/parser/cssSymbolScope.ts b/packages/parser/src/cssSymbolScope.ts similarity index 73% rename from packages/vscode-css-languageservice/src/parser/cssSymbolScope.ts rename to packages/parser/src/cssSymbolScope.ts index 85f92870..c02e245d 100644 --- a/packages/vscode-css-languageservice/src/parser/cssSymbolScope.ts +++ b/packages/parser/src/cssSymbolScope.ts @@ -2,10 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -"use strict"; - import * as nodes from "./cssNodes"; -import { findFirst } from "../utils/arrays"; +import { findFirst } from "./utils/arrays"; export class Scope { public parent: Scope | null; @@ -91,7 +89,12 @@ export class Symbol { public type: nodes.ReferenceType; public node: nodes.Node; - constructor(name: string, value: string | undefined, node: nodes.Node, type: nodes.ReferenceType) { + constructor( + name: string, + value: string | undefined, + node: nodes.Node, + type: nodes.ReferenceType, + ) { this.name = name; this.value = value; this.node = node; @@ -106,7 +109,12 @@ export class ScopeBuilder implements nodes.IVisitor { this.scope = scope; } - private addSymbol(node: nodes.Node, name: string, value: string | undefined, type: nodes.ReferenceType): void { + private addSymbol( + node: nodes.Node, + name: string, + value: string | undefined, + type: nodes.ReferenceType, + ): void { if (node.offset !== -1) { const current = this.scope.findScope(node.offset, node.length); if (current) { @@ -118,7 +126,10 @@ export class ScopeBuilder implements nodes.IVisitor { private addScope(node: nodes.Node): Scope | null { if (node.offset !== -1) { const current = this.scope.findScope(node.offset, node.length); - if (current && (current.offset !== node.offset || current.length !== node.length)) { + if ( + current && + (current.offset !== node.offset || current.length !== node.length) + ) { // scope already known? const newScope = new Scope(node.offset, node.length); current.addChild(newScope); @@ -147,19 +158,38 @@ export class ScopeBuilder implements nodes.IVisitor { public visitNode(node: nodes.Node): boolean { switch (node.type) { case nodes.NodeType.Keyframe: - this.addSymbol(node, (node).getName(), void 0, nodes.ReferenceType.Keyframe); + this.addSymbol( + node, + (node).getName(), + void 0, + nodes.ReferenceType.Keyframe, + ); return true; case nodes.NodeType.CustomPropertyDeclaration: - return this.visitCustomPropertyDeclarationNode(node); + return this.visitCustomPropertyDeclarationNode( + node, + ); case nodes.NodeType.VariableDeclaration: - return this.visitVariableDeclarationNode(node); + return this.visitVariableDeclarationNode( + node, + ); case nodes.NodeType.Ruleset: return this.visitRuleSet(node); case nodes.NodeType.MixinDeclaration: - this.addSymbol(node, (node).getName(), void 0, nodes.ReferenceType.Mixin); + this.addSymbol( + node, + (node).getName(), + void 0, + nodes.ReferenceType.Mixin, + ); return true; case nodes.NodeType.FunctionDeclaration: - this.addSymbol(node, (node).getName(), void 0, nodes.ReferenceType.Function); + this.addSymbol( + node, + (node).getName(), + void 0, + nodes.ReferenceType.Function, + ); return true; case nodes.NodeType.FunctionParameter: { return this.visitFunctionParameterNode(node); @@ -167,7 +197,7 @@ export class ScopeBuilder implements nodes.IVisitor { case nodes.NodeType.Declarations: this.addScope(node); return true; - case nodes.NodeType.For: + case nodes.NodeType.For: { const forNode = node; const scopeNode = forNode.getDeclarations(); if (scopeNode && forNode.variable) { @@ -180,13 +210,22 @@ export class ScopeBuilder implements nodes.IVisitor { ); } return true; + } case nodes.NodeType.Each: { const eachNode = node; const scopeNode = eachNode.getDeclarations(); if (scopeNode) { - const variables = eachNode.getVariables().getChildren(); + const variables = ( + eachNode.getVariables().getChildren() + ); for (const variable of variables) { - this.addSymbolToChildScope(scopeNode, variable, variable.getName(), void 0, nodes.ReferenceType.Variable); + this.addSymbolToChildScope( + scopeNode, + variable, + variable.getName(), + void 0, + nodes.ReferenceType.Variable, + ); } } return true; @@ -202,7 +241,14 @@ export class ScopeBuilder implements nodes.IVisitor { if (child instanceof nodes.Selector) { if (child.getChildren().length === 1) { // only selectors with a single element can be extended - current.addSymbol(new Symbol(child.getChild(0)!.getText(), void 0, child, nodes.ReferenceType.Rule)); + current.addSymbol( + new Symbol( + child.getChild(0)!.getText(), + void 0, + child, + nodes.ReferenceType.Rule, + ), + ); } } } @@ -210,7 +256,9 @@ export class ScopeBuilder implements nodes.IVisitor { return true; } - public visitVariableDeclarationNode(node: nodes.VariableDeclaration): boolean { + public visitVariableDeclarationNode( + node: nodes.VariableDeclaration, + ): boolean { const value = node.getValue() ? node.getValue()!.getText() : void 0; this.addSymbol(node, node.getName(), value, nodes.ReferenceType.Variable); return true; @@ -218,22 +266,42 @@ export class ScopeBuilder implements nodes.IVisitor { public visitFunctionParameterNode(node: nodes.FunctionParameter): boolean { // parameters are part of the body scope - const scopeNode = (node.getParent()).getDeclarations(); + const scopeNode = (( + node.getParent() + )).getDeclarations(); if (scopeNode) { const valueNode = (node).getDefaultValue(); const value = valueNode ? valueNode.getText() : void 0; - this.addSymbolToChildScope(scopeNode, node, node.getName(), value, nodes.ReferenceType.Variable); + this.addSymbolToChildScope( + scopeNode, + node, + node.getName(), + value, + nodes.ReferenceType.Variable, + ); } return true; } - public visitCustomPropertyDeclarationNode(node: nodes.CustomPropertyDeclaration): boolean { + public visitCustomPropertyDeclarationNode( + node: nodes.CustomPropertyDeclaration, + ): boolean { const value = node.getValue() ? node.getValue()!.getText() : ""; - this.addCSSVariable(node.getProperty()!, node.getProperty()!.getName(), value, nodes.ReferenceType.Variable); + this.addCSSVariable( + node.getProperty()!, + node.getProperty()!.getName(), + value, + nodes.ReferenceType.Variable, + ); return true; } - private addCSSVariable(node: nodes.Node, name: string, value: string, type: nodes.ReferenceType): void { + private addCSSVariable( + node: nodes.Node, + name: string, + value: string, + type: nodes.ReferenceType, + ): void { if (node.offset !== -1) { this.scope.addSymbol(new Symbol(name, value, node, type)); } @@ -248,7 +316,10 @@ export class Symbols { node.acceptVisitor(new ScopeBuilder(this.global)); } - public findSymbolsAtOffset(offset: number, referenceType: nodes.ReferenceType): Symbol[] { + public findSymbolsAtOffset( + offset: number, + referenceType: nodes.ReferenceType, + ): Symbol[] { let scope = this.global.findScope(offset, 0); const result: Symbol[] = []; const names: { [name: string]: boolean } = {}; @@ -266,17 +337,32 @@ export class Symbols { return result; } - private internalFindSymbol(node: nodes.Node, referenceTypes: nodes.ReferenceType[]): Symbol | null { + private internalFindSymbol( + node: nodes.Node, + referenceTypes: nodes.ReferenceType[], + ): Symbol | null { let scopeNode: nodes.Node | undefined = node; - if (node.parent instanceof nodes.FunctionParameter && node.parent.getParent() instanceof nodes.BodyDeclaration) { - scopeNode = (node.parent.getParent()).getDeclarations(); + if ( + node.parent instanceof nodes.FunctionParameter && + node.parent.getParent() instanceof nodes.BodyDeclaration + ) { + scopeNode = (( + node.parent.getParent() + )).getDeclarations(); } - if (node.parent instanceof nodes.FunctionArgument && node.parent.getParent() instanceof nodes.Function) { + if ( + node.parent instanceof nodes.FunctionArgument && + node.parent.getParent() instanceof nodes.Function + ) { const funcId = (node.parent.getParent()).getIdentifier(); if (funcId) { - const functionSymbol = this.internalFindSymbol(funcId, [nodes.ReferenceType.Function]); + const functionSymbol = this.internalFindSymbol(funcId, [ + nodes.ReferenceType.Function, + ]); if (functionSymbol) { - scopeNode = (functionSymbol.node).getDeclarations(); + scopeNode = (( + functionSymbol.node + )).getDeclarations(); } } } @@ -298,7 +384,9 @@ export class Symbols { return null; } - private evaluateReferenceTypes(node: nodes.Node): nodes.ReferenceType[] | null { + private evaluateReferenceTypes( + node: nodes.Node, + ): nodes.ReferenceType[] | null { if (node instanceof nodes.Identifier) { const referenceTypes = (node).referenceTypes; if (referenceTypes) { @@ -312,7 +400,8 @@ export class Symbols { if (decl) { const propertyName = decl.getNonPrefixedPropertyName(); if ( - (propertyName === "animation" || propertyName === "animation-name") && + (propertyName === "animation" || + propertyName === "animation-name") && decl.getValue() && decl.getValue()!.offset === node.offset ) { @@ -323,7 +412,10 @@ export class Symbols { } else if (node instanceof nodes.Variable) { return [nodes.ReferenceType.Variable]; } - const selector = node.findAParent(nodes.NodeType.Selector, nodes.NodeType.ExtendsReference); + const selector = node.findAParent( + nodes.NodeType.Selector, + nodes.NodeType.ExtendsReference, + ); if (selector) { return [nodes.ReferenceType.Rule]; } @@ -365,7 +457,11 @@ export class Symbols { return nodeSymbol === symbol; } - public findSymbol(name: string, type: nodes.ReferenceType, offset: number): Symbol | null { + public findSymbol( + name: string, + type: nodes.ReferenceType, + offset: number, + ): Symbol | null { let scope = this.global.findScope(offset); while (scope) { const symbol = scope.getSymbol(name, type); diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts new file mode 100644 index 00000000..b8935f65 --- /dev/null +++ b/packages/parser/src/parser.ts @@ -0,0 +1,4 @@ +export * from "./cssNodes"; +export { TokenType, IToken, Scanner } from "./cssScanner"; +export { SCSSScanner } from "./scssScanner"; +export * from "./scssParser"; diff --git a/packages/vscode-css-languageservice/src/parser/scssErrors.ts b/packages/parser/src/scssErrors.ts similarity index 78% rename from packages/vscode-css-languageservice/src/parser/scssErrors.ts rename to packages/parser/src/scssErrors.ts index 83a36d62..7923e82c 100644 --- a/packages/vscode-css-languageservice/src/parser/scssErrors.ts +++ b/packages/parser/src/scssErrors.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ "use strict"; -import * as nodes from "./cssNodes"; - import * as l10n from "@vscode/l10n"; +import * as nodes from "./cssNodes"; export class SCSSIssueType implements nodes.IRule { id: string; @@ -19,7 +18,13 @@ export class SCSSIssueType implements nodes.IRule { } export const SCSSParseError = { - FromExpected: new SCSSIssueType("scss-fromexpected", l10n.t("'from' expected")), - ThroughOrToExpected: new SCSSIssueType("scss-throughexpected", l10n.t("'through' or 'to' expected")), + FromExpected: new SCSSIssueType( + "scss-fromexpected", + l10n.t("'from' expected"), + ), + ThroughOrToExpected: new SCSSIssueType( + "scss-throughexpected", + l10n.t("'through' or 'to' expected"), + ), InExpected: new SCSSIssueType("scss-fromexpected", l10n.t("'in' expected")), }; diff --git a/packages/parser/src/scssParser.test.ts b/packages/parser/src/scssParser.test.ts new file mode 100644 index 00000000..b7df25aa --- /dev/null +++ b/packages/parser/src/scssParser.test.ts @@ -0,0 +1,2243 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { suite, test } from "vitest"; +import { ParseError } from "./cssErrors.js"; +import { assertNode, assertError } from "./cssParser.test.js"; +import { SCSSParseError } from "./scssErrors.js"; +import { SCSSParser } from "./scssParser.js"; + +suite("SCSS - Parser", () => { + test("Comments", function () { + const parser = new SCSSParser(); + assertNode( + " a { b: /* comment */ c }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + " a { b: /* comment \n * is several\n * lines long\n */ c }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + " a { b: // single line comment\n c }", + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("Variable", function () { + const parser = new SCSSParser(); + assertNode("$color", parser, parser._parseVariable.bind(parser)); + assertNode("$co42lor", parser, parser._parseVariable.bind(parser)); + assertNode("$-co42lor", parser, parser._parseVariable.bind(parser)); + }); + + test("Module variable", function () { + const parser = new SCSSParser(); + assertNode("module.$color", parser, parser._parseModuleMember.bind(parser)); + assertNode( + "module.$co42lor", + parser, + parser._parseModuleMember.bind(parser), + ); + assertNode( + "module.$-co42lor", + parser, + parser._parseModuleMember.bind(parser), + ); + assertNode( + "module.function()", + parser, + parser._parseModuleMember.bind(parser), + ); + + assertError( + "module.", + parser, + parser._parseModuleMember.bind(parser), + ParseError.IdentifierOrVariableExpected, + ); + }); + + test("VariableDeclaration", function () { + const parser = new SCSSParser(); + assertNode( + "$color: #F5F5F5", + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode( + "$color: 0", + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode( + "$color: 255", + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode( + "$color: 25.5", + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode( + "$color: 25px", + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode( + "$color: 25.5px !default", + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode( + "$text-color: green !global", + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode( + '$_RESOURCES: append($_RESOURCES, "clean") !global', + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode( + "$footer-height: 40px !default !global", + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode( + '$primary-font: "wf_SegoeUI","Segoe UI","Segoe","Segoe WP"', + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode( + "$color: red !important", + parser, + parser._parseVariableDeclaration.bind(parser), + ); + + assertError( + "$color: red !def", + parser, + parser._parseVariableDeclaration.bind(parser), + ParseError.UnknownKeyword, + ); + assertError( + "$color : !default", + parser, + parser._parseVariableDeclaration.bind(parser), + ParseError.VariableValueExpected, + ); + assertError( + "$color !default", + parser, + parser._parseVariableDeclaration.bind(parser), + ParseError.ColonExpected, + ); + }); + + test("Expr", function () { + const parser = new SCSSParser(); + assertNode("($const + 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($const - 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($const * 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($const / 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 - $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 * $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 / $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 / 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode( + "(20 + 20 + 20 + $const)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "(20 + 20 + 20 + 20 + $const)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "(20 + 20 + $const + 20 + 20 + $const)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode("(20 + 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($var1 + $var2)", parser, parser._parseExpr.bind(parser)); + assertNode("(($const + 5) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode( + "(($const + (5 + 2)) * 2)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "($const + ((5 + 2) * 2))", + parser, + parser._parseExpr.bind(parser), + ); + assertNode("$color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, 42%", parser, parser._parseExpr.bind(parser)); + assertNode("$color, 42%, $color", parser, parser._parseExpr.bind(parser)); + assertNode( + "$color - ($color + 10%)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode("($base + $filler)", parser, parser._parseExpr.bind(parser)); + assertNode("(100% / 2 + $filler)", parser, parser._parseExpr.bind(parser)); + assertNode("100% / 2 + $filler", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and $b) or $c", parser, parser._parseExpr.bind(parser)); + + assertNode("(module.$const + 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const - 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const * 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const / 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 - module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 * module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 / module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode( + "(20 + 20 + module.$const)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "(20 + 20 + 20 + module.$const)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "(20 + 20 + 20 + 20 + module.$const)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "(20 + 20 + module.$const + 20 + 20 + module.$const)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "($var1 + module.$var2)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "(module.$var1 + $var2)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "(module.$var1 + module.$var2)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "((module.$const + 5) * 2)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "((module.$const + (5 + 2)) * 2)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "(module.$const + ((5 + 2) * 2))", + parser, + parser._parseExpr.bind(parser), + ); + assertNode("module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode( + "module.$color, module.$color", + parser, + parser._parseExpr.bind(parser), + ); + assertNode("module.$color, 42%", parser, parser._parseExpr.bind(parser)); + assertNode( + "module.$color, 42%, $color", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "$color, 42%, module.$color", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "module.$color, 42%, module.$color", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "module.$color - ($color + 10%)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "$color - (module.$color + 10%)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "module.$color - (module.$color + 10%)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "(module.$base + $filler)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "($base + module.$filler)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "(module.$base + module.$filler)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "(100% / 2 + module.$filler)", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "100% / 2 + module.$filler", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "not (module.$v and $b) or $c", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "not ($v and module.$b) or $c", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "not ($v and $b) or module.$c", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "not (module.$v and module.$b) or $c", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "not (module.$v and $b) or module.$c", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "not ($v and module.$b) or module.$c", + parser, + parser._parseExpr.bind(parser), + ); + assertNode( + "not (module.$v and module.$b) or module.$c", + parser, + parser._parseExpr.bind(parser), + ); + assertNode("not module.$v", parser, parser._parseExpr.bind(parser)); + + assertError( + "(20 + 20", + parser, + parser._parseExpr.bind(parser), + ParseError.RightParenthesisExpected, + ); + }); + + test("SCSSOperator", function () { + const parser = new SCSSParser(); + assertNode(">=", parser, parser._parseOperator.bind(parser)); + assertNode(">", parser, parser._parseOperator.bind(parser)); + assertNode("<", parser, parser._parseOperator.bind(parser)); + assertNode("<=", parser, parser._parseOperator.bind(parser)); + assertNode("==", parser, parser._parseOperator.bind(parser)); + assertNode("!=", parser, parser._parseOperator.bind(parser)); + assertNode("and", parser, parser._parseOperator.bind(parser)); + assertNode("+", parser, parser._parseOperator.bind(parser)); + assertNode("-", parser, parser._parseOperator.bind(parser)); + assertNode("*", parser, parser._parseOperator.bind(parser)); + assertNode("/", parser, parser._parseOperator.bind(parser)); + assertNode("%", parser, parser._parseOperator.bind(parser)); + assertNode("not", parser, parser._parseUnaryOperator.bind(parser)); + }); + + test("Interpolation", function () { + const parser = new SCSSParser(); + // assertNode('#{red}', parser, parser._parseIdent.bind(parser)); + // assertNode('#{$color}', parser, parser._parseIdent.bind(parser)); + // assertNode('#{3 + 4}', parser, parser._parseIdent.bind(parser)); + // assertNode('#{3 + #{3 + 4}}', parser, parser._parseIdent.bind(parser)); + // assertNode('#{$d}-style: 0', parser, parser._parseDeclaration.bind(parser)); + // assertNode('foo-#{$d}: 1', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{$d}-bar-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('foo-#{$d}-bar: 1', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('&:nth-child(#{$query}+1) { clear: $opposite-direction; }', parser, parser._parseRuleset.bind(parser)); + // assertNode('--#{$propname}: some-value', parser, parser._parseDeclaration.bind(parser)); + // assertNode('some-property: var(--#{$propname})', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{}', parser, parser._parseIdent.bind(parser)); + // assertError('#{1 + 2', parser, parser._parseIdent.bind(parser), ParseError.RightCurlyExpected); + + // assertNode('#{module.$color}', parser, parser._parseIdent.bind(parser)); + // assertNode('#{module.$d}-style: 0', parser, parser._parseDeclaration.bind(parser)); + // assertNode('foo-#{module.$d}: 1', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{module.$d}-bar-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{$d}-bar-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{module.$d}-bar-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('foo-#{module.$d}-bar: 1', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{module.$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{$d}-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{module.$d}-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('&:nth-child(#{module.$query}+1) { clear: $opposite-direction; }', parser, parser._parseRuleset.bind(parser)); + // assertNode('&:nth-child(#{$query}+1) { clear: module.$opposite-direction; }', parser, parser._parseRuleset.bind(parser)); + // assertNode('&:nth-child(#{module.$query}+1) { clear: module.$opposite-direction; }', parser, parser._parseRuleset.bind(parser)); + assertNode( + "--#{module.$propname}: some-value", + parser, + parser._parseDeclaration.bind(parser), + ); + // assertNode('some-property: var(--#{module.$propname})', parser, parser._parseDeclaration.bind(parser)); + // assertNode('@supports #{$val} { }', parser, parser._parseStylesheet.bind(parser)); // #88283 + // assertNode('.mb-#{$i}0np {} .push-up-#{$i}0 {} .mt-#{$i}0vh {}', parser, parser._parseStylesheet.bind(parser)); + }); + + test("Declaration", function () { + const parser = new SCSSParser(); + assertNode( + "border: thin solid 1px", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode("dummy: $color", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: blue", parser, parser._parseDeclaration.bind(parser)); + assertNode( + "dummy: (20 / $const)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "dummy: (20 / 20 + $const)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "dummy: func($red)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "dummy: func($red) !important", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "dummy: desaturate($red, 10%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "dummy: desaturate(16, 10%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "color: $base-color + #111", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "color: 100% / 2 + $ref", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "border: ($width * 2) solid black", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "property: $class", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "prop-erty: fnc($t, 10%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "width: (1em + 2em) * 3", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "color: #010203 + #040506", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + 'font-family: sans- + "serif"', + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "margin: 3px + 4px auto", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "color: hsl(0, 100%, 50%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "color: hsl($hue: 0, $saturation: 100%, $lightness: 50%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if($value == 'default', flex-gutter(), $value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if(true, !important, null)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "color: selector-replace(&, 1)", + parser, + parser._parseDeclaration.bind(parser), + ); + + assertNode( + "dummy: module.$color", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "dummy: (20 / module.$const)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "dummy: (20 / 20 + module.$const)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "dummy: module.func($red)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "dummy: module.func($red) !important", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "dummy: module.desaturate($red, 10%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "dummy: desaturate(module.$red, 10%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "dummy: module.desaturate(module.$red, 10%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "dummy: module.desaturate(16, 10%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "color: module.$base-color + #111", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "color: 100% / 2 + module.$ref", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "border: (module.$width * 2) solid black", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "property: module.$class", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "prop-erty: module.fnc($t, 10%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "prop-erty: fnc(module.$t, 10%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "prop-erty: module.fnc(module.$t, 10%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "width: (1em + 2em) * 3", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "color: #010203 + #040506", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + 'font-family: sans- + "serif"', + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "margin: 3px + 4px auto", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "color: color.hsl(0, 100%, 50%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "color: color.hsl($hue: 0, $saturation: 100%, $lightness: 50%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if(module.$value == 'default', flex-gutter(), $value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if($value == 'default', module.flex-gutter(), $value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if($value == 'default', flex-gutter(), module.$value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if(module.$value == 'default', module.flex-gutter(), $value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if($value == 'default', module.flex-gutter(), module.$value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if(module.$value == 'default', module.flex-gutter(), module.$value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "color: selector.replace(&, 1)", + parser, + parser._parseDeclaration.bind(parser), + ); + + assertError( + "fo = 8", + parser, + parser._parseDeclaration.bind(parser), + ParseError.ColonExpected, + ); + assertError( + "fo:", + parser, + parser._parseDeclaration.bind(parser), + ParseError.PropertyValueExpected, + ); + assertError( + "color: hsl($hue: 0,", + parser, + parser._parseDeclaration.bind(parser), + ParseError.ExpressionExpected, + ); + assertError( + "color: hsl($hue: 0", + parser, + parser._parseDeclaration.bind(parser), + ParseError.RightParenthesisExpected, + ); + }); + + test("Stylesheet", function () { + const parser = new SCSSParser(); + assertNode( + "$color: #F5F5F5;", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "$color: #F5F5F5; $color: #F5F5F5;", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "$color: #F5F5F5; $color: #F5F5F5; $color: #F5F5F5;", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "$color: #F5F5F5 !important;", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "#main { width: 97%; p, div { a { font-weight: bold; } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "a { &:hover { color: red; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "fo { font: 2px/3px { family: fantasy; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".foo { bar: { yoo: fantasy; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "selector { propsuffix: { nested: 1px; } rule: 1px; nested.selector { foo: 1; } nested:selector { foo: 2 }}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:s(1)}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@mixin keyframe { @keyframes name { @content; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@include keyframe { 10% { top: 3px; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".class{&--sub-class-with-ampersand{color: red;}}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertError( + "fo { font: 2px/3px { family } }", + parser, + parser._parseStylesheet.bind(parser), + ParseError.ColonExpected, + ); + + assertNode( + "legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:m.s(1)}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@include module.keyframe { 10% { top: 3px; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@import", function () { + const parser = new SCSSParser(); + assertNode('@import "test.css"', parser, parser._parseImport.bind(parser)); + assertNode( + '@import url("test.css")', + parser, + parser._parseImport.bind(parser), + ); + assertNode( + '@import "test.css", "bar.css"', + parser, + parser._parseImport.bind(parser), + ); + assertNode( + '@import "test.css", "bar.css" screen, projection', + parser, + parser._parseImport.bind(parser), + ); + assertNode( + 'foo { @import "test.css"; }', + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + '@import "test.css" "bar.css"', + parser, + parser._parseStylesheet.bind(parser), + ParseError.MediaQueryExpected, + ); + assertError( + '@import "test.css", screen', + parser, + parser._parseImport.bind(parser), + ParseError.URIOrStringExpected, + ); + assertError( + "@import", + parser, + parser._parseImport.bind(parser), + ParseError.URIOrStringExpected, + ); + assertNode( + '@import url("override.css") layer;', + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@layer", function () { + const parser = new SCSSParser(); + assertNode("@layer #{$layer} { }", parser, parser._parseLayer.bind(parser)); + }); + + test("@container", function () { + const parser = new SCSSParser(); + assertNode( + `@container (min-width: #{$minWidth}) { .scss-interpolation { line-height: 10cqh; } }`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.item-icon { @container (max-height: 100px) { .item-icon { display: none; } } }`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `:root { @container (max-height: 100px) { display: none;} }`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@use", function () { + const parser = new SCSSParser(); + assertNode('@use "test"', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" as foo', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" as *', parser, parser._parseUse.bind(parser)); + assertNode( + '@use "test" with ($foo: "test", $bar: 1)', + parser, + parser._parseUse.bind(parser), + ); + assertNode( + '@use "test" as foo with ($foo: "test", $bar: 1)', + parser, + parser._parseUse.bind(parser), + ); + + assertError( + "@use", + parser, + parser._parseUse.bind(parser), + ParseError.StringLiteralExpected, + ); + assertError( + '@use "test" foo', + parser, + parser._parseUse.bind(parser), + ParseError.UnknownKeyword, + ); + assertError( + '@use "test" as', + parser, + parser._parseUse.bind(parser), + ParseError.IdentifierOrWildcardExpected, + ); + assertError( + '@use "test" with', + parser, + parser._parseUse.bind(parser), + ParseError.LeftParenthesisExpected, + ); + assertError( + '@use "test" with ($foo)', + parser, + parser._parseUse.bind(parser), + ParseError.VariableValueExpected, + ); + assertError( + '@use "test" with ("bar")', + parser, + parser._parseUse.bind(parser), + ParseError.VariableNameExpected, + ); + assertError( + '@use "test" with ($foo: 1, "bar")', + parser, + parser._parseUse.bind(parser), + ParseError.VariableNameExpected, + ); + assertError( + '@use "test" with ($foo: "bar"', + parser, + parser._parseUse.bind(parser), + ParseError.RightParenthesisExpected, + ); + + assertNode( + '@forward "test"; @use "lib"', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@use "test"; @use "lib"', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '$test: "test"; @use "lib"', + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@forward", function () { + const parser = new SCSSParser(); + assertNode('@forward "test"', parser, parser._parseForward.bind(parser)); + assertNode( + '@forward "test" as foo-*', + parser, + parser._parseForward.bind(parser), + ); + assertNode( + '@forward "test" hide this', + parser, + parser._parseForward.bind(parser), + ); + assertNode( + '@forward "test" hide $that', + parser, + parser._parseForward.bind(parser), + ); + assertNode( + '@forward "test" hide this $that', + parser, + parser._parseForward.bind(parser), + ); + assertNode( + '@forward "test" hide this, $that', + parser, + parser._parseForward.bind(parser), + ); + assertNode( + '@forward "abstracts/functions" show px-to-rem, theme-color', + parser, + parser._parseForward.bind(parser), + ); + assertNode( + '@forward "test" show this', + parser, + parser._parseForward.bind(parser), + ); + assertNode( + '@forward "test" show $that', + parser, + parser._parseForward.bind(parser), + ); + assertNode( + '@forward "test" show this $that', + parser, + parser._parseForward.bind(parser), + ); + assertNode( + '@forward "test" as foo-* show this $that', + parser, + parser._parseForward.bind(parser), + ); + + assertError( + "@forward", + parser, + parser._parseForward.bind(parser), + ParseError.StringLiteralExpected, + ); + assertError( + '@forward "test" foo', + parser, + parser._parseForward.bind(parser), + ParseError.SemiColonExpected, + ); + assertError( + '@forward "test" as', + parser, + parser._parseForward.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + '@forward "test" as foo-', + parser, + parser._parseForward.bind(parser), + ParseError.WildcardExpected, + ); + assertError( + '@forward "test" as foo- *', + parser, + parser._parseForward.bind(parser), + ParseError.WildcardExpected, + ); + assertError( + '@forward "test" show', + parser, + parser._parseForward.bind(parser), + ParseError.IdentifierOrVariableExpected, + ); + assertError( + '@forward "test" hide', + parser, + parser._parseForward.bind(parser), + ParseError.IdentifierOrVariableExpected, + ); + + assertNode( + '@forward "test" with ( $black: #222 !default, $border-radius: 0.1rem !default )', + parser, + parser._parseForward.bind(parser), + ); + assertNode( + '@forward "../forms.scss" as components-* with ( $field-border: false )', + parser, + parser._parseForward.bind(parser), + ); // #145108 + + assertNode( + '@use "lib"; @forward "test"', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@forward "test"; @forward "lib"', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '$test: "test"; @forward "test"', + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@media", function () { + const parser = new SCSSParser(); + assertNode( + "@media screen { .sidebar { @media (orientation: landscape) { width: 500px; } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media #{$media} and ($feature: $value) {}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media only screen and #{$query} {}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "foo { bar { @media screen and (orientation: landscape) {}} }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media screen and (nth($query, 1): nth($query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + ".something { @media (max-width: 760px) { > .test { color: blue; } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".something { @media (max-width: 760px) { ~ div { display: block; } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".something { @media (max-width: 760px) { + div { display: block; } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media (max-width: 760px) { + div { display: block; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media (height <= 600px) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media (height >= 600px) { }", + parser, + parser._parseMedia.bind(parser), + ); + + assertNode( + "@media #{layout.$media} and ($feature: $value) {}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media #{$media} and (layout.$feature: $value) {}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media #{$media} and ($feature: layout.$value) {}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media #{layout.$media} and (layout.$feature: $value) {}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media #{$media} and (layout.$feature: layout.$value) {}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media #{layout.$media} and (layout.$feature: layout.$value) {}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media screen and (list.nth($query, 1): nth($query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (nth(list.$query, 1): nth($query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (nth($query, 1): list.nth($query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (nth($query, 1): nth(list.$query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (list.nth(list.$query, 1): nth($query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (list.nth($query, 1): list.nth($query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (list.nth($query, 1): nth(list.$query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (nth(list.$query, 1): list.nth($query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (nth(list.$query, 1): nth(list.$query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (nth($query, 1): list.nth(list.$query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (list.nth(list.$query, 1): list.nth($query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (nth(list.$query, 1): list.nth(list.$query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (list.nth(list.$query, 1): list.nth(list.$query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + }); + + test("@keyframe", function () { + const parser = new SCSSParser(); + assertNode( + "@keyframes name { @content; }", + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + "@keyframes name { @for $i from 0 through $steps { #{$i * (100%/$steps)} { transform: $rotate $translate; } } }", + parser, + parser._parseKeyframe.bind(parser), + ); // issue 42086 + assertNode( + '@keyframes test-keyframe { @for $i from 1 through 60 { $s: ($i * 100) / 60 + "%"; } }', + parser, + parser._parseKeyframe.bind(parser), + ); + + assertNode( + "@keyframes name { @for $i from 0 through m.$steps { #{$i * (100%/$steps)} { transform: $rotate $translate; } } }", + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + "@keyframes name { @function bar() { } }", + parser, + parser._parseKeyframe.bind(parser), + ); // #197742 + assertNode( + "@keyframes name { @include keyframe-mixin(); }", + parser, + parser._parseKeyframe.bind(parser), + ); // #197742 + }); + + test("@extend", function () { + const parser = new SCSSParser(); + assertNode( + ".themable { @extend %theme; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "foo { @extend .error; border-width: 3px; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "a.important { @extend .notice !optional; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".hoverlink { @extend a:hover; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".seriousError { @extend .error; @extend .attention; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "#context a%extreme { color: blue; } .notice { @extend %extreme }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media print { .error { } .seriousError { @extend .error; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@mixin error($a: false) { @extend .#{$a}; @extend ##{$a}; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".foo { @extend .text-center, .uppercase; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".foo { @extend .text-center, .uppercase, ; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".foo { @extend .text-center, .uppercase !optional ; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertError( + ".hoverlink { @extend }", + parser, + parser._parseStylesheet.bind(parser), + ParseError.SelectorExpected, + ); + assertError( + ".hoverlink { @extend %extreme !default }", + parser, + parser._parseStylesheet.bind(parser), + ParseError.UnknownKeyword, + ); + }); + + test("@debug", function () { + const parser = new SCSSParser(); + assertNode("@debug test;", parser, parser._parseStylesheet.bind(parser)); + assertNode( + "foo { @debug 1 + 4; nested { @warn 1 4; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@if $foo == 1 { @debug 1 + 4 }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@function setStyle($map, $object, $style) { @warn "The key ´#{$object} is not available in the map."; @return null; }', + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@if", function () { + const parser = new SCSSParser(); + assertNode( + "@if 1 + 1 == 2 { border: 1px solid; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "@if 5 < 3 { border: 2px dotted; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "@if null { border: 3px double; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "@if 1 <= $const { border: 3px; } @else { border: 4px; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "@if 1 >= (1 + $foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "p { @if $i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@if (index($_RESOURCES, "clean") != null) { @error "sdssd"; }', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@if $i == 1 { p { x: 3px; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertError( + "@if { border: 1px solid; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.ExpressionExpected, + ); + assertError( + "@if 1 }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.LeftCurlyExpected, + ); + + assertNode( + "@if 1 <= m.$const { border: 3px; } @else { border: 4px; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "@if 1 >= (1 + m.$foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "p { @if m.$i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "p { @if $i == 1 { x: 3px; } @else if m.$i == 1 { x: 4px; } @else { x: 4px; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "p { @if m.$i == 1 { x: 3px; } @else if m.$i == 1 { x: 4px; } @else { x: 4px; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@if (list.index($_RESOURCES, "clean") != null) { @error "sdssd"; }', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@if (index(m.$_RESOURCES, "clean") != null) { @error "sdssd"; }', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@if (list.index(m.$_RESOURCES, "clean") != null) { @error "sdssd"; }', + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@for", function () { + const parser = new SCSSParser(); + assertNode( + "@for $i from 1 to 5 { .item-#{$i} { width: 2em * $i; } }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "@for $k from 1 + $x through 5 + $x { }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertError( + "@for i from 0 to 4 {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.VariableNameExpected, + ); + assertError( + "@for $i to 4 {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + SCSSParseError.FromExpected, + ); + assertError( + "@for $i from 0 by 4 {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + SCSSParseError.ThroughOrToExpected, + ); + assertError( + "@for $i from {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.ExpressionExpected, + ); + assertError( + "@for $i from 0 to {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.ExpressionExpected, + ); + assertNode( + '@for $i from 1 through 60 { $s: $i + "%"; }', + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + + assertNode( + "@for $k from 1 + m.$x through 5 + $x { }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "@for $k from 1 + $x through 5 + m.$x { }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "@for $k from 1 + m.$x through 5 + m.$x { }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + }); + + test("@each", function () { + const parser = new SCSSParser(); + assertNode( + "@each $i in 1, 2, 3 { }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "@each $i in 1 2 3 { }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "@each $animal, $color, $cursor in (puma, black, default), (egret, white, move) {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertError( + "@each i in 4 {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.VariableNameExpected, + ); + assertError( + "@each $i from 4 {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + SCSSParseError.InExpected, + ); + assertError( + "@each $i in {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.ExpressionExpected, + ); + assertError( + "@each $animal, in (1, 1, 1), (2, 2, 2) {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.VariableNameExpected, + ); + }); + + test("@while", function () { + const parser = new SCSSParser(); + assertNode( + "@while $i < 0 { .item-#{$i} { width: 2em * $i; } $i: $i - 2; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertError( + "@while {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.ExpressionExpected, + ); + assertError( + "@while $i != 4", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.LeftCurlyExpected, + ); + assertError( + "@while ($i >= 4) {", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.RightCurlyExpected, + ); + }); + + test("@mixin", function () { + const parser = new SCSSParser(); + assertNode( + "@mixin large-text { font: { family: Arial; size: 20px; } color: #ff0000; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@mixin sexy-border($color, $width: 1in) { color: black; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@mixin box-shadow($shadows...) { -moz-box-shadow: $shadows; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@mixin apply-to-ie6-only { * html { @content; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@mixin #{foo}($color){}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@mixin foo ($i:4) { size: $i; @include wee ($i - 1); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@mixin foo ($i,) { }", + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + "@mixin $1 {}", + parser, + parser._parseStylesheet.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + "@mixin foo() i {}", + parser, + parser._parseStylesheet.bind(parser), + ParseError.LeftCurlyExpected, + ); + assertError( + "@mixin foo(1) {}", + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + "@mixin foo($color = 9) {}", + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + "@mixin foo($color)", + parser, + parser._parseStylesheet.bind(parser), + ParseError.LeftCurlyExpected, + ); + assertError( + "@mixin foo($color){", + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightCurlyExpected, + ); + assertError( + "@mixin foo($color,){", + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightCurlyExpected, + ); + }); + + test("@content", function () { + const parser = new SCSSParser(); + assertNode("@content", parser, parser._parseMixinContent.bind(parser)); + assertNode( + "@content($type)", + parser, + parser._parseMixinContent.bind(parser), + ); + }); + + test("@include", function () { + const parser = new SCSSParser(); + assertNode( + "p { @include sexy-border(blue); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".shadows { @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "$values: #ff0000, #00ff00, #0000ff; .primary { @include colors($values...); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@include colors(this("styles")...);', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".test { @include fontsize(16px, 21px !important); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "p { @include apply-to-ie6-only { #logo { background-image: url(/logo.gif); } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "p { @include foo($values,) }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "p { @include foo($values,); }", + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + "p { @include sexy-border blue", + parser, + parser._parseStylesheet.bind(parser), + ParseError.SemiColonExpected, + ); + assertError( + "p { @include sexy-border($values blue", + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + "p { @include }", + parser, + parser._parseStylesheet.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + "p { @include foo($values }", + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + "p { @include foo($values, }", + parser, + parser._parseStylesheet.bind(parser), + ParseError.ExpressionExpected, + ); + + assertNode( + "p { @include lib.sexy-border(blue); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".shadows { @include lib.box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "$values: #ff0000, #00ff00, #0000ff; .primary { @include lib.colors($values...); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".primary { @include colors(lib.$values...); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".primary { @include lib.colors(lib.$values...); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@include lib.colors(this("styles")...);', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@include colors(lib.this("styles")...);', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@include lib.colors(lib.this("styles")...);', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".test { @include lib.fontsize(16px, 21px !important); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "p { @include lib.apply-to-ie6-only { #logo { background-image: url(/logo.gif); } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "p { @include lib.foo($values,) }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "p { @include foo(lib.$values,) }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "p { @include lib.foo(m.$values,); }", + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + "p { @include foo.($values) }", + parser, + parser._parseStylesheet.bind(parser), + ParseError.IdentifierExpected, + ); + + assertNode( + '@include rtl("left") using ($dir) { margin-#{$dir}: 10px; }', + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@function", function () { + const parser = new SCSSParser(); + assertNode( + "@function grid-width($n) { @return $n * $grid-width + ($n - 1) * $gutter-width; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@function grid-width($n: 1, $e) { @return 0; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@function foo($total, $a) { @for $i from 0 to $total { } @return $grid; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@function foo() { @if (unit($a) == "%") and ($i == ($total - 1)) { @return 0; } @return 1; }', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@function is-even($int) { @if $int%2 == 0 { @return true; } @return false }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@function bar ($i) { @if $i > 0 { @return $i * bar($i - 1); } @return 1; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@function foo($a,) {} ", + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + "@function foo {} ", + parser, + parser._parseStylesheet.bind(parser), + ParseError.LeftParenthesisExpected, + ); + assertError( + "@function {} ", + parser, + parser._parseStylesheet.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + "@function foo($a $b) {} ", + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + "@function foo($a {} ", + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + "@function foo($a...) { @return; }", + parser, + parser._parseStylesheet.bind(parser), + ParseError.ExpressionExpected, + ); + assertError( + "@function foo($a:) {} ", + parser, + parser._parseStylesheet.bind(parser), + ParseError.VariableValueExpected, + ); + }); + + test("@at-root", function () { + const parser = new SCSSParser(); + assertNode( + "@mixin unify-parent($child) { @at-root #{selector.unify(&, $child)} { }}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@at-root #main2 .some-class { padding-left: calc( #{$a-variable} + 8px ); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media print { .page { @at-root (without: media) { } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media print { .page { @at-root (with: rule) { } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("Ruleset", function () { + const parser = new SCSSParser(); + assertNode( + ".selector { prop: erty $const 1px; }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".selector { prop: erty $const 1px m.$foo; }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "selector:active { property:value; nested:hover {}}", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode("selector {}", parser, parser._parseRuleset.bind(parser)); + assertNode( + "selector { property: declaration }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "selector { $variable: declaration }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "selector { nested {}}", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "selector { nested, a, b {}}", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "selector { property: value; property: $value; }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "selector { property: value; @keyframes foo {} @-moz-keyframes foo {}}", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode("foo|bar { }", parser, parser._parseRuleset.bind(parser)); + }); + + test("Nested Ruleset", function () { + const parser = new SCSSParser(); + assertNode( + ".class1 { $const: 1; .class { $const: 2; three: $const; const: 3; } one: $const; }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".class1 { $const: 1; .class { $const: m.$foo; } one: $const; }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".class1 { > .class2 { & > .class4 { rule1: v1; } } }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "foo { @at-root { display: none; } }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + 'th, tr { @at-root #{selector-replace(&, "tr")} { border-bottom: 0; } }', + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".foo { @supports(display: grid) { .bar { display: none; }}}", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".foo { @supports(display: grid) { display: none; }}", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".foo { @supports (position: sticky) { @media (min-width: map-get($grid-breakpoints, md)) { position: sticky; } }}", + parser, + parser._parseRuleset.bind(parser), + ); // issue #152 + }); + + test("Selector Interpolation", function () { + const parser = new SCSSParser(); + assertNode(".#{$name} { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".#{$name}-foo { }", parser, parser._parseRuleset.bind(parser)); + assertNode( + ".#{$name}-foo-3 { }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode(".#{$name}-1 { }", parser, parser._parseRuleset.bind(parser)); + assertNode( + ".sc-col#{$postfix}-2-1 { }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "p.#{$name} { #{$attr}-color: blue; }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "sans-#{serif} { a-#{1 + 2}-color-#{$attr}: blue; }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "##{f} .#{f} #{f}:#{f} { }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".foo-#{&} .foo-#{&-sub} { }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode(".-#{$variable} { }", parser, parser._parseRuleset.bind(parser)); + assertNode( + "#{&}([foo=bar][bar=foo]) { }", + parser, + parser._parseRuleset.bind(parser), + ); // #49589 + + assertNode( + ".#{module.$name} { }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".#{module.$name}-foo { }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".#{module.$name}-foo-3 { }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".#{module.$name}-1 { }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".sc-col#{module.$postfix}-2-1 { }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "p.#{module.$name} { #{$attr}-color: blue; }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "p.#{$name} { #{module.$attr}-color: blue; }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "p.#{module.$name} { #{module.$attr}-color: blue; }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + "sans-#{serif} { a-#{1 + 2}-color-#{module.$attr}: blue; }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".-#{module.$variable} { }", + parser, + parser._parseRuleset.bind(parser), + ); + }); + + test("Parent Selector", function () { + const parser = new SCSSParser(); + assertNode("&:hover", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&.float", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-bar", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-1", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&1", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-foo-1", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&&", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-10-thing", parser, parser._parseSimpleSelector.bind(parser)); + }); + + test("Selector Placeholder", function () { + const parser = new SCSSParser(); + assertNode("%hover", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("a%float", parser, parser._parseSimpleSelector.bind(parser)); + }); + + test("Map", function () { + const parser = new SCSSParser(); + assertNode( + "(key1: 1px, key2: solid + px, key3: (2+3))", + parser, + parser._parseExpr.bind(parser), + ); + assertNode("($key1 + 3: 1px)", parser, parser._parseExpr.bind(parser)); + }); + + test("Url", function () { + const parser = new SCSSParser(); + assertNode("url(foo())", parser, parser._parseURILiteral.bind(parser)); + assertNode( + "url('data:image/svg+xml;utf8,%3Csvg%20fill%3D%22%23' + $color + 'foo')", + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode( + "url(//yourdomain/yourpath.png)", + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode( + "url('http://msft.com')", + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode( + 'url("http://msft.com")', + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode( + 'url( "http://msft.com")', + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode( + 'url(\t"http://msft.com")', + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode( + 'url(\n"http://msft.com")', + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode( + 'url("http://msft.com"\n)', + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode('url("")', parser, parser._parseURILiteral.bind(parser)); + assertNode('uRL("")', parser, parser._parseURILiteral.bind(parser)); + assertNode('URL("")', parser, parser._parseURILiteral.bind(parser)); + assertNode( + "url(http://msft.com)", + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode("url()", parser, parser._parseURILiteral.bind(parser)); + assertNode( + "url('http://msft.com\n)", + parser, + parser._parseURILiteral.bind(parser), + ); + assertError( + 'url("http://msft.com"', + parser, + parser._parseURILiteral.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + "url(http://msft.com')", + parser, + parser._parseURILiteral.bind(parser), + ParseError.RightParenthesisExpected, + ); + }); + + test("@font-face", function () { + const parser = new SCSSParser(); + assertNode("@font-face {}", parser, parser._parseFontFace.bind(parser)); + assertNode( + "@font-face { src: url(http://test) }", + parser, + parser._parseFontFace.bind(parser), + ); + assertNode( + "@font-face { font-style: normal; font-stretch: normal; }", + parser, + parser._parseFontFace.bind(parser), + ); + assertNode( + "@font-face { unicode-range: U+0021-007F, u+1f49C, U+4??, U+??????; }", + parser, + parser._parseFontFace.bind(parser), + ); + assertError( + "@font-face { font-style: normal font-stretch: normal; }", + parser, + parser._parseFontFace.bind(parser), + ParseError.SemiColonExpected, + ); + }); +}); diff --git a/packages/vscode-css-languageservice/src/parser/scssParser.ts b/packages/parser/src/scssParser.ts similarity index 85% rename from packages/vscode-css-languageservice/src/parser/scssParser.ts rename to packages/parser/src/scssParser.ts index 384a6515..817968fb 100644 --- a/packages/vscode-css-languageservice/src/parser/scssParser.ts +++ b/packages/parser/src/scssParser.ts @@ -2,14 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -"use strict"; -import * as scssScanner from "./scssScanner"; -import { TokenType } from "./cssScanner"; -import * as cssParser from "./cssParser"; +import { ParseError } from "./cssErrors"; import * as nodes from "./cssNodes"; - +import * as cssParser from "./cssParser"; +import { TokenType } from "./cssScanner"; import { SCSSParseError } from "./scssErrors"; -import { ParseError } from "./cssErrors"; +import * as scssScanner from "./scssScanner"; /// /// A parser for scss @@ -20,7 +18,9 @@ export class SCSSParser extends cssParser.Parser { super(new scssScanner.SCSSScanner()); } - public _parseStylesheetStatement(isNested: boolean = false): nodes.Node | null { + public _parseStylesheetStatement( + isNested: boolean = false, + ): nodes.Node | null { if (this.peek(TokenType.AtKeyword)) { return ( this._parseWarnAndDebug() || // @warn, @debug and @error statements @@ -45,11 +45,17 @@ export class SCSSParser extends cssParser.Parser { const node = this.create(nodes.Import); this.consumeToken(); - if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) { + if ( + !node.addChild(this._parseURILiteral()) && + !node.addChild(this._parseStringLiteral()) + ) { return this.finish(node, ParseError.URIOrStringExpected); } while (this.accept(TokenType.Comma)) { - if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) { + if ( + !node.addChild(this._parseURILiteral()) && + !node.addChild(this._parseStringLiteral()) + ) { return this.finish(node, ParseError.URIOrStringExpected); } } @@ -58,12 +64,16 @@ export class SCSSParser extends cssParser.Parser { } // scss variables: $font-size: 12px; - public _parseVariableDeclaration(panic: TokenType[] = []): nodes.VariableDeclaration | null { + public _parseVariableDeclaration( + panic: TokenType[] = [], + ): nodes.VariableDeclaration | null { if (!this.peek(scssScanner.VariableName)) { return null; } - const node = this.create(nodes.VariableDeclaration); + const node = ( + this.create(nodes.VariableDeclaration) + ); if (!node.setVariable(this._parseVariable())) { return null; @@ -149,7 +159,11 @@ export class SCSSParser extends cssParser.Parser { return null; } - if (this.hasWhitespace() || !this.acceptDelim(".") || this.hasWhitespace()) { + if ( + this.hasWhitespace() || + !this.acceptDelim(".") || + this.hasWhitespace() + ) { this.restoreAtMark(pos); return null; } @@ -161,8 +175,14 @@ export class SCSSParser extends cssParser.Parser { return node; } - public _parseIdent(referenceTypes?: nodes.ReferenceType[]): nodes.Identifier | null { - if (!this.peek(TokenType.Ident) && !this.peek(scssScanner.InterpolationFunction) && !this.peekDelim("-")) { + public _parseIdent( + referenceTypes?: nodes.ReferenceType[], + ): nodes.Identifier | null { + if ( + !this.peek(TokenType.Ident) && + !this.peek(scssScanner.InterpolationFunction) && + !this.peekDelim("-") + ) { return null; } @@ -295,7 +315,12 @@ export class SCSSParser extends cssParser.Parser { } if (!this.accept(TokenType.Colon)) { - return this.finish(node, ParseError.ColonExpected, [TokenType.Colon], stopTokens || [TokenType.SemiColon]); + return this.finish( + node, + ParseError.ColonExpected, + [TokenType.Colon], + stopTokens || [TokenType.SemiColon], + ); } if (this.prevToken) { node.colonPosition = this.prevToken.offset; @@ -331,7 +356,11 @@ export class SCSSParser extends cssParser.Parser { if ( !node .getSelectors() - .addChild(this.peekDelim("%") ? this._parseSelectorPlaceholder() : this._parseSimpleSelector()) + .addChild( + this.peekDelim("%") + ? this._parseSelectorPlaceholder() + : this._parseSimpleSelector(), + ) ) { return this.finish(node, ParseError.SelectorExpected); } @@ -391,7 +420,9 @@ export class SCSSParser extends cssParser.Parser { return this.finish(node, ParseError.IdentifierExpected); } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.RightParenthesisExpected, [ + TokenType.CurlyR, + ]); } } return this.finish(node); @@ -415,7 +446,11 @@ export class SCSSParser extends cssParser.Parser { } public _parseWarnAndDebug(): nodes.Node | null { - if (!this.peekKeyword("@debug") && !this.peekKeyword("@warn") && !this.peekKeyword("@error")) { + if ( + !this.peekKeyword("@debug") && + !this.peekKeyword("@warn") && + !this.peekKeyword("@error") + ) { return null; } const node = this.createNode(nodes.NodeType.Debug); @@ -425,7 +460,9 @@ export class SCSSParser extends cssParser.Parser { } public _parseControlStatement( - parseStatement: () => nodes.Node | null = this._parseRuleSetDeclaration.bind(this), + parseStatement: () => nodes.Node | null = this._parseRuleSetDeclaration.bind( + this, + ), ): nodes.Node | null { if (!this.peek(TokenType.AtKeyword)) { return null; @@ -438,14 +475,18 @@ export class SCSSParser extends cssParser.Parser { ); } - public _parseIfStatement(parseStatement: () => nodes.Node | null): nodes.Node | null { + public _parseIfStatement( + parseStatement: () => nodes.Node | null, + ): nodes.Node | null { if (!this.peekKeyword("@if")) { return null; } return this._internalParseIfStatement(parseStatement); } - private _internalParseIfStatement(parseStatement: () => nodes.Node | null): nodes.IfStatement { + private _internalParseIfStatement( + parseStatement: () => nodes.Node | null, + ): nodes.IfStatement { const node = this.create(nodes.IfStatement); this.consumeToken(); // @if or if if (!node.setExpression(this._parseExpr(true))) { @@ -456,7 +497,9 @@ export class SCSSParser extends cssParser.Parser { if (this.peekIdent("if")) { node.setElseClause(this._internalParseIfStatement(parseStatement)); } else if (this.peek(TokenType.CurlyL)) { - const elseNode = this.create(nodes.ElseStatement); + const elseNode = ( + this.create(nodes.ElseStatement) + ); this._parseBody(elseNode, parseStatement); node.setElseClause(elseNode); } @@ -464,7 +507,9 @@ export class SCSSParser extends cssParser.Parser { return this.finish(node); } - public _parseForStatement(parseStatement: () => nodes.Node | null): nodes.Node | null { + public _parseForStatement( + parseStatement: () => nodes.Node | null, + ): nodes.Node | null { if (!this.peekKeyword("@for")) { return null; } @@ -472,25 +517,35 @@ export class SCSSParser extends cssParser.Parser { const node = this.create(nodes.ForStatement); this.consumeToken(); // @for if (!node.setVariable(this._parseVariable())) { - return this.finish(node, ParseError.VariableNameExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.VariableNameExpected, [ + TokenType.CurlyR, + ]); } if (!this.acceptIdent("from")) { return this.finish(node, SCSSParseError.FromExpected, [TokenType.CurlyR]); } if (!node.addChild(this._parseBinaryExpr())) { - return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.ExpressionExpected, [ + TokenType.CurlyR, + ]); } if (!this.acceptIdent("to") && !this.acceptIdent("through")) { - return this.finish(node, SCSSParseError.ThroughOrToExpected, [TokenType.CurlyR]); + return this.finish(node, SCSSParseError.ThroughOrToExpected, [ + TokenType.CurlyR, + ]); } if (!node.addChild(this._parseBinaryExpr())) { - return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.ExpressionExpected, [ + TokenType.CurlyR, + ]); } return this._parseBody(node, parseStatement); } - public _parseEachStatement(parseStatement: () => nodes.Node | null): nodes.Node | null { + public _parseEachStatement( + parseStatement: () => nodes.Node | null, + ): nodes.Node | null { if (!this.peekKeyword("@each")) { return null; } @@ -499,11 +554,15 @@ export class SCSSParser extends cssParser.Parser { this.consumeToken(); // @each const variables = node.getVariables(); if (!variables.addChild(this._parseVariable())) { - return this.finish(node, ParseError.VariableNameExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.VariableNameExpected, [ + TokenType.CurlyR, + ]); } while (this.accept(TokenType.Comma)) { if (!variables.addChild(this._parseVariable())) { - return this.finish(node, ParseError.VariableNameExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.VariableNameExpected, [ + TokenType.CurlyR, + ]); } } this.finish(variables); @@ -511,13 +570,17 @@ export class SCSSParser extends cssParser.Parser { return this.finish(node, SCSSParseError.InExpected, [TokenType.CurlyR]); } if (!node.addChild(this._parseExpr())) { - return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.ExpressionExpected, [ + TokenType.CurlyR, + ]); } return this._parseBody(node, parseStatement); } - public _parseWhileStatement(parseStatement: () => nodes.Node | null): nodes.Node | null { + public _parseWhileStatement( + parseStatement: () => nodes.Node | null, + ): nodes.Node | null { if (!this.peekKeyword("@while")) { return null; } @@ -525,7 +588,9 @@ export class SCSSParser extends cssParser.Parser { const node = this.create(nodes.WhileStatement); this.consumeToken(); // @while if (!node.addChild(this._parseBinaryExpr())) { - return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.ExpressionExpected, [ + TokenType.CurlyR, + ]); } return this._parseBody(node, parseStatement); @@ -545,15 +610,21 @@ export class SCSSParser extends cssParser.Parser { return null; } - const node = this.create(nodes.FunctionDeclaration); + const node = ( + this.create(nodes.FunctionDeclaration) + ); this.consumeToken(); // @function if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Function]))) { - return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.IdentifierExpected, [ + TokenType.CurlyR, + ]); } if (!this.accept(TokenType.ParenthesisL)) { - return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.LeftParenthesisExpected, [ + TokenType.CurlyR, + ]); } if (node.getParameters().addChild(this._parseParameterDeclaration())) { @@ -568,7 +639,9 @@ export class SCSSParser extends cssParser.Parser { } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.RightParenthesisExpected, [ + TokenType.CurlyR, + ]); } return this._parseBody(node, this._parseFunctionBodyDeclaration.bind(this)); @@ -597,7 +670,9 @@ export class SCSSParser extends cssParser.Parser { this.consumeToken(); if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Mixin]))) { - return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.IdentifierExpected, [ + TokenType.CurlyR, + ]); } if (this.accept(TokenType.ParenthesisL)) { @@ -606,14 +681,18 @@ export class SCSSParser extends cssParser.Parser { if (this.peek(TokenType.ParenthesisR)) { break; } - if (!node.getParameters().addChild(this._parseParameterDeclaration())) { + if ( + !node.getParameters().addChild(this._parseParameterDeclaration()) + ) { return this.finish(node, ParseError.VariableNameExpected); } } } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.RightParenthesisExpected, [ + TokenType.CurlyR, + ]); } } @@ -633,7 +712,12 @@ export class SCSSParser extends cssParser.Parser { if (this.accept(TokenType.Colon)) { if (!node.setDefaultValue(this._parseExpr(true))) { - return this.finish(node, ParseError.VariableValueExpected, [], [TokenType.Comma, TokenType.ParenthesisR]); + return this.finish( + node, + ParseError.VariableValueExpected, + [], + [TokenType.Comma, TokenType.ParenthesisR], + ); } } return this.finish(node); @@ -675,11 +759,17 @@ export class SCSSParser extends cssParser.Parser { // Could be module or mixin identifier, set as mixin as default. const firstIdent = this._parseIdent([nodes.ReferenceType.Mixin]); if (!node.setIdentifier(firstIdent)) { - return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.IdentifierExpected, [ + TokenType.CurlyR, + ]); } // Is a module accessor. - if (!this.hasWhitespace() && this.acceptDelim(".") && !this.hasWhitespace()) { + if ( + !this.hasWhitespace() && + this.acceptDelim(".") && + !this.hasWhitespace() + ) { const secondIdent = this._parseIdent([nodes.ReferenceType.Mixin]); const moduleToken = this.create(nodes.Module); @@ -692,7 +782,9 @@ export class SCSSParser extends cssParser.Parser { node.addChild(moduleToken); if (!secondIdent) { - return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.IdentifierExpected, [ + TokenType.CurlyR, + ]); } } @@ -720,24 +812,32 @@ export class SCSSParser extends cssParser.Parser { } public _parseMixinContentDeclaration() { - const node = this.create(nodes.MixinContentDeclaration); + const node = ( + this.create(nodes.MixinContentDeclaration) + ); if (this.acceptIdent("using")) { if (!this.accept(TokenType.ParenthesisL)) { - return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.CurlyL]); + return this.finish(node, ParseError.LeftParenthesisExpected, [ + TokenType.CurlyL, + ]); } if (node.getParameters().addChild(this._parseParameterDeclaration())) { while (this.accept(TokenType.Comma)) { if (this.peek(TokenType.ParenthesisR)) { break; } - if (!node.getParameters().addChild(this._parseParameterDeclaration())) { + if ( + !node.getParameters().addChild(this._parseParameterDeclaration()) + ) { return this.finish(node, ParseError.VariableNameExpected); } } } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyL]); + return this.finish(node, ParseError.RightParenthesisExpected, [ + TokenType.CurlyL, + ]); } } @@ -847,7 +947,9 @@ export class SCSSParser extends cssParser.Parser { } if (this.acceptIdent("as")) { - if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Module]))) { + if ( + !node.setIdentifier(this._parseIdent([nodes.ReferenceType.Module])) + ) { const hasWildcard = this.peekDelim("*"); if (hasWildcard) { const mnode = this.create(nodes.Identifier); @@ -862,11 +964,15 @@ export class SCSSParser extends cssParser.Parser { if (this.acceptIdent("with")) { if (!this.accept(TokenType.ParenthesisL)) { - return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.ParenthesisR]); + return this.finish(node, ParseError.LeftParenthesisExpected, [ + TokenType.ParenthesisR, + ]); } // First variable statement, no comma. - if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) { + if ( + !node.getParameters().addChild(this._parseModuleConfigDeclaration()) + ) { return this.finish(node, ParseError.VariableNameExpected); } @@ -874,7 +980,9 @@ export class SCSSParser extends cssParser.Parser { if (this.peek(TokenType.ParenthesisR)) { break; } - if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) { + if ( + !node.getParameters().addChild(this._parseModuleConfigDeclaration()) + ) { return this.finish(node, ParseError.VariableNameExpected); } } @@ -893,14 +1001,24 @@ export class SCSSParser extends cssParser.Parser { } public _parseModuleConfigDeclaration(): nodes.Node | null { - const node = this.create(nodes.ModuleConfiguration); + const node = ( + this.create(nodes.ModuleConfiguration) + ); if (!node.setIdentifier(this._parseVariable())) { return null; } - if (!this.accept(TokenType.Colon) || !node.setValue(this._parseExpr(true))) { - return this.finish(node, ParseError.VariableValueExpected, [], [TokenType.Comma, TokenType.ParenthesisR]); + if ( + !this.accept(TokenType.Colon) || + !node.setValue(this._parseExpr(true)) + ) { + return this.finish( + node, + ParseError.VariableValueExpected, + [], + [TokenType.Comma, TokenType.ParenthesisR], + ); } if (this.accept(TokenType.Exclamation)) { @@ -938,11 +1056,15 @@ export class SCSSParser extends cssParser.Parser { if (this.acceptIdent("with")) { if (!this.accept(TokenType.ParenthesisL)) { - return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.ParenthesisR]); + return this.finish(node, ParseError.LeftParenthesisExpected, [ + TokenType.ParenthesisR, + ]); } // First variable statement, no comma. - if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) { + if ( + !node.getParameters().addChild(this._parseModuleConfigDeclaration()) + ) { return this.finish(node, ParseError.VariableNameExpected); } @@ -950,7 +1072,9 @@ export class SCSSParser extends cssParser.Parser { if (this.peek(TokenType.ParenthesisR)) { break; } - if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) { + if ( + !node.getParameters().addChild(this._parseModuleConfigDeclaration()) + ) { return this.finish(node, ParseError.VariableNameExpected); } } diff --git a/packages/vscode-css-languageservice/src/parser/scssScanner.ts b/packages/parser/src/scssScanner.ts similarity index 98% rename from packages/vscode-css-languageservice/src/parser/scssScanner.ts rename to packages/parser/src/scssScanner.ts index bf321136..ec9f0201 100644 --- a/packages/vscode-css-languageservice/src/parser/scssScanner.ts +++ b/packages/parser/src/scssScanner.ts @@ -2,8 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -"use strict"; - import { TokenType, Scanner, IToken } from "./cssScanner"; const _FSL = "/".charCodeAt(0); @@ -19,7 +17,6 @@ const _BNG = "!".charCodeAt(0); const _LAN = "<".charCodeAt(0); const _RAN = ">".charCodeAt(0); const _DOT = ".".charCodeAt(0); -const _ATS = "@".charCodeAt(0); let customTokenValue = TokenType.CustomToken; diff --git a/packages/parser/src/utils/arrays.ts b/packages/parser/src/utils/arrays.ts new file mode 100644 index 00000000..ae03f485 --- /dev/null +++ b/packages/parser/src/utils/arrays.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +"use strict"; + +/** + * Takes a sorted array and a function p. The array is sorted in such a way that all elements where p(x) is false + * are located before all elements where p(x) is true. + * @returns the least x for which p(x) is true or array.length if no element fullfills the given function. + */ +export function findFirst(array: T[], p: (x: T) => boolean): number { + let low = 0, + high = array.length; + if (high === 0) { + return 0; // no children + } + while (low < high) { + const mid = Math.floor((low + high) / 2); + if (p(array[mid])) { + high = mid; + } else { + low = mid + 1; + } + } + return low; +} + +export function includes(array: T[], item: T): boolean { + return array.indexOf(item) !== -1; +} + +export function union(...arrays: T[][]): T[] { + const result: T[] = []; + for (const array of arrays) { + for (const item of array) { + if (!includes(result, item)) { + result.push(item); + } + } + } + return result; +} diff --git a/packages/parser/src/utils/objects.ts b/packages/parser/src/utils/objects.ts new file mode 100644 index 00000000..042b750b --- /dev/null +++ b/packages/parser/src/utils/objects.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function values(obj: { [s: string]: T }): T[] { + return Object.keys(obj).map((key) => obj[key]); +} + +export function isDefined(obj: T | undefined): obj is T { + return typeof obj !== "undefined"; +} diff --git a/packages/parser/src/utils/resources.ts b/packages/parser/src/utils/resources.ts new file mode 100644 index 00000000..4f2824d0 --- /dev/null +++ b/packages/parser/src/utils/resources.ts @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI, Utils } from "vscode-uri"; + +export function dirname(uriString: string): string { + return Utils.dirname(URI.parse(uriString)).toString(true); +} + +export function joinPath(uriString: string, ...paths: string[]): string { + return Utils.joinPath(URI.parse(uriString), ...paths).toString(true); +} diff --git a/packages/parser/src/utils/strings.ts b/packages/parser/src/utils/strings.ts new file mode 100644 index 00000000..aff58b56 --- /dev/null +++ b/packages/parser/src/utils/strings.ts @@ -0,0 +1,111 @@ +/* eslint-disable prefer-const */ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function startsWith(haystack: string, needle: string): boolean { + if (haystack.length < needle.length) { + return false; + } + + for (let i = 0; i < needle.length; i++) { + if (haystack[i] !== needle[i]) { + return false; + } + } + + return true; +} + +/** + * Determines if haystack ends with needle. + */ +export function endsWith(haystack: string, needle: string): boolean { + let diff = haystack.length - needle.length; + if (diff > 0) { + return haystack.lastIndexOf(needle) === diff; + } else if (diff === 0) { + return haystack === needle; + } else { + return false; + } +} + +/** + * Computes the difference score for two strings. More similar strings have a higher score. + * We use largest common subsequence dynamic programming approach but penalize in the end for length differences. + * Strings that have a large length difference will get a bad default score 0. + * Complexity - both time and space O(first.length * second.length) + * Dynamic programming LCS computation http://en.wikipedia.org/wiki/Longest_common_subsequence_problem + * + * @param first a string + * @param second a string + */ +export function difference( + first: string, + second: string, + maxLenDelta: number = 4, +): number { + let lengthDifference = Math.abs(first.length - second.length); + // We only compute score if length of the currentWord and length of entry.name are similar. + if (lengthDifference > maxLenDelta) { + return 0; + } + // Initialize LCS (largest common subsequence) matrix. + let LCS: number[][] = []; + let zeroArray: number[] = []; + let i: number, j: number; + for (i = 0; i < second.length + 1; ++i) { + zeroArray.push(0); + } + for (i = 0; i < first.length + 1; ++i) { + LCS.push(zeroArray); + } + for (i = 1; i < first.length + 1; ++i) { + for (j = 1; j < second.length + 1; ++j) { + if (first[i - 1] === second[j - 1]) { + LCS[i][j] = LCS[i - 1][j - 1] + 1; + } else { + LCS[i][j] = Math.max(LCS[i - 1][j], LCS[i][j - 1]); + } + } + } + return LCS[first.length][second.length] - Math.sqrt(lengthDifference); +} + +/** + * Limit of string length. + */ +export function getLimitedString(str: string, ellipsis = true): string { + if (!str) { + return ""; + } + if (str.length < 140) { + return str; + } + return str.slice(0, 140) + (ellipsis ? "\u2026" : ""); +} + +/** + * Limit of string length. + */ +export function trim(str: string, regexp: RegExp): string { + const m = regexp.exec(str); + if (m && m[0].length) { + return str.substr(0, str.length - m[0].length); + } + return str; +} + +export function repeat(value: string, count: number) { + let s = ""; + while (count > 0) { + if ((count & 1) === 1) { + s += value; + } + value += value; + count = count >>> 1; + } + return s; +} diff --git a/packages/parser/tsconfig.json b/packages/parser/tsconfig.json new file mode 100644 index 00000000..2b0655a9 --- /dev/null +++ b/packages/parser/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es2020", + "lib": ["ES2020", "WebWorker"], + "sourceMap": true, + "module": "commonjs", + "moduleResolution": "node", + "declaration": true, + "rootDir": "src", + "outDir": "dist", + "strict": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/parser/vitest.config.mts b/packages/parser/vitest.config.mts new file mode 100644 index 00000000..e2df9da5 --- /dev/null +++ b/packages/parser/vitest.config.mts @@ -0,0 +1,10 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + coverage: { + provider: "v8", + reporter: ["text", "json", "html"], + }, + }, +}); diff --git a/packages/vscode-css-languageservice/src/cssLanguageTypes.ts b/packages/vscode-css-languageservice/src/cssLanguageTypes.ts index 217466f6..4d3a1410 100644 --- a/packages/vscode-css-languageservice/src/cssLanguageTypes.ts +++ b/packages/vscode-css-languageservice/src/cssLanguageTypes.ts @@ -228,7 +228,6 @@ export interface LanguageServiceOptions { clientCapabilities?: ClientCapabilities; } -export type EntryStatus = "standard" | "experimental" | "nonstandard" | "obsolete"; export interface IReference { name: string; diff --git a/packages/vscode-css-languageservice/src/languageFacts/colors.ts b/packages/vscode-css-languageservice/src/languageFacts/colors.ts deleted file mode 100644 index de6573fa..00000000 --- a/packages/vscode-css-languageservice/src/languageFacts/colors.ts +++ /dev/null @@ -1,645 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Color } from "../cssLanguageService"; - -import * as nodes from "../parser/cssNodes"; - -import * as l10n from "@vscode/l10n"; - -const hexColorRegExp = /(^#([0-9A-F]{3}){1,2}$)|(^#([0-9A-F]{4}){1,2}$)/i; - -export const colorFunctions = [ - { - label: "rgb", - func: "rgb($red, $green, $blue)", - insertText: "rgb(${1:red}, ${2:green}, ${3:blue})", - desc: l10n.t("Creates a Color from red, green, and blue values."), - }, - { - label: "rgba", - func: "rgba($red, $green, $blue, $alpha)", - insertText: "rgba(${1:red}, ${2:green}, ${3:blue}, ${4:alpha})", - desc: l10n.t("Creates a Color from red, green, blue, and alpha values."), - }, - { - label: "rgb relative", - func: "rgb(from $color $red $green $blue)", - insertText: "rgb(from ${1:color} ${2:r} ${3:g} ${4:b})", - desc: l10n.t("Creates a Color from the red, green, and blue values of another Color."), - }, - { - label: "hsl", - func: "hsl($hue, $saturation, $lightness)", - insertText: "hsl(${1:hue}, ${2:saturation}, ${3:lightness})", - desc: l10n.t("Creates a Color from hue, saturation, and lightness values."), - }, - { - label: "hsla", - func: "hsla($hue, $saturation, $lightness, $alpha)", - insertText: "hsla(${1:hue}, ${2:saturation}, ${3:lightness}, ${4:alpha})", - desc: l10n.t("Creates a Color from hue, saturation, lightness, and alpha values."), - }, - { - label: "hsl relative", - func: "hsl(from $color $hue $saturation $lightness)", - insertText: "hsl(from ${1:color} ${2:h} ${3:s} ${4:l})", - desc: l10n.t("Creates a Color from the hue, saturation, and lightness values of another Color."), - }, - { - label: "hwb", - func: "hwb($hue $white $black)", - insertText: "hwb(${1:hue} ${2:white} ${3:black})", - desc: l10n.t("Creates a Color from hue, white, and black values."), - }, - { - label: "hwb relative", - func: "hwb(from $color $hue $white $black)", - insertText: "hwb(from ${1:color} ${2:h} ${3:w} ${4:b})", - desc: l10n.t("Creates a Color from the hue, white, and black values of another Color."), - }, - { - label: "lab", - func: "lab($lightness $a $b)", - insertText: "lab(${1:lightness} ${2:a} ${3:b})", - desc: l10n.t("Creates a Color from lightness, a, and b values."), - }, - { - label: "lab relative", - func: "lab(from $color $lightness $a $b)", - insertText: "lab(from ${1:color} ${2:l} ${3:a} ${4:b})", - desc: l10n.t("Creates a Color from the lightness, a, and b values of another Color."), - }, - { - label: "oklab", - func: "oklab($lightness $a $b)", - insertText: "oklab(${1:lightness} ${2:a} ${3:b})", - desc: l10n.t("Creates a Color from lightness, a, and b values."), - }, - { - label: "oklab relative", - func: "oklab(from $color $lightness $a $b)", - insertText: "oklab(from ${1:color} ${2:l} ${3:a} ${4:b})", - desc: l10n.t("Creates a Color from the lightness, a, and b values of another Color."), - }, - { - label: "lch", - func: "lch($lightness $chroma $hue)", - insertText: "lch(${1:lightness} ${2:chroma} ${3:hue})", - desc: l10n.t("Creates a Color from lightness, chroma, and hue values."), - }, - { - label: "lch relative", - func: "lch(from $color $lightness $chroma $hue)", - insertText: "lch(from ${1:color} ${2:l} ${3:c} ${4:h})", - desc: l10n.t("Creates a Color from the lightness, chroma, and hue values of another Color."), - }, - { - label: "oklch", - func: "oklch($lightness $chroma $hue)", - insertText: "oklch(${1:lightness} ${2:chroma} ${3:hue})", - desc: l10n.t("Creates a Color from lightness, chroma, and hue values."), - }, - { - label: "oklch relative", - func: "oklch(from $color $lightness $chroma $hue)", - insertText: "oklch(from ${1:color} ${2:l} ${3:c} ${4:h})", - desc: l10n.t("Creates a Color from the lightness, chroma, and hue values of another Color."), - }, - { - label: "color", - func: "color($color-space $red $green $blue)", - insertText: - "color(${1|srgb,srgb-linear,display-p3,a98-rgb,prophoto-rgb,rec2020,xyx,xyz-d50,xyz-d65|} ${2:red} ${3:green} ${4:blue})", - desc: l10n.t("Creates a Color in a specific color space from red, green, and blue values."), - }, - { - label: "color relative", - func: "color(from $color $color-space $red $green $blue)", - insertText: - "color(from ${1:color} ${2|srgb,srgb-linear,display-p3,a98-rgb,prophoto-rgb,rec2020,xyx,xyz-d50,xyz-d65|} ${3:r} ${4:g} ${5:b})", - desc: l10n.t("Creates a Color in a specific color space from the red, green, and blue values of another Color."), - }, - { - label: "color-mix", - func: "color-mix(in $color-space, $color $percentage, $color $percentage)", - insertText: - "color-mix(in ${1|srgb,srgb-linear,lab,oklab,xyz,xyz-d50,xyz-d65|}, ${3:color} ${4:percentage}, ${5:color} ${6:percentage})", - desc: l10n.t("Mix two colors together in a rectangular color space."), - }, - { - label: "color-mix hue", - func: "color-mix(in $color-space $interpolation-method hue, $color $percentage, $color $percentage)", - insertText: - "color-mix(in ${1|hsl,hwb,lch,oklch|} ${2|shorter hue,longer hue,increasing hue,decreasing hue|}, ${3:color} ${4:percentage}, ${5:color} ${6:percentage})", - desc: l10n.t("Mix two colors together in a polar color space."), - }, -]; - -const colorFunctionNameRegExp = /^(rgb|rgba|hsl|hsla|hwb)$/i; - -export const colors: { [name: string]: string } = { - aliceblue: "#f0f8ff", - antiquewhite: "#faebd7", - aqua: "#00ffff", - aquamarine: "#7fffd4", - azure: "#f0ffff", - beige: "#f5f5dc", - bisque: "#ffe4c4", - black: "#000000", - blanchedalmond: "#ffebcd", - blue: "#0000ff", - blueviolet: "#8a2be2", - brown: "#a52a2a", - burlywood: "#deb887", - cadetblue: "#5f9ea0", - chartreuse: "#7fff00", - chocolate: "#d2691e", - coral: "#ff7f50", - cornflowerblue: "#6495ed", - cornsilk: "#fff8dc", - crimson: "#dc143c", - cyan: "#00ffff", - darkblue: "#00008b", - darkcyan: "#008b8b", - darkgoldenrod: "#b8860b", - darkgray: "#a9a9a9", - darkgrey: "#a9a9a9", - darkgreen: "#006400", - darkkhaki: "#bdb76b", - darkmagenta: "#8b008b", - darkolivegreen: "#556b2f", - darkorange: "#ff8c00", - darkorchid: "#9932cc", - darkred: "#8b0000", - darksalmon: "#e9967a", - darkseagreen: "#8fbc8f", - darkslateblue: "#483d8b", - darkslategray: "#2f4f4f", - darkslategrey: "#2f4f4f", - darkturquoise: "#00ced1", - darkviolet: "#9400d3", - deeppink: "#ff1493", - deepskyblue: "#00bfff", - dimgray: "#696969", - dimgrey: "#696969", - dodgerblue: "#1e90ff", - firebrick: "#b22222", - floralwhite: "#fffaf0", - forestgreen: "#228b22", - fuchsia: "#ff00ff", - gainsboro: "#dcdcdc", - ghostwhite: "#f8f8ff", - gold: "#ffd700", - goldenrod: "#daa520", - gray: "#808080", - grey: "#808080", - green: "#008000", - greenyellow: "#adff2f", - honeydew: "#f0fff0", - hotpink: "#ff69b4", - indianred: "#cd5c5c", - indigo: "#4b0082", - ivory: "#fffff0", - khaki: "#f0e68c", - lavender: "#e6e6fa", - lavenderblush: "#fff0f5", - lawngreen: "#7cfc00", - lemonchiffon: "#fffacd", - lightblue: "#add8e6", - lightcoral: "#f08080", - lightcyan: "#e0ffff", - lightgoldenrodyellow: "#fafad2", - lightgray: "#d3d3d3", - lightgrey: "#d3d3d3", - lightgreen: "#90ee90", - lightpink: "#ffb6c1", - lightsalmon: "#ffa07a", - lightseagreen: "#20b2aa", - lightskyblue: "#87cefa", - lightslategray: "#778899", - lightslategrey: "#778899", - lightsteelblue: "#b0c4de", - lightyellow: "#ffffe0", - lime: "#00ff00", - limegreen: "#32cd32", - linen: "#faf0e6", - magenta: "#ff00ff", - maroon: "#800000", - mediumaquamarine: "#66cdaa", - mediumblue: "#0000cd", - mediumorchid: "#ba55d3", - mediumpurple: "#9370d8", - mediumseagreen: "#3cb371", - mediumslateblue: "#7b68ee", - mediumspringgreen: "#00fa9a", - mediumturquoise: "#48d1cc", - mediumvioletred: "#c71585", - midnightblue: "#191970", - mintcream: "#f5fffa", - mistyrose: "#ffe4e1", - moccasin: "#ffe4b5", - navajowhite: "#ffdead", - navy: "#000080", - oldlace: "#fdf5e6", - olive: "#808000", - olivedrab: "#6b8e23", - orange: "#ffa500", - orangered: "#ff4500", - orchid: "#da70d6", - palegoldenrod: "#eee8aa", - palegreen: "#98fb98", - paleturquoise: "#afeeee", - palevioletred: "#d87093", - papayawhip: "#ffefd5", - peachpuff: "#ffdab9", - peru: "#cd853f", - pink: "#ffc0cb", - plum: "#dda0dd", - powderblue: "#b0e0e6", - purple: "#800080", - red: "#ff0000", - rebeccapurple: "#663399", - rosybrown: "#bc8f8f", - royalblue: "#4169e1", - saddlebrown: "#8b4513", - salmon: "#fa8072", - sandybrown: "#f4a460", - seagreen: "#2e8b57", - seashell: "#fff5ee", - sienna: "#a0522d", - silver: "#c0c0c0", - skyblue: "#87ceeb", - slateblue: "#6a5acd", - slategray: "#708090", - slategrey: "#708090", - snow: "#fffafa", - springgreen: "#00ff7f", - steelblue: "#4682b4", - tan: "#d2b48c", - teal: "#008080", - thistle: "#d8bfd8", - tomato: "#ff6347", - turquoise: "#40e0d0", - violet: "#ee82ee", - wheat: "#f5deb3", - white: "#ffffff", - whitesmoke: "#f5f5f5", - yellow: "#ffff00", - yellowgreen: "#9acd32", -}; - -const colorsRegExp = new RegExp(`^(${Object.keys(colors).join("|")})$`, "i"); - -export const colorKeywords: { [name: string]: string } = { - currentColor: - "The value of the 'color' property. The computed value of the 'currentColor' keyword is the computed value of the 'color' property. If the 'currentColor' keyword is set on the 'color' property itself, it is treated as 'color:inherit' at parse time.", - transparent: - "Fully transparent. This keyword can be considered a shorthand for rgba(0,0,0,0) which is its computed value.", -}; - -const colorKeywordsRegExp = new RegExp(`^(${Object.keys(colorKeywords).join("|")})$`, "i"); - -function getNumericValue(node: nodes.Node, factor: number) { - const val = node.getText(); - const m = val.match(/^([-+]?[0-9]*\.?[0-9]+)(%?)$/); - if (m) { - if (m[2]) { - factor = 100.0; - } - const result = parseFloat(m[1]) / factor; - if (result >= 0 && result <= 1) { - return result; - } - } - throw new Error(); -} - -function getAngle(node: nodes.Node) { - const val = node.getText(); - const m = val.match(/^([-+]?[0-9]*\.?[0-9]+)(deg|rad|grad|turn)?$/); - if (m) { - switch (m[2]) { - case "deg": - return parseFloat(val) % 360; - case "rad": - return ((parseFloat(val) * 180) / Math.PI) % 360; - case "grad": - return (parseFloat(val) * 0.9) % 360; - case "turn": - return (parseFloat(val) * 360) % 360; - default: - if ("undefined" === typeof m[2]) { - return parseFloat(val) % 360; - } - } - } - - throw new Error(); -} - -export function isColorConstructor(node: nodes.Function): boolean { - const name = node.getName(); - if (!name) { - return false; - } - return colorFunctionNameRegExp.test(name); -} - -export function isColorString(s: string) { - return hexColorRegExp.test(s) || colorsRegExp.test(s) || colorKeywordsRegExp.test(s); -} - -/** - * Returns true if the node is a color value - either - * defined a hex number, as rgb or rgba function, or - * as color name. - */ -export function isColorValue(node: nodes.Node): boolean { - if (node.type === nodes.NodeType.HexColorValue) { - return true; - } else if (node.type === nodes.NodeType.Function) { - return isColorConstructor(node); - } else if (node.type === nodes.NodeType.Identifier) { - if (node.parent && node.parent.type !== nodes.NodeType.Term) { - return false; - } - const candidateColor = node.getText().toLowerCase(); - if (candidateColor === "none") { - return false; - } - if (colors[candidateColor]) { - return true; - } - } - return false; -} - -const Digit0 = 48; -const Digit9 = 57; -const A = 65; -const F = 70; -const a = 97; -const f = 102; - -export function hexDigit(charCode: number) { - if (charCode < Digit0) { - return 0; - } - if (charCode <= Digit9) { - return charCode - Digit0; - } - if (charCode < a) { - charCode += a - A; - } - if (charCode >= a && charCode <= f) { - return charCode - a + 10; - } - return 0; -} - -export function colorFromHex(text: string): Color | null { - if (text[0] !== "#") { - return null; - } - switch (text.length) { - case 4: - return { - red: (hexDigit(text.charCodeAt(1)) * 0x11) / 255.0, - green: (hexDigit(text.charCodeAt(2)) * 0x11) / 255.0, - blue: (hexDigit(text.charCodeAt(3)) * 0x11) / 255.0, - alpha: 1, - }; - case 5: - return { - red: (hexDigit(text.charCodeAt(1)) * 0x11) / 255.0, - green: (hexDigit(text.charCodeAt(2)) * 0x11) / 255.0, - blue: (hexDigit(text.charCodeAt(3)) * 0x11) / 255.0, - alpha: (hexDigit(text.charCodeAt(4)) * 0x11) / 255.0, - }; - case 7: - return { - red: (hexDigit(text.charCodeAt(1)) * 0x10 + hexDigit(text.charCodeAt(2))) / 255.0, - green: (hexDigit(text.charCodeAt(3)) * 0x10 + hexDigit(text.charCodeAt(4))) / 255.0, - blue: (hexDigit(text.charCodeAt(5)) * 0x10 + hexDigit(text.charCodeAt(6))) / 255.0, - alpha: 1, - }; - case 9: - return { - red: (hexDigit(text.charCodeAt(1)) * 0x10 + hexDigit(text.charCodeAt(2))) / 255.0, - green: (hexDigit(text.charCodeAt(3)) * 0x10 + hexDigit(text.charCodeAt(4))) / 255.0, - blue: (hexDigit(text.charCodeAt(5)) * 0x10 + hexDigit(text.charCodeAt(6))) / 255.0, - alpha: (hexDigit(text.charCodeAt(7)) * 0x10 + hexDigit(text.charCodeAt(8))) / 255.0, - }; - } - return null; -} - -export function colorFrom256RGB(red: number, green: number, blue: number, alpha: number = 1.0): Color { - return { - red: red / 255.0, - green: green / 255.0, - blue: blue / 255.0, - alpha, - }; -} - -export function colorFromHSL(hue: number, sat: number, light: number, alpha: number = 1.0): Color { - hue = hue / 60.0; - if (sat === 0) { - return { red: light, green: light, blue: light, alpha }; - } else { - const hueToRgb = (t1: number, t2: number, hue: number) => { - while (hue < 0) { - hue += 6; - } - while (hue >= 6) { - hue -= 6; - } - - if (hue < 1) { - return (t2 - t1) * hue + t1; - } - if (hue < 3) { - return t2; - } - if (hue < 4) { - return (t2 - t1) * (4 - hue) + t1; - } - return t1; - }; - const t2 = light <= 0.5 ? light * (sat + 1) : light + sat - light * sat; - const t1 = light * 2 - t2; - return { red: hueToRgb(t1, t2, hue + 2), green: hueToRgb(t1, t2, hue), blue: hueToRgb(t1, t2, hue - 2), alpha }; - } -} - -export interface HSLA { - h: number; - s: number; - l: number; - a: number; -} - -export function hslFromColor(rgba: Color): HSLA { - const r = rgba.red; - const g = rgba.green; - const b = rgba.blue; - const a = rgba.alpha; - - const max = Math.max(r, g, b); - const min = Math.min(r, g, b); - let h = 0; - let s = 0; - const l = (min + max) / 2; - const chroma = max - min; - - if (chroma > 0) { - s = Math.min(l <= 0.5 ? chroma / (2 * l) : chroma / (2 - 2 * l), 1); - - switch (max) { - case r: - h = (g - b) / chroma + (g < b ? 6 : 0); - break; - case g: - h = (b - r) / chroma + 2; - break; - case b: - h = (r - g) / chroma + 4; - break; - } - - h *= 60; - h = Math.round(h); - } - return { h, s, l, a }; -} - -export function colorFromHWB(hue: number, white: number, black: number, alpha: number = 1.0): Color { - if (white + black >= 1) { - const gray = white / (white + black); - return { red: gray, green: gray, blue: gray, alpha }; - } - - const rgb = colorFromHSL(hue, 1, 0.5, alpha); - let red = rgb.red; - red *= 1 - white - black; - red += white; - - let green = rgb.green; - green *= 1 - white - black; - green += white; - - let blue = rgb.blue; - blue *= 1 - white - black; - blue += white; - - return { - red: red, - green: green, - blue: blue, - alpha, - }; -} - -export interface HWBA { - h: number; - w: number; - b: number; - a: number; -} - -export function hwbFromColor(rgba: Color): HWBA { - const hsl = hslFromColor(rgba); - const white = Math.min(rgba.red, rgba.green, rgba.blue); - const black = 1 - Math.max(rgba.red, rgba.green, rgba.blue); - - return { - h: hsl.h, - w: white, - b: black, - a: hsl.a, - }; -} - -export function getColorValue(node: nodes.Node): Color | null { - if (node.type === nodes.NodeType.HexColorValue) { - const text = node.getText(); - return colorFromHex(text); - } else if (node.type === nodes.NodeType.Function) { - const functionNode = node; - const name = functionNode.getName(); - let colorValues = functionNode.getArguments().getChildren(); - if (colorValues.length === 1) { - const functionArg = colorValues[0].getChildren(); - if (functionArg.length === 1 && functionArg[0].type === nodes.NodeType.Expression) { - colorValues = functionArg[0].getChildren(); - if (colorValues.length === 3) { - const lastValue = colorValues[2]; - if (lastValue instanceof nodes.BinaryExpression) { - const left = lastValue.getLeft(), - right = lastValue.getRight(), - operator = lastValue.getOperator(); - if (left && right && operator && operator.matches("/")) { - colorValues = [colorValues[0], colorValues[1], left, right]; - } - } - } - } - } - if (!name || colorValues.length < 3 || colorValues.length > 4) { - return null; - } - try { - const alpha = colorValues.length === 4 ? getNumericValue(colorValues[3], 1) : 1; - if (name === "rgb" || name === "rgba") { - return { - red: getNumericValue(colorValues[0], 255.0), - green: getNumericValue(colorValues[1], 255.0), - blue: getNumericValue(colorValues[2], 255.0), - alpha, - }; - } else if (name === "hsl" || name === "hsla") { - const h = getAngle(colorValues[0]); - const s = getNumericValue(colorValues[1], 100.0); - const l = getNumericValue(colorValues[2], 100.0); - return colorFromHSL(h, s, l, alpha); - } else if (name === "hwb") { - const h = getAngle(colorValues[0]); - const w = getNumericValue(colorValues[1], 100.0); - const b = getNumericValue(colorValues[2], 100.0); - return colorFromHWB(h, w, b, alpha); - } - } catch (e) { - // parse error on numeric value - return null; - } - } else if (node.type === nodes.NodeType.Identifier) { - if (node.parent && node.parent.type !== nodes.NodeType.Term) { - return null; - } - const term = node.parent; - if (term && term.parent && term.parent.type === nodes.NodeType.BinaryExpression) { - const expression = term.parent; - if ( - expression.parent && - expression.parent.type === nodes.NodeType.ListEntry && - (expression.parent).key === expression - ) { - return null; - } - } - - const candidateColor = node.getText().toLowerCase(); - if (candidateColor === "none") { - return null; - } - const colorHex = colors[candidateColor]; - if (colorHex) { - return colorFromHex(colorHex); - } - } - return null; -} diff --git a/packages/vscode-css-languageservice/src/languageFacts/dataManager.ts b/packages/vscode-css-languageservice/src/languageFacts/dataManager.ts deleted file mode 100644 index 87750d85..00000000 --- a/packages/vscode-css-languageservice/src/languageFacts/dataManager.ts +++ /dev/null @@ -1,119 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -"use strict"; - -import { - ICSSDataProvider, - IPropertyData, - IAtDirectiveData, - IPseudoClassData, - IPseudoElementData, -} from "../cssLanguageTypes"; - -import * as objects from "../utils/objects"; -import { cssData } from "../data/webCustomData"; -import { CSSDataProvider } from "./dataProvider"; - -export class CSSDataManager { - private dataProviders: ICSSDataProvider[] = []; - - private _propertySet: { [k: string]: IPropertyData } = {}; - private _atDirectiveSet: { [k: string]: IAtDirectiveData } = {}; - private _pseudoClassSet: { [k: string]: IPseudoClassData } = {}; - private _pseudoElementSet: { [k: string]: IPseudoElementData } = {}; - - private _properties: IPropertyData[] = []; - private _atDirectives: IAtDirectiveData[] = []; - private _pseudoClasses: IPseudoClassData[] = []; - private _pseudoElements: IPseudoElementData[] = []; - - constructor(options?: { useDefaultDataProvider?: boolean; customDataProviders?: ICSSDataProvider[] }) { - this.setDataProviders(options?.useDefaultDataProvider !== false, options?.customDataProviders || []); - } - - setDataProviders(builtIn: boolean, providers: ICSSDataProvider[]) { - this.dataProviders = []; - if (builtIn) { - this.dataProviders.push(new CSSDataProvider(cssData)); - } - this.dataProviders.push(...providers); - this.collectData(); - } - - /** - * Collect all data & handle duplicates - */ - private collectData() { - this._propertySet = {}; - this._atDirectiveSet = {}; - this._pseudoClassSet = {}; - this._pseudoElementSet = {}; - - this.dataProviders.forEach((provider) => { - provider.provideProperties().forEach((p) => { - if (!this._propertySet[p.name]) { - this._propertySet[p.name] = p; - } - }); - provider.provideAtDirectives().forEach((p) => { - if (!this._atDirectiveSet[p.name]) { - this._atDirectiveSet[p.name] = p; - } - }); - provider.providePseudoClasses().forEach((p) => { - if (!this._pseudoClassSet[p.name]) { - this._pseudoClassSet[p.name] = p; - } - }); - provider.providePseudoElements().forEach((p) => { - if (!this._pseudoElementSet[p.name]) { - this._pseudoElementSet[p.name] = p; - } - }); - }); - - this._properties = objects.values(this._propertySet); - this._atDirectives = objects.values(this._atDirectiveSet); - this._pseudoClasses = objects.values(this._pseudoClassSet); - this._pseudoElements = objects.values(this._pseudoElementSet); - } - - getProperty(name: string): IPropertyData | undefined { - return this._propertySet[name]; - } - getAtDirective(name: string): IAtDirectiveData | undefined { - return this._atDirectiveSet[name]; - } - getPseudoClass(name: string): IPseudoClassData | undefined { - return this._pseudoClassSet[name]; - } - getPseudoElement(name: string): IPseudoElementData | undefined { - return this._pseudoElementSet[name]; - } - - getProperties(): IPropertyData[] { - return this._properties; - } - getAtDirectives(): IAtDirectiveData[] { - return this._atDirectives; - } - getPseudoClasses(): IPseudoClassData[] { - return this._pseudoClasses; - } - getPseudoElements(): IPseudoElementData[] { - return this._pseudoElements; - } - - isKnownProperty(name: string): boolean { - return name.toLowerCase() in this._propertySet; - } - - isStandardProperty(name: string): boolean { - return ( - this.isKnownProperty(name) && - (!this._propertySet[name.toLowerCase()].status || this._propertySet[name.toLowerCase()].status === "standard") - ); - } -} diff --git a/packages/vscode-css-languageservice/src/languageFacts/dataProvider.ts b/packages/vscode-css-languageservice/src/languageFacts/dataProvider.ts deleted file mode 100644 index 6413b695..00000000 --- a/packages/vscode-css-languageservice/src/languageFacts/dataProvider.ts +++ /dev/null @@ -1,90 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -"use strict"; - -import { - CSSDataV1, - ICSSDataProvider, - IPropertyData, - IAtDirectiveData, - IPseudoClassData, - IPseudoElementData, -} from "../cssLanguageTypes"; - -export class CSSDataProvider implements ICSSDataProvider { - private _properties: IPropertyData[] = []; - private _atDirectives: IAtDirectiveData[] = []; - private _pseudoClasses: IPseudoClassData[] = []; - private _pseudoElements: IPseudoElementData[] = []; - - /** - * Currently, unversioned data uses the V1 implementation - * In the future when the provider handles multiple versions of HTML custom data, - * use the latest implementation for unversioned data - */ - constructor(data: CSSDataV1) { - this.addData(data); - } - - provideProperties() { - return this._properties; - } - provideAtDirectives() { - return this._atDirectives; - } - providePseudoClasses() { - return this._pseudoClasses; - } - providePseudoElements() { - return this._pseudoElements; - } - - private addData(data: CSSDataV1) { - if (Array.isArray(data.properties)) { - for (const prop of data.properties) { - if (isPropertyData(prop)) { - this._properties.push(prop); - } - } - } - if (Array.isArray(data.atDirectives)) { - for (const prop of data.atDirectives) { - if (isAtDirective(prop)) { - this._atDirectives.push(prop); - } - } - } - if (Array.isArray(data.pseudoClasses)) { - for (const prop of data.pseudoClasses) { - if (isPseudoClassData(prop)) { - this._pseudoClasses.push(prop); - } - } - } - if (Array.isArray(data.pseudoElements)) { - for (const prop of data.pseudoElements) { - if (isPseudoElementData(prop)) { - this._pseudoElements.push(prop); - } - } - } - } -} - -function isPropertyData(d: any): d is IPropertyData { - return typeof d.name === "string"; -} - -function isAtDirective(d: any): d is IAtDirectiveData { - return typeof d.name === "string"; -} - -function isPseudoClassData(d: any): d is IPseudoClassData { - return typeof d.name === "string"; -} - -function isPseudoElementData(d: any): d is IPseudoElementData { - return typeof d.name === "string"; -} diff --git a/packages/vscode-css-languageservice/src/languageFacts/entry.ts b/packages/vscode-css-languageservice/src/languageFacts/entry.ts deleted file mode 100644 index 1787a470..00000000 --- a/packages/vscode-css-languageservice/src/languageFacts/entry.ts +++ /dev/null @@ -1,213 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -"use strict"; - -import { - EntryStatus, - IPropertyData, - IAtDirectiveData, - IPseudoClassData, - IPseudoElementData, - IValueData, - MarkupContent, - MarkupKind, - MarkedString, - HoverSettings, -} from "../cssLanguageTypes"; - -export interface Browsers { - E?: string; - FF?: string; - IE?: string; - O?: string; - C?: string; - S?: string; - count: number; - all: boolean; - onCodeComplete: boolean; -} - -export const browserNames = { - E: "Edge", - FF: "Firefox", - S: "Safari", - C: "Chrome", - IE: "IE", - O: "Opera", -}; - -function getEntryStatus(status: EntryStatus) { - switch (status) { - case "experimental": - return "⚠️ Property is experimental. Be cautious when using it.️\n\n"; - case "nonstandard": - return "🚨️ Property is nonstandard. Avoid using it.\n\n"; - case "obsolete": - return "🚨️️️ Property is obsolete. Avoid using it.\n\n"; - default: - return ""; - } -} - -export function getEntryDescription( - entry: IEntry2, - doesSupportMarkdown: boolean, - settings?: HoverSettings, -): MarkupContent | undefined { - let result: MarkupContent; - - if (doesSupportMarkdown) { - result = { - kind: "markdown", - value: getEntryMarkdownDescription(entry, settings), - }; - } else { - result = { - kind: "plaintext", - value: getEntryStringDescription(entry, settings), - }; - } - - if (result.value === "") { - return undefined; - } - - return result; -} - -export function textToMarkedString(text: string): MarkedString { - text = text.replace(/[\\`*_{}[\]()#+\-.!]/g, "\\$&"); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash - return text.replace(//g, ">"); -} - -function getEntryStringDescription(entry: IEntry2, settings?: HoverSettings): string { - if (!entry.description || entry.description === "") { - return ""; - } - - if (typeof entry.description !== "string") { - return entry.description.value; - } - - let result: string = ""; - - if (settings?.documentation !== false) { - if (entry.status) { - result += getEntryStatus(entry.status); - } - result += entry.description; - - const browserLabel = getBrowserLabel(entry.browsers); - if (browserLabel) { - result += "\n(" + browserLabel + ")"; - } - if ("syntax" in entry) { - result += `\n\nSyntax: ${entry.syntax}`; - } - } - if (entry.references && entry.references.length > 0 && settings?.references !== false) { - if (result.length > 0) { - result += "\n\n"; - } - result += entry.references - .map((r) => { - return `${r.name}: ${r.url}`; - }) - .join(" | "); - } - - return result; -} - -function getEntryMarkdownDescription(entry: IEntry2, settings?: HoverSettings): string { - if (!entry.description || entry.description === "") { - return ""; - } - - let result: string = ""; - if (settings?.documentation !== false) { - if (entry.status) { - result += getEntryStatus(entry.status); - } - - if (typeof entry.description === "string") { - result += textToMarkedString(entry.description); - } else { - result += - entry.description.kind === MarkupKind.Markdown - ? entry.description.value - : textToMarkedString(entry.description.value); - } - - const browserLabel = getBrowserLabel(entry.browsers); - if (browserLabel) { - result += "\n\n(" + textToMarkedString(browserLabel) + ")"; - } - if ("syntax" in entry && entry.syntax) { - result += `\n\nSyntax: ${textToMarkedString(entry.syntax)}`; - } - } - if (entry.references && entry.references.length > 0 && settings?.references !== false) { - if (result.length > 0) { - result += "\n\n"; - } - result += entry.references - .map((r) => { - return `[${r.name}](${r.url})`; - }) - .join(" | "); - } - - return result; -} - -/** - * Input is like `["E12","FF49","C47","IE","O"]` - * Output is like `Edge 12, Firefox 49, Chrome 47, IE, Opera` - */ -export function getBrowserLabel(browsers: string[] = []): string | null { - if (browsers.length === 0) { - return null; - } - - return browsers - .map((b) => { - let result = ""; - const matches = b.match(/([A-Z]+)(\d+)?/)!; - - const name = matches[1]; - const version = matches[2]; - - if (name in browserNames) { - result += browserNames[name as keyof typeof browserNames]; - } - if (version) { - result += " " + version; - } - return result; - }) - .join(", "); -} - -export type IEntry2 = IPropertyData | IAtDirectiveData | IPseudoClassData | IPseudoElementData | IValueData; - -/** - * Todo@Pine: Drop these two types and use IEntry2 - */ -export interface IEntry { - name: string; - description?: string | MarkupContent; - browsers?: string[]; - restrictions?: string[]; - status?: EntryStatus; - syntax?: string; - values?: IValue[]; -} - -export interface IValue { - name: string; - description?: string | MarkupContent; - browsers?: string[]; -} diff --git a/packages/vscode-css-languageservice/src/parser/cssErrors.ts b/packages/vscode-css-languageservice/src/parser/cssErrors.ts deleted file mode 100644 index 7df6d7a7..00000000 --- a/packages/vscode-css-languageservice/src/parser/cssErrors.ts +++ /dev/null @@ -1,58 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -"use strict"; - -import * as nodes from "./cssNodes"; - -import * as l10n from "@vscode/l10n"; - -export class CSSIssueType implements nodes.IRule { - id: string; - message: string; - - public constructor(id: string, message: string) { - this.id = id; - this.message = message; - } -} - -export const ParseError = { - NumberExpected: new CSSIssueType("css-numberexpected", l10n.t("number expected")), - ConditionExpected: new CSSIssueType("css-conditionexpected", l10n.t("condition expected")), - RuleOrSelectorExpected: new CSSIssueType("css-ruleorselectorexpected", l10n.t("at-rule or selector expected")), - DotExpected: new CSSIssueType("css-dotexpected", l10n.t("dot expected")), - ColonExpected: new CSSIssueType("css-colonexpected", l10n.t("colon expected")), - SemiColonExpected: new CSSIssueType("css-semicolonexpected", l10n.t("semi-colon expected")), - TermExpected: new CSSIssueType("css-termexpected", l10n.t("term expected")), - ExpressionExpected: new CSSIssueType("css-expressionexpected", l10n.t("expression expected")), - OperatorExpected: new CSSIssueType("css-operatorexpected", l10n.t("operator expected")), - IdentifierExpected: new CSSIssueType("css-identifierexpected", l10n.t("identifier expected")), - PercentageExpected: new CSSIssueType("css-percentageexpected", l10n.t("percentage expected")), - URIOrStringExpected: new CSSIssueType("css-uriorstringexpected", l10n.t("uri or string expected")), - URIExpected: new CSSIssueType("css-uriexpected", l10n.t("URI expected")), - VariableNameExpected: new CSSIssueType("css-varnameexpected", l10n.t("variable name expected")), - VariableValueExpected: new CSSIssueType("css-varvalueexpected", l10n.t("variable value expected")), - PropertyValueExpected: new CSSIssueType("css-propertyvalueexpected", l10n.t("property value expected")), - LeftCurlyExpected: new CSSIssueType("css-lcurlyexpected", l10n.t("{ expected")), - RightCurlyExpected: new CSSIssueType("css-rcurlyexpected", l10n.t("} expected")), - LeftSquareBracketExpected: new CSSIssueType("css-rbracketexpected", l10n.t("[ expected")), - RightSquareBracketExpected: new CSSIssueType("css-lbracketexpected", l10n.t("] expected")), - LeftParenthesisExpected: new CSSIssueType("css-lparentexpected", l10n.t("( expected")), - RightParenthesisExpected: new CSSIssueType("css-rparentexpected", l10n.t(") expected")), - CommaExpected: new CSSIssueType("css-commaexpected", l10n.t("comma expected")), - PageDirectiveOrDeclarationExpected: new CSSIssueType( - "css-pagedirordeclexpected", - l10n.t("page directive or declaraton expected"), - ), - UnknownAtRule: new CSSIssueType("css-unknownatrule", l10n.t("at-rule unknown")), - UnknownKeyword: new CSSIssueType("css-unknownkeyword", l10n.t("unknown keyword")), - SelectorExpected: new CSSIssueType("css-selectorexpected", l10n.t("selector expected")), - StringLiteralExpected: new CSSIssueType("css-stringliteralexpected", l10n.t("string literal expected")), - WhitespaceExpected: new CSSIssueType("css-whitespaceexpected", l10n.t("whitespace expected")), - MediaQueryExpected: new CSSIssueType("css-mediaqueryexpected", l10n.t("media query expected")), - IdentifierOrWildcardExpected: new CSSIssueType("css-idorwildcardexpected", l10n.t("identifier or wildcard expected")), - WildcardExpected: new CSSIssueType("css-wildcardexpected", l10n.t("wildcard expected")), - IdentifierOrVariableExpected: new CSSIssueType("css-idorvarexpected", l10n.t("identifier or variable expected")), -}; diff --git a/packages/vscode-css-languageservice/src/services/cssCompletion.ts b/packages/vscode-css-languageservice/src/services/cssCompletion.ts index c4fb815b..85954b04 100644 --- a/packages/vscode-css-languageservice/src/services/cssCompletion.ts +++ b/packages/vscode-css-languageservice/src/services/cssCompletion.ts @@ -1188,7 +1188,7 @@ export class CSSCompletion { } } -function isDeprecated(entry: languageFacts.IEntry2): boolean { +function isDeprecated(entry: languageFacts.IEntry): boolean { if (entry.status && (entry.status === "nonstandard" || entry.status === "obsolete")) { return true; } diff --git a/packages/vscode-css-languageservice/src/test/scss/parser.test.ts b/packages/vscode-css-languageservice/src/test/scss/parser.test.ts deleted file mode 100644 index c333c062..00000000 --- a/packages/vscode-css-languageservice/src/test/scss/parser.test.ts +++ /dev/null @@ -1,1156 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -"use strict"; - -import { SCSSParser } from "../../parser/scssParser"; -import { ParseError } from "../../parser/cssErrors"; -import { SCSSParseError } from "../../parser/scssErrors"; - -import { assertNode, assertError } from "../css/parser.test"; - -suite("SCSS - Parser", () => { - test("Comments", function () { - const parser = new SCSSParser(); - assertNode(" a { b: /* comment */ c }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - " a { b: /* comment \n * is several\n * lines long\n */ c }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode(" a { b: // single line comment\n c }", parser, parser._parseStylesheet.bind(parser)); - }); - - test("Variable", function () { - const parser = new SCSSParser(); - assertNode("$color", parser, parser._parseVariable.bind(parser)); - assertNode("$co42lor", parser, parser._parseVariable.bind(parser)); - assertNode("$-co42lor", parser, parser._parseVariable.bind(parser)); - }); - - test("Module variable", function () { - const parser = new SCSSParser(); - assertNode("module.$color", parser, parser._parseModuleMember.bind(parser)); - assertNode("module.$co42lor", parser, parser._parseModuleMember.bind(parser)); - assertNode("module.$-co42lor", parser, parser._parseModuleMember.bind(parser)); - assertNode("module.function()", parser, parser._parseModuleMember.bind(parser)); - - assertError("module.", parser, parser._parseModuleMember.bind(parser), ParseError.IdentifierOrVariableExpected); - }); - - test("VariableDeclaration", function () { - const parser = new SCSSParser(); - assertNode("$color: #F5F5F5", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$color: 0", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$color: 255", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$color: 25.5", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$color: 25px", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$color: 25.5px !default", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$text-color: green !global", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode( - '$_RESOURCES: append($_RESOURCES, "clean") !global', - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode("$footer-height: 40px !default !global", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode( - '$primary-font: "wf_SegoeUI","Segoe UI","Segoe","Segoe WP"', - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode("$color: red !important", parser, parser._parseVariableDeclaration.bind(parser)); - - assertError("$color: red !def", parser, parser._parseVariableDeclaration.bind(parser), ParseError.UnknownKeyword); - assertError( - "$color : !default", - parser, - parser._parseVariableDeclaration.bind(parser), - ParseError.VariableValueExpected, - ); - assertError("$color !default", parser, parser._parseVariableDeclaration.bind(parser), ParseError.ColonExpected); - }); - - test("Expr", function () { - const parser = new SCSSParser(); - assertNode("($const + 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($const - 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($const * 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($const / 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 - $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 * $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 / $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 / 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + $const + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($var1 + $var2)", parser, parser._parseExpr.bind(parser)); - assertNode("(($const + 5) * 2)", parser, parser._parseExpr.bind(parser)); - assertNode("(($const + (5 + 2)) * 2)", parser, parser._parseExpr.bind(parser)); - assertNode("($const + ((5 + 2) * 2))", parser, parser._parseExpr.bind(parser)); - assertNode("$color", parser, parser._parseExpr.bind(parser)); - assertNode("$color, $color", parser, parser._parseExpr.bind(parser)); - assertNode("$color, 42%", parser, parser._parseExpr.bind(parser)); - assertNode("$color, 42%, $color", parser, parser._parseExpr.bind(parser)); - assertNode("$color - ($color + 10%)", parser, parser._parseExpr.bind(parser)); - assertNode("($base + $filler)", parser, parser._parseExpr.bind(parser)); - assertNode("(100% / 2 + $filler)", parser, parser._parseExpr.bind(parser)); - assertNode("100% / 2 + $filler", parser, parser._parseExpr.bind(parser)); - assertNode("not ($v and $b) or $c", parser, parser._parseExpr.bind(parser)); - - assertNode("(module.$const + 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$const - 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$const * 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$const / 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 - module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 * module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 / module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + module.$const + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("($var1 + module.$var2)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$var1 + $var2)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$var1 + module.$var2)", parser, parser._parseExpr.bind(parser)); - assertNode("((module.$const + 5) * 2)", parser, parser._parseExpr.bind(parser)); - assertNode("((module.$const + (5 + 2)) * 2)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$const + ((5 + 2) * 2))", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, $color", parser, parser._parseExpr.bind(parser)); - assertNode("$color, module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, 42%", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, 42%, $color", parser, parser._parseExpr.bind(parser)); - assertNode("$color, 42%, module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, 42%, module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color - ($color + 10%)", parser, parser._parseExpr.bind(parser)); - assertNode("$color - (module.$color + 10%)", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color - (module.$color + 10%)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$base + $filler)", parser, parser._parseExpr.bind(parser)); - assertNode("($base + module.$filler)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$base + module.$filler)", parser, parser._parseExpr.bind(parser)); - assertNode("(100% / 2 + module.$filler)", parser, parser._parseExpr.bind(parser)); - assertNode("100% / 2 + module.$filler", parser, parser._parseExpr.bind(parser)); - assertNode("not (module.$v and $b) or $c", parser, parser._parseExpr.bind(parser)); - assertNode("not ($v and module.$b) or $c", parser, parser._parseExpr.bind(parser)); - assertNode("not ($v and $b) or module.$c", parser, parser._parseExpr.bind(parser)); - assertNode("not (module.$v and module.$b) or $c", parser, parser._parseExpr.bind(parser)); - assertNode("not (module.$v and $b) or module.$c", parser, parser._parseExpr.bind(parser)); - assertNode("not ($v and module.$b) or module.$c", parser, parser._parseExpr.bind(parser)); - assertNode("not (module.$v and module.$b) or module.$c", parser, parser._parseExpr.bind(parser)); - assertNode("not module.$v", parser, parser._parseExpr.bind(parser)); - - assertError("(20 + 20", parser, parser._parseExpr.bind(parser), ParseError.RightParenthesisExpected); - }); - - test("SCSSOperator", function () { - const parser = new SCSSParser(); - assertNode(">=", parser, parser._parseOperator.bind(parser)); - assertNode(">", parser, parser._parseOperator.bind(parser)); - assertNode("<", parser, parser._parseOperator.bind(parser)); - assertNode("<=", parser, parser._parseOperator.bind(parser)); - assertNode("==", parser, parser._parseOperator.bind(parser)); - assertNode("!=", parser, parser._parseOperator.bind(parser)); - assertNode("and", parser, parser._parseOperator.bind(parser)); - assertNode("+", parser, parser._parseOperator.bind(parser)); - assertNode("-", parser, parser._parseOperator.bind(parser)); - assertNode("*", parser, parser._parseOperator.bind(parser)); - assertNode("/", parser, parser._parseOperator.bind(parser)); - assertNode("%", parser, parser._parseOperator.bind(parser)); - assertNode("not", parser, parser._parseUnaryOperator.bind(parser)); - }); - - test("Interpolation", function () { - const parser = new SCSSParser(); - // assertNode('#{red}', parser, parser._parseIdent.bind(parser)); - // assertNode('#{$color}', parser, parser._parseIdent.bind(parser)); - // assertNode('#{3 + 4}', parser, parser._parseIdent.bind(parser)); - // assertNode('#{3 + #{3 + 4}}', parser, parser._parseIdent.bind(parser)); - // assertNode('#{$d}-style: 0', parser, parser._parseDeclaration.bind(parser)); - // assertNode('foo-#{$d}: 1', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{$d}-bar-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('foo-#{$d}-bar: 1', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('&:nth-child(#{$query}+1) { clear: $opposite-direction; }', parser, parser._parseRuleset.bind(parser)); - // assertNode('--#{$propname}: some-value', parser, parser._parseDeclaration.bind(parser)); - // assertNode('some-property: var(--#{$propname})', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{}', parser, parser._parseIdent.bind(parser)); - // assertError('#{1 + 2', parser, parser._parseIdent.bind(parser), ParseError.RightCurlyExpected); - - // assertNode('#{module.$color}', parser, parser._parseIdent.bind(parser)); - // assertNode('#{module.$d}-style: 0', parser, parser._parseDeclaration.bind(parser)); - // assertNode('foo-#{module.$d}: 1', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{module.$d}-bar-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{$d}-bar-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{module.$d}-bar-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('foo-#{module.$d}-bar: 1', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{module.$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{$d}-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{module.$d}-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('&:nth-child(#{module.$query}+1) { clear: $opposite-direction; }', parser, parser._parseRuleset.bind(parser)); - // assertNode('&:nth-child(#{$query}+1) { clear: module.$opposite-direction; }', parser, parser._parseRuleset.bind(parser)); - // assertNode('&:nth-child(#{module.$query}+1) { clear: module.$opposite-direction; }', parser, parser._parseRuleset.bind(parser)); - assertNode("--#{module.$propname}: some-value", parser, parser._parseDeclaration.bind(parser)); - // assertNode('some-property: var(--#{module.$propname})', parser, parser._parseDeclaration.bind(parser)); - // assertNode('@supports #{$val} { }', parser, parser._parseStylesheet.bind(parser)); // #88283 - // assertNode('.mb-#{$i}0np {} .push-up-#{$i}0 {} .mt-#{$i}0vh {}', parser, parser._parseStylesheet.bind(parser)); - }); - - test("Declaration", function () { - const parser = new SCSSParser(); - assertNode("border: thin solid 1px", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: $color", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: blue", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: (20 / $const)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: (20 / 20 + $const)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: func($red)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: func($red) !important", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: desaturate($red, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: desaturate(16, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: $base-color + #111", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: 100% / 2 + $ref", parser, parser._parseDeclaration.bind(parser)); - assertNode("border: ($width * 2) solid black", parser, parser._parseDeclaration.bind(parser)); - assertNode("property: $class", parser, parser._parseDeclaration.bind(parser)); - assertNode("prop-erty: fnc($t, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("width: (1em + 2em) * 3", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: #010203 + #040506", parser, parser._parseDeclaration.bind(parser)); - assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser)); - assertNode("margin: 3px + 4px auto", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: hsl(0, 100%, 50%)", parser, parser._parseDeclaration.bind(parser)); - assertNode( - "color: hsl($hue: 0, $saturation: 100%, $lightness: 50%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode("foo: if($value == 'default', flex-gutter(), $value)", parser, parser._parseDeclaration.bind(parser)); - assertNode("foo: if(true, !important, null)", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: selector-replace(&, 1)", parser, parser._parseDeclaration.bind(parser)); - - assertNode("dummy: module.$color", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: (20 / module.$const)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: (20 / 20 + module.$const)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: module.func($red)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: module.func($red) !important", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: module.desaturate($red, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: desaturate(module.$red, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: module.desaturate(module.$red, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: module.desaturate(16, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: module.$base-color + #111", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: 100% / 2 + module.$ref", parser, parser._parseDeclaration.bind(parser)); - assertNode("border: (module.$width * 2) solid black", parser, parser._parseDeclaration.bind(parser)); - assertNode("property: module.$class", parser, parser._parseDeclaration.bind(parser)); - assertNode("prop-erty: module.fnc($t, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("prop-erty: fnc(module.$t, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("prop-erty: module.fnc(module.$t, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("width: (1em + 2em) * 3", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: #010203 + #040506", parser, parser._parseDeclaration.bind(parser)); - assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser)); - assertNode("margin: 3px + 4px auto", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: color.hsl(0, 100%, 50%)", parser, parser._parseDeclaration.bind(parser)); - assertNode( - "color: color.hsl($hue: 0, $saturation: 100%, $lightness: 50%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if(module.$value == 'default', flex-gutter(), $value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if($value == 'default', module.flex-gutter(), $value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if($value == 'default', flex-gutter(), module.$value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if(module.$value == 'default', module.flex-gutter(), $value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if($value == 'default', module.flex-gutter(), module.$value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if(module.$value == 'default', module.flex-gutter(), module.$value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode("color: selector.replace(&, 1)", parser, parser._parseDeclaration.bind(parser)); - - assertError("fo = 8", parser, parser._parseDeclaration.bind(parser), ParseError.ColonExpected); - assertError("fo:", parser, parser._parseDeclaration.bind(parser), ParseError.PropertyValueExpected); - assertError("color: hsl($hue: 0,", parser, parser._parseDeclaration.bind(parser), ParseError.ExpressionExpected); - assertError( - "color: hsl($hue: 0", - parser, - parser._parseDeclaration.bind(parser), - ParseError.RightParenthesisExpected, - ); - }); - - test("Stylesheet", function () { - const parser = new SCSSParser(); - assertNode("$color: #F5F5F5;", parser, parser._parseStylesheet.bind(parser)); - assertNode("$color: #F5F5F5; $color: #F5F5F5;", parser, parser._parseStylesheet.bind(parser)); - assertNode("$color: #F5F5F5; $color: #F5F5F5; $color: #F5F5F5;", parser, parser._parseStylesheet.bind(parser)); - assertNode("$color: #F5F5F5 !important;", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "#main { width: 97%; p, div { a { font-weight: bold; } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("a { &:hover { color: red; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode("fo { font: 2px/3px { family: fantasy; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode(".foo { bar: { yoo: fantasy; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "selector { propsuffix: { nested: 1px; } rule: 1px; nested.selector { foo: 1; } nested:selector { foo: 2 }}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:s(1)}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@mixin keyframe { @keyframes name { @content; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode("@include keyframe { 10% { top: 3px; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode(".class{&--sub-class-with-ampersand{color: red;}}", parser, parser._parseStylesheet.bind(parser)); - assertError( - "fo { font: 2px/3px { family } }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.ColonExpected, - ); - - assertNode( - "legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:m.s(1)}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@include module.keyframe { 10% { top: 3px; } }", parser, parser._parseStylesheet.bind(parser)); - }); - - test("@import", function () { - const parser = new SCSSParser(); - assertNode('@import "test.css"', parser, parser._parseImport.bind(parser)); - assertNode('@import url("test.css")', parser, parser._parseImport.bind(parser)); - assertNode('@import "test.css", "bar.css"', parser, parser._parseImport.bind(parser)); - assertNode('@import "test.css", "bar.css" screen, projection', parser, parser._parseImport.bind(parser)); - assertNode('foo { @import "test.css"; }', parser, parser._parseStylesheet.bind(parser)); - - assertError( - '@import "test.css" "bar.css"', - parser, - parser._parseStylesheet.bind(parser), - ParseError.MediaQueryExpected, - ); - assertError('@import "test.css", screen', parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); - assertError("@import", parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); - assertNode('@import url("override.css") layer;', parser, parser._parseStylesheet.bind(parser)); - }); - - test("@layer", function () { - const parser = new SCSSParser(); - assertNode("@layer #{$layer} { }", parser, parser._parseLayer.bind(parser)); - }); - - test("@container", function () { - const parser = new SCSSParser(); - assertNode( - `@container (min-width: #{$minWidth}) { .scss-interpolation { line-height: 10cqh; } }`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `.item-icon { @container (max-height: 100px) { .item-icon { display: none; } } }`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `:root { @container (max-height: 100px) { display: none;} }`, - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@use", function () { - const parser = new SCSSParser(); - assertNode('@use "test"', parser, parser._parseUse.bind(parser)); - assertNode('@use "test" as foo', parser, parser._parseUse.bind(parser)); - assertNode('@use "test" as *', parser, parser._parseUse.bind(parser)); - assertNode('@use "test" with ($foo: "test", $bar: 1)', parser, parser._parseUse.bind(parser)); - assertNode('@use "test" as foo with ($foo: "test", $bar: 1)', parser, parser._parseUse.bind(parser)); - - assertError("@use", parser, parser._parseUse.bind(parser), ParseError.StringLiteralExpected); - assertError('@use "test" foo', parser, parser._parseUse.bind(parser), ParseError.UnknownKeyword); - assertError('@use "test" as', parser, parser._parseUse.bind(parser), ParseError.IdentifierOrWildcardExpected); - assertError('@use "test" with', parser, parser._parseUse.bind(parser), ParseError.LeftParenthesisExpected); - assertError('@use "test" with ($foo)', parser, parser._parseUse.bind(parser), ParseError.VariableValueExpected); - assertError('@use "test" with ("bar")', parser, parser._parseUse.bind(parser), ParseError.VariableNameExpected); - assertError( - '@use "test" with ($foo: 1, "bar")', - parser, - parser._parseUse.bind(parser), - ParseError.VariableNameExpected, - ); - assertError( - '@use "test" with ($foo: "bar"', - parser, - parser._parseUse.bind(parser), - ParseError.RightParenthesisExpected, - ); - - assertNode('@forward "test"; @use "lib"', parser, parser._parseStylesheet.bind(parser)); - assertNode('@use "test"; @use "lib"', parser, parser._parseStylesheet.bind(parser)); - assertNode('$test: "test"; @use "lib"', parser, parser._parseStylesheet.bind(parser)); - }); - - test("@forward", function () { - const parser = new SCSSParser(); - assertNode('@forward "test"', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" as foo-*', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" hide this', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" hide $that', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" hide this $that', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" hide this, $that', parser, parser._parseForward.bind(parser)); - assertNode('@forward "abstracts/functions" show px-to-rem, theme-color', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" show this', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" show $that', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" show this $that', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" as foo-* show this $that', parser, parser._parseForward.bind(parser)); - - assertError("@forward", parser, parser._parseForward.bind(parser), ParseError.StringLiteralExpected); - assertError('@forward "test" foo', parser, parser._parseForward.bind(parser), ParseError.SemiColonExpected); - assertError('@forward "test" as', parser, parser._parseForward.bind(parser), ParseError.IdentifierExpected); - assertError('@forward "test" as foo-', parser, parser._parseForward.bind(parser), ParseError.WildcardExpected); - assertError('@forward "test" as foo- *', parser, parser._parseForward.bind(parser), ParseError.WildcardExpected); - assertError( - '@forward "test" show', - parser, - parser._parseForward.bind(parser), - ParseError.IdentifierOrVariableExpected, - ); - assertError( - '@forward "test" hide', - parser, - parser._parseForward.bind(parser), - ParseError.IdentifierOrVariableExpected, - ); - - assertNode( - '@forward "test" with ( $black: #222 !default, $border-radius: 0.1rem !default )', - parser, - parser._parseForward.bind(parser), - ); - assertNode( - '@forward "../forms.scss" as components-* with ( $field-border: false )', - parser, - parser._parseForward.bind(parser), - ); // #145108 - - assertNode('@use "lib"; @forward "test"', parser, parser._parseStylesheet.bind(parser)); - assertNode('@forward "test"; @forward "lib"', parser, parser._parseStylesheet.bind(parser)); - assertNode('$test: "test"; @forward "test"', parser, parser._parseStylesheet.bind(parser)); - }); - - test("@media", function () { - const parser = new SCSSParser(); - assertNode( - "@media screen { .sidebar { @media (orientation: landscape) { width: 500px; } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@media #{$media} and ($feature: $value) {}", parser, parser._parseStylesheet.bind(parser)); - assertNode("@media only screen and #{$query} {}", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "foo { bar { @media screen and (orientation: landscape) {}} }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@media screen and (nth($query, 1): nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); - assertNode( - ".something { @media (max-width: 760px) { > .test { color: blue; } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".something { @media (max-width: 760px) { ~ div { display: block; } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".something { @media (max-width: 760px) { + div { display: block; } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@media (max-width: 760px) { + div { display: block; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode("@media (height <= 600px) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media (height >= 600px) { }", parser, parser._parseMedia.bind(parser)); - - assertNode("@media #{layout.$media} and ($feature: $value) {}", parser, parser._parseStylesheet.bind(parser)); - assertNode("@media #{$media} and (layout.$feature: $value) {}", parser, parser._parseStylesheet.bind(parser)); - assertNode("@media #{$media} and ($feature: layout.$value) {}", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "@media #{layout.$media} and (layout.$feature: $value) {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media #{$media} and (layout.$feature: layout.$value) {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media #{layout.$media} and (layout.$feature: layout.$value) {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@media screen and (list.nth($query, 1): nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media screen and (nth(list.$query, 1): nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media screen and (nth($query, 1): list.nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media screen and (nth($query, 1): nth(list.$query, 2)) { }", parser, parser._parseMedia.bind(parser)); - assertNode( - "@media screen and (list.nth(list.$query, 1): nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (list.nth($query, 1): list.nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (list.nth($query, 1): nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (nth(list.$query, 1): list.nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (nth(list.$query, 1): nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (nth($query, 1): list.nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (list.nth(list.$query, 1): list.nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (nth(list.$query, 1): list.nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (list.nth(list.$query, 1): list.nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - }); - - test("@keyframe", function () { - const parser = new SCSSParser(); - assertNode("@keyframes name { @content; }", parser, parser._parseKeyframe.bind(parser)); - assertNode( - "@keyframes name { @for $i from 0 through $steps { #{$i * (100%/$steps)} { transform: $rotate $translate; } } }", - parser, - parser._parseKeyframe.bind(parser), - ); // issue 42086 - assertNode( - '@keyframes test-keyframe { @for $i from 1 through 60 { $s: ($i * 100) / 60 + "%"; } }', - parser, - parser._parseKeyframe.bind(parser), - ); - - assertNode( - "@keyframes name { @for $i from 0 through m.$steps { #{$i * (100%/$steps)} { transform: $rotate $translate; } } }", - parser, - parser._parseKeyframe.bind(parser), - ); - assertNode("@keyframes name { @function bar() { } }", parser, parser._parseKeyframe.bind(parser)); // #197742 - assertNode("@keyframes name { @include keyframe-mixin(); }", parser, parser._parseKeyframe.bind(parser)); // #197742 - }); - - test("@extend", function () { - const parser = new SCSSParser(); - assertNode(".themable { @extend %theme; }", parser, parser._parseStylesheet.bind(parser)); - assertNode("foo { @extend .error; border-width: 3px; }", parser, parser._parseStylesheet.bind(parser)); - assertNode("a.important { @extend .notice !optional; }", parser, parser._parseStylesheet.bind(parser)); - assertNode(".hoverlink { @extend a:hover; }", parser, parser._parseStylesheet.bind(parser)); - assertNode(".seriousError { @extend .error; @extend .attention; }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "#context a%extreme { color: blue; } .notice { @extend %extreme }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media print { .error { } .seriousError { @extend .error; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@mixin error($a: false) { @extend .#{$a}; @extend ##{$a}; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode(".foo { @extend .text-center, .uppercase; }", parser, parser._parseStylesheet.bind(parser)); - assertNode(".foo { @extend .text-center, .uppercase, ; }", parser, parser._parseStylesheet.bind(parser)); - assertNode(".foo { @extend .text-center, .uppercase !optional ; }", parser, parser._parseStylesheet.bind(parser)); - assertError(".hoverlink { @extend }", parser, parser._parseStylesheet.bind(parser), ParseError.SelectorExpected); - assertError( - ".hoverlink { @extend %extreme !default }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.UnknownKeyword, - ); - }); - - test("@debug", function () { - const parser = new SCSSParser(); - assertNode("@debug test;", parser, parser._parseStylesheet.bind(parser)); - assertNode("foo { @debug 1 + 4; nested { @warn 1 4; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode("@if $foo == 1 { @debug 1 + 4 }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - '@function setStyle($map, $object, $style) { @warn "The key ´#{$object} is not available in the map."; @return null; }', - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@if", function () { - const parser = new SCSSParser(); - assertNode("@if 1 + 1 == 2 { border: 1px solid; }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode("@if 5 < 3 { border: 2px dotted; }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode("@if null { border: 3px double; }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode( - "@if 1 <= $const { border: 3px; } @else { border: 4px; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "@if 1 >= (1 + $foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "p { @if $i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@if (index($_RESOURCES, "clean") != null) { @error "sdssd"; }', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@if $i == 1 { p { x: 3px; } }", parser, parser._parseStylesheet.bind(parser)); - assertError( - "@if { border: 1px solid; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.ExpressionExpected, - ); - assertError("@if 1 }", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.LeftCurlyExpected); - - assertNode( - "@if 1 <= m.$const { border: 3px; } @else { border: 4px; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "@if 1 >= (1 + m.$foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "p { @if m.$i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "p { @if $i == 1 { x: 3px; } @else if m.$i == 1 { x: 4px; } @else { x: 4px; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "p { @if m.$i == 1 { x: 3px; } @else if m.$i == 1 { x: 4px; } @else { x: 4px; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@if (list.index($_RESOURCES, "clean") != null) { @error "sdssd"; }', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@if (index(m.$_RESOURCES, "clean") != null) { @error "sdssd"; }', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@if (list.index(m.$_RESOURCES, "clean") != null) { @error "sdssd"; }', - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@for", function () { - const parser = new SCSSParser(); - assertNode( - "@for $i from 1 to 5 { .item-#{$i} { width: 2em * $i; } }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode("@for $k from 1 + $x through 5 + $x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertError( - "@for i from 0 to 4 {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.VariableNameExpected, - ); - assertError("@for $i to 4 {}", parser, parser._parseRuleSetDeclaration.bind(parser), SCSSParseError.FromExpected); - assertError( - "@for $i from 0 by 4 {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - SCSSParseError.ThroughOrToExpected, - ); - assertError("@for $i from {}", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); - assertError( - "@for $i from 0 to {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.ExpressionExpected, - ); - assertNode('@for $i from 1 through 60 { $s: $i + "%"; }', parser, parser._parseRuleSetDeclaration.bind(parser)); - - assertNode("@for $k from 1 + m.$x through 5 + $x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode("@for $k from 1 + $x through 5 + m.$x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode("@for $k from 1 + m.$x through 5 + m.$x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); - }); - - test("@each", function () { - const parser = new SCSSParser(); - assertNode("@each $i in 1, 2, 3 { }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode("@each $i in 1 2 3 { }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode( - "@each $animal, $color, $cursor in (puma, black, default), (egret, white, move) {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertError( - "@each i in 4 {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.VariableNameExpected, - ); - assertError("@each $i from 4 {}", parser, parser._parseRuleSetDeclaration.bind(parser), SCSSParseError.InExpected); - assertError("@each $i in {}", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); - assertError( - "@each $animal, in (1, 1, 1), (2, 2, 2) {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.VariableNameExpected, - ); - }); - - test("@while", function () { - const parser = new SCSSParser(); - assertNode( - "@while $i < 0 { .item-#{$i} { width: 2em * $i; } $i: $i - 2; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertError("@while {}", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); - assertError("@while $i != 4", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.LeftCurlyExpected); - assertError( - "@while ($i >= 4) {", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.RightCurlyExpected, - ); - }); - - test("@mixin", function () { - const parser = new SCSSParser(); - assertNode( - "@mixin large-text { font: { family: Arial; size: 20px; } color: #ff0000; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@mixin sexy-border($color, $width: 1in) { color: black; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@mixin box-shadow($shadows...) { -moz-box-shadow: $shadows; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@mixin apply-to-ie6-only { * html { @content; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode("@mixin #{foo}($color){}", parser, parser._parseStylesheet.bind(parser)); - assertNode("@mixin foo ($i:4) { size: $i; @include wee ($i - 1); }", parser, parser._parseStylesheet.bind(parser)); - assertNode("@mixin foo ($i,) { }", parser, parser._parseStylesheet.bind(parser)); - - assertError("@mixin $1 {}", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); - assertError("@mixin foo() i {}", parser, parser._parseStylesheet.bind(parser), ParseError.LeftCurlyExpected); - assertError("@mixin foo(1) {}", parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected); - assertError( - "@mixin foo($color = 9) {}", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError("@mixin foo($color)", parser, parser._parseStylesheet.bind(parser), ParseError.LeftCurlyExpected); - assertError("@mixin foo($color){", parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected); - assertError("@mixin foo($color,){", parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected); - }); - - test("@content", function () { - const parser = new SCSSParser(); - assertNode("@content", parser, parser._parseMixinContent.bind(parser)); - assertNode("@content($type)", parser, parser._parseMixinContent.bind(parser)); - }); - - test("@include", function () { - const parser = new SCSSParser(); - assertNode("p { @include sexy-border(blue); }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - ".shadows { @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "$values: #ff0000, #00ff00, #0000ff; .primary { @include colors($values...); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode('@include colors(this("styles")...);', parser, parser._parseStylesheet.bind(parser)); - assertNode(".test { @include fontsize(16px, 21px !important); }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "p { @include apply-to-ie6-only { #logo { background-image: url(/logo.gif); } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("p { @include foo($values,) }", parser, parser._parseStylesheet.bind(parser)); - assertNode("p { @include foo($values,); }", parser, parser._parseStylesheet.bind(parser)); - - assertError( - "p { @include sexy-border blue", - parser, - parser._parseStylesheet.bind(parser), - ParseError.SemiColonExpected, - ); - assertError( - "p { @include sexy-border($values blue", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError("p { @include }", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); - assertError( - "p { @include foo($values }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "p { @include foo($values, }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.ExpressionExpected, - ); - - assertNode("p { @include lib.sexy-border(blue); }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - ".shadows { @include lib.box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "$values: #ff0000, #00ff00, #0000ff; .primary { @include lib.colors($values...); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode(".primary { @include colors(lib.$values...); }", parser, parser._parseStylesheet.bind(parser)); - assertNode(".primary { @include lib.colors(lib.$values...); }", parser, parser._parseStylesheet.bind(parser)); - assertNode('@include lib.colors(this("styles")...);', parser, parser._parseStylesheet.bind(parser)); - assertNode('@include colors(lib.this("styles")...);', parser, parser._parseStylesheet.bind(parser)); - assertNode('@include lib.colors(lib.this("styles")...);', parser, parser._parseStylesheet.bind(parser)); - assertNode(".test { @include lib.fontsize(16px, 21px !important); }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "p { @include lib.apply-to-ie6-only { #logo { background-image: url(/logo.gif); } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("p { @include lib.foo($values,) }", parser, parser._parseStylesheet.bind(parser)); - assertNode("p { @include foo(lib.$values,) }", parser, parser._parseStylesheet.bind(parser)); - assertNode("p { @include lib.foo(m.$values,); }", parser, parser._parseStylesheet.bind(parser)); - - assertError( - "p { @include foo.($values) }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.IdentifierExpected, - ); - - assertNode( - '@include rtl("left") using ($dir) { margin-#{$dir}: 10px; }', - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@function", function () { - const parser = new SCSSParser(); - assertNode( - "@function grid-width($n) { @return $n * $grid-width + ($n - 1) * $gutter-width; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@function grid-width($n: 1, $e) { @return 0; }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "@function foo($total, $a) { @for $i from 0 to $total { } @return $grid; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@function foo() { @if (unit($a) == "%") and ($i == ($total - 1)) { @return 0; } @return 1; }', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@function is-even($int) { @if $int%2 == 0 { @return true; } @return false }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@function bar ($i) { @if $i > 0 { @return $i * bar($i - 1); } @return 1; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@function foo($a,) {} ", parser, parser._parseStylesheet.bind(parser)); - - assertError("@function foo {} ", parser, parser._parseStylesheet.bind(parser), ParseError.LeftParenthesisExpected); - assertError("@function {} ", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); - assertError( - "@function foo($a $b) {} ", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "@function foo($a {} ", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "@function foo($a...) { @return; }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.ExpressionExpected, - ); - assertError( - "@function foo($a:) {} ", - parser, - parser._parseStylesheet.bind(parser), - ParseError.VariableValueExpected, - ); - }); - - test("@at-root", function () { - const parser = new SCSSParser(); - assertNode( - "@mixin unify-parent($child) { @at-root #{selector.unify(&, $child)} { }}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@at-root #main2 .some-class { padding-left: calc( #{$a-variable} + 8px ); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media print { .page { @at-root (without: media) { } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@media print { .page { @at-root (with: rule) { } } }", parser, parser._parseStylesheet.bind(parser)); - }); - - test("Ruleset", function () { - const parser = new SCSSParser(); - assertNode(".selector { prop: erty $const 1px; }", parser, parser._parseRuleset.bind(parser)); - assertNode(".selector { prop: erty $const 1px m.$foo; }", parser, parser._parseRuleset.bind(parser)); - assertNode("selector:active { property:value; nested:hover {}}", parser, parser._parseRuleset.bind(parser)); - assertNode("selector {}", parser, parser._parseRuleset.bind(parser)); - assertNode("selector { property: declaration }", parser, parser._parseRuleset.bind(parser)); - assertNode("selector { $variable: declaration }", parser, parser._parseRuleset.bind(parser)); - assertNode("selector { nested {}}", parser, parser._parseRuleset.bind(parser)); - assertNode("selector { nested, a, b {}}", parser, parser._parseRuleset.bind(parser)); - assertNode("selector { property: value; property: $value; }", parser, parser._parseRuleset.bind(parser)); - assertNode( - "selector { property: value; @keyframes foo {} @-moz-keyframes foo {}}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode("foo|bar { }", parser, parser._parseRuleset.bind(parser)); - }); - - test("Nested Ruleset", function () { - const parser = new SCSSParser(); - assertNode( - ".class1 { $const: 1; .class { $const: 2; three: $const; const: 3; } one: $const; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".class1 { $const: 1; .class { $const: m.$foo; } one: $const; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode(".class1 { > .class2 { & > .class4 { rule1: v1; } } }", parser, parser._parseRuleset.bind(parser)); - assertNode("foo { @at-root { display: none; } }", parser, parser._parseRuleset.bind(parser)); - assertNode( - 'th, tr { @at-root #{selector-replace(&, "tr")} { border-bottom: 0; } }', - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".foo { @supports(display: grid) { .bar { display: none; }}}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode(".foo { @supports(display: grid) { display: none; }}", parser, parser._parseRuleset.bind(parser)); - assertNode( - ".foo { @supports (position: sticky) { @media (min-width: map-get($grid-breakpoints, md)) { position: sticky; } }}", - parser, - parser._parseRuleset.bind(parser), - ); // issue #152 - }); - - test("Selector Interpolation", function () { - const parser = new SCSSParser(); - assertNode(".#{$name} { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".#{$name}-foo { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".#{$name}-foo-3 { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".#{$name}-1 { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".sc-col#{$postfix}-2-1 { }", parser, parser._parseRuleset.bind(parser)); - assertNode("p.#{$name} { #{$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); - assertNode("sans-#{serif} { a-#{1 + 2}-color-#{$attr}: blue; }", parser, parser._parseRuleset.bind(parser)); - assertNode("##{f} .#{f} #{f}:#{f} { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".foo-#{&} .foo-#{&-sub} { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".-#{$variable} { }", parser, parser._parseRuleset.bind(parser)); - assertNode("#{&}([foo=bar][bar=foo]) { }", parser, parser._parseRuleset.bind(parser)); // #49589 - - assertNode(".#{module.$name} { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".#{module.$name}-foo { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".#{module.$name}-foo-3 { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".#{module.$name}-1 { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".sc-col#{module.$postfix}-2-1 { }", parser, parser._parseRuleset.bind(parser)); - assertNode("p.#{module.$name} { #{$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); - assertNode("p.#{$name} { #{module.$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); - assertNode("p.#{module.$name} { #{module.$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); - assertNode("sans-#{serif} { a-#{1 + 2}-color-#{module.$attr}: blue; }", parser, parser._parseRuleset.bind(parser)); - assertNode(".-#{module.$variable} { }", parser, parser._parseRuleset.bind(parser)); - }); - - test("Parent Selector", function () { - const parser = new SCSSParser(); - assertNode("&:hover", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&.float", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-bar", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-1", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&1", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-foo-1", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&&", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-10-thing", parser, parser._parseSimpleSelector.bind(parser)); - }); - - test("Selector Placeholder", function () { - const parser = new SCSSParser(); - assertNode("%hover", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("a%float", parser, parser._parseSimpleSelector.bind(parser)); - }); - - test("Map", function () { - const parser = new SCSSParser(); - assertNode("(key1: 1px, key2: solid + px, key3: (2+3))", parser, parser._parseExpr.bind(parser)); - assertNode("($key1 + 3: 1px)", parser, parser._parseExpr.bind(parser)); - }); - - test("Url", function () { - const parser = new SCSSParser(); - assertNode("url(foo())", parser, parser._parseURILiteral.bind(parser)); - assertNode( - "url('data:image/svg+xml;utf8,%3Csvg%20fill%3D%22%23' + $color + 'foo')", - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode("url(//yourdomain/yourpath.png)", parser, parser._parseURILiteral.bind(parser)); - assertNode("url('http://msft.com')", parser, parser._parseURILiteral.bind(parser)); - assertNode('url("http://msft.com")', parser, parser._parseURILiteral.bind(parser)); - assertNode('url( "http://msft.com")', parser, parser._parseURILiteral.bind(parser)); - assertNode('url(\t"http://msft.com")', parser, parser._parseURILiteral.bind(parser)); - assertNode('url(\n"http://msft.com")', parser, parser._parseURILiteral.bind(parser)); - assertNode('url("http://msft.com"\n)', parser, parser._parseURILiteral.bind(parser)); - assertNode('url("")', parser, parser._parseURILiteral.bind(parser)); - assertNode('uRL("")', parser, parser._parseURILiteral.bind(parser)); - assertNode('URL("")', parser, parser._parseURILiteral.bind(parser)); - assertNode("url(http://msft.com)", parser, parser._parseURILiteral.bind(parser)); - assertNode("url()", parser, parser._parseURILiteral.bind(parser)); - assertNode("url('http://msft.com\n)", parser, parser._parseURILiteral.bind(parser)); - assertError( - 'url("http://msft.com"', - parser, - parser._parseURILiteral.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "url(http://msft.com')", - parser, - parser._parseURILiteral.bind(parser), - ParseError.RightParenthesisExpected, - ); - }); - - test("@font-face", function () { - const parser = new SCSSParser(); - assertNode("@font-face {}", parser, parser._parseFontFace.bind(parser)); - assertNode("@font-face { src: url(http://test) }", parser, parser._parseFontFace.bind(parser)); - assertNode("@font-face { font-style: normal; font-stretch: normal; }", parser, parser._parseFontFace.bind(parser)); - assertNode( - "@font-face { unicode-range: U+0021-007F, u+1f49C, U+4??, U+??????; }", - parser, - parser._parseFontFace.bind(parser), - ); - assertError( - "@font-face { font-style: normal font-stretch: normal; }", - parser, - parser._parseFontFace.bind(parser), - ParseError.SemiColonExpected, - ); - }); -}); diff --git a/vitest.workspace.js b/vitest.workspace.js new file mode 100644 index 00000000..976b4948 --- /dev/null +++ b/vitest.workspace.js @@ -0,0 +1,8 @@ +import { defineWorkspace } from "vitest/config"; + +export default defineWorkspace([ + "./packages/language-services/vitest.config.mts", + "./packages/language-server/vitest.config.mts", + "./packages/parser/vitest.config.mts", + "./packages/language-facts/vitest.config.mts", +]); From fbbe3229e4c25959acfc64b98eb2e440bcefeb89 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Mon, 13 May 2024 20:46:59 +0200 Subject: [PATCH 002/138] Revert "refactor: split?" This reverts commit da0ca59e10b024839c2a80c43b1c70f9bfb95c11. --- packages/language-facts/README.md | 3 - packages/language-facts/package.json | 46 - packages/language-facts/src/colors.ts | 311 --- packages/language-facts/src/entry.ts | 97 - packages/language-facts/tsconfig.json | 15 - packages/language-facts/vitest.config.mts | 10 - packages/parser/README.md | 3 - packages/parser/package.json | 47 - packages/parser/src/cssErrors.ts | 142 -- packages/parser/src/parser.ts | 4 - packages/parser/src/scssParser.test.ts | 2243 ----------------- packages/parser/src/utils/arrays.ts | 43 - packages/parser/src/utils/objects.ts | 12 - packages/parser/src/utils/resources.ts | 14 - packages/parser/src/utils/strings.ts | 111 - packages/parser/tsconfig.json | 15 - packages/parser/vitest.config.mts | 10 - .../src/cssLanguageTypes.ts | 1 + .../src/languageFacts}/builtinData.ts | 121 +- .../src/languageFacts/colors.ts | 645 +++++ .../src/languageFacts/dataManager.ts | 119 + .../src/languageFacts/dataProvider.ts | 90 + .../src/languageFacts/entry.ts | 213 ++ .../src/languageFacts}/facts.ts | 0 .../src/parser/cssErrors.ts | 58 + .../src/parser}/cssNodes.ts | 75 +- .../src/parser}/cssParser.ts | 407 +-- .../src/parser}/cssScanner.ts | 68 +- .../src/parser}/cssSymbolScope.ts | 160 +- .../src/parser}/scssErrors.ts | 13 +- .../src/parser}/scssParser.ts | 240 +- .../src/parser}/scssScanner.ts | 3 + .../src/services/cssCompletion.ts | 2 +- .../src/test/css/parser.test.ts} | 1216 ++------- .../src/test/scss/parser.test.ts | 1156 +++++++++ vitest.workspace.js | 8 - 36 files changed, 2741 insertions(+), 4980 deletions(-) delete mode 100644 packages/language-facts/README.md delete mode 100644 packages/language-facts/package.json delete mode 100644 packages/language-facts/src/colors.ts delete mode 100644 packages/language-facts/src/entry.ts delete mode 100644 packages/language-facts/tsconfig.json delete mode 100644 packages/language-facts/vitest.config.mts delete mode 100644 packages/parser/README.md delete mode 100644 packages/parser/package.json delete mode 100644 packages/parser/src/cssErrors.ts delete mode 100644 packages/parser/src/parser.ts delete mode 100644 packages/parser/src/scssParser.test.ts delete mode 100644 packages/parser/src/utils/arrays.ts delete mode 100644 packages/parser/src/utils/objects.ts delete mode 100644 packages/parser/src/utils/resources.ts delete mode 100644 packages/parser/src/utils/strings.ts delete mode 100644 packages/parser/tsconfig.json delete mode 100644 packages/parser/vitest.config.mts rename packages/{language-facts/src => vscode-css-languageservice/src/languageFacts}/builtinData.ts (69%) create mode 100644 packages/vscode-css-languageservice/src/languageFacts/colors.ts create mode 100644 packages/vscode-css-languageservice/src/languageFacts/dataManager.ts create mode 100644 packages/vscode-css-languageservice/src/languageFacts/dataProvider.ts create mode 100644 packages/vscode-css-languageservice/src/languageFacts/entry.ts rename packages/{language-facts/src => vscode-css-languageservice/src/languageFacts}/facts.ts (100%) create mode 100644 packages/vscode-css-languageservice/src/parser/cssErrors.ts rename packages/{parser/src => vscode-css-languageservice/src/parser}/cssNodes.ts (96%) rename packages/{parser/src => vscode-css-languageservice/src/parser}/cssParser.ts (87%) rename packages/{parser/src => vscode-css-languageservice/src/parser}/cssScanner.ts (92%) rename packages/{parser/src => vscode-css-languageservice/src/parser}/cssSymbolScope.ts (73%) rename packages/{parser/src => vscode-css-languageservice/src/parser}/scssErrors.ts (78%) rename packages/{parser/src => vscode-css-languageservice/src/parser}/scssParser.ts (85%) rename packages/{parser/src => vscode-css-languageservice/src/parser}/scssScanner.ts (98%) rename packages/{parser/src/cssParser.test.ts => vscode-css-languageservice/src/test/css/parser.test.ts} (54%) create mode 100644 packages/vscode-css-languageservice/src/test/scss/parser.test.ts delete mode 100644 vitest.workspace.js diff --git a/packages/language-facts/README.md b/packages/language-facts/README.md deleted file mode 100644 index b3953486..00000000 --- a/packages/language-facts/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @somesass/parser - -See [Server architecture](https://wkillerud.github.io/some-sass/contributing/architecture.html#server-architecture) for information. diff --git a/packages/language-facts/package.json b/packages/language-facts/package.json deleted file mode 100644 index 33b636e3..00000000 --- a/packages/language-facts/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "@somesass/language-facts", - "version": "1.0.0", - "private": true, - "keywords": [ - "scss", - "sass" - ], - "engines": { - "node": ">=20" - }, - "homepage": "https://github.com/wkillerud/some-sass/blob/main/packages/language-facts#readme", - "repository": { - "type": "git", - "url": "git+ssh://git@github.com/wkillerud/some-sass.git" - }, - "bugs": { - "url": "https://github.com/wkillerud/some-sass/issues" - }, - "files": [ - "dist/", - "!dist/test/", - "!dist/**/*.test.js" - ], - "main": "dist/facts.js", - "types": "dist/facts.d.ts", - "author": "William Killerud (https://www.williamkillerud.com/)", - "license": "MIT", - "scripts": { - "build": "tsc", - "clean": "shx rm -rf dist", - "test": "vitest", - "coverage": "vitest run --coverage" - }, - "dependencies": { - "@vscode/l10n": "0.0.18", - "vscode-languageserver-textdocument": "1.0.11", - "vscode-uri": "3.0.8" - }, - "devDependencies": { - "@vitest/coverage-v8": "1.5.3", - "shx": "0.3.4", - "typescript": "5.4.5", - "vitest": "1.5.3" - } -} diff --git a/packages/language-facts/src/colors.ts b/packages/language-facts/src/colors.ts deleted file mode 100644 index 649c3fcf..00000000 --- a/packages/language-facts/src/colors.ts +++ /dev/null @@ -1,311 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as l10n from "@vscode/l10n"; - -export const colorFunctions = [ - { - label: "rgb", - func: "rgb($red, $green, $blue)", - insertText: "rgb(${1:red}, ${2:green}, ${3:blue})", - desc: l10n.t("Creates a Color from red, green, and blue values."), - }, - { - label: "rgba", - func: "rgba($red, $green, $blue, $alpha)", - insertText: "rgba(${1:red}, ${2:green}, ${3:blue}, ${4:alpha})", - desc: l10n.t("Creates a Color from red, green, blue, and alpha values."), - }, - { - label: "rgb relative", - func: "rgb(from $color $red $green $blue)", - insertText: "rgb(from ${1:color} ${2:r} ${3:g} ${4:b})", - desc: l10n.t( - "Creates a Color from the red, green, and blue values of another Color.", - ), - }, - { - label: "hsl", - func: "hsl($hue, $saturation, $lightness)", - insertText: "hsl(${1:hue}, ${2:saturation}, ${3:lightness})", - desc: l10n.t("Creates a Color from hue, saturation, and lightness values."), - }, - { - label: "hsla", - func: "hsla($hue, $saturation, $lightness, $alpha)", - insertText: "hsla(${1:hue}, ${2:saturation}, ${3:lightness}, ${4:alpha})", - desc: l10n.t( - "Creates a Color from hue, saturation, lightness, and alpha values.", - ), - }, - { - label: "hsl relative", - func: "hsl(from $color $hue $saturation $lightness)", - insertText: "hsl(from ${1:color} ${2:h} ${3:s} ${4:l})", - desc: l10n.t( - "Creates a Color from the hue, saturation, and lightness values of another Color.", - ), - }, - { - label: "hwb", - func: "hwb($hue $white $black)", - insertText: "hwb(${1:hue} ${2:white} ${3:black})", - desc: l10n.t("Creates a Color from hue, white, and black values."), - }, - { - label: "hwb relative", - func: "hwb(from $color $hue $white $black)", - insertText: "hwb(from ${1:color} ${2:h} ${3:w} ${4:b})", - desc: l10n.t( - "Creates a Color from the hue, white, and black values of another Color.", - ), - }, - { - label: "lab", - func: "lab($lightness $a $b)", - insertText: "lab(${1:lightness} ${2:a} ${3:b})", - desc: l10n.t("Creates a Color from lightness, a, and b values."), - }, - { - label: "lab relative", - func: "lab(from $color $lightness $a $b)", - insertText: "lab(from ${1:color} ${2:l} ${3:a} ${4:b})", - desc: l10n.t( - "Creates a Color from the lightness, a, and b values of another Color.", - ), - }, - { - label: "oklab", - func: "oklab($lightness $a $b)", - insertText: "oklab(${1:lightness} ${2:a} ${3:b})", - desc: l10n.t("Creates a Color from lightness, a, and b values."), - }, - { - label: "oklab relative", - func: "oklab(from $color $lightness $a $b)", - insertText: "oklab(from ${1:color} ${2:l} ${3:a} ${4:b})", - desc: l10n.t( - "Creates a Color from the lightness, a, and b values of another Color.", - ), - }, - { - label: "lch", - func: "lch($lightness $chroma $hue)", - insertText: "lch(${1:lightness} ${2:chroma} ${3:hue})", - desc: l10n.t("Creates a Color from lightness, chroma, and hue values."), - }, - { - label: "lch relative", - func: "lch(from $color $lightness $chroma $hue)", - insertText: "lch(from ${1:color} ${2:l} ${3:c} ${4:h})", - desc: l10n.t( - "Creates a Color from the lightness, chroma, and hue values of another Color.", - ), - }, - { - label: "oklch", - func: "oklch($lightness $chroma $hue)", - insertText: "oklch(${1:lightness} ${2:chroma} ${3:hue})", - desc: l10n.t("Creates a Color from lightness, chroma, and hue values."), - }, - { - label: "oklch relative", - func: "oklch(from $color $lightness $chroma $hue)", - insertText: "oklch(from ${1:color} ${2:l} ${3:c} ${4:h})", - desc: l10n.t( - "Creates a Color from the lightness, chroma, and hue values of another Color.", - ), - }, - { - label: "color", - func: "color($color-space $red $green $blue)", - insertText: - "color(${1|srgb,srgb-linear,display-p3,a98-rgb,prophoto-rgb,rec2020,xyx,xyz-d50,xyz-d65|} ${2:red} ${3:green} ${4:blue})", - desc: l10n.t( - "Creates a Color in a specific color space from red, green, and blue values.", - ), - }, - { - label: "color relative", - func: "color(from $color $color-space $red $green $blue)", - insertText: - "color(from ${1:color} ${2|srgb,srgb-linear,display-p3,a98-rgb,prophoto-rgb,rec2020,xyx,xyz-d50,xyz-d65|} ${3:r} ${4:g} ${5:b})", - desc: l10n.t( - "Creates a Color in a specific color space from the red, green, and blue values of another Color.", - ), - }, - { - label: "color-mix", - func: "color-mix(in $color-space, $color $percentage, $color $percentage)", - insertText: - "color-mix(in ${1|srgb,srgb-linear,lab,oklab,xyz,xyz-d50,xyz-d65|}, ${3:color} ${4:percentage}, ${5:color} ${6:percentage})", - desc: l10n.t("Mix two colors together in a rectangular color space."), - }, - { - label: "color-mix hue", - func: "color-mix(in $color-space $interpolation-method hue, $color $percentage, $color $percentage)", - insertText: - "color-mix(in ${1|hsl,hwb,lch,oklch|} ${2|shorter hue,longer hue,increasing hue,decreasing hue|}, ${3:color} ${4:percentage}, ${5:color} ${6:percentage})", - desc: l10n.t("Mix two colors together in a polar color space."), - }, -]; - -export const colors: { [name: string]: string } = { - aliceblue: "#f0f8ff", - antiquewhite: "#faebd7", - aqua: "#00ffff", - aquamarine: "#7fffd4", - azure: "#f0ffff", - beige: "#f5f5dc", - bisque: "#ffe4c4", - black: "#000000", - blanchedalmond: "#ffebcd", - blue: "#0000ff", - blueviolet: "#8a2be2", - brown: "#a52a2a", - burlywood: "#deb887", - cadetblue: "#5f9ea0", - chartreuse: "#7fff00", - chocolate: "#d2691e", - coral: "#ff7f50", - cornflowerblue: "#6495ed", - cornsilk: "#fff8dc", - crimson: "#dc143c", - cyan: "#00ffff", - darkblue: "#00008b", - darkcyan: "#008b8b", - darkgoldenrod: "#b8860b", - darkgray: "#a9a9a9", - darkgrey: "#a9a9a9", - darkgreen: "#006400", - darkkhaki: "#bdb76b", - darkmagenta: "#8b008b", - darkolivegreen: "#556b2f", - darkorange: "#ff8c00", - darkorchid: "#9932cc", - darkred: "#8b0000", - darksalmon: "#e9967a", - darkseagreen: "#8fbc8f", - darkslateblue: "#483d8b", - darkslategray: "#2f4f4f", - darkslategrey: "#2f4f4f", - darkturquoise: "#00ced1", - darkviolet: "#9400d3", - deeppink: "#ff1493", - deepskyblue: "#00bfff", - dimgray: "#696969", - dimgrey: "#696969", - dodgerblue: "#1e90ff", - firebrick: "#b22222", - floralwhite: "#fffaf0", - forestgreen: "#228b22", - fuchsia: "#ff00ff", - gainsboro: "#dcdcdc", - ghostwhite: "#f8f8ff", - gold: "#ffd700", - goldenrod: "#daa520", - gray: "#808080", - grey: "#808080", - green: "#008000", - greenyellow: "#adff2f", - honeydew: "#f0fff0", - hotpink: "#ff69b4", - indianred: "#cd5c5c", - indigo: "#4b0082", - ivory: "#fffff0", - khaki: "#f0e68c", - lavender: "#e6e6fa", - lavenderblush: "#fff0f5", - lawngreen: "#7cfc00", - lemonchiffon: "#fffacd", - lightblue: "#add8e6", - lightcoral: "#f08080", - lightcyan: "#e0ffff", - lightgoldenrodyellow: "#fafad2", - lightgray: "#d3d3d3", - lightgrey: "#d3d3d3", - lightgreen: "#90ee90", - lightpink: "#ffb6c1", - lightsalmon: "#ffa07a", - lightseagreen: "#20b2aa", - lightskyblue: "#87cefa", - lightslategray: "#778899", - lightslategrey: "#778899", - lightsteelblue: "#b0c4de", - lightyellow: "#ffffe0", - lime: "#00ff00", - limegreen: "#32cd32", - linen: "#faf0e6", - magenta: "#ff00ff", - maroon: "#800000", - mediumaquamarine: "#66cdaa", - mediumblue: "#0000cd", - mediumorchid: "#ba55d3", - mediumpurple: "#9370d8", - mediumseagreen: "#3cb371", - mediumslateblue: "#7b68ee", - mediumspringgreen: "#00fa9a", - mediumturquoise: "#48d1cc", - mediumvioletred: "#c71585", - midnightblue: "#191970", - mintcream: "#f5fffa", - mistyrose: "#ffe4e1", - moccasin: "#ffe4b5", - navajowhite: "#ffdead", - navy: "#000080", - oldlace: "#fdf5e6", - olive: "#808000", - olivedrab: "#6b8e23", - orange: "#ffa500", - orangered: "#ff4500", - orchid: "#da70d6", - palegoldenrod: "#eee8aa", - palegreen: "#98fb98", - paleturquoise: "#afeeee", - palevioletred: "#d87093", - papayawhip: "#ffefd5", - peachpuff: "#ffdab9", - peru: "#cd853f", - pink: "#ffc0cb", - plum: "#dda0dd", - powderblue: "#b0e0e6", - purple: "#800080", - red: "#ff0000", - rebeccapurple: "#663399", - rosybrown: "#bc8f8f", - royalblue: "#4169e1", - saddlebrown: "#8b4513", - salmon: "#fa8072", - sandybrown: "#f4a460", - seagreen: "#2e8b57", - seashell: "#fff5ee", - sienna: "#a0522d", - silver: "#c0c0c0", - skyblue: "#87ceeb", - slateblue: "#6a5acd", - slategray: "#708090", - slategrey: "#708090", - snow: "#fffafa", - springgreen: "#00ff7f", - steelblue: "#4682b4", - tan: "#d2b48c", - teal: "#008080", - thistle: "#d8bfd8", - tomato: "#ff6347", - turquoise: "#40e0d0", - violet: "#ee82ee", - wheat: "#f5deb3", - white: "#ffffff", - whitesmoke: "#f5f5f5", - yellow: "#ffff00", - yellowgreen: "#9acd32", -}; - -export const colorKeywords: { [name: string]: string } = { - currentColor: - "The value of the 'color' property. The computed value of the 'currentColor' keyword is the computed value of the 'color' property. If the 'currentColor' keyword is set on the 'color' property itself, it is treated as 'color:inherit' at parse time.", - transparent: - "Fully transparent. This keyword can be considered a shorthand for rgba(0,0,0,0) which is its computed value.", -}; diff --git a/packages/language-facts/src/entry.ts b/packages/language-facts/src/entry.ts deleted file mode 100644 index c27efb30..00000000 --- a/packages/language-facts/src/entry.ts +++ /dev/null @@ -1,97 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { - IPropertyData, - IAtDirectiveData, - IPseudoClassData, - IPseudoElementData, - IValueData, - MarkupContent, - MarkupKind, - MarkedString, - HoverSettings, -} from "vscode-languageserver-types"; - -export type EntryStatus = - | "standard" - | "experimental" - | "nonstandard" - | "obsolete"; - -export interface Browsers { - E?: string; - FF?: string; - IE?: string; - O?: string; - C?: string; - S?: string; - count: number; - all: boolean; - onCodeComplete: boolean; -} - -export const browserNames = { - E: "Edge", - FF: "Firefox", - S: "Safari", - C: "Chrome", - IE: "IE", - O: "Opera", -}; - -function getEntryStatus(status: EntryStatus) { - switch (status) { - case "experimental": - return "⚠️ Property is experimental. Be cautious when using it.️\n\n"; - case "nonstandard": - return "🚨️ Property is nonstandard. Avoid using it.\n\n"; - case "obsolete": - return "🚨️️️ Property is obsolete. Avoid using it.\n\n"; - default: - return ""; - } -} - -/** - * Input is like `["E12","FF49","C47","IE","O"]` - * Output is like `Edge 12, Firefox 49, Chrome 47, IE, Opera` - */ -export function getBrowserLabel(browsers: string[] = []): string | null { - if (browsers.length === 0) { - return null; - } - - return browsers - .map((b) => { - let result = ""; - const matches = b.match(/([A-Z]+)(\d+)?/)!; - - const name = matches[1]; - const version = matches[2]; - - if (name in browserNames) { - result += browserNames[name as keyof typeof browserNames]; - } - if (version) { - result += " " + version; - } - return result; - }) - .join(", "); -} - -export type IEntry = - | IPropertyData - | IAtDirectiveData - | IPseudoClassData - | IPseudoElementData - | IValueData; - -export interface IValue { - name: string; - description?: string | MarkupContent; - browsers?: string[]; -} diff --git a/packages/language-facts/tsconfig.json b/packages/language-facts/tsconfig.json deleted file mode 100644 index 2b0655a9..00000000 --- a/packages/language-facts/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "es2020", - "lib": ["ES2020", "WebWorker"], - "sourceMap": true, - "module": "commonjs", - "moduleResolution": "node", - "declaration": true, - "rootDir": "src", - "outDir": "dist", - "strict": true - }, - "include": ["src/**/*.ts"], - "exclude": ["src/**/*.test.ts"] -} diff --git a/packages/language-facts/vitest.config.mts b/packages/language-facts/vitest.config.mts deleted file mode 100644 index e2df9da5..00000000 --- a/packages/language-facts/vitest.config.mts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - coverage: { - provider: "v8", - reporter: ["text", "json", "html"], - }, - }, -}); diff --git a/packages/parser/README.md b/packages/parser/README.md deleted file mode 100644 index b3953486..00000000 --- a/packages/parser/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @somesass/parser - -See [Server architecture](https://wkillerud.github.io/some-sass/contributing/architecture.html#server-architecture) for information. diff --git a/packages/parser/package.json b/packages/parser/package.json deleted file mode 100644 index 2e42fa17..00000000 --- a/packages/parser/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "@somesass/parser", - "version": "1.0.0", - "private": true, - "description": "The parser powering some-sass-language-server", - "keywords": [ - "scss", - "sass" - ], - "engines": { - "node": ">=20" - }, - "homepage": "https://github.com/wkillerud/some-sass/blob/main/packages/parser#readme", - "repository": { - "type": "git", - "url": "git+ssh://git@github.com/wkillerud/some-sass.git" - }, - "bugs": { - "url": "https://github.com/wkillerud/some-sass/issues" - }, - "files": [ - "dist/", - "!dist/test/", - "!dist/**/*.test.js" - ], - "main": "dist/parser.js", - "types": "dist/parser.d.ts", - "author": "William Killerud (https://www.williamkillerud.com/)", - "license": "MIT", - "scripts": { - "build": "tsc", - "clean": "shx rm -rf dist", - "test": "vitest", - "coverage": "vitest run --coverage" - }, - "dependencies": { - "@vscode/l10n": "0.0.18", - "vscode-languageserver-textdocument": "1.0.11", - "vscode-uri": "3.0.8" - }, - "devDependencies": { - "@vitest/coverage-v8": "1.5.3", - "shx": "0.3.4", - "typescript": "5.4.5", - "vitest": "1.5.3" - } -} diff --git a/packages/parser/src/cssErrors.ts b/packages/parser/src/cssErrors.ts deleted file mode 100644 index a9cb26be..00000000 --- a/packages/parser/src/cssErrors.ts +++ /dev/null @@ -1,142 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import * as l10n from "@vscode/l10n"; -import * as nodes from "./cssNodes"; - -export class CSSIssueType implements nodes.IRule { - id: string; - message: string; - - public constructor(id: string, message: string) { - this.id = id; - this.message = message; - } -} - -export const ParseError = { - NumberExpected: new CSSIssueType( - "css-numberexpected", - l10n.t("number expected"), - ), - ConditionExpected: new CSSIssueType( - "css-conditionexpected", - l10n.t("condition expected"), - ), - RuleOrSelectorExpected: new CSSIssueType( - "css-ruleorselectorexpected", - l10n.t("at-rule or selector expected"), - ), - DotExpected: new CSSIssueType("css-dotexpected", l10n.t("dot expected")), - ColonExpected: new CSSIssueType( - "css-colonexpected", - l10n.t("colon expected"), - ), - SemiColonExpected: new CSSIssueType( - "css-semicolonexpected", - l10n.t("semi-colon expected"), - ), - TermExpected: new CSSIssueType("css-termexpected", l10n.t("term expected")), - ExpressionExpected: new CSSIssueType( - "css-expressionexpected", - l10n.t("expression expected"), - ), - OperatorExpected: new CSSIssueType( - "css-operatorexpected", - l10n.t("operator expected"), - ), - IdentifierExpected: new CSSIssueType( - "css-identifierexpected", - l10n.t("identifier expected"), - ), - PercentageExpected: new CSSIssueType( - "css-percentageexpected", - l10n.t("percentage expected"), - ), - URIOrStringExpected: new CSSIssueType( - "css-uriorstringexpected", - l10n.t("uri or string expected"), - ), - URIExpected: new CSSIssueType("css-uriexpected", l10n.t("URI expected")), - VariableNameExpected: new CSSIssueType( - "css-varnameexpected", - l10n.t("variable name expected"), - ), - VariableValueExpected: new CSSIssueType( - "css-varvalueexpected", - l10n.t("variable value expected"), - ), - PropertyValueExpected: new CSSIssueType( - "css-propertyvalueexpected", - l10n.t("property value expected"), - ), - LeftCurlyExpected: new CSSIssueType( - "css-lcurlyexpected", - l10n.t("{ expected"), - ), - RightCurlyExpected: new CSSIssueType( - "css-rcurlyexpected", - l10n.t("} expected"), - ), - LeftSquareBracketExpected: new CSSIssueType( - "css-rbracketexpected", - l10n.t("[ expected"), - ), - RightSquareBracketExpected: new CSSIssueType( - "css-lbracketexpected", - l10n.t("] expected"), - ), - LeftParenthesisExpected: new CSSIssueType( - "css-lparentexpected", - l10n.t("( expected"), - ), - RightParenthesisExpected: new CSSIssueType( - "css-rparentexpected", - l10n.t(") expected"), - ), - CommaExpected: new CSSIssueType( - "css-commaexpected", - l10n.t("comma expected"), - ), - PageDirectiveOrDeclarationExpected: new CSSIssueType( - "css-pagedirordeclexpected", - l10n.t("page directive or declaraton expected"), - ), - UnknownAtRule: new CSSIssueType( - "css-unknownatrule", - l10n.t("at-rule unknown"), - ), - UnknownKeyword: new CSSIssueType( - "css-unknownkeyword", - l10n.t("unknown keyword"), - ), - SelectorExpected: new CSSIssueType( - "css-selectorexpected", - l10n.t("selector expected"), - ), - StringLiteralExpected: new CSSIssueType( - "css-stringliteralexpected", - l10n.t("string literal expected"), - ), - WhitespaceExpected: new CSSIssueType( - "css-whitespaceexpected", - l10n.t("whitespace expected"), - ), - MediaQueryExpected: new CSSIssueType( - "css-mediaqueryexpected", - l10n.t("media query expected"), - ), - IdentifierOrWildcardExpected: new CSSIssueType( - "css-idorwildcardexpected", - l10n.t("identifier or wildcard expected"), - ), - WildcardExpected: new CSSIssueType( - "css-wildcardexpected", - l10n.t("wildcard expected"), - ), - IdentifierOrVariableExpected: new CSSIssueType( - "css-idorvarexpected", - l10n.t("identifier or variable expected"), - ), -}; diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts deleted file mode 100644 index b8935f65..00000000 --- a/packages/parser/src/parser.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./cssNodes"; -export { TokenType, IToken, Scanner } from "./cssScanner"; -export { SCSSScanner } from "./scssScanner"; -export * from "./scssParser"; diff --git a/packages/parser/src/scssParser.test.ts b/packages/parser/src/scssParser.test.ts deleted file mode 100644 index b7df25aa..00000000 --- a/packages/parser/src/scssParser.test.ts +++ /dev/null @@ -1,2243 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { suite, test } from "vitest"; -import { ParseError } from "./cssErrors.js"; -import { assertNode, assertError } from "./cssParser.test.js"; -import { SCSSParseError } from "./scssErrors.js"; -import { SCSSParser } from "./scssParser.js"; - -suite("SCSS - Parser", () => { - test("Comments", function () { - const parser = new SCSSParser(); - assertNode( - " a { b: /* comment */ c }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - " a { b: /* comment \n * is several\n * lines long\n */ c }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - " a { b: // single line comment\n c }", - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("Variable", function () { - const parser = new SCSSParser(); - assertNode("$color", parser, parser._parseVariable.bind(parser)); - assertNode("$co42lor", parser, parser._parseVariable.bind(parser)); - assertNode("$-co42lor", parser, parser._parseVariable.bind(parser)); - }); - - test("Module variable", function () { - const parser = new SCSSParser(); - assertNode("module.$color", parser, parser._parseModuleMember.bind(parser)); - assertNode( - "module.$co42lor", - parser, - parser._parseModuleMember.bind(parser), - ); - assertNode( - "module.$-co42lor", - parser, - parser._parseModuleMember.bind(parser), - ); - assertNode( - "module.function()", - parser, - parser._parseModuleMember.bind(parser), - ); - - assertError( - "module.", - parser, - parser._parseModuleMember.bind(parser), - ParseError.IdentifierOrVariableExpected, - ); - }); - - test("VariableDeclaration", function () { - const parser = new SCSSParser(); - assertNode( - "$color: #F5F5F5", - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode( - "$color: 0", - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode( - "$color: 255", - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode( - "$color: 25.5", - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode( - "$color: 25px", - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode( - "$color: 25.5px !default", - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode( - "$text-color: green !global", - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode( - '$_RESOURCES: append($_RESOURCES, "clean") !global', - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode( - "$footer-height: 40px !default !global", - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode( - '$primary-font: "wf_SegoeUI","Segoe UI","Segoe","Segoe WP"', - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode( - "$color: red !important", - parser, - parser._parseVariableDeclaration.bind(parser), - ); - - assertError( - "$color: red !def", - parser, - parser._parseVariableDeclaration.bind(parser), - ParseError.UnknownKeyword, - ); - assertError( - "$color : !default", - parser, - parser._parseVariableDeclaration.bind(parser), - ParseError.VariableValueExpected, - ); - assertError( - "$color !default", - parser, - parser._parseVariableDeclaration.bind(parser), - ParseError.ColonExpected, - ); - }); - - test("Expr", function () { - const parser = new SCSSParser(); - assertNode("($const + 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($const - 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($const * 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($const / 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 - $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 * $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 / $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 / 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode( - "(20 + 20 + 20 + $const)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "(20 + 20 + 20 + 20 + $const)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "(20 + 20 + $const + 20 + 20 + $const)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode("(20 + 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($var1 + $var2)", parser, parser._parseExpr.bind(parser)); - assertNode("(($const + 5) * 2)", parser, parser._parseExpr.bind(parser)); - assertNode( - "(($const + (5 + 2)) * 2)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "($const + ((5 + 2) * 2))", - parser, - parser._parseExpr.bind(parser), - ); - assertNode("$color", parser, parser._parseExpr.bind(parser)); - assertNode("$color, $color", parser, parser._parseExpr.bind(parser)); - assertNode("$color, 42%", parser, parser._parseExpr.bind(parser)); - assertNode("$color, 42%, $color", parser, parser._parseExpr.bind(parser)); - assertNode( - "$color - ($color + 10%)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode("($base + $filler)", parser, parser._parseExpr.bind(parser)); - assertNode("(100% / 2 + $filler)", parser, parser._parseExpr.bind(parser)); - assertNode("100% / 2 + $filler", parser, parser._parseExpr.bind(parser)); - assertNode("not ($v and $b) or $c", parser, parser._parseExpr.bind(parser)); - - assertNode("(module.$const + 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$const - 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$const * 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$const / 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 - module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 * module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 / module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode( - "(20 + 20 + module.$const)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "(20 + 20 + 20 + module.$const)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "(20 + 20 + 20 + 20 + module.$const)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "(20 + 20 + module.$const + 20 + 20 + module.$const)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "($var1 + module.$var2)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "(module.$var1 + $var2)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "(module.$var1 + module.$var2)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "((module.$const + 5) * 2)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "((module.$const + (5 + 2)) * 2)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "(module.$const + ((5 + 2) * 2))", - parser, - parser._parseExpr.bind(parser), - ); - assertNode("module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, $color", parser, parser._parseExpr.bind(parser)); - assertNode("$color, module.$color", parser, parser._parseExpr.bind(parser)); - assertNode( - "module.$color, module.$color", - parser, - parser._parseExpr.bind(parser), - ); - assertNode("module.$color, 42%", parser, parser._parseExpr.bind(parser)); - assertNode( - "module.$color, 42%, $color", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "$color, 42%, module.$color", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "module.$color, 42%, module.$color", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "module.$color - ($color + 10%)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "$color - (module.$color + 10%)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "module.$color - (module.$color + 10%)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "(module.$base + $filler)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "($base + module.$filler)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "(module.$base + module.$filler)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "(100% / 2 + module.$filler)", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "100% / 2 + module.$filler", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "not (module.$v and $b) or $c", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "not ($v and module.$b) or $c", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "not ($v and $b) or module.$c", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "not (module.$v and module.$b) or $c", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "not (module.$v and $b) or module.$c", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "not ($v and module.$b) or module.$c", - parser, - parser._parseExpr.bind(parser), - ); - assertNode( - "not (module.$v and module.$b) or module.$c", - parser, - parser._parseExpr.bind(parser), - ); - assertNode("not module.$v", parser, parser._parseExpr.bind(parser)); - - assertError( - "(20 + 20", - parser, - parser._parseExpr.bind(parser), - ParseError.RightParenthesisExpected, - ); - }); - - test("SCSSOperator", function () { - const parser = new SCSSParser(); - assertNode(">=", parser, parser._parseOperator.bind(parser)); - assertNode(">", parser, parser._parseOperator.bind(parser)); - assertNode("<", parser, parser._parseOperator.bind(parser)); - assertNode("<=", parser, parser._parseOperator.bind(parser)); - assertNode("==", parser, parser._parseOperator.bind(parser)); - assertNode("!=", parser, parser._parseOperator.bind(parser)); - assertNode("and", parser, parser._parseOperator.bind(parser)); - assertNode("+", parser, parser._parseOperator.bind(parser)); - assertNode("-", parser, parser._parseOperator.bind(parser)); - assertNode("*", parser, parser._parseOperator.bind(parser)); - assertNode("/", parser, parser._parseOperator.bind(parser)); - assertNode("%", parser, parser._parseOperator.bind(parser)); - assertNode("not", parser, parser._parseUnaryOperator.bind(parser)); - }); - - test("Interpolation", function () { - const parser = new SCSSParser(); - // assertNode('#{red}', parser, parser._parseIdent.bind(parser)); - // assertNode('#{$color}', parser, parser._parseIdent.bind(parser)); - // assertNode('#{3 + 4}', parser, parser._parseIdent.bind(parser)); - // assertNode('#{3 + #{3 + 4}}', parser, parser._parseIdent.bind(parser)); - // assertNode('#{$d}-style: 0', parser, parser._parseDeclaration.bind(parser)); - // assertNode('foo-#{$d}: 1', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{$d}-bar-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('foo-#{$d}-bar: 1', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('&:nth-child(#{$query}+1) { clear: $opposite-direction; }', parser, parser._parseRuleset.bind(parser)); - // assertNode('--#{$propname}: some-value', parser, parser._parseDeclaration.bind(parser)); - // assertNode('some-property: var(--#{$propname})', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{}', parser, parser._parseIdent.bind(parser)); - // assertError('#{1 + 2', parser, parser._parseIdent.bind(parser), ParseError.RightCurlyExpected); - - // assertNode('#{module.$color}', parser, parser._parseIdent.bind(parser)); - // assertNode('#{module.$d}-style: 0', parser, parser._parseDeclaration.bind(parser)); - // assertNode('foo-#{module.$d}: 1', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{module.$d}-bar-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{$d}-bar-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{module.$d}-bar-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('foo-#{module.$d}-bar: 1', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{module.$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{$d}-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{module.$d}-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('&:nth-child(#{module.$query}+1) { clear: $opposite-direction; }', parser, parser._parseRuleset.bind(parser)); - // assertNode('&:nth-child(#{$query}+1) { clear: module.$opposite-direction; }', parser, parser._parseRuleset.bind(parser)); - // assertNode('&:nth-child(#{module.$query}+1) { clear: module.$opposite-direction; }', parser, parser._parseRuleset.bind(parser)); - assertNode( - "--#{module.$propname}: some-value", - parser, - parser._parseDeclaration.bind(parser), - ); - // assertNode('some-property: var(--#{module.$propname})', parser, parser._parseDeclaration.bind(parser)); - // assertNode('@supports #{$val} { }', parser, parser._parseStylesheet.bind(parser)); // #88283 - // assertNode('.mb-#{$i}0np {} .push-up-#{$i}0 {} .mt-#{$i}0vh {}', parser, parser._parseStylesheet.bind(parser)); - }); - - test("Declaration", function () { - const parser = new SCSSParser(); - assertNode( - "border: thin solid 1px", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode("dummy: $color", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: blue", parser, parser._parseDeclaration.bind(parser)); - assertNode( - "dummy: (20 / $const)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "dummy: (20 / 20 + $const)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "dummy: func($red)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "dummy: func($red) !important", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "dummy: desaturate($red, 10%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "dummy: desaturate(16, 10%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "color: $base-color + #111", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "color: 100% / 2 + $ref", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "border: ($width * 2) solid black", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "property: $class", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "prop-erty: fnc($t, 10%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "width: (1em + 2em) * 3", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "color: #010203 + #040506", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - 'font-family: sans- + "serif"', - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "margin: 3px + 4px auto", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "color: hsl(0, 100%, 50%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "color: hsl($hue: 0, $saturation: 100%, $lightness: 50%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if($value == 'default', flex-gutter(), $value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if(true, !important, null)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "color: selector-replace(&, 1)", - parser, - parser._parseDeclaration.bind(parser), - ); - - assertNode( - "dummy: module.$color", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "dummy: (20 / module.$const)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "dummy: (20 / 20 + module.$const)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "dummy: module.func($red)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "dummy: module.func($red) !important", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "dummy: module.desaturate($red, 10%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "dummy: desaturate(module.$red, 10%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "dummy: module.desaturate(module.$red, 10%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "dummy: module.desaturate(16, 10%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "color: module.$base-color + #111", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "color: 100% / 2 + module.$ref", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "border: (module.$width * 2) solid black", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "property: module.$class", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "prop-erty: module.fnc($t, 10%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "prop-erty: fnc(module.$t, 10%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "prop-erty: module.fnc(module.$t, 10%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "width: (1em + 2em) * 3", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "color: #010203 + #040506", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - 'font-family: sans- + "serif"', - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "margin: 3px + 4px auto", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "color: color.hsl(0, 100%, 50%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "color: color.hsl($hue: 0, $saturation: 100%, $lightness: 50%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if(module.$value == 'default', flex-gutter(), $value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if($value == 'default', module.flex-gutter(), $value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if($value == 'default', flex-gutter(), module.$value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if(module.$value == 'default', module.flex-gutter(), $value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if($value == 'default', module.flex-gutter(), module.$value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if(module.$value == 'default', module.flex-gutter(), module.$value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "color: selector.replace(&, 1)", - parser, - parser._parseDeclaration.bind(parser), - ); - - assertError( - "fo = 8", - parser, - parser._parseDeclaration.bind(parser), - ParseError.ColonExpected, - ); - assertError( - "fo:", - parser, - parser._parseDeclaration.bind(parser), - ParseError.PropertyValueExpected, - ); - assertError( - "color: hsl($hue: 0,", - parser, - parser._parseDeclaration.bind(parser), - ParseError.ExpressionExpected, - ); - assertError( - "color: hsl($hue: 0", - parser, - parser._parseDeclaration.bind(parser), - ParseError.RightParenthesisExpected, - ); - }); - - test("Stylesheet", function () { - const parser = new SCSSParser(); - assertNode( - "$color: #F5F5F5;", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "$color: #F5F5F5; $color: #F5F5F5;", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "$color: #F5F5F5; $color: #F5F5F5; $color: #F5F5F5;", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "$color: #F5F5F5 !important;", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "#main { width: 97%; p, div { a { font-weight: bold; } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "a { &:hover { color: red; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "fo { font: 2px/3px { family: fantasy; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".foo { bar: { yoo: fantasy; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "selector { propsuffix: { nested: 1px; } rule: 1px; nested.selector { foo: 1; } nested:selector { foo: 2 }}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:s(1)}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@mixin keyframe { @keyframes name { @content; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@include keyframe { 10% { top: 3px; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".class{&--sub-class-with-ampersand{color: red;}}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertError( - "fo { font: 2px/3px { family } }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.ColonExpected, - ); - - assertNode( - "legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:m.s(1)}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@include module.keyframe { 10% { top: 3px; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@import", function () { - const parser = new SCSSParser(); - assertNode('@import "test.css"', parser, parser._parseImport.bind(parser)); - assertNode( - '@import url("test.css")', - parser, - parser._parseImport.bind(parser), - ); - assertNode( - '@import "test.css", "bar.css"', - parser, - parser._parseImport.bind(parser), - ); - assertNode( - '@import "test.css", "bar.css" screen, projection', - parser, - parser._parseImport.bind(parser), - ); - assertNode( - 'foo { @import "test.css"; }', - parser, - parser._parseStylesheet.bind(parser), - ); - - assertError( - '@import "test.css" "bar.css"', - parser, - parser._parseStylesheet.bind(parser), - ParseError.MediaQueryExpected, - ); - assertError( - '@import "test.css", screen', - parser, - parser._parseImport.bind(parser), - ParseError.URIOrStringExpected, - ); - assertError( - "@import", - parser, - parser._parseImport.bind(parser), - ParseError.URIOrStringExpected, - ); - assertNode( - '@import url("override.css") layer;', - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@layer", function () { - const parser = new SCSSParser(); - assertNode("@layer #{$layer} { }", parser, parser._parseLayer.bind(parser)); - }); - - test("@container", function () { - const parser = new SCSSParser(); - assertNode( - `@container (min-width: #{$minWidth}) { .scss-interpolation { line-height: 10cqh; } }`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `.item-icon { @container (max-height: 100px) { .item-icon { display: none; } } }`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `:root { @container (max-height: 100px) { display: none;} }`, - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@use", function () { - const parser = new SCSSParser(); - assertNode('@use "test"', parser, parser._parseUse.bind(parser)); - assertNode('@use "test" as foo', parser, parser._parseUse.bind(parser)); - assertNode('@use "test" as *', parser, parser._parseUse.bind(parser)); - assertNode( - '@use "test" with ($foo: "test", $bar: 1)', - parser, - parser._parseUse.bind(parser), - ); - assertNode( - '@use "test" as foo with ($foo: "test", $bar: 1)', - parser, - parser._parseUse.bind(parser), - ); - - assertError( - "@use", - parser, - parser._parseUse.bind(parser), - ParseError.StringLiteralExpected, - ); - assertError( - '@use "test" foo', - parser, - parser._parseUse.bind(parser), - ParseError.UnknownKeyword, - ); - assertError( - '@use "test" as', - parser, - parser._parseUse.bind(parser), - ParseError.IdentifierOrWildcardExpected, - ); - assertError( - '@use "test" with', - parser, - parser._parseUse.bind(parser), - ParseError.LeftParenthesisExpected, - ); - assertError( - '@use "test" with ($foo)', - parser, - parser._parseUse.bind(parser), - ParseError.VariableValueExpected, - ); - assertError( - '@use "test" with ("bar")', - parser, - parser._parseUse.bind(parser), - ParseError.VariableNameExpected, - ); - assertError( - '@use "test" with ($foo: 1, "bar")', - parser, - parser._parseUse.bind(parser), - ParseError.VariableNameExpected, - ); - assertError( - '@use "test" with ($foo: "bar"', - parser, - parser._parseUse.bind(parser), - ParseError.RightParenthesisExpected, - ); - - assertNode( - '@forward "test"; @use "lib"', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@use "test"; @use "lib"', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '$test: "test"; @use "lib"', - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@forward", function () { - const parser = new SCSSParser(); - assertNode('@forward "test"', parser, parser._parseForward.bind(parser)); - assertNode( - '@forward "test" as foo-*', - parser, - parser._parseForward.bind(parser), - ); - assertNode( - '@forward "test" hide this', - parser, - parser._parseForward.bind(parser), - ); - assertNode( - '@forward "test" hide $that', - parser, - parser._parseForward.bind(parser), - ); - assertNode( - '@forward "test" hide this $that', - parser, - parser._parseForward.bind(parser), - ); - assertNode( - '@forward "test" hide this, $that', - parser, - parser._parseForward.bind(parser), - ); - assertNode( - '@forward "abstracts/functions" show px-to-rem, theme-color', - parser, - parser._parseForward.bind(parser), - ); - assertNode( - '@forward "test" show this', - parser, - parser._parseForward.bind(parser), - ); - assertNode( - '@forward "test" show $that', - parser, - parser._parseForward.bind(parser), - ); - assertNode( - '@forward "test" show this $that', - parser, - parser._parseForward.bind(parser), - ); - assertNode( - '@forward "test" as foo-* show this $that', - parser, - parser._parseForward.bind(parser), - ); - - assertError( - "@forward", - parser, - parser._parseForward.bind(parser), - ParseError.StringLiteralExpected, - ); - assertError( - '@forward "test" foo', - parser, - parser._parseForward.bind(parser), - ParseError.SemiColonExpected, - ); - assertError( - '@forward "test" as', - parser, - parser._parseForward.bind(parser), - ParseError.IdentifierExpected, - ); - assertError( - '@forward "test" as foo-', - parser, - parser._parseForward.bind(parser), - ParseError.WildcardExpected, - ); - assertError( - '@forward "test" as foo- *', - parser, - parser._parseForward.bind(parser), - ParseError.WildcardExpected, - ); - assertError( - '@forward "test" show', - parser, - parser._parseForward.bind(parser), - ParseError.IdentifierOrVariableExpected, - ); - assertError( - '@forward "test" hide', - parser, - parser._parseForward.bind(parser), - ParseError.IdentifierOrVariableExpected, - ); - - assertNode( - '@forward "test" with ( $black: #222 !default, $border-radius: 0.1rem !default )', - parser, - parser._parseForward.bind(parser), - ); - assertNode( - '@forward "../forms.scss" as components-* with ( $field-border: false )', - parser, - parser._parseForward.bind(parser), - ); // #145108 - - assertNode( - '@use "lib"; @forward "test"', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@forward "test"; @forward "lib"', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '$test: "test"; @forward "test"', - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@media", function () { - const parser = new SCSSParser(); - assertNode( - "@media screen { .sidebar { @media (orientation: landscape) { width: 500px; } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media #{$media} and ($feature: $value) {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media only screen and #{$query} {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "foo { bar { @media screen and (orientation: landscape) {}} }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media screen and (nth($query, 1): nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - ".something { @media (max-width: 760px) { > .test { color: blue; } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".something { @media (max-width: 760px) { ~ div { display: block; } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".something { @media (max-width: 760px) { + div { display: block; } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media (max-width: 760px) { + div { display: block; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media (height <= 600px) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media (height >= 600px) { }", - parser, - parser._parseMedia.bind(parser), - ); - - assertNode( - "@media #{layout.$media} and ($feature: $value) {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media #{$media} and (layout.$feature: $value) {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media #{$media} and ($feature: layout.$value) {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media #{layout.$media} and (layout.$feature: $value) {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media #{$media} and (layout.$feature: layout.$value) {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media #{layout.$media} and (layout.$feature: layout.$value) {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media screen and (list.nth($query, 1): nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (nth(list.$query, 1): nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (nth($query, 1): list.nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (nth($query, 1): nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (list.nth(list.$query, 1): nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (list.nth($query, 1): list.nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (list.nth($query, 1): nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (nth(list.$query, 1): list.nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (nth(list.$query, 1): nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (nth($query, 1): list.nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (list.nth(list.$query, 1): list.nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (nth(list.$query, 1): list.nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (list.nth(list.$query, 1): list.nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - }); - - test("@keyframe", function () { - const parser = new SCSSParser(); - assertNode( - "@keyframes name { @content; }", - parser, - parser._parseKeyframe.bind(parser), - ); - assertNode( - "@keyframes name { @for $i from 0 through $steps { #{$i * (100%/$steps)} { transform: $rotate $translate; } } }", - parser, - parser._parseKeyframe.bind(parser), - ); // issue 42086 - assertNode( - '@keyframes test-keyframe { @for $i from 1 through 60 { $s: ($i * 100) / 60 + "%"; } }', - parser, - parser._parseKeyframe.bind(parser), - ); - - assertNode( - "@keyframes name { @for $i from 0 through m.$steps { #{$i * (100%/$steps)} { transform: $rotate $translate; } } }", - parser, - parser._parseKeyframe.bind(parser), - ); - assertNode( - "@keyframes name { @function bar() { } }", - parser, - parser._parseKeyframe.bind(parser), - ); // #197742 - assertNode( - "@keyframes name { @include keyframe-mixin(); }", - parser, - parser._parseKeyframe.bind(parser), - ); // #197742 - }); - - test("@extend", function () { - const parser = new SCSSParser(); - assertNode( - ".themable { @extend %theme; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "foo { @extend .error; border-width: 3px; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "a.important { @extend .notice !optional; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".hoverlink { @extend a:hover; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".seriousError { @extend .error; @extend .attention; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "#context a%extreme { color: blue; } .notice { @extend %extreme }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media print { .error { } .seriousError { @extend .error; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@mixin error($a: false) { @extend .#{$a}; @extend ##{$a}; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".foo { @extend .text-center, .uppercase; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".foo { @extend .text-center, .uppercase, ; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".foo { @extend .text-center, .uppercase !optional ; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertError( - ".hoverlink { @extend }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.SelectorExpected, - ); - assertError( - ".hoverlink { @extend %extreme !default }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.UnknownKeyword, - ); - }); - - test("@debug", function () { - const parser = new SCSSParser(); - assertNode("@debug test;", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "foo { @debug 1 + 4; nested { @warn 1 4; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@if $foo == 1 { @debug 1 + 4 }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@function setStyle($map, $object, $style) { @warn "The key ´#{$object} is not available in the map."; @return null; }', - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@if", function () { - const parser = new SCSSParser(); - assertNode( - "@if 1 + 1 == 2 { border: 1px solid; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "@if 5 < 3 { border: 2px dotted; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "@if null { border: 3px double; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "@if 1 <= $const { border: 3px; } @else { border: 4px; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "@if 1 >= (1 + $foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "p { @if $i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@if (index($_RESOURCES, "clean") != null) { @error "sdssd"; }', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@if $i == 1 { p { x: 3px; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertError( - "@if { border: 1px solid; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.ExpressionExpected, - ); - assertError( - "@if 1 }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.LeftCurlyExpected, - ); - - assertNode( - "@if 1 <= m.$const { border: 3px; } @else { border: 4px; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "@if 1 >= (1 + m.$foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "p { @if m.$i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "p { @if $i == 1 { x: 3px; } @else if m.$i == 1 { x: 4px; } @else { x: 4px; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "p { @if m.$i == 1 { x: 3px; } @else if m.$i == 1 { x: 4px; } @else { x: 4px; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@if (list.index($_RESOURCES, "clean") != null) { @error "sdssd"; }', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@if (index(m.$_RESOURCES, "clean") != null) { @error "sdssd"; }', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@if (list.index(m.$_RESOURCES, "clean") != null) { @error "sdssd"; }', - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@for", function () { - const parser = new SCSSParser(); - assertNode( - "@for $i from 1 to 5 { .item-#{$i} { width: 2em * $i; } }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "@for $k from 1 + $x through 5 + $x { }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertError( - "@for i from 0 to 4 {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.VariableNameExpected, - ); - assertError( - "@for $i to 4 {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - SCSSParseError.FromExpected, - ); - assertError( - "@for $i from 0 by 4 {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - SCSSParseError.ThroughOrToExpected, - ); - assertError( - "@for $i from {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.ExpressionExpected, - ); - assertError( - "@for $i from 0 to {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.ExpressionExpected, - ); - assertNode( - '@for $i from 1 through 60 { $s: $i + "%"; }', - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - - assertNode( - "@for $k from 1 + m.$x through 5 + $x { }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "@for $k from 1 + $x through 5 + m.$x { }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "@for $k from 1 + m.$x through 5 + m.$x { }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - }); - - test("@each", function () { - const parser = new SCSSParser(); - assertNode( - "@each $i in 1, 2, 3 { }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "@each $i in 1 2 3 { }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "@each $animal, $color, $cursor in (puma, black, default), (egret, white, move) {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertError( - "@each i in 4 {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.VariableNameExpected, - ); - assertError( - "@each $i from 4 {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - SCSSParseError.InExpected, - ); - assertError( - "@each $i in {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.ExpressionExpected, - ); - assertError( - "@each $animal, in (1, 1, 1), (2, 2, 2) {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.VariableNameExpected, - ); - }); - - test("@while", function () { - const parser = new SCSSParser(); - assertNode( - "@while $i < 0 { .item-#{$i} { width: 2em * $i; } $i: $i - 2; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertError( - "@while {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.ExpressionExpected, - ); - assertError( - "@while $i != 4", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.LeftCurlyExpected, - ); - assertError( - "@while ($i >= 4) {", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.RightCurlyExpected, - ); - }); - - test("@mixin", function () { - const parser = new SCSSParser(); - assertNode( - "@mixin large-text { font: { family: Arial; size: 20px; } color: #ff0000; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@mixin sexy-border($color, $width: 1in) { color: black; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@mixin box-shadow($shadows...) { -moz-box-shadow: $shadows; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@mixin apply-to-ie6-only { * html { @content; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@mixin #{foo}($color){}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@mixin foo ($i:4) { size: $i; @include wee ($i - 1); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@mixin foo ($i,) { }", - parser, - parser._parseStylesheet.bind(parser), - ); - - assertError( - "@mixin $1 {}", - parser, - parser._parseStylesheet.bind(parser), - ParseError.IdentifierExpected, - ); - assertError( - "@mixin foo() i {}", - parser, - parser._parseStylesheet.bind(parser), - ParseError.LeftCurlyExpected, - ); - assertError( - "@mixin foo(1) {}", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "@mixin foo($color = 9) {}", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "@mixin foo($color)", - parser, - parser._parseStylesheet.bind(parser), - ParseError.LeftCurlyExpected, - ); - assertError( - "@mixin foo($color){", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightCurlyExpected, - ); - assertError( - "@mixin foo($color,){", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightCurlyExpected, - ); - }); - - test("@content", function () { - const parser = new SCSSParser(); - assertNode("@content", parser, parser._parseMixinContent.bind(parser)); - assertNode( - "@content($type)", - parser, - parser._parseMixinContent.bind(parser), - ); - }); - - test("@include", function () { - const parser = new SCSSParser(); - assertNode( - "p { @include sexy-border(blue); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".shadows { @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "$values: #ff0000, #00ff00, #0000ff; .primary { @include colors($values...); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@include colors(this("styles")...);', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".test { @include fontsize(16px, 21px !important); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "p { @include apply-to-ie6-only { #logo { background-image: url(/logo.gif); } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "p { @include foo($values,) }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "p { @include foo($values,); }", - parser, - parser._parseStylesheet.bind(parser), - ); - - assertError( - "p { @include sexy-border blue", - parser, - parser._parseStylesheet.bind(parser), - ParseError.SemiColonExpected, - ); - assertError( - "p { @include sexy-border($values blue", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "p { @include }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.IdentifierExpected, - ); - assertError( - "p { @include foo($values }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "p { @include foo($values, }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.ExpressionExpected, - ); - - assertNode( - "p { @include lib.sexy-border(blue); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".shadows { @include lib.box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "$values: #ff0000, #00ff00, #0000ff; .primary { @include lib.colors($values...); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".primary { @include colors(lib.$values...); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".primary { @include lib.colors(lib.$values...); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@include lib.colors(this("styles")...);', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@include colors(lib.this("styles")...);', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@include lib.colors(lib.this("styles")...);', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".test { @include lib.fontsize(16px, 21px !important); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "p { @include lib.apply-to-ie6-only { #logo { background-image: url(/logo.gif); } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "p { @include lib.foo($values,) }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "p { @include foo(lib.$values,) }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "p { @include lib.foo(m.$values,); }", - parser, - parser._parseStylesheet.bind(parser), - ); - - assertError( - "p { @include foo.($values) }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.IdentifierExpected, - ); - - assertNode( - '@include rtl("left") using ($dir) { margin-#{$dir}: 10px; }', - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@function", function () { - const parser = new SCSSParser(); - assertNode( - "@function grid-width($n) { @return $n * $grid-width + ($n - 1) * $gutter-width; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@function grid-width($n: 1, $e) { @return 0; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@function foo($total, $a) { @for $i from 0 to $total { } @return $grid; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@function foo() { @if (unit($a) == "%") and ($i == ($total - 1)) { @return 0; } @return 1; }', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@function is-even($int) { @if $int%2 == 0 { @return true; } @return false }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@function bar ($i) { @if $i > 0 { @return $i * bar($i - 1); } @return 1; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@function foo($a,) {} ", - parser, - parser._parseStylesheet.bind(parser), - ); - - assertError( - "@function foo {} ", - parser, - parser._parseStylesheet.bind(parser), - ParseError.LeftParenthesisExpected, - ); - assertError( - "@function {} ", - parser, - parser._parseStylesheet.bind(parser), - ParseError.IdentifierExpected, - ); - assertError( - "@function foo($a $b) {} ", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "@function foo($a {} ", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "@function foo($a...) { @return; }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.ExpressionExpected, - ); - assertError( - "@function foo($a:) {} ", - parser, - parser._parseStylesheet.bind(parser), - ParseError.VariableValueExpected, - ); - }); - - test("@at-root", function () { - const parser = new SCSSParser(); - assertNode( - "@mixin unify-parent($child) { @at-root #{selector.unify(&, $child)} { }}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@at-root #main2 .some-class { padding-left: calc( #{$a-variable} + 8px ); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media print { .page { @at-root (without: media) { } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media print { .page { @at-root (with: rule) { } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("Ruleset", function () { - const parser = new SCSSParser(); - assertNode( - ".selector { prop: erty $const 1px; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".selector { prop: erty $const 1px m.$foo; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "selector:active { property:value; nested:hover {}}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode("selector {}", parser, parser._parseRuleset.bind(parser)); - assertNode( - "selector { property: declaration }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "selector { $variable: declaration }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "selector { nested {}}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "selector { nested, a, b {}}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "selector { property: value; property: $value; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "selector { property: value; @keyframes foo {} @-moz-keyframes foo {}}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode("foo|bar { }", parser, parser._parseRuleset.bind(parser)); - }); - - test("Nested Ruleset", function () { - const parser = new SCSSParser(); - assertNode( - ".class1 { $const: 1; .class { $const: 2; three: $const; const: 3; } one: $const; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".class1 { $const: 1; .class { $const: m.$foo; } one: $const; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".class1 { > .class2 { & > .class4 { rule1: v1; } } }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "foo { @at-root { display: none; } }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - 'th, tr { @at-root #{selector-replace(&, "tr")} { border-bottom: 0; } }', - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".foo { @supports(display: grid) { .bar { display: none; }}}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".foo { @supports(display: grid) { display: none; }}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".foo { @supports (position: sticky) { @media (min-width: map-get($grid-breakpoints, md)) { position: sticky; } }}", - parser, - parser._parseRuleset.bind(parser), - ); // issue #152 - }); - - test("Selector Interpolation", function () { - const parser = new SCSSParser(); - assertNode(".#{$name} { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".#{$name}-foo { }", parser, parser._parseRuleset.bind(parser)); - assertNode( - ".#{$name}-foo-3 { }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode(".#{$name}-1 { }", parser, parser._parseRuleset.bind(parser)); - assertNode( - ".sc-col#{$postfix}-2-1 { }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "p.#{$name} { #{$attr}-color: blue; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "sans-#{serif} { a-#{1 + 2}-color-#{$attr}: blue; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "##{f} .#{f} #{f}:#{f} { }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".foo-#{&} .foo-#{&-sub} { }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode(".-#{$variable} { }", parser, parser._parseRuleset.bind(parser)); - assertNode( - "#{&}([foo=bar][bar=foo]) { }", - parser, - parser._parseRuleset.bind(parser), - ); // #49589 - - assertNode( - ".#{module.$name} { }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".#{module.$name}-foo { }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".#{module.$name}-foo-3 { }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".#{module.$name}-1 { }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".sc-col#{module.$postfix}-2-1 { }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "p.#{module.$name} { #{$attr}-color: blue; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "p.#{$name} { #{module.$attr}-color: blue; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "p.#{module.$name} { #{module.$attr}-color: blue; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "sans-#{serif} { a-#{1 + 2}-color-#{module.$attr}: blue; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".-#{module.$variable} { }", - parser, - parser._parseRuleset.bind(parser), - ); - }); - - test("Parent Selector", function () { - const parser = new SCSSParser(); - assertNode("&:hover", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&.float", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-bar", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-1", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&1", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-foo-1", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&&", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-10-thing", parser, parser._parseSimpleSelector.bind(parser)); - }); - - test("Selector Placeholder", function () { - const parser = new SCSSParser(); - assertNode("%hover", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("a%float", parser, parser._parseSimpleSelector.bind(parser)); - }); - - test("Map", function () { - const parser = new SCSSParser(); - assertNode( - "(key1: 1px, key2: solid + px, key3: (2+3))", - parser, - parser._parseExpr.bind(parser), - ); - assertNode("($key1 + 3: 1px)", parser, parser._parseExpr.bind(parser)); - }); - - test("Url", function () { - const parser = new SCSSParser(); - assertNode("url(foo())", parser, parser._parseURILiteral.bind(parser)); - assertNode( - "url('data:image/svg+xml;utf8,%3Csvg%20fill%3D%22%23' + $color + 'foo')", - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode( - "url(//yourdomain/yourpath.png)", - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode( - "url('http://msft.com')", - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode( - 'url("http://msft.com")', - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode( - 'url( "http://msft.com")', - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode( - 'url(\t"http://msft.com")', - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode( - 'url(\n"http://msft.com")', - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode( - 'url("http://msft.com"\n)', - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode('url("")', parser, parser._parseURILiteral.bind(parser)); - assertNode('uRL("")', parser, parser._parseURILiteral.bind(parser)); - assertNode('URL("")', parser, parser._parseURILiteral.bind(parser)); - assertNode( - "url(http://msft.com)", - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode("url()", parser, parser._parseURILiteral.bind(parser)); - assertNode( - "url('http://msft.com\n)", - parser, - parser._parseURILiteral.bind(parser), - ); - assertError( - 'url("http://msft.com"', - parser, - parser._parseURILiteral.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "url(http://msft.com')", - parser, - parser._parseURILiteral.bind(parser), - ParseError.RightParenthesisExpected, - ); - }); - - test("@font-face", function () { - const parser = new SCSSParser(); - assertNode("@font-face {}", parser, parser._parseFontFace.bind(parser)); - assertNode( - "@font-face { src: url(http://test) }", - parser, - parser._parseFontFace.bind(parser), - ); - assertNode( - "@font-face { font-style: normal; font-stretch: normal; }", - parser, - parser._parseFontFace.bind(parser), - ); - assertNode( - "@font-face { unicode-range: U+0021-007F, u+1f49C, U+4??, U+??????; }", - parser, - parser._parseFontFace.bind(parser), - ); - assertError( - "@font-face { font-style: normal font-stretch: normal; }", - parser, - parser._parseFontFace.bind(parser), - ParseError.SemiColonExpected, - ); - }); -}); diff --git a/packages/parser/src/utils/arrays.ts b/packages/parser/src/utils/arrays.ts deleted file mode 100644 index ae03f485..00000000 --- a/packages/parser/src/utils/arrays.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -"use strict"; - -/** - * Takes a sorted array and a function p. The array is sorted in such a way that all elements where p(x) is false - * are located before all elements where p(x) is true. - * @returns the least x for which p(x) is true or array.length if no element fullfills the given function. - */ -export function findFirst(array: T[], p: (x: T) => boolean): number { - let low = 0, - high = array.length; - if (high === 0) { - return 0; // no children - } - while (low < high) { - const mid = Math.floor((low + high) / 2); - if (p(array[mid])) { - high = mid; - } else { - low = mid + 1; - } - } - return low; -} - -export function includes(array: T[], item: T): boolean { - return array.indexOf(item) !== -1; -} - -export function union(...arrays: T[][]): T[] { - const result: T[] = []; - for (const array of arrays) { - for (const item of array) { - if (!includes(result, item)) { - result.push(item); - } - } - } - return result; -} diff --git a/packages/parser/src/utils/objects.ts b/packages/parser/src/utils/objects.ts deleted file mode 100644 index 042b750b..00000000 --- a/packages/parser/src/utils/objects.ts +++ /dev/null @@ -1,12 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export function values(obj: { [s: string]: T }): T[] { - return Object.keys(obj).map((key) => obj[key]); -} - -export function isDefined(obj: T | undefined): obj is T { - return typeof obj !== "undefined"; -} diff --git a/packages/parser/src/utils/resources.ts b/packages/parser/src/utils/resources.ts deleted file mode 100644 index 4f2824d0..00000000 --- a/packages/parser/src/utils/resources.ts +++ /dev/null @@ -1,14 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI, Utils } from "vscode-uri"; - -export function dirname(uriString: string): string { - return Utils.dirname(URI.parse(uriString)).toString(true); -} - -export function joinPath(uriString: string, ...paths: string[]): string { - return Utils.joinPath(URI.parse(uriString), ...paths).toString(true); -} diff --git a/packages/parser/src/utils/strings.ts b/packages/parser/src/utils/strings.ts deleted file mode 100644 index aff58b56..00000000 --- a/packages/parser/src/utils/strings.ts +++ /dev/null @@ -1,111 +0,0 @@ -/* eslint-disable prefer-const */ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -export function startsWith(haystack: string, needle: string): boolean { - if (haystack.length < needle.length) { - return false; - } - - for (let i = 0; i < needle.length; i++) { - if (haystack[i] !== needle[i]) { - return false; - } - } - - return true; -} - -/** - * Determines if haystack ends with needle. - */ -export function endsWith(haystack: string, needle: string): boolean { - let diff = haystack.length - needle.length; - if (diff > 0) { - return haystack.lastIndexOf(needle) === diff; - } else if (diff === 0) { - return haystack === needle; - } else { - return false; - } -} - -/** - * Computes the difference score for two strings. More similar strings have a higher score. - * We use largest common subsequence dynamic programming approach but penalize in the end for length differences. - * Strings that have a large length difference will get a bad default score 0. - * Complexity - both time and space O(first.length * second.length) - * Dynamic programming LCS computation http://en.wikipedia.org/wiki/Longest_common_subsequence_problem - * - * @param first a string - * @param second a string - */ -export function difference( - first: string, - second: string, - maxLenDelta: number = 4, -): number { - let lengthDifference = Math.abs(first.length - second.length); - // We only compute score if length of the currentWord and length of entry.name are similar. - if (lengthDifference > maxLenDelta) { - return 0; - } - // Initialize LCS (largest common subsequence) matrix. - let LCS: number[][] = []; - let zeroArray: number[] = []; - let i: number, j: number; - for (i = 0; i < second.length + 1; ++i) { - zeroArray.push(0); - } - for (i = 0; i < first.length + 1; ++i) { - LCS.push(zeroArray); - } - for (i = 1; i < first.length + 1; ++i) { - for (j = 1; j < second.length + 1; ++j) { - if (first[i - 1] === second[j - 1]) { - LCS[i][j] = LCS[i - 1][j - 1] + 1; - } else { - LCS[i][j] = Math.max(LCS[i - 1][j], LCS[i][j - 1]); - } - } - } - return LCS[first.length][second.length] - Math.sqrt(lengthDifference); -} - -/** - * Limit of string length. - */ -export function getLimitedString(str: string, ellipsis = true): string { - if (!str) { - return ""; - } - if (str.length < 140) { - return str; - } - return str.slice(0, 140) + (ellipsis ? "\u2026" : ""); -} - -/** - * Limit of string length. - */ -export function trim(str: string, regexp: RegExp): string { - const m = regexp.exec(str); - if (m && m[0].length) { - return str.substr(0, str.length - m[0].length); - } - return str; -} - -export function repeat(value: string, count: number) { - let s = ""; - while (count > 0) { - if ((count & 1) === 1) { - s += value; - } - value += value; - count = count >>> 1; - } - return s; -} diff --git a/packages/parser/tsconfig.json b/packages/parser/tsconfig.json deleted file mode 100644 index 2b0655a9..00000000 --- a/packages/parser/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "es2020", - "lib": ["ES2020", "WebWorker"], - "sourceMap": true, - "module": "commonjs", - "moduleResolution": "node", - "declaration": true, - "rootDir": "src", - "outDir": "dist", - "strict": true - }, - "include": ["src/**/*.ts"], - "exclude": ["src/**/*.test.ts"] -} diff --git a/packages/parser/vitest.config.mts b/packages/parser/vitest.config.mts deleted file mode 100644 index e2df9da5..00000000 --- a/packages/parser/vitest.config.mts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from "vitest/config"; - -export default defineConfig({ - test: { - coverage: { - provider: "v8", - reporter: ["text", "json", "html"], - }, - }, -}); diff --git a/packages/vscode-css-languageservice/src/cssLanguageTypes.ts b/packages/vscode-css-languageservice/src/cssLanguageTypes.ts index 4d3a1410..217466f6 100644 --- a/packages/vscode-css-languageservice/src/cssLanguageTypes.ts +++ b/packages/vscode-css-languageservice/src/cssLanguageTypes.ts @@ -228,6 +228,7 @@ export interface LanguageServiceOptions { clientCapabilities?: ClientCapabilities; } +export type EntryStatus = "standard" | "experimental" | "nonstandard" | "obsolete"; export interface IReference { name: string; diff --git a/packages/language-facts/src/builtinData.ts b/packages/vscode-css-languageservice/src/languageFacts/builtinData.ts similarity index 69% rename from packages/language-facts/src/builtinData.ts rename to packages/vscode-css-languageservice/src/languageFacts/builtinData.ts index 99b7ccaa..9a54916c 100644 --- a/packages/language-facts/src/builtinData.ts +++ b/packages/vscode-css-languageservice/src/languageFacts/builtinData.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +"use strict"; export const positionKeywords: { [name: string]: string } = { bottom: @@ -16,8 +17,7 @@ export const positionKeywords: { [name: string]: string } = { export const repeatStyleKeywords: { [name: string]: string } = { "no-repeat": "Placed once and not repeated in this direction.", - repeat: - "Repeated in this direction as often as needed to cover the background painting area.", + repeat: "Repeated in this direction as often as needed to cover the background painting area.", "repeat-x": "Computes to ‘repeat no-repeat’.", "repeat-y": "Computes to ‘no-repeat repeat’.", round: @@ -33,11 +33,9 @@ export const lineStyleKeywords: { [name: string]: string } = { groove: "Looks as if it were carved in the canvas.", hidden: "Same as ‘none’, but has different behavior in the border conflict resolution rules for border-collapsed tables.", - inset: - "Looks as if the content on the inside of the border is sunken into the canvas.", + inset: "Looks as if the content on the inside of the border is sunken into the canvas.", none: "No border. Color and width are ignored.", - outset: - "Looks as if the content on the inside of the border is coming out of the canvas.", + outset: "Looks as if the content on the inside of the border is coming out of the canvas.", ridge: "Looks as if it were coming out of the canvas.", solid: "A single line segment.", }; @@ -46,10 +44,8 @@ export const lineWidthKeywords = ["medium", "thick", "thin"]; export const boxKeywords: { [name: string]: string } = { "border-box": "The background is painted within (clipped to) the border box.", - "content-box": - "The background is painted within (clipped to) the content box.", - "padding-box": - "The background is painted within (clipped to) the padding box.", + "content-box": "The background is painted within (clipped to) the content box.", + "padding-box": "The background is painted within (clipped to) the padding box.", }; export const geometryBoxKeywords: { [name: string]: string } = { @@ -61,58 +57,41 @@ export const geometryBoxKeywords: { [name: string]: string } = { export const cssWideKeywords: { [name: string]: string } = { initial: "Represents the value specified as the property’s initial value.", - inherit: - "Represents the computed value of the property on the element’s parent.", - unset: - "Acts as either `inherit` or `initial`, depending on whether the property is inherited or not.", + inherit: "Represents the computed value of the property on the element’s parent.", + unset: "Acts as either `inherit` or `initial`, depending on whether the property is inherited or not.", }; export const cssWideFunctions: { [name: string]: string } = { "var()": "Evaluates the value of a custom variable.", - "calc()": - "Evaluates an mathematical expression. The following operators can be used: + - * /.", + "calc()": "Evaluates an mathematical expression. The following operators can be used: + - * /.", }; export const imageFunctions: { [name: string]: string } = { "url()": "Reference an image file by URL", "image()": "Provide image fallbacks and annotations.", - "-webkit-image-set()": - "Provide multiple resolutions. Remember to use unprefixed image-set() in addition.", + "-webkit-image-set()": "Provide multiple resolutions. Remember to use unprefixed image-set() in addition.", "image-set()": "Provide multiple resolutions of an image and const the UA decide which is most appropriate in a given situation.", - "-moz-element()": - "Use an element in the document as an image. Remember to use unprefixed element() in addition.", + "-moz-element()": "Use an element in the document as an image. Remember to use unprefixed element() in addition.", "element()": "Use an element in the document as an image.", - "cross-fade()": - "Indicates the two images to be combined and how far along in the transition the combination is.", - "-webkit-gradient()": - "Deprecated. Use modern linear-gradient() or radial-gradient() instead.", - "-webkit-linear-gradient()": - "Linear gradient. Remember to use unprefixed version in addition.", - "-moz-linear-gradient()": - "Linear gradient. Remember to use unprefixed version in addition.", - "-o-linear-gradient()": - "Linear gradient. Remember to use unprefixed version in addition.", + "cross-fade()": "Indicates the two images to be combined and how far along in the transition the combination is.", + "-webkit-gradient()": "Deprecated. Use modern linear-gradient() or radial-gradient() instead.", + "-webkit-linear-gradient()": "Linear gradient. Remember to use unprefixed version in addition.", + "-moz-linear-gradient()": "Linear gradient. Remember to use unprefixed version in addition.", + "-o-linear-gradient()": "Linear gradient. Remember to use unprefixed version in addition.", "linear-gradient()": "A linear gradient is created by specifying a straight gradient line, and then several colors placed along that line.", - "-webkit-repeating-linear-gradient()": - "Repeating Linear gradient. Remember to use unprefixed version in addition.", - "-moz-repeating-linear-gradient()": - "Repeating Linear gradient. Remember to use unprefixed version in addition.", - "-o-repeating-linear-gradient()": - "Repeating Linear gradient. Remember to use unprefixed version in addition.", + "-webkit-repeating-linear-gradient()": "Repeating Linear gradient. Remember to use unprefixed version in addition.", + "-moz-repeating-linear-gradient()": "Repeating Linear gradient. Remember to use unprefixed version in addition.", + "-o-repeating-linear-gradient()": "Repeating Linear gradient. Remember to use unprefixed version in addition.", "repeating-linear-gradient()": "Same as linear-gradient, except the color-stops are repeated infinitely in both directions, with their positions shifted by multiples of the difference between the last specified color-stop’s position and the first specified color-stop’s position.", - "-webkit-radial-gradient()": - "Radial gradient. Remember to use unprefixed version in addition.", - "-moz-radial-gradient()": - "Radial gradient. Remember to use unprefixed version in addition.", + "-webkit-radial-gradient()": "Radial gradient. Remember to use unprefixed version in addition.", + "-moz-radial-gradient()": "Radial gradient. Remember to use unprefixed version in addition.", "radial-gradient()": "Colors emerge from a single point and smoothly spread outward in a circular or elliptical shape.", - "-webkit-repeating-radial-gradient()": - "Repeating radial gradient. Remember to use unprefixed version in addition.", - "-moz-repeating-radial-gradient()": - "Repeating radial gradient. Remember to use unprefixed version in addition.", + "-webkit-repeating-radial-gradient()": "Repeating radial gradient. Remember to use unprefixed version in addition.", + "-moz-repeating-radial-gradient()": "Repeating radial gradient. Remember to use unprefixed version in addition.", "repeating-radial-gradient()": "Same as radial-gradient, except the color-stops are repeated infinitely in both directions, with their positions shifted by multiples of the difference between the last specified color-stop’s position and the first specified color-stop’s position.", }; @@ -132,45 +111,27 @@ export const transitionTimingFunctions: { [name: string]: string } = { "cubic-bezier(0.6, -0.28, 0.735, 0.045)": "Ease-in Back. Overshoots.", "cubic-bezier(0.68, -0.55, 0.265, 1.55)": "Ease-in-out Back. Overshoots.", "cubic-bezier(0.175, 0.885, 0.32, 1.275)": "Ease-out Back. Overshoots.", - "cubic-bezier(0.6, 0.04, 0.98, 0.335)": - "Ease-in Circular. Based on half circle.", - "cubic-bezier(0.785, 0.135, 0.15, 0.86)": - "Ease-in-out Circular. Based on half circle.", - "cubic-bezier(0.075, 0.82, 0.165, 1)": - "Ease-out Circular. Based on half circle.", - "cubic-bezier(0.55, 0.055, 0.675, 0.19)": - "Ease-in Cubic. Based on power of three.", - "cubic-bezier(0.645, 0.045, 0.355, 1)": - "Ease-in-out Cubic. Based on power of three.", - "cubic-bezier(0.215, 0.610, 0.355, 1)": - "Ease-out Cubic. Based on power of three.", - "cubic-bezier(0.95, 0.05, 0.795, 0.035)": - "Ease-in Exponential. Based on two to the power ten.", - "cubic-bezier(1, 0, 0, 1)": - "Ease-in-out Exponential. Based on two to the power ten.", - "cubic-bezier(0.19, 1, 0.22, 1)": - "Ease-out Exponential. Based on two to the power ten.", + "cubic-bezier(0.6, 0.04, 0.98, 0.335)": "Ease-in Circular. Based on half circle.", + "cubic-bezier(0.785, 0.135, 0.15, 0.86)": "Ease-in-out Circular. Based on half circle.", + "cubic-bezier(0.075, 0.82, 0.165, 1)": "Ease-out Circular. Based on half circle.", + "cubic-bezier(0.55, 0.055, 0.675, 0.19)": "Ease-in Cubic. Based on power of three.", + "cubic-bezier(0.645, 0.045, 0.355, 1)": "Ease-in-out Cubic. Based on power of three.", + "cubic-bezier(0.215, 0.610, 0.355, 1)": "Ease-out Cubic. Based on power of three.", + "cubic-bezier(0.95, 0.05, 0.795, 0.035)": "Ease-in Exponential. Based on two to the power ten.", + "cubic-bezier(1, 0, 0, 1)": "Ease-in-out Exponential. Based on two to the power ten.", + "cubic-bezier(0.19, 1, 0.22, 1)": "Ease-out Exponential. Based on two to the power ten.", "cubic-bezier(0.47, 0, 0.745, 0.715)": "Ease-in Sine.", "cubic-bezier(0.445, 0.05, 0.55, 0.95)": "Ease-in-out Sine.", "cubic-bezier(0.39, 0.575, 0.565, 1)": "Ease-out Sine.", - "cubic-bezier(0.55, 0.085, 0.68, 0.53)": - "Ease-in Quadratic. Based on power of two.", - "cubic-bezier(0.455, 0.03, 0.515, 0.955)": - "Ease-in-out Quadratic. Based on power of two.", - "cubic-bezier(0.25, 0.46, 0.45, 0.94)": - "Ease-out Quadratic. Based on power of two.", - "cubic-bezier(0.895, 0.03, 0.685, 0.22)": - "Ease-in Quartic. Based on power of four.", - "cubic-bezier(0.77, 0, 0.175, 1)": - "Ease-in-out Quartic. Based on power of four.", - "cubic-bezier(0.165, 0.84, 0.44, 1)": - "Ease-out Quartic. Based on power of four.", - "cubic-bezier(0.755, 0.05, 0.855, 0.06)": - "Ease-in Quintic. Based on power of five.", - "cubic-bezier(0.86, 0, 0.07, 1)": - "Ease-in-out Quintic. Based on power of five.", - "cubic-bezier(0.23, 1, 0.320, 1)": - "Ease-out Quintic. Based on power of five.", + "cubic-bezier(0.55, 0.085, 0.68, 0.53)": "Ease-in Quadratic. Based on power of two.", + "cubic-bezier(0.455, 0.03, 0.515, 0.955)": "Ease-in-out Quadratic. Based on power of two.", + "cubic-bezier(0.25, 0.46, 0.45, 0.94)": "Ease-out Quadratic. Based on power of two.", + "cubic-bezier(0.895, 0.03, 0.685, 0.22)": "Ease-in Quartic. Based on power of four.", + "cubic-bezier(0.77, 0, 0.175, 1)": "Ease-in-out Quartic. Based on power of four.", + "cubic-bezier(0.165, 0.84, 0.44, 1)": "Ease-out Quartic. Based on power of four.", + "cubic-bezier(0.755, 0.05, 0.855, 0.06)": "Ease-in Quintic. Based on power of five.", + "cubic-bezier(0.86, 0, 0.07, 1)": "Ease-in-out Quintic. Based on power of five.", + "cubic-bezier(0.23, 1, 0.320, 1)": "Ease-out Quintic. Based on power of five.", }; export const basicShapeFunctions: { [name: string]: string } = { diff --git a/packages/vscode-css-languageservice/src/languageFacts/colors.ts b/packages/vscode-css-languageservice/src/languageFacts/colors.ts new file mode 100644 index 00000000..de6573fa --- /dev/null +++ b/packages/vscode-css-languageservice/src/languageFacts/colors.ts @@ -0,0 +1,645 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Color } from "../cssLanguageService"; + +import * as nodes from "../parser/cssNodes"; + +import * as l10n from "@vscode/l10n"; + +const hexColorRegExp = /(^#([0-9A-F]{3}){1,2}$)|(^#([0-9A-F]{4}){1,2}$)/i; + +export const colorFunctions = [ + { + label: "rgb", + func: "rgb($red, $green, $blue)", + insertText: "rgb(${1:red}, ${2:green}, ${3:blue})", + desc: l10n.t("Creates a Color from red, green, and blue values."), + }, + { + label: "rgba", + func: "rgba($red, $green, $blue, $alpha)", + insertText: "rgba(${1:red}, ${2:green}, ${3:blue}, ${4:alpha})", + desc: l10n.t("Creates a Color from red, green, blue, and alpha values."), + }, + { + label: "rgb relative", + func: "rgb(from $color $red $green $blue)", + insertText: "rgb(from ${1:color} ${2:r} ${3:g} ${4:b})", + desc: l10n.t("Creates a Color from the red, green, and blue values of another Color."), + }, + { + label: "hsl", + func: "hsl($hue, $saturation, $lightness)", + insertText: "hsl(${1:hue}, ${2:saturation}, ${3:lightness})", + desc: l10n.t("Creates a Color from hue, saturation, and lightness values."), + }, + { + label: "hsla", + func: "hsla($hue, $saturation, $lightness, $alpha)", + insertText: "hsla(${1:hue}, ${2:saturation}, ${3:lightness}, ${4:alpha})", + desc: l10n.t("Creates a Color from hue, saturation, lightness, and alpha values."), + }, + { + label: "hsl relative", + func: "hsl(from $color $hue $saturation $lightness)", + insertText: "hsl(from ${1:color} ${2:h} ${3:s} ${4:l})", + desc: l10n.t("Creates a Color from the hue, saturation, and lightness values of another Color."), + }, + { + label: "hwb", + func: "hwb($hue $white $black)", + insertText: "hwb(${1:hue} ${2:white} ${3:black})", + desc: l10n.t("Creates a Color from hue, white, and black values."), + }, + { + label: "hwb relative", + func: "hwb(from $color $hue $white $black)", + insertText: "hwb(from ${1:color} ${2:h} ${3:w} ${4:b})", + desc: l10n.t("Creates a Color from the hue, white, and black values of another Color."), + }, + { + label: "lab", + func: "lab($lightness $a $b)", + insertText: "lab(${1:lightness} ${2:a} ${3:b})", + desc: l10n.t("Creates a Color from lightness, a, and b values."), + }, + { + label: "lab relative", + func: "lab(from $color $lightness $a $b)", + insertText: "lab(from ${1:color} ${2:l} ${3:a} ${4:b})", + desc: l10n.t("Creates a Color from the lightness, a, and b values of another Color."), + }, + { + label: "oklab", + func: "oklab($lightness $a $b)", + insertText: "oklab(${1:lightness} ${2:a} ${3:b})", + desc: l10n.t("Creates a Color from lightness, a, and b values."), + }, + { + label: "oklab relative", + func: "oklab(from $color $lightness $a $b)", + insertText: "oklab(from ${1:color} ${2:l} ${3:a} ${4:b})", + desc: l10n.t("Creates a Color from the lightness, a, and b values of another Color."), + }, + { + label: "lch", + func: "lch($lightness $chroma $hue)", + insertText: "lch(${1:lightness} ${2:chroma} ${3:hue})", + desc: l10n.t("Creates a Color from lightness, chroma, and hue values."), + }, + { + label: "lch relative", + func: "lch(from $color $lightness $chroma $hue)", + insertText: "lch(from ${1:color} ${2:l} ${3:c} ${4:h})", + desc: l10n.t("Creates a Color from the lightness, chroma, and hue values of another Color."), + }, + { + label: "oklch", + func: "oklch($lightness $chroma $hue)", + insertText: "oklch(${1:lightness} ${2:chroma} ${3:hue})", + desc: l10n.t("Creates a Color from lightness, chroma, and hue values."), + }, + { + label: "oklch relative", + func: "oklch(from $color $lightness $chroma $hue)", + insertText: "oklch(from ${1:color} ${2:l} ${3:c} ${4:h})", + desc: l10n.t("Creates a Color from the lightness, chroma, and hue values of another Color."), + }, + { + label: "color", + func: "color($color-space $red $green $blue)", + insertText: + "color(${1|srgb,srgb-linear,display-p3,a98-rgb,prophoto-rgb,rec2020,xyx,xyz-d50,xyz-d65|} ${2:red} ${3:green} ${4:blue})", + desc: l10n.t("Creates a Color in a specific color space from red, green, and blue values."), + }, + { + label: "color relative", + func: "color(from $color $color-space $red $green $blue)", + insertText: + "color(from ${1:color} ${2|srgb,srgb-linear,display-p3,a98-rgb,prophoto-rgb,rec2020,xyx,xyz-d50,xyz-d65|} ${3:r} ${4:g} ${5:b})", + desc: l10n.t("Creates a Color in a specific color space from the red, green, and blue values of another Color."), + }, + { + label: "color-mix", + func: "color-mix(in $color-space, $color $percentage, $color $percentage)", + insertText: + "color-mix(in ${1|srgb,srgb-linear,lab,oklab,xyz,xyz-d50,xyz-d65|}, ${3:color} ${4:percentage}, ${5:color} ${6:percentage})", + desc: l10n.t("Mix two colors together in a rectangular color space."), + }, + { + label: "color-mix hue", + func: "color-mix(in $color-space $interpolation-method hue, $color $percentage, $color $percentage)", + insertText: + "color-mix(in ${1|hsl,hwb,lch,oklch|} ${2|shorter hue,longer hue,increasing hue,decreasing hue|}, ${3:color} ${4:percentage}, ${5:color} ${6:percentage})", + desc: l10n.t("Mix two colors together in a polar color space."), + }, +]; + +const colorFunctionNameRegExp = /^(rgb|rgba|hsl|hsla|hwb)$/i; + +export const colors: { [name: string]: string } = { + aliceblue: "#f0f8ff", + antiquewhite: "#faebd7", + aqua: "#00ffff", + aquamarine: "#7fffd4", + azure: "#f0ffff", + beige: "#f5f5dc", + bisque: "#ffe4c4", + black: "#000000", + blanchedalmond: "#ffebcd", + blue: "#0000ff", + blueviolet: "#8a2be2", + brown: "#a52a2a", + burlywood: "#deb887", + cadetblue: "#5f9ea0", + chartreuse: "#7fff00", + chocolate: "#d2691e", + coral: "#ff7f50", + cornflowerblue: "#6495ed", + cornsilk: "#fff8dc", + crimson: "#dc143c", + cyan: "#00ffff", + darkblue: "#00008b", + darkcyan: "#008b8b", + darkgoldenrod: "#b8860b", + darkgray: "#a9a9a9", + darkgrey: "#a9a9a9", + darkgreen: "#006400", + darkkhaki: "#bdb76b", + darkmagenta: "#8b008b", + darkolivegreen: "#556b2f", + darkorange: "#ff8c00", + darkorchid: "#9932cc", + darkred: "#8b0000", + darksalmon: "#e9967a", + darkseagreen: "#8fbc8f", + darkslateblue: "#483d8b", + darkslategray: "#2f4f4f", + darkslategrey: "#2f4f4f", + darkturquoise: "#00ced1", + darkviolet: "#9400d3", + deeppink: "#ff1493", + deepskyblue: "#00bfff", + dimgray: "#696969", + dimgrey: "#696969", + dodgerblue: "#1e90ff", + firebrick: "#b22222", + floralwhite: "#fffaf0", + forestgreen: "#228b22", + fuchsia: "#ff00ff", + gainsboro: "#dcdcdc", + ghostwhite: "#f8f8ff", + gold: "#ffd700", + goldenrod: "#daa520", + gray: "#808080", + grey: "#808080", + green: "#008000", + greenyellow: "#adff2f", + honeydew: "#f0fff0", + hotpink: "#ff69b4", + indianred: "#cd5c5c", + indigo: "#4b0082", + ivory: "#fffff0", + khaki: "#f0e68c", + lavender: "#e6e6fa", + lavenderblush: "#fff0f5", + lawngreen: "#7cfc00", + lemonchiffon: "#fffacd", + lightblue: "#add8e6", + lightcoral: "#f08080", + lightcyan: "#e0ffff", + lightgoldenrodyellow: "#fafad2", + lightgray: "#d3d3d3", + lightgrey: "#d3d3d3", + lightgreen: "#90ee90", + lightpink: "#ffb6c1", + lightsalmon: "#ffa07a", + lightseagreen: "#20b2aa", + lightskyblue: "#87cefa", + lightslategray: "#778899", + lightslategrey: "#778899", + lightsteelblue: "#b0c4de", + lightyellow: "#ffffe0", + lime: "#00ff00", + limegreen: "#32cd32", + linen: "#faf0e6", + magenta: "#ff00ff", + maroon: "#800000", + mediumaquamarine: "#66cdaa", + mediumblue: "#0000cd", + mediumorchid: "#ba55d3", + mediumpurple: "#9370d8", + mediumseagreen: "#3cb371", + mediumslateblue: "#7b68ee", + mediumspringgreen: "#00fa9a", + mediumturquoise: "#48d1cc", + mediumvioletred: "#c71585", + midnightblue: "#191970", + mintcream: "#f5fffa", + mistyrose: "#ffe4e1", + moccasin: "#ffe4b5", + navajowhite: "#ffdead", + navy: "#000080", + oldlace: "#fdf5e6", + olive: "#808000", + olivedrab: "#6b8e23", + orange: "#ffa500", + orangered: "#ff4500", + orchid: "#da70d6", + palegoldenrod: "#eee8aa", + palegreen: "#98fb98", + paleturquoise: "#afeeee", + palevioletred: "#d87093", + papayawhip: "#ffefd5", + peachpuff: "#ffdab9", + peru: "#cd853f", + pink: "#ffc0cb", + plum: "#dda0dd", + powderblue: "#b0e0e6", + purple: "#800080", + red: "#ff0000", + rebeccapurple: "#663399", + rosybrown: "#bc8f8f", + royalblue: "#4169e1", + saddlebrown: "#8b4513", + salmon: "#fa8072", + sandybrown: "#f4a460", + seagreen: "#2e8b57", + seashell: "#fff5ee", + sienna: "#a0522d", + silver: "#c0c0c0", + skyblue: "#87ceeb", + slateblue: "#6a5acd", + slategray: "#708090", + slategrey: "#708090", + snow: "#fffafa", + springgreen: "#00ff7f", + steelblue: "#4682b4", + tan: "#d2b48c", + teal: "#008080", + thistle: "#d8bfd8", + tomato: "#ff6347", + turquoise: "#40e0d0", + violet: "#ee82ee", + wheat: "#f5deb3", + white: "#ffffff", + whitesmoke: "#f5f5f5", + yellow: "#ffff00", + yellowgreen: "#9acd32", +}; + +const colorsRegExp = new RegExp(`^(${Object.keys(colors).join("|")})$`, "i"); + +export const colorKeywords: { [name: string]: string } = { + currentColor: + "The value of the 'color' property. The computed value of the 'currentColor' keyword is the computed value of the 'color' property. If the 'currentColor' keyword is set on the 'color' property itself, it is treated as 'color:inherit' at parse time.", + transparent: + "Fully transparent. This keyword can be considered a shorthand for rgba(0,0,0,0) which is its computed value.", +}; + +const colorKeywordsRegExp = new RegExp(`^(${Object.keys(colorKeywords).join("|")})$`, "i"); + +function getNumericValue(node: nodes.Node, factor: number) { + const val = node.getText(); + const m = val.match(/^([-+]?[0-9]*\.?[0-9]+)(%?)$/); + if (m) { + if (m[2]) { + factor = 100.0; + } + const result = parseFloat(m[1]) / factor; + if (result >= 0 && result <= 1) { + return result; + } + } + throw new Error(); +} + +function getAngle(node: nodes.Node) { + const val = node.getText(); + const m = val.match(/^([-+]?[0-9]*\.?[0-9]+)(deg|rad|grad|turn)?$/); + if (m) { + switch (m[2]) { + case "deg": + return parseFloat(val) % 360; + case "rad": + return ((parseFloat(val) * 180) / Math.PI) % 360; + case "grad": + return (parseFloat(val) * 0.9) % 360; + case "turn": + return (parseFloat(val) * 360) % 360; + default: + if ("undefined" === typeof m[2]) { + return parseFloat(val) % 360; + } + } + } + + throw new Error(); +} + +export function isColorConstructor(node: nodes.Function): boolean { + const name = node.getName(); + if (!name) { + return false; + } + return colorFunctionNameRegExp.test(name); +} + +export function isColorString(s: string) { + return hexColorRegExp.test(s) || colorsRegExp.test(s) || colorKeywordsRegExp.test(s); +} + +/** + * Returns true if the node is a color value - either + * defined a hex number, as rgb or rgba function, or + * as color name. + */ +export function isColorValue(node: nodes.Node): boolean { + if (node.type === nodes.NodeType.HexColorValue) { + return true; + } else if (node.type === nodes.NodeType.Function) { + return isColorConstructor(node); + } else if (node.type === nodes.NodeType.Identifier) { + if (node.parent && node.parent.type !== nodes.NodeType.Term) { + return false; + } + const candidateColor = node.getText().toLowerCase(); + if (candidateColor === "none") { + return false; + } + if (colors[candidateColor]) { + return true; + } + } + return false; +} + +const Digit0 = 48; +const Digit9 = 57; +const A = 65; +const F = 70; +const a = 97; +const f = 102; + +export function hexDigit(charCode: number) { + if (charCode < Digit0) { + return 0; + } + if (charCode <= Digit9) { + return charCode - Digit0; + } + if (charCode < a) { + charCode += a - A; + } + if (charCode >= a && charCode <= f) { + return charCode - a + 10; + } + return 0; +} + +export function colorFromHex(text: string): Color | null { + if (text[0] !== "#") { + return null; + } + switch (text.length) { + case 4: + return { + red: (hexDigit(text.charCodeAt(1)) * 0x11) / 255.0, + green: (hexDigit(text.charCodeAt(2)) * 0x11) / 255.0, + blue: (hexDigit(text.charCodeAt(3)) * 0x11) / 255.0, + alpha: 1, + }; + case 5: + return { + red: (hexDigit(text.charCodeAt(1)) * 0x11) / 255.0, + green: (hexDigit(text.charCodeAt(2)) * 0x11) / 255.0, + blue: (hexDigit(text.charCodeAt(3)) * 0x11) / 255.0, + alpha: (hexDigit(text.charCodeAt(4)) * 0x11) / 255.0, + }; + case 7: + return { + red: (hexDigit(text.charCodeAt(1)) * 0x10 + hexDigit(text.charCodeAt(2))) / 255.0, + green: (hexDigit(text.charCodeAt(3)) * 0x10 + hexDigit(text.charCodeAt(4))) / 255.0, + blue: (hexDigit(text.charCodeAt(5)) * 0x10 + hexDigit(text.charCodeAt(6))) / 255.0, + alpha: 1, + }; + case 9: + return { + red: (hexDigit(text.charCodeAt(1)) * 0x10 + hexDigit(text.charCodeAt(2))) / 255.0, + green: (hexDigit(text.charCodeAt(3)) * 0x10 + hexDigit(text.charCodeAt(4))) / 255.0, + blue: (hexDigit(text.charCodeAt(5)) * 0x10 + hexDigit(text.charCodeAt(6))) / 255.0, + alpha: (hexDigit(text.charCodeAt(7)) * 0x10 + hexDigit(text.charCodeAt(8))) / 255.0, + }; + } + return null; +} + +export function colorFrom256RGB(red: number, green: number, blue: number, alpha: number = 1.0): Color { + return { + red: red / 255.0, + green: green / 255.0, + blue: blue / 255.0, + alpha, + }; +} + +export function colorFromHSL(hue: number, sat: number, light: number, alpha: number = 1.0): Color { + hue = hue / 60.0; + if (sat === 0) { + return { red: light, green: light, blue: light, alpha }; + } else { + const hueToRgb = (t1: number, t2: number, hue: number) => { + while (hue < 0) { + hue += 6; + } + while (hue >= 6) { + hue -= 6; + } + + if (hue < 1) { + return (t2 - t1) * hue + t1; + } + if (hue < 3) { + return t2; + } + if (hue < 4) { + return (t2 - t1) * (4 - hue) + t1; + } + return t1; + }; + const t2 = light <= 0.5 ? light * (sat + 1) : light + sat - light * sat; + const t1 = light * 2 - t2; + return { red: hueToRgb(t1, t2, hue + 2), green: hueToRgb(t1, t2, hue), blue: hueToRgb(t1, t2, hue - 2), alpha }; + } +} + +export interface HSLA { + h: number; + s: number; + l: number; + a: number; +} + +export function hslFromColor(rgba: Color): HSLA { + const r = rgba.red; + const g = rgba.green; + const b = rgba.blue; + const a = rgba.alpha; + + const max = Math.max(r, g, b); + const min = Math.min(r, g, b); + let h = 0; + let s = 0; + const l = (min + max) / 2; + const chroma = max - min; + + if (chroma > 0) { + s = Math.min(l <= 0.5 ? chroma / (2 * l) : chroma / (2 - 2 * l), 1); + + switch (max) { + case r: + h = (g - b) / chroma + (g < b ? 6 : 0); + break; + case g: + h = (b - r) / chroma + 2; + break; + case b: + h = (r - g) / chroma + 4; + break; + } + + h *= 60; + h = Math.round(h); + } + return { h, s, l, a }; +} + +export function colorFromHWB(hue: number, white: number, black: number, alpha: number = 1.0): Color { + if (white + black >= 1) { + const gray = white / (white + black); + return { red: gray, green: gray, blue: gray, alpha }; + } + + const rgb = colorFromHSL(hue, 1, 0.5, alpha); + let red = rgb.red; + red *= 1 - white - black; + red += white; + + let green = rgb.green; + green *= 1 - white - black; + green += white; + + let blue = rgb.blue; + blue *= 1 - white - black; + blue += white; + + return { + red: red, + green: green, + blue: blue, + alpha, + }; +} + +export interface HWBA { + h: number; + w: number; + b: number; + a: number; +} + +export function hwbFromColor(rgba: Color): HWBA { + const hsl = hslFromColor(rgba); + const white = Math.min(rgba.red, rgba.green, rgba.blue); + const black = 1 - Math.max(rgba.red, rgba.green, rgba.blue); + + return { + h: hsl.h, + w: white, + b: black, + a: hsl.a, + }; +} + +export function getColorValue(node: nodes.Node): Color | null { + if (node.type === nodes.NodeType.HexColorValue) { + const text = node.getText(); + return colorFromHex(text); + } else if (node.type === nodes.NodeType.Function) { + const functionNode = node; + const name = functionNode.getName(); + let colorValues = functionNode.getArguments().getChildren(); + if (colorValues.length === 1) { + const functionArg = colorValues[0].getChildren(); + if (functionArg.length === 1 && functionArg[0].type === nodes.NodeType.Expression) { + colorValues = functionArg[0].getChildren(); + if (colorValues.length === 3) { + const lastValue = colorValues[2]; + if (lastValue instanceof nodes.BinaryExpression) { + const left = lastValue.getLeft(), + right = lastValue.getRight(), + operator = lastValue.getOperator(); + if (left && right && operator && operator.matches("/")) { + colorValues = [colorValues[0], colorValues[1], left, right]; + } + } + } + } + } + if (!name || colorValues.length < 3 || colorValues.length > 4) { + return null; + } + try { + const alpha = colorValues.length === 4 ? getNumericValue(colorValues[3], 1) : 1; + if (name === "rgb" || name === "rgba") { + return { + red: getNumericValue(colorValues[0], 255.0), + green: getNumericValue(colorValues[1], 255.0), + blue: getNumericValue(colorValues[2], 255.0), + alpha, + }; + } else if (name === "hsl" || name === "hsla") { + const h = getAngle(colorValues[0]); + const s = getNumericValue(colorValues[1], 100.0); + const l = getNumericValue(colorValues[2], 100.0); + return colorFromHSL(h, s, l, alpha); + } else if (name === "hwb") { + const h = getAngle(colorValues[0]); + const w = getNumericValue(colorValues[1], 100.0); + const b = getNumericValue(colorValues[2], 100.0); + return colorFromHWB(h, w, b, alpha); + } + } catch (e) { + // parse error on numeric value + return null; + } + } else if (node.type === nodes.NodeType.Identifier) { + if (node.parent && node.parent.type !== nodes.NodeType.Term) { + return null; + } + const term = node.parent; + if (term && term.parent && term.parent.type === nodes.NodeType.BinaryExpression) { + const expression = term.parent; + if ( + expression.parent && + expression.parent.type === nodes.NodeType.ListEntry && + (expression.parent).key === expression + ) { + return null; + } + } + + const candidateColor = node.getText().toLowerCase(); + if (candidateColor === "none") { + return null; + } + const colorHex = colors[candidateColor]; + if (colorHex) { + return colorFromHex(colorHex); + } + } + return null; +} diff --git a/packages/vscode-css-languageservice/src/languageFacts/dataManager.ts b/packages/vscode-css-languageservice/src/languageFacts/dataManager.ts new file mode 100644 index 00000000..87750d85 --- /dev/null +++ b/packages/vscode-css-languageservice/src/languageFacts/dataManager.ts @@ -0,0 +1,119 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +"use strict"; + +import { + ICSSDataProvider, + IPropertyData, + IAtDirectiveData, + IPseudoClassData, + IPseudoElementData, +} from "../cssLanguageTypes"; + +import * as objects from "../utils/objects"; +import { cssData } from "../data/webCustomData"; +import { CSSDataProvider } from "./dataProvider"; + +export class CSSDataManager { + private dataProviders: ICSSDataProvider[] = []; + + private _propertySet: { [k: string]: IPropertyData } = {}; + private _atDirectiveSet: { [k: string]: IAtDirectiveData } = {}; + private _pseudoClassSet: { [k: string]: IPseudoClassData } = {}; + private _pseudoElementSet: { [k: string]: IPseudoElementData } = {}; + + private _properties: IPropertyData[] = []; + private _atDirectives: IAtDirectiveData[] = []; + private _pseudoClasses: IPseudoClassData[] = []; + private _pseudoElements: IPseudoElementData[] = []; + + constructor(options?: { useDefaultDataProvider?: boolean; customDataProviders?: ICSSDataProvider[] }) { + this.setDataProviders(options?.useDefaultDataProvider !== false, options?.customDataProviders || []); + } + + setDataProviders(builtIn: boolean, providers: ICSSDataProvider[]) { + this.dataProviders = []; + if (builtIn) { + this.dataProviders.push(new CSSDataProvider(cssData)); + } + this.dataProviders.push(...providers); + this.collectData(); + } + + /** + * Collect all data & handle duplicates + */ + private collectData() { + this._propertySet = {}; + this._atDirectiveSet = {}; + this._pseudoClassSet = {}; + this._pseudoElementSet = {}; + + this.dataProviders.forEach((provider) => { + provider.provideProperties().forEach((p) => { + if (!this._propertySet[p.name]) { + this._propertySet[p.name] = p; + } + }); + provider.provideAtDirectives().forEach((p) => { + if (!this._atDirectiveSet[p.name]) { + this._atDirectiveSet[p.name] = p; + } + }); + provider.providePseudoClasses().forEach((p) => { + if (!this._pseudoClassSet[p.name]) { + this._pseudoClassSet[p.name] = p; + } + }); + provider.providePseudoElements().forEach((p) => { + if (!this._pseudoElementSet[p.name]) { + this._pseudoElementSet[p.name] = p; + } + }); + }); + + this._properties = objects.values(this._propertySet); + this._atDirectives = objects.values(this._atDirectiveSet); + this._pseudoClasses = objects.values(this._pseudoClassSet); + this._pseudoElements = objects.values(this._pseudoElementSet); + } + + getProperty(name: string): IPropertyData | undefined { + return this._propertySet[name]; + } + getAtDirective(name: string): IAtDirectiveData | undefined { + return this._atDirectiveSet[name]; + } + getPseudoClass(name: string): IPseudoClassData | undefined { + return this._pseudoClassSet[name]; + } + getPseudoElement(name: string): IPseudoElementData | undefined { + return this._pseudoElementSet[name]; + } + + getProperties(): IPropertyData[] { + return this._properties; + } + getAtDirectives(): IAtDirectiveData[] { + return this._atDirectives; + } + getPseudoClasses(): IPseudoClassData[] { + return this._pseudoClasses; + } + getPseudoElements(): IPseudoElementData[] { + return this._pseudoElements; + } + + isKnownProperty(name: string): boolean { + return name.toLowerCase() in this._propertySet; + } + + isStandardProperty(name: string): boolean { + return ( + this.isKnownProperty(name) && + (!this._propertySet[name.toLowerCase()].status || this._propertySet[name.toLowerCase()].status === "standard") + ); + } +} diff --git a/packages/vscode-css-languageservice/src/languageFacts/dataProvider.ts b/packages/vscode-css-languageservice/src/languageFacts/dataProvider.ts new file mode 100644 index 00000000..6413b695 --- /dev/null +++ b/packages/vscode-css-languageservice/src/languageFacts/dataProvider.ts @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +"use strict"; + +import { + CSSDataV1, + ICSSDataProvider, + IPropertyData, + IAtDirectiveData, + IPseudoClassData, + IPseudoElementData, +} from "../cssLanguageTypes"; + +export class CSSDataProvider implements ICSSDataProvider { + private _properties: IPropertyData[] = []; + private _atDirectives: IAtDirectiveData[] = []; + private _pseudoClasses: IPseudoClassData[] = []; + private _pseudoElements: IPseudoElementData[] = []; + + /** + * Currently, unversioned data uses the V1 implementation + * In the future when the provider handles multiple versions of HTML custom data, + * use the latest implementation for unversioned data + */ + constructor(data: CSSDataV1) { + this.addData(data); + } + + provideProperties() { + return this._properties; + } + provideAtDirectives() { + return this._atDirectives; + } + providePseudoClasses() { + return this._pseudoClasses; + } + providePseudoElements() { + return this._pseudoElements; + } + + private addData(data: CSSDataV1) { + if (Array.isArray(data.properties)) { + for (const prop of data.properties) { + if (isPropertyData(prop)) { + this._properties.push(prop); + } + } + } + if (Array.isArray(data.atDirectives)) { + for (const prop of data.atDirectives) { + if (isAtDirective(prop)) { + this._atDirectives.push(prop); + } + } + } + if (Array.isArray(data.pseudoClasses)) { + for (const prop of data.pseudoClasses) { + if (isPseudoClassData(prop)) { + this._pseudoClasses.push(prop); + } + } + } + if (Array.isArray(data.pseudoElements)) { + for (const prop of data.pseudoElements) { + if (isPseudoElementData(prop)) { + this._pseudoElements.push(prop); + } + } + } + } +} + +function isPropertyData(d: any): d is IPropertyData { + return typeof d.name === "string"; +} + +function isAtDirective(d: any): d is IAtDirectiveData { + return typeof d.name === "string"; +} + +function isPseudoClassData(d: any): d is IPseudoClassData { + return typeof d.name === "string"; +} + +function isPseudoElementData(d: any): d is IPseudoElementData { + return typeof d.name === "string"; +} diff --git a/packages/vscode-css-languageservice/src/languageFacts/entry.ts b/packages/vscode-css-languageservice/src/languageFacts/entry.ts new file mode 100644 index 00000000..1787a470 --- /dev/null +++ b/packages/vscode-css-languageservice/src/languageFacts/entry.ts @@ -0,0 +1,213 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +"use strict"; + +import { + EntryStatus, + IPropertyData, + IAtDirectiveData, + IPseudoClassData, + IPseudoElementData, + IValueData, + MarkupContent, + MarkupKind, + MarkedString, + HoverSettings, +} from "../cssLanguageTypes"; + +export interface Browsers { + E?: string; + FF?: string; + IE?: string; + O?: string; + C?: string; + S?: string; + count: number; + all: boolean; + onCodeComplete: boolean; +} + +export const browserNames = { + E: "Edge", + FF: "Firefox", + S: "Safari", + C: "Chrome", + IE: "IE", + O: "Opera", +}; + +function getEntryStatus(status: EntryStatus) { + switch (status) { + case "experimental": + return "⚠️ Property is experimental. Be cautious when using it.️\n\n"; + case "nonstandard": + return "🚨️ Property is nonstandard. Avoid using it.\n\n"; + case "obsolete": + return "🚨️️️ Property is obsolete. Avoid using it.\n\n"; + default: + return ""; + } +} + +export function getEntryDescription( + entry: IEntry2, + doesSupportMarkdown: boolean, + settings?: HoverSettings, +): MarkupContent | undefined { + let result: MarkupContent; + + if (doesSupportMarkdown) { + result = { + kind: "markdown", + value: getEntryMarkdownDescription(entry, settings), + }; + } else { + result = { + kind: "plaintext", + value: getEntryStringDescription(entry, settings), + }; + } + + if (result.value === "") { + return undefined; + } + + return result; +} + +export function textToMarkedString(text: string): MarkedString { + text = text.replace(/[\\`*_{}[\]()#+\-.!]/g, "\\$&"); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash + return text.replace(//g, ">"); +} + +function getEntryStringDescription(entry: IEntry2, settings?: HoverSettings): string { + if (!entry.description || entry.description === "") { + return ""; + } + + if (typeof entry.description !== "string") { + return entry.description.value; + } + + let result: string = ""; + + if (settings?.documentation !== false) { + if (entry.status) { + result += getEntryStatus(entry.status); + } + result += entry.description; + + const browserLabel = getBrowserLabel(entry.browsers); + if (browserLabel) { + result += "\n(" + browserLabel + ")"; + } + if ("syntax" in entry) { + result += `\n\nSyntax: ${entry.syntax}`; + } + } + if (entry.references && entry.references.length > 0 && settings?.references !== false) { + if (result.length > 0) { + result += "\n\n"; + } + result += entry.references + .map((r) => { + return `${r.name}: ${r.url}`; + }) + .join(" | "); + } + + return result; +} + +function getEntryMarkdownDescription(entry: IEntry2, settings?: HoverSettings): string { + if (!entry.description || entry.description === "") { + return ""; + } + + let result: string = ""; + if (settings?.documentation !== false) { + if (entry.status) { + result += getEntryStatus(entry.status); + } + + if (typeof entry.description === "string") { + result += textToMarkedString(entry.description); + } else { + result += + entry.description.kind === MarkupKind.Markdown + ? entry.description.value + : textToMarkedString(entry.description.value); + } + + const browserLabel = getBrowserLabel(entry.browsers); + if (browserLabel) { + result += "\n\n(" + textToMarkedString(browserLabel) + ")"; + } + if ("syntax" in entry && entry.syntax) { + result += `\n\nSyntax: ${textToMarkedString(entry.syntax)}`; + } + } + if (entry.references && entry.references.length > 0 && settings?.references !== false) { + if (result.length > 0) { + result += "\n\n"; + } + result += entry.references + .map((r) => { + return `[${r.name}](${r.url})`; + }) + .join(" | "); + } + + return result; +} + +/** + * Input is like `["E12","FF49","C47","IE","O"]` + * Output is like `Edge 12, Firefox 49, Chrome 47, IE, Opera` + */ +export function getBrowserLabel(browsers: string[] = []): string | null { + if (browsers.length === 0) { + return null; + } + + return browsers + .map((b) => { + let result = ""; + const matches = b.match(/([A-Z]+)(\d+)?/)!; + + const name = matches[1]; + const version = matches[2]; + + if (name in browserNames) { + result += browserNames[name as keyof typeof browserNames]; + } + if (version) { + result += " " + version; + } + return result; + }) + .join(", "); +} + +export type IEntry2 = IPropertyData | IAtDirectiveData | IPseudoClassData | IPseudoElementData | IValueData; + +/** + * Todo@Pine: Drop these two types and use IEntry2 + */ +export interface IEntry { + name: string; + description?: string | MarkupContent; + browsers?: string[]; + restrictions?: string[]; + status?: EntryStatus; + syntax?: string; + values?: IValue[]; +} + +export interface IValue { + name: string; + description?: string | MarkupContent; + browsers?: string[]; +} diff --git a/packages/language-facts/src/facts.ts b/packages/vscode-css-languageservice/src/languageFacts/facts.ts similarity index 100% rename from packages/language-facts/src/facts.ts rename to packages/vscode-css-languageservice/src/languageFacts/facts.ts diff --git a/packages/vscode-css-languageservice/src/parser/cssErrors.ts b/packages/vscode-css-languageservice/src/parser/cssErrors.ts new file mode 100644 index 00000000..7df6d7a7 --- /dev/null +++ b/packages/vscode-css-languageservice/src/parser/cssErrors.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +"use strict"; + +import * as nodes from "./cssNodes"; + +import * as l10n from "@vscode/l10n"; + +export class CSSIssueType implements nodes.IRule { + id: string; + message: string; + + public constructor(id: string, message: string) { + this.id = id; + this.message = message; + } +} + +export const ParseError = { + NumberExpected: new CSSIssueType("css-numberexpected", l10n.t("number expected")), + ConditionExpected: new CSSIssueType("css-conditionexpected", l10n.t("condition expected")), + RuleOrSelectorExpected: new CSSIssueType("css-ruleorselectorexpected", l10n.t("at-rule or selector expected")), + DotExpected: new CSSIssueType("css-dotexpected", l10n.t("dot expected")), + ColonExpected: new CSSIssueType("css-colonexpected", l10n.t("colon expected")), + SemiColonExpected: new CSSIssueType("css-semicolonexpected", l10n.t("semi-colon expected")), + TermExpected: new CSSIssueType("css-termexpected", l10n.t("term expected")), + ExpressionExpected: new CSSIssueType("css-expressionexpected", l10n.t("expression expected")), + OperatorExpected: new CSSIssueType("css-operatorexpected", l10n.t("operator expected")), + IdentifierExpected: new CSSIssueType("css-identifierexpected", l10n.t("identifier expected")), + PercentageExpected: new CSSIssueType("css-percentageexpected", l10n.t("percentage expected")), + URIOrStringExpected: new CSSIssueType("css-uriorstringexpected", l10n.t("uri or string expected")), + URIExpected: new CSSIssueType("css-uriexpected", l10n.t("URI expected")), + VariableNameExpected: new CSSIssueType("css-varnameexpected", l10n.t("variable name expected")), + VariableValueExpected: new CSSIssueType("css-varvalueexpected", l10n.t("variable value expected")), + PropertyValueExpected: new CSSIssueType("css-propertyvalueexpected", l10n.t("property value expected")), + LeftCurlyExpected: new CSSIssueType("css-lcurlyexpected", l10n.t("{ expected")), + RightCurlyExpected: new CSSIssueType("css-rcurlyexpected", l10n.t("} expected")), + LeftSquareBracketExpected: new CSSIssueType("css-rbracketexpected", l10n.t("[ expected")), + RightSquareBracketExpected: new CSSIssueType("css-lbracketexpected", l10n.t("] expected")), + LeftParenthesisExpected: new CSSIssueType("css-lparentexpected", l10n.t("( expected")), + RightParenthesisExpected: new CSSIssueType("css-rparentexpected", l10n.t(") expected")), + CommaExpected: new CSSIssueType("css-commaexpected", l10n.t("comma expected")), + PageDirectiveOrDeclarationExpected: new CSSIssueType( + "css-pagedirordeclexpected", + l10n.t("page directive or declaraton expected"), + ), + UnknownAtRule: new CSSIssueType("css-unknownatrule", l10n.t("at-rule unknown")), + UnknownKeyword: new CSSIssueType("css-unknownkeyword", l10n.t("unknown keyword")), + SelectorExpected: new CSSIssueType("css-selectorexpected", l10n.t("selector expected")), + StringLiteralExpected: new CSSIssueType("css-stringliteralexpected", l10n.t("string literal expected")), + WhitespaceExpected: new CSSIssueType("css-whitespaceexpected", l10n.t("whitespace expected")), + MediaQueryExpected: new CSSIssueType("css-mediaqueryexpected", l10n.t("media query expected")), + IdentifierOrWildcardExpected: new CSSIssueType("css-idorwildcardexpected", l10n.t("identifier or wildcard expected")), + WildcardExpected: new CSSIssueType("css-wildcardexpected", l10n.t("wildcard expected")), + IdentifierOrVariableExpected: new CSSIssueType("css-idorvarexpected", l10n.t("identifier or variable expected")), +}; diff --git a/packages/parser/src/cssNodes.ts b/packages/vscode-css-languageservice/src/parser/cssNodes.ts similarity index 96% rename from packages/parser/src/cssNodes.ts rename to packages/vscode-css-languageservice/src/parser/cssNodes.ts index aa7e5c99..c57de927 100644 --- a/packages/parser/src/cssNodes.ts +++ b/packages/vscode-css-languageservice/src/parser/cssNodes.ts @@ -1,11 +1,10 @@ -/* eslint-disable no-prototype-builtins */ -/* eslint-disable prefer-spread */ -/* eslint-disable @typescript-eslint/no-this-alias */ /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { trim } from "./utils/strings"; +"use strict"; + +import { trim } from "../utils/strings"; /// /// Nodes for the css 2.1 specification. See for reference: @@ -218,24 +217,15 @@ export class Node { } public matches(str: string): boolean { - return ( - this.length === str.length && - this.getTextProvider()(this.offset, this.length) === str - ); + return this.length === str.length && this.getTextProvider()(this.offset, this.length) === str; } public startsWith(str: string): boolean { - return ( - this.length >= str.length && - this.getTextProvider()(this.offset, str.length) === str - ); + return this.length >= str.length && this.getTextProvider()(this.offset, str.length) === str; } public endsWith(str: string): boolean { - return ( - this.length >= str.length && - this.getTextProvider()(this.end - str.length, str.length) === str - ); + return this.length >= str.length && this.getTextProvider()(this.end - str.length, str.length) === str; } public accept(visitor: IVisitorFunction): void { @@ -291,28 +281,17 @@ export class Node { } public hasIssue(rule: IRule): boolean { - return ( - Array.isArray(this.issues) && - this.issues.some((i) => i.getRule() === rule) - ); + return Array.isArray(this.issues) && this.issues.some((i) => i.getRule() === rule); } public isErroneous(recursive: boolean = false): boolean { if (this.issues && this.issues.length > 0) { return true; } - return ( - recursive && - Array.isArray(this.children) && - this.children.some((c) => c.isErroneous(true)) - ); - } - - public setNode( - field: keyof this, - node: Node | null, - index: number = -1, - ): boolean { + return recursive && Array.isArray(this.children) && this.children.some((c) => c.isErroneous(true)); + } + + public setNode(field: keyof this, node: Node | null, index: number = -1): boolean { if (node) { node.attachTo(this, index); (this)[field] = node; @@ -390,10 +369,7 @@ export class Node { } public encloses(candidate: Node): boolean { - return ( - this.offset <= candidate.offset && - this.offset + this.length >= candidate.offset + candidate.length - ); + return this.offset <= candidate.offset && this.offset + this.length >= candidate.offset + candidate.length; } public getParent(): Node | null { @@ -553,9 +529,7 @@ export class RuleSet extends BodyDeclaration { } public isNested(): boolean { - return ( - !!this.parent && this.parent.findParent(NodeType.Declarations) !== null - ); + return !!this.parent && this.parent.findParent(NodeType.Declarations) !== null; } } @@ -650,10 +624,7 @@ export class Declaration extends AbstractDeclaration { public getFullPropertyName(): string { const propertyName = this.property ? this.property.getName() : "unknown"; - if ( - this.parent instanceof Declarations && - this.parent.getParent() instanceof NestedProperties - ) { + if (this.parent instanceof Declarations && this.parent.getParent() instanceof NestedProperties) { const parentDecl = this.parent.getParent()!.getParent(); if (parentDecl instanceof Declaration) { return (parentDecl).getFullPropertyName() + propertyName; @@ -681,9 +652,7 @@ export class Declaration extends AbstractDeclaration { return this.value; } - public setNestedProperties( - value: NestedProperties | null, - ): value is NestedProperties { + public setNestedProperties(value: NestedProperties | null): value is NestedProperties { return this.setNode("nestedProperties", value); } @@ -703,9 +672,7 @@ export class CustomPropertyDeclaration extends Declaration { return NodeType.CustomPropertyDeclaration; } - public setPropertySet( - value: CustomPropertySet | null, - ): value is CustomPropertySet { + public setPropertySet(value: CustomPropertySet | null): value is CustomPropertySet { return this.setNode("propertySet", value); } @@ -733,7 +700,7 @@ export class Property extends Node { } public getName(): string { - return trim(this.getText(), /[_+]+$/); /* +_: less merge */ + return trim(this.getText(), /[_\+]+$/); /* +_: less merge */ } public isCustomProperty(): boolean { @@ -866,9 +833,7 @@ export class IfStatement extends BodyDeclaration { return this.setNode("expression", node, 0); } - public setElseClause( - elseClause: BodyDeclaration | null, - ): elseClause is BodyDeclaration { + public setElseClause(elseClause: BodyDeclaration | null): elseClause is BodyDeclaration { return this.setNode("elseClause", elseClause); } } @@ -1674,9 +1639,7 @@ export class MixinReference extends Node { return this.arguments; } - public setContent( - node: MixinContentDeclaration | null, - ): node is MixinContentDeclaration { + public setContent(node: MixinContentDeclaration | null): node is MixinContentDeclaration { return this.setNode("content", node); } diff --git a/packages/parser/src/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts similarity index 87% rename from packages/parser/src/cssParser.ts rename to packages/vscode-css-languageservice/src/parser/cssParser.ts index 9b0ec3a8..299a4bcc 100644 --- a/packages/parser/src/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -2,13 +2,13 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - -import { TextDocument } from "vscode-languageserver-textdocument"; -import { ParseError, CSSIssueType } from "./cssErrors"; -import * as nodes from "./cssNodes"; +"use strict"; import { TokenType, Scanner, IToken } from "./cssScanner"; +import * as nodes from "./cssNodes"; +import { ParseError, CSSIssueType } from "./cssErrors"; import * as languageFacts from "../languageFacts/facts"; -import { isDefined } from "./utils/objects"; +import { TextDocument } from "../cssLanguageTypes"; +import { isDefined } from "../utils/objects"; export interface IMark { prev?: IToken; @@ -70,10 +70,7 @@ export class Parser { } public hasWhitespace(): boolean { - return ( - !!this.prevToken && - this.prevToken.offset + this.prevToken.len !== this.token.offset - ); + return !!this.prevToken && this.prevToken.offset + this.prevToken.len !== this.token.offset; } public consumeToken(): void { @@ -118,10 +115,7 @@ export class Parser { public acceptOneKeyword(keywords: string[]): boolean { if (TokenType.AtKeyword === this.token.type) { for (const keyword of keywords) { - if ( - keyword.length === this.token.text.length && - keyword === this.token.text.toLowerCase() - ) { + if (keyword.length === this.token.text.length && keyword === this.token.text.toLowerCase()) { this.consumeToken(); return true; } @@ -189,18 +183,12 @@ export class Parser { return false; } - public resync( - resyncTokens: TokenType[] | undefined, - resyncStopTokens: TokenType[] | undefined, - ): boolean { + public resync(resyncTokens: TokenType[] | undefined, resyncStopTokens: TokenType[] | undefined): boolean { while (true) { if (resyncTokens && resyncTokens.indexOf(this.token.type) !== -1) { this.consumeToken(); return true; - } else if ( - resyncStopTokens && - resyncStopTokens.indexOf(this.token.type) !== -1 - ) { + } else if (resyncStopTokens && resyncStopTokens.indexOf(this.token.type) !== -1) { return true; } else { if (this.token.type === TokenType.EOF) { @@ -249,16 +237,7 @@ export class Parser { ): void { if (this.token !== this.lastErrorToken) { // do not report twice on the same token - node.addIssue( - new nodes.Marker( - node, - error, - nodes.Level.Error, - undefined, - this.token.offset, - this.token.len, - ), - ); + node.addIssue(new nodes.Marker(node, error, nodes.Level.Error, undefined, this.token.offset, this.token.len)); this.lastErrorToken = this.token; } if (resyncTokens || resyncStopTokens) { @@ -321,19 +300,11 @@ export class Parser { node.addChild(statement); hasMatch = true; inRecovery = false; - if ( - !this.peek(TokenType.EOF) && - this._needsSemicolonAfter(statement) && - !this.accept(TokenType.SemiColon) - ) { + if (!this.peek(TokenType.EOF) && this._needsSemicolonAfter(statement) && !this.accept(TokenType.SemiColon)) { this.markError(node, ParseError.SemiColonExpected); } } - while ( - this.accept(TokenType.SemiColon) || - this.accept(TokenType.CDO) || - this.accept(TokenType.CDC) - ) { + while (this.accept(TokenType.SemiColon) || this.accept(TokenType.CDO) || this.accept(TokenType.CDC)) { // accept empty statements hasMatch = true; inRecovery = false; @@ -362,18 +333,14 @@ export class Parser { return this._parseCharset(); } - public _parseStylesheetStatement( - isNested: boolean = false, - ): nodes.Node | null { + public _parseStylesheetStatement(isNested: boolean = false): nodes.Node | null { if (this.peek(TokenType.AtKeyword)) { return this._parseStylesheetAtStatement(isNested); } return this._parseRuleset(isNested); } - public _parseStylesheetAtStatement( - isNested: boolean = false, - ): nodes.Node | null { + public _parseStylesheetAtStatement(isNested: boolean = false): nodes.Node | null { return ( this._parseImport() || this._parseMedia(isNested) || @@ -480,9 +447,7 @@ export class Parser { return false; } - public _parseDeclarations( - parseDeclaration: () => nodes.Node | null, - ): nodes.Declarations | null { + public _parseDeclarations(parseDeclaration: () => nodes.Node | null): nodes.Declarations | null { const node = this.create(nodes.Declarations); if (!this.accept(TokenType.CurlyL)) { return null; @@ -493,21 +458,11 @@ export class Parser { if (this.peek(TokenType.CurlyR)) { break; } - if ( - this._needsSemicolonAfter(decl) && - !this.accept(TokenType.SemiColon) - ) { - return this.finish(node, ParseError.SemiColonExpected, [ - TokenType.SemiColon, - TokenType.CurlyR, - ]); + if (this._needsSemicolonAfter(decl) && !this.accept(TokenType.SemiColon)) { + return this.finish(node, ParseError.SemiColonExpected, [TokenType.SemiColon, TokenType.CurlyR]); } // We accepted semicolon token. Link it to declaration. - if ( - decl && - this.prevToken && - this.prevToken.type === TokenType.SemiColon - ) { + if (decl && this.prevToken && this.prevToken.type === TokenType.SemiColon) { (decl as nodes.Declaration).semicolonPosition = this.prevToken.offset; } while (this.accept(TokenType.SemiColon)) { @@ -517,23 +472,14 @@ export class Parser { } if (!this.accept(TokenType.CurlyR)) { - return this.finish(node, ParseError.RightCurlyExpected, [ - TokenType.CurlyR, - TokenType.SemiColon, - ]); + return this.finish(node, ParseError.RightCurlyExpected, [TokenType.CurlyR, TokenType.SemiColon]); } return this.finish(node); } - public _parseBody( - node: T, - parseDeclaration: () => nodes.Node | null, - ): T { + public _parseBody(node: T, parseDeclaration: () => nodes.Node | null): T { if (!node.setDeclarations(this._parseDeclarations(parseDeclaration))) { - return this.finish(node, ParseError.LeftCurlyExpected, [ - TokenType.CurlyR, - TokenType.SemiColon, - ]); + return this.finish(node, ParseError.LeftCurlyExpected, [TokenType.CurlyR, TokenType.SemiColon]); } return this.finish(node); } @@ -566,12 +512,7 @@ export class Parser { if (!this.accept(TokenType.Colon)) { return ( - this.finish( - node, - ParseError.ColonExpected, - [TokenType.Colon], - stopTokens || [TokenType.SemiColon], - ) + this.finish(node, ParseError.ColonExpected, [TokenType.Colon], stopTokens || [TokenType.SemiColon]) ); } if (this.prevToken) { @@ -589,9 +530,7 @@ export class Parser { return this.finish(node); } - public _tryParseCustomPropertyDeclaration( - stopTokens?: TokenType[], - ): nodes.CustomPropertyDeclaration | null { + public _tryParseCustomPropertyDeclaration(stopTokens?: TokenType[]): nodes.CustomPropertyDeclaration | null { if (!this.peekRegExp(TokenType.Ident, /^--/)) { return null; } @@ -611,13 +550,8 @@ export class Parser { if (this.peek(TokenType.CurlyL)) { // try to parse it as nested declaration const propertySet = this.create(nodes.CustomPropertySet); - const declarations = this._parseDeclarations( - this._parseRuleSetDeclaration.bind(this), - ); - if ( - propertySet.setDeclarations(declarations) && - !declarations.isErroneous(true) - ) { + const declarations = this._parseDeclarations(this._parseRuleSetDeclaration.bind(this)); + if (propertySet.setDeclarations(declarations) && !declarations.isErroneous(true)) { propertySet.addChild(this._parsePrio()); if (this.peek(TokenType.SemiColon)) { this.finish(propertySet); @@ -633,9 +567,7 @@ export class Parser { const expression = this._parseExpr(); if (expression && !expression.isErroneous(true)) { this._parsePrio(); - if ( - this.peekOne(...(stopTokens || []), TokenType.SemiColon, TokenType.EOF) - ) { + if (this.peekOne(...(stopTokens || []), TokenType.SemiColon, TokenType.EOF)) { node.setValue(expression); if (this.peek(TokenType.SemiColon)) { node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist @@ -646,10 +578,7 @@ export class Parser { this.restoreAtMark(mark); node.addChild(this._parseCustomPropertyValue(stopTokens)); node.addChild(this._parsePrio()); - if ( - isDefined(node.colonPosition) && - this.token.offset === node.colonPosition + 1 - ) { + if (isDefined(node.colonPosition) && this.token.offset === node.colonPosition + 1) { return this.finish(node, ParseError.PropertyValueExpected); } return this.finish(node); @@ -666,12 +595,9 @@ export class Parser { * terminators like semicolons and !important directives (when not inside * of delimitors). */ - public _parseCustomPropertyValue( - stopTokens: TokenType[] = [TokenType.CurlyR], - ): nodes.Node { + public _parseCustomPropertyValue(stopTokens: TokenType[] = [TokenType.CurlyR]): nodes.Node { const node = this.create(nodes.Node); - const isTopLevel = () => - curlyDepth === 0 && parensDepth === 0 && bracketsDepth === 0; + const isTopLevel = () => curlyDepth === 0 && parensDepth === 0 && bracketsDepth === 0; const onStopToken = () => stopTokens.indexOf(this.token.type) !== -1; let curlyDepth = 0; let parensDepth = 0; @@ -743,9 +669,7 @@ export class Parser { return this.finish(node); } - public _tryToParseDeclaration( - stopTokens?: TokenType[], - ): nodes.Declaration | null { + public _tryToParseDeclaration(stopTokens?: TokenType[]): nodes.Declaration | null { const mark = this.mark(); if (this._parseProperty() && this.accept(TokenType.Colon)) { // looks like a declaration, go ahead @@ -808,10 +732,7 @@ export class Parser { const node = this.create(nodes.Import); this.consumeToken(); // @import - if ( - !node.addChild(this._parseURILiteral()) && - !node.addChild(this._parseStringLiteral()) - ) { + if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) { return this.finish(node, ParseError.URIOrStringExpected); } @@ -822,32 +743,18 @@ export class Parser { if (this.acceptIdent("layer")) { if (this.accept(TokenType.ParenthesisL)) { if (!node.addChild(this._parseLayerName())) { - return this.finish(node, ParseError.IdentifierExpected, [ - TokenType.SemiColon, - ]); + return this.finish(node, ParseError.IdentifierExpected, [TokenType.SemiColon]); } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish( - node, - ParseError.RightParenthesisExpected, - [TokenType.ParenthesisR], - [], - ); + return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.ParenthesisR], []); } } } if (this.acceptIdent("supports")) { if (this.accept(TokenType.ParenthesisL)) { - node.addChild( - this._tryToParseDeclaration() || this._parseSupportsCondition(), - ); + node.addChild(this._tryToParseDeclaration() || this._parseSupportsCondition()); if (!this.accept(TokenType.ParenthesisR)) { - return this.finish( - node, - ParseError.RightParenthesisExpected, - [TokenType.ParenthesisR], - [], - ); + return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.ParenthesisR], []); } } } @@ -872,10 +779,7 @@ export class Parser { // url literal also starts with ident node.addChild(this._parseIdent()); // optional prefix - if ( - !node.addChild(this._parseURILiteral()) && - !node.addChild(this._parseStringLiteral()) - ) { + if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) { return this.finish(node, ParseError.URIExpected, [TokenType.SemiColon]); } } @@ -898,11 +802,7 @@ export class Parser { } public _parseViewPort(): nodes.Node | null { - if ( - !this.peekKeyword("@-ms-viewport") && - !this.peekKeyword("@-o-viewport") && - !this.peekKeyword("@viewport") - ) { + if (!this.peekKeyword("@-ms-viewport") && !this.peekKeyword("@-o-viewport") && !this.peekKeyword("@viewport")) { return null; } const node = this.create(nodes.ViewPort); @@ -928,9 +828,7 @@ export class Parser { } if (!node.setIdentifier(this._parseKeyframeIdent())) { - return this.finish(node, ParseError.IdentifierExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); } return this._parseBody(node, this._parseKeyframeSelector.bind(this)); @@ -1025,10 +923,7 @@ export class Parser { const node = this.create(nodes.PropertyAtRule); this.consumeToken(); // @layer - if ( - !this.peekRegExp(TokenType.Ident, /^--/) || - !node.setName(this._parseIdent([nodes.ReferenceType.Property])) - ) { + if (!this.peekRegExp(TokenType.Ident, /^--/) || !node.setName(this._parseIdent([nodes.ReferenceType.Property]))) { return this.finish(node, ParseError.IdentifierExpected); } @@ -1051,14 +946,8 @@ export class Parser { if (names) { node.setNames(names); } - if ( - (!names || names.getChildren().length === 1) && - this.peek(TokenType.CurlyL) - ) { - return this._parseBody( - node, - this._parseLayerDeclaration.bind(this, isNested), - ); + if ((!names || names.getChildren().length === 1) && this.peek(TokenType.CurlyL)) { + return this._parseBody(node, this._parseLayerDeclaration.bind(this, isNested)); } if (!this.accept(TokenType.SemiColon)) { return this.finish(node, ParseError.SemiColonExpected); @@ -1069,11 +958,7 @@ export class Parser { public _parseLayerDeclaration(isNested = false): nodes.Node | null { if (isNested) { // if nested, the body can contain rulesets, but also declarations - return ( - this._tryParseRuleset(true) || - this._tryToParseDeclaration() || - this._parseStylesheetStatement(true) - ); + return this._tryParseRuleset(true) || this._tryToParseDeclaration() || this._parseStylesheetStatement(true); } return this._parseStylesheetStatement(false); } @@ -1115,20 +1000,13 @@ export class Parser { this.consumeToken(); // @supports node.addChild(this._parseSupportsCondition()); - return this._parseBody( - node, - this._parseSupportsDeclaration.bind(this, isNested), - ); + return this._parseBody(node, this._parseSupportsDeclaration.bind(this, isNested)); } public _parseSupportsDeclaration(isNested = false): nodes.Node | null { if (isNested) { // if nested, the body can contain rulesets, but also declarations - return ( - this._tryParseRuleset(true) || - this._tryToParseDeclaration() || - this._parseStylesheetStatement(true) - ); + return this._tryParseRuleset(true) || this._tryToParseDeclaration() || this._parseStylesheetStatement(true); } return this._parseStylesheetStatement(false); } @@ -1163,20 +1041,13 @@ export class Parser { if (this.prevToken) { node.lParent = this.prevToken.offset; } - if ( - !node.addChild(this._tryToParseDeclaration([TokenType.ParenthesisR])) - ) { + if (!node.addChild(this._tryToParseDeclaration([TokenType.ParenthesisR]))) { if (!this._parseSupportsCondition()) { return this.finish(node, ParseError.ConditionExpected); } } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish( - node, - ParseError.RightParenthesisExpected, - [TokenType.ParenthesisR], - [], - ); + return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.ParenthesisR], []); } if (this.prevToken) { node.rParent = this.prevToken.offset; @@ -1200,22 +1071,13 @@ export class Parser { this.restoreAtMark(pos); } } - return this.finish( - node, - ParseError.LeftParenthesisExpected, - [], - [TokenType.ParenthesisL], - ); + return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.ParenthesisL]); } public _parseMediaDeclaration(isNested = false): nodes.Node | null { if (isNested) { // if nested, the body can contain rulesets, but also declarations - return ( - this._tryParseRuleset(true) || - this._tryToParseDeclaration() || - this._parseStylesheetStatement(true) - ); + return this._tryParseRuleset(true) || this._tryToParseDeclaration() || this._parseStylesheetStatement(true); } return this._parseStylesheetStatement(false); } @@ -1232,10 +1094,7 @@ export class Parser { if (!node.addChild(this._parseMediaQueryList())) { return this.finish(node, ParseError.MediaQueryExpected); } - return this._parseBody( - node, - this._parseMediaDeclaration.bind(this, isNested), - ); + return this._parseBody(node, this._parseMediaDeclaration.bind(this, isNested)); } public _parseMediaQueryList(): nodes.Medialist { @@ -1303,12 +1162,7 @@ export class Parser { while (parseExpression) { if (!this.accept(TokenType.ParenthesisL)) { - return this.finish( - node, - ParseError.LeftParenthesisExpected, - [], - [TokenType.CurlyL], - ); + return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]); } if (this.peek(TokenType.ParenthesisL) || this.peekIdent("not")) { // @@ -1318,12 +1172,7 @@ export class Parser { } // not yet implemented: general enclosed if (!this.accept(TokenType.ParenthesisR)) { - return this.finish( - node, - ParseError.RightParenthesisExpected, - [], - [TokenType.CurlyL], - ); + return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]); } parseExpression = this.acceptIdent("and") || this.acceptIdent("or"); } @@ -1342,30 +1191,15 @@ export class Parser { if (node.addChild(this._parseMediaFeatureName())) { if (this.accept(TokenType.Colon)) { if (!node.addChild(this._parseMediaFeatureValue())) { - return this.finish( - node, - ParseError.TermExpected, - [], - resyncStopToken, - ); + return this.finish(node, ParseError.TermExpected, [], resyncStopToken); } } else if (this._parseMediaFeatureRangeOperator()) { if (!node.addChild(this._parseMediaFeatureValue())) { - return this.finish( - node, - ParseError.TermExpected, - [], - resyncStopToken, - ); + return this.finish(node, ParseError.TermExpected, [], resyncStopToken); } if (this._parseMediaFeatureRangeOperator()) { if (!node.addChild(this._parseMediaFeatureValue())) { - return this.finish( - node, - ParseError.TermExpected, - [], - resyncStopToken, - ); + return this.finish(node, ParseError.TermExpected, [], resyncStopToken); } } } else { @@ -1373,38 +1207,18 @@ export class Parser { } } else if (node.addChild(this._parseMediaFeatureValue())) { if (!this._parseMediaFeatureRangeOperator()) { - return this.finish( - node, - ParseError.OperatorExpected, - [], - resyncStopToken, - ); + return this.finish(node, ParseError.OperatorExpected, [], resyncStopToken); } if (!node.addChild(this._parseMediaFeatureName())) { - return this.finish( - node, - ParseError.IdentifierExpected, - [], - resyncStopToken, - ); + return this.finish(node, ParseError.IdentifierExpected, [], resyncStopToken); } if (this._parseMediaFeatureRangeOperator()) { if (!node.addChild(this._parseMediaFeatureValue())) { - return this.finish( - node, - ParseError.TermExpected, - [], - resyncStopToken, - ); + return this.finish(node, ParseError.TermExpected, [], resyncStopToken); } } } else { - return this.finish( - node, - ParseError.IdentifierExpected, - [], - resyncStopToken, - ); + return this.finish(node, ParseError.IdentifierExpected, [], resyncStopToken); } return this.finish(node); } @@ -1510,11 +1324,7 @@ export class Parser { public _parseContainerDeclaration(isNested = false): nodes.Node | null { if (isNested) { // if nested, the body can contain rulesets, but also declarations - return ( - this._tryParseRuleset(true) || - this._tryToParseDeclaration() || - this._parseStylesheetStatement(true) - ); + return this._tryParseRuleset(true) || this._tryToParseDeclaration() || this._parseStylesheetStatement(true); } return this._parseStylesheetStatement(false); } @@ -1529,10 +1339,7 @@ export class Parser { node.addChild(this._parseIdent()); // optional container name node.addChild(this._parseContainerQuery()); - return this._parseBody( - node, - this._parseContainerDeclaration.bind(this, isNested), - ); + return this._parseBody(node, this._parseContainerDeclaration.bind(this, isNested)); } public _parseContainerQuery(): nodes.Node | null { @@ -1569,38 +1376,18 @@ export class Parser { node.addChild(this._parseMediaFeature()); } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish( - node, - ParseError.RightParenthesisExpected, - [], - [TokenType.CurlyL], - ); + return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]); } } else if (this.acceptIdent("style")) { if (this.hasWhitespace() || !this.accept(TokenType.ParenthesisL)) { - return this.finish( - node, - ParseError.LeftParenthesisExpected, - [], - [TokenType.CurlyL], - ); + return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]); } node.addChild(this._parseStyleQuery()); if (!this.accept(TokenType.ParenthesisR)) { - return this.finish( - node, - ParseError.RightParenthesisExpected, - [], - [TokenType.CurlyL], - ); + return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]); } } else { - return this.finish( - node, - ParseError.LeftParenthesisExpected, - [], - [TokenType.CurlyL], - ); + return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]); } return this.finish(node); } @@ -1638,20 +1425,10 @@ export class Parser { if (this.accept(TokenType.ParenthesisL)) { node.addChild(this._parseStyleQuery()); if (!this.accept(TokenType.ParenthesisR)) { - return this.finish( - node, - ParseError.RightParenthesisExpected, - [], - [TokenType.CurlyL], - ); + return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]); } } else { - return this.finish( - node, - ParseError.LeftParenthesisExpected, - [], - [TokenType.CurlyL], - ); + return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]); } return this.finish(node); } @@ -1665,8 +1442,7 @@ export class Parser { const node = this.create(nodes.UnknownAtRule); node.addChild(this._parseUnknownAtRuleName()); - const isTopLevel = () => - curlyDepth === 0 && parensDepth === 0 && bracketsDepth === 0; + const isTopLevel = () => curlyDepth === 0 && parensDepth === 0 && bracketsDepth === 0; let curlyLCount = 0; let curlyDepth = 0; let parensDepth = 0; @@ -1810,12 +1586,7 @@ export class Parser { const node = this.create(nodes.Node); this.consumeToken(); const mark = this.mark(); - if ( - !this.hasWhitespace() && - this.acceptIdent("deep") && - !this.hasWhitespace() && - this.acceptDelim("/") - ) { + if (!this.hasWhitespace() && this.acceptIdent("deep") && !this.hasWhitespace() && this.acceptDelim("/")) { node.type = nodes.NodeType.SelectorCombinatorShadowPiercingDescendant; return this.finish(node); } @@ -1831,15 +1602,10 @@ export class Parser { const node = this.create(nodes.SimpleSelector); let c = 0; - if ( - node.addChild(this._parseElementName() || this._parseNestingSelector()) - ) { + if (node.addChild(this._parseElementName() || this._parseNestingSelector())) { c++; } - while ( - (c === 0 || !this.hasWhitespace()) && - node.addChild(this._parseSimpleSelectorBody()) - ) { + while ((c === 0 || !this.hasWhitespace()) && node.addChild(this._parseSimpleSelectorBody())) { c++; } return c > 0 ? this.finish(node) : null; @@ -1855,12 +1621,7 @@ export class Parser { } public _parseSimpleSelectorBody(): nodes.Node | null { - return ( - this._parsePseudo() || - this._parseHash() || - this._parseClass() || - this._parseAttrib() - ); + return this._parsePseudo() || this._parseHash() || this._parseClass() || this._parseAttrib(); } public _parseSelectorIdent(): nodes.Node | null { @@ -1957,10 +1718,7 @@ export class Parser { if (!selectors.addChild(this._parseSelector(true))) { return null; } - while ( - this.accept(TokenType.Comma) && - selectors.addChild(this._parseSelector(true)) - ) { + while (this.accept(TokenType.Comma) && selectors.addChild(this._parseSelector(true))) { // loop } if (this.peek(TokenType.ParenthesisR)) { @@ -2202,19 +1960,13 @@ export class Parser { public _parseURLArgument(): nodes.Node | null { const node = this.create(nodes.Node); - if ( - !this.accept(TokenType.String) && - !this.accept(TokenType.BadString) && - !this.acceptUnquotedString() - ) { + if (!this.accept(TokenType.String) && !this.accept(TokenType.BadString) && !this.acceptUnquotedString()) { return null; } return this.finish(node); } - public _parseIdent( - referenceTypes?: nodes.ReferenceType[], - ): nodes.Identifier | null { + public _parseIdent(referenceTypes?: nodes.ReferenceType[]): nodes.Identifier | null { if (!this.peek(TokenType.Ident)) { return null; } @@ -2252,9 +2004,7 @@ export class Parser { } if (!this.accept(TokenType.ParenthesisR)) { - return ( - this.finish(node, ParseError.RightParenthesisExpected) - ); + return this.finish(node, ParseError.RightParenthesisExpected); } return this.finish(node); } @@ -2289,12 +2039,7 @@ export class Parser { } public _parseHexColor(): nodes.Node | null { - if ( - this.peekRegExp( - TokenType.Hash, - /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/g, - ) - ) { + if (this.peekRegExp(TokenType.Hash, /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/g)) { const node = this.create(nodes.HexColorValue); this.consumeToken(); return this.finish(node); diff --git a/packages/parser/src/cssScanner.ts b/packages/vscode-css-languageservice/src/parser/cssScanner.ts similarity index 92% rename from packages/parser/src/cssScanner.ts rename to packages/vscode-css-languageservice/src/parser/cssScanner.ts index 579eee3d..fa521baf 100644 --- a/packages/parser/src/cssScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/cssScanner.ts @@ -1,9 +1,8 @@ -/* eslint-disable prefer-const */ -/* eslint-disable no-constant-condition */ /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +"use strict"; export enum TokenType { Ident, @@ -130,10 +129,7 @@ export class MultiLineStream { public advanceWhileChar(condition: (ch: number) => boolean): number { const posNow = this.position; - while ( - this.position < this.len && - condition(this.source.charCodeAt(this.position)) - ) { + while (this.position < this.len && condition(this.source.charCodeAt(this.position))) { this.position++; } return this.position - posNow; @@ -143,6 +139,7 @@ export class MultiLineStream { const _a = "a".charCodeAt(0); const _f = "f".charCodeAt(0); const _z = "z".charCodeAt(0); +const _u = "u".charCodeAt(0); const _A = "A".charCodeAt(0); const _F = "F".charCodeAt(0); const _Z = "Z".charCodeAt(0); @@ -257,11 +254,7 @@ export class Scanner { const offset = this.stream.pos(); const content: string[] = []; if (this._unquotedString(content)) { - return this.finishToken( - offset, - TokenType.UnquotedString, - content.join(""), - ); + return this.finishToken(offset, TokenType.UnquotedString, content.join("")); } return null; } @@ -356,11 +349,7 @@ export class Scanner { return this.finishToken(offset, tokenType, content.join("")); } else { // Unknown dimension 43ft - return this.finishToken( - offset, - TokenType.Dimension, - content.join(""), - ); + return this.finishToken(offset, TokenType.Dimension, content.join("")); } } @@ -494,22 +483,14 @@ export class Scanner { this.stream.advance(1); ch = this.stream.peekChar(); let hexNumCount = 0; - while ( - hexNumCount < 6 && - ((ch >= _0 && ch <= _9) || - (ch >= _a && ch <= _f) || - (ch >= _A && ch <= _F)) - ) { + while (hexNumCount < 6 && ((ch >= _0 && ch <= _9) || (ch >= _a && ch <= _f) || (ch >= _A && ch <= _F))) { this.stream.advance(1); ch = this.stream.peekChar(); hexNumCount++; } if (hexNumCount > 0) { try { - const hexVal = parseInt( - this.stream.substring(this.stream.pos() - hexNumCount), - 16, - ); + const hexVal = parseInt(this.stream.substring(this.stream.pos() - hexNumCount), 16); if (hexVal) { result.push(String.fromCharCode(hexVal)); } @@ -539,14 +520,7 @@ export class Scanner { private _stringChar(closeQuote: number, result: string[]) { // not closeQuote, not backslash, not newline const ch = this.stream.peekChar(); - if ( - ch !== 0 && - ch !== closeQuote && - ch !== _BSL && - ch !== _CAR && - ch !== _LFD && - ch !== _NWL - ) { + if (ch !== 0 && ch !== closeQuote && ch !== _BSL && ch !== _CAR && ch !== _LFD && ch !== _NWL) { this.stream.advance(1); result.push(String.fromCharCode(ch)); return true; @@ -559,10 +533,7 @@ export class Scanner { const closeQuote = this.stream.nextChar(); result.push(String.fromCharCode(closeQuote)); - while ( - this._stringChar(closeQuote, result) || - this._escape(result, true) - ) { + while (this._stringChar(closeQuote, result) || this._escape(result, true)) { // loop } @@ -610,9 +581,7 @@ export class Scanner { private _whitespace(): boolean { const n = this.stream.advanceWhileChar((ch) => { - return ( - ch === _WSP || ch === _TAB || ch === _NWL || ch === _LFD || ch === _CAR - ); + return ch === _WSP || ch === _TAB || ch === _NWL || ch === _LFD || ch === _CAR; }); return n > 0; } @@ -629,11 +598,7 @@ export class Scanner { const pos = this.stream.pos(); const hasMinus = this._minus(result); if (hasMinus) { - if ( - this._minus(result) /* -- */ || - this._identFirstChar(result) || - this._escape(result) - ) { + if (this._minus(result) /* -- */ || this._identFirstChar(result) || this._escape(result)) { while (this._identChar(result) || this._escape(result)) { // loop } @@ -698,14 +663,9 @@ export class Scanner { // assume u has already been parsed if (this.stream.advanceIfChar(_PLS)) { - const isHexDigit = (ch: number) => - (ch >= _0 && ch <= _9) || - (ch >= _a && ch <= _f) || - (ch >= _A && ch <= _F); - - const codePoints = - this.stream.advanceWhileChar(isHexDigit) + - this.stream.advanceWhileChar((ch) => ch === _QSM); + const isHexDigit = (ch: number) => (ch >= _0 && ch <= _9) || (ch >= _a && ch <= _f) || (ch >= _A && ch <= _F); + + const codePoints = this.stream.advanceWhileChar(isHexDigit) + this.stream.advanceWhileChar((ch) => ch === _QSM); if (codePoints >= 1 && codePoints <= 6) { if (this.stream.advanceIfChar(_MIN)) { const digits = this.stream.advanceWhileChar(isHexDigit); diff --git a/packages/parser/src/cssSymbolScope.ts b/packages/vscode-css-languageservice/src/parser/cssSymbolScope.ts similarity index 73% rename from packages/parser/src/cssSymbolScope.ts rename to packages/vscode-css-languageservice/src/parser/cssSymbolScope.ts index c02e245d..85f92870 100644 --- a/packages/parser/src/cssSymbolScope.ts +++ b/packages/vscode-css-languageservice/src/parser/cssSymbolScope.ts @@ -2,8 +2,10 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +"use strict"; + import * as nodes from "./cssNodes"; -import { findFirst } from "./utils/arrays"; +import { findFirst } from "../utils/arrays"; export class Scope { public parent: Scope | null; @@ -89,12 +91,7 @@ export class Symbol { public type: nodes.ReferenceType; public node: nodes.Node; - constructor( - name: string, - value: string | undefined, - node: nodes.Node, - type: nodes.ReferenceType, - ) { + constructor(name: string, value: string | undefined, node: nodes.Node, type: nodes.ReferenceType) { this.name = name; this.value = value; this.node = node; @@ -109,12 +106,7 @@ export class ScopeBuilder implements nodes.IVisitor { this.scope = scope; } - private addSymbol( - node: nodes.Node, - name: string, - value: string | undefined, - type: nodes.ReferenceType, - ): void { + private addSymbol(node: nodes.Node, name: string, value: string | undefined, type: nodes.ReferenceType): void { if (node.offset !== -1) { const current = this.scope.findScope(node.offset, node.length); if (current) { @@ -126,10 +118,7 @@ export class ScopeBuilder implements nodes.IVisitor { private addScope(node: nodes.Node): Scope | null { if (node.offset !== -1) { const current = this.scope.findScope(node.offset, node.length); - if ( - current && - (current.offset !== node.offset || current.length !== node.length) - ) { + if (current && (current.offset !== node.offset || current.length !== node.length)) { // scope already known? const newScope = new Scope(node.offset, node.length); current.addChild(newScope); @@ -158,38 +147,19 @@ export class ScopeBuilder implements nodes.IVisitor { public visitNode(node: nodes.Node): boolean { switch (node.type) { case nodes.NodeType.Keyframe: - this.addSymbol( - node, - (node).getName(), - void 0, - nodes.ReferenceType.Keyframe, - ); + this.addSymbol(node, (node).getName(), void 0, nodes.ReferenceType.Keyframe); return true; case nodes.NodeType.CustomPropertyDeclaration: - return this.visitCustomPropertyDeclarationNode( - node, - ); + return this.visitCustomPropertyDeclarationNode(node); case nodes.NodeType.VariableDeclaration: - return this.visitVariableDeclarationNode( - node, - ); + return this.visitVariableDeclarationNode(node); case nodes.NodeType.Ruleset: return this.visitRuleSet(node); case nodes.NodeType.MixinDeclaration: - this.addSymbol( - node, - (node).getName(), - void 0, - nodes.ReferenceType.Mixin, - ); + this.addSymbol(node, (node).getName(), void 0, nodes.ReferenceType.Mixin); return true; case nodes.NodeType.FunctionDeclaration: - this.addSymbol( - node, - (node).getName(), - void 0, - nodes.ReferenceType.Function, - ); + this.addSymbol(node, (node).getName(), void 0, nodes.ReferenceType.Function); return true; case nodes.NodeType.FunctionParameter: { return this.visitFunctionParameterNode(node); @@ -197,7 +167,7 @@ export class ScopeBuilder implements nodes.IVisitor { case nodes.NodeType.Declarations: this.addScope(node); return true; - case nodes.NodeType.For: { + case nodes.NodeType.For: const forNode = node; const scopeNode = forNode.getDeclarations(); if (scopeNode && forNode.variable) { @@ -210,22 +180,13 @@ export class ScopeBuilder implements nodes.IVisitor { ); } return true; - } case nodes.NodeType.Each: { const eachNode = node; const scopeNode = eachNode.getDeclarations(); if (scopeNode) { - const variables = ( - eachNode.getVariables().getChildren() - ); + const variables = eachNode.getVariables().getChildren(); for (const variable of variables) { - this.addSymbolToChildScope( - scopeNode, - variable, - variable.getName(), - void 0, - nodes.ReferenceType.Variable, - ); + this.addSymbolToChildScope(scopeNode, variable, variable.getName(), void 0, nodes.ReferenceType.Variable); } } return true; @@ -241,14 +202,7 @@ export class ScopeBuilder implements nodes.IVisitor { if (child instanceof nodes.Selector) { if (child.getChildren().length === 1) { // only selectors with a single element can be extended - current.addSymbol( - new Symbol( - child.getChild(0)!.getText(), - void 0, - child, - nodes.ReferenceType.Rule, - ), - ); + current.addSymbol(new Symbol(child.getChild(0)!.getText(), void 0, child, nodes.ReferenceType.Rule)); } } } @@ -256,9 +210,7 @@ export class ScopeBuilder implements nodes.IVisitor { return true; } - public visitVariableDeclarationNode( - node: nodes.VariableDeclaration, - ): boolean { + public visitVariableDeclarationNode(node: nodes.VariableDeclaration): boolean { const value = node.getValue() ? node.getValue()!.getText() : void 0; this.addSymbol(node, node.getName(), value, nodes.ReferenceType.Variable); return true; @@ -266,42 +218,22 @@ export class ScopeBuilder implements nodes.IVisitor { public visitFunctionParameterNode(node: nodes.FunctionParameter): boolean { // parameters are part of the body scope - const scopeNode = (( - node.getParent() - )).getDeclarations(); + const scopeNode = (node.getParent()).getDeclarations(); if (scopeNode) { const valueNode = (node).getDefaultValue(); const value = valueNode ? valueNode.getText() : void 0; - this.addSymbolToChildScope( - scopeNode, - node, - node.getName(), - value, - nodes.ReferenceType.Variable, - ); + this.addSymbolToChildScope(scopeNode, node, node.getName(), value, nodes.ReferenceType.Variable); } return true; } - public visitCustomPropertyDeclarationNode( - node: nodes.CustomPropertyDeclaration, - ): boolean { + public visitCustomPropertyDeclarationNode(node: nodes.CustomPropertyDeclaration): boolean { const value = node.getValue() ? node.getValue()!.getText() : ""; - this.addCSSVariable( - node.getProperty()!, - node.getProperty()!.getName(), - value, - nodes.ReferenceType.Variable, - ); + this.addCSSVariable(node.getProperty()!, node.getProperty()!.getName(), value, nodes.ReferenceType.Variable); return true; } - private addCSSVariable( - node: nodes.Node, - name: string, - value: string, - type: nodes.ReferenceType, - ): void { + private addCSSVariable(node: nodes.Node, name: string, value: string, type: nodes.ReferenceType): void { if (node.offset !== -1) { this.scope.addSymbol(new Symbol(name, value, node, type)); } @@ -316,10 +248,7 @@ export class Symbols { node.acceptVisitor(new ScopeBuilder(this.global)); } - public findSymbolsAtOffset( - offset: number, - referenceType: nodes.ReferenceType, - ): Symbol[] { + public findSymbolsAtOffset(offset: number, referenceType: nodes.ReferenceType): Symbol[] { let scope = this.global.findScope(offset, 0); const result: Symbol[] = []; const names: { [name: string]: boolean } = {}; @@ -337,32 +266,17 @@ export class Symbols { return result; } - private internalFindSymbol( - node: nodes.Node, - referenceTypes: nodes.ReferenceType[], - ): Symbol | null { + private internalFindSymbol(node: nodes.Node, referenceTypes: nodes.ReferenceType[]): Symbol | null { let scopeNode: nodes.Node | undefined = node; - if ( - node.parent instanceof nodes.FunctionParameter && - node.parent.getParent() instanceof nodes.BodyDeclaration - ) { - scopeNode = (( - node.parent.getParent() - )).getDeclarations(); + if (node.parent instanceof nodes.FunctionParameter && node.parent.getParent() instanceof nodes.BodyDeclaration) { + scopeNode = (node.parent.getParent()).getDeclarations(); } - if ( - node.parent instanceof nodes.FunctionArgument && - node.parent.getParent() instanceof nodes.Function - ) { + if (node.parent instanceof nodes.FunctionArgument && node.parent.getParent() instanceof nodes.Function) { const funcId = (node.parent.getParent()).getIdentifier(); if (funcId) { - const functionSymbol = this.internalFindSymbol(funcId, [ - nodes.ReferenceType.Function, - ]); + const functionSymbol = this.internalFindSymbol(funcId, [nodes.ReferenceType.Function]); if (functionSymbol) { - scopeNode = (( - functionSymbol.node - )).getDeclarations(); + scopeNode = (functionSymbol.node).getDeclarations(); } } } @@ -384,9 +298,7 @@ export class Symbols { return null; } - private evaluateReferenceTypes( - node: nodes.Node, - ): nodes.ReferenceType[] | null { + private evaluateReferenceTypes(node: nodes.Node): nodes.ReferenceType[] | null { if (node instanceof nodes.Identifier) { const referenceTypes = (node).referenceTypes; if (referenceTypes) { @@ -400,8 +312,7 @@ export class Symbols { if (decl) { const propertyName = decl.getNonPrefixedPropertyName(); if ( - (propertyName === "animation" || - propertyName === "animation-name") && + (propertyName === "animation" || propertyName === "animation-name") && decl.getValue() && decl.getValue()!.offset === node.offset ) { @@ -412,10 +323,7 @@ export class Symbols { } else if (node instanceof nodes.Variable) { return [nodes.ReferenceType.Variable]; } - const selector = node.findAParent( - nodes.NodeType.Selector, - nodes.NodeType.ExtendsReference, - ); + const selector = node.findAParent(nodes.NodeType.Selector, nodes.NodeType.ExtendsReference); if (selector) { return [nodes.ReferenceType.Rule]; } @@ -457,11 +365,7 @@ export class Symbols { return nodeSymbol === symbol; } - public findSymbol( - name: string, - type: nodes.ReferenceType, - offset: number, - ): Symbol | null { + public findSymbol(name: string, type: nodes.ReferenceType, offset: number): Symbol | null { let scope = this.global.findScope(offset); while (scope) { const symbol = scope.getSymbol(name, type); diff --git a/packages/parser/src/scssErrors.ts b/packages/vscode-css-languageservice/src/parser/scssErrors.ts similarity index 78% rename from packages/parser/src/scssErrors.ts rename to packages/vscode-css-languageservice/src/parser/scssErrors.ts index 7923e82c..83a36d62 100644 --- a/packages/parser/src/scssErrors.ts +++ b/packages/vscode-css-languageservice/src/parser/scssErrors.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ "use strict"; -import * as l10n from "@vscode/l10n"; import * as nodes from "./cssNodes"; +import * as l10n from "@vscode/l10n"; + export class SCSSIssueType implements nodes.IRule { id: string; message: string; @@ -18,13 +19,7 @@ export class SCSSIssueType implements nodes.IRule { } export const SCSSParseError = { - FromExpected: new SCSSIssueType( - "scss-fromexpected", - l10n.t("'from' expected"), - ), - ThroughOrToExpected: new SCSSIssueType( - "scss-throughexpected", - l10n.t("'through' or 'to' expected"), - ), + FromExpected: new SCSSIssueType("scss-fromexpected", l10n.t("'from' expected")), + ThroughOrToExpected: new SCSSIssueType("scss-throughexpected", l10n.t("'through' or 'to' expected")), InExpected: new SCSSIssueType("scss-fromexpected", l10n.t("'in' expected")), }; diff --git a/packages/parser/src/scssParser.ts b/packages/vscode-css-languageservice/src/parser/scssParser.ts similarity index 85% rename from packages/parser/src/scssParser.ts rename to packages/vscode-css-languageservice/src/parser/scssParser.ts index 817968fb..384a6515 100644 --- a/packages/parser/src/scssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/scssParser.ts @@ -2,12 +2,14 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ParseError } from "./cssErrors"; -import * as nodes from "./cssNodes"; -import * as cssParser from "./cssParser"; +"use strict"; +import * as scssScanner from "./scssScanner"; import { TokenType } from "./cssScanner"; +import * as cssParser from "./cssParser"; +import * as nodes from "./cssNodes"; + import { SCSSParseError } from "./scssErrors"; -import * as scssScanner from "./scssScanner"; +import { ParseError } from "./cssErrors"; /// /// A parser for scss @@ -18,9 +20,7 @@ export class SCSSParser extends cssParser.Parser { super(new scssScanner.SCSSScanner()); } - public _parseStylesheetStatement( - isNested: boolean = false, - ): nodes.Node | null { + public _parseStylesheetStatement(isNested: boolean = false): nodes.Node | null { if (this.peek(TokenType.AtKeyword)) { return ( this._parseWarnAndDebug() || // @warn, @debug and @error statements @@ -45,17 +45,11 @@ export class SCSSParser extends cssParser.Parser { const node = this.create(nodes.Import); this.consumeToken(); - if ( - !node.addChild(this._parseURILiteral()) && - !node.addChild(this._parseStringLiteral()) - ) { + if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) { return this.finish(node, ParseError.URIOrStringExpected); } while (this.accept(TokenType.Comma)) { - if ( - !node.addChild(this._parseURILiteral()) && - !node.addChild(this._parseStringLiteral()) - ) { + if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) { return this.finish(node, ParseError.URIOrStringExpected); } } @@ -64,16 +58,12 @@ export class SCSSParser extends cssParser.Parser { } // scss variables: $font-size: 12px; - public _parseVariableDeclaration( - panic: TokenType[] = [], - ): nodes.VariableDeclaration | null { + public _parseVariableDeclaration(panic: TokenType[] = []): nodes.VariableDeclaration | null { if (!this.peek(scssScanner.VariableName)) { return null; } - const node = ( - this.create(nodes.VariableDeclaration) - ); + const node = this.create(nodes.VariableDeclaration); if (!node.setVariable(this._parseVariable())) { return null; @@ -159,11 +149,7 @@ export class SCSSParser extends cssParser.Parser { return null; } - if ( - this.hasWhitespace() || - !this.acceptDelim(".") || - this.hasWhitespace() - ) { + if (this.hasWhitespace() || !this.acceptDelim(".") || this.hasWhitespace()) { this.restoreAtMark(pos); return null; } @@ -175,14 +161,8 @@ export class SCSSParser extends cssParser.Parser { return node; } - public _parseIdent( - referenceTypes?: nodes.ReferenceType[], - ): nodes.Identifier | null { - if ( - !this.peek(TokenType.Ident) && - !this.peek(scssScanner.InterpolationFunction) && - !this.peekDelim("-") - ) { + public _parseIdent(referenceTypes?: nodes.ReferenceType[]): nodes.Identifier | null { + if (!this.peek(TokenType.Ident) && !this.peek(scssScanner.InterpolationFunction) && !this.peekDelim("-")) { return null; } @@ -315,12 +295,7 @@ export class SCSSParser extends cssParser.Parser { } if (!this.accept(TokenType.Colon)) { - return this.finish( - node, - ParseError.ColonExpected, - [TokenType.Colon], - stopTokens || [TokenType.SemiColon], - ); + return this.finish(node, ParseError.ColonExpected, [TokenType.Colon], stopTokens || [TokenType.SemiColon]); } if (this.prevToken) { node.colonPosition = this.prevToken.offset; @@ -356,11 +331,7 @@ export class SCSSParser extends cssParser.Parser { if ( !node .getSelectors() - .addChild( - this.peekDelim("%") - ? this._parseSelectorPlaceholder() - : this._parseSimpleSelector(), - ) + .addChild(this.peekDelim("%") ? this._parseSelectorPlaceholder() : this._parseSimpleSelector()) ) { return this.finish(node, ParseError.SelectorExpected); } @@ -420,9 +391,7 @@ export class SCSSParser extends cssParser.Parser { return this.finish(node, ParseError.IdentifierExpected); } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyR]); } } return this.finish(node); @@ -446,11 +415,7 @@ export class SCSSParser extends cssParser.Parser { } public _parseWarnAndDebug(): nodes.Node | null { - if ( - !this.peekKeyword("@debug") && - !this.peekKeyword("@warn") && - !this.peekKeyword("@error") - ) { + if (!this.peekKeyword("@debug") && !this.peekKeyword("@warn") && !this.peekKeyword("@error")) { return null; } const node = this.createNode(nodes.NodeType.Debug); @@ -460,9 +425,7 @@ export class SCSSParser extends cssParser.Parser { } public _parseControlStatement( - parseStatement: () => nodes.Node | null = this._parseRuleSetDeclaration.bind( - this, - ), + parseStatement: () => nodes.Node | null = this._parseRuleSetDeclaration.bind(this), ): nodes.Node | null { if (!this.peek(TokenType.AtKeyword)) { return null; @@ -475,18 +438,14 @@ export class SCSSParser extends cssParser.Parser { ); } - public _parseIfStatement( - parseStatement: () => nodes.Node | null, - ): nodes.Node | null { + public _parseIfStatement(parseStatement: () => nodes.Node | null): nodes.Node | null { if (!this.peekKeyword("@if")) { return null; } return this._internalParseIfStatement(parseStatement); } - private _internalParseIfStatement( - parseStatement: () => nodes.Node | null, - ): nodes.IfStatement { + private _internalParseIfStatement(parseStatement: () => nodes.Node | null): nodes.IfStatement { const node = this.create(nodes.IfStatement); this.consumeToken(); // @if or if if (!node.setExpression(this._parseExpr(true))) { @@ -497,9 +456,7 @@ export class SCSSParser extends cssParser.Parser { if (this.peekIdent("if")) { node.setElseClause(this._internalParseIfStatement(parseStatement)); } else if (this.peek(TokenType.CurlyL)) { - const elseNode = ( - this.create(nodes.ElseStatement) - ); + const elseNode = this.create(nodes.ElseStatement); this._parseBody(elseNode, parseStatement); node.setElseClause(elseNode); } @@ -507,9 +464,7 @@ export class SCSSParser extends cssParser.Parser { return this.finish(node); } - public _parseForStatement( - parseStatement: () => nodes.Node | null, - ): nodes.Node | null { + public _parseForStatement(parseStatement: () => nodes.Node | null): nodes.Node | null { if (!this.peekKeyword("@for")) { return null; } @@ -517,35 +472,25 @@ export class SCSSParser extends cssParser.Parser { const node = this.create(nodes.ForStatement); this.consumeToken(); // @for if (!node.setVariable(this._parseVariable())) { - return this.finish(node, ParseError.VariableNameExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.VariableNameExpected, [TokenType.CurlyR]); } if (!this.acceptIdent("from")) { return this.finish(node, SCSSParseError.FromExpected, [TokenType.CurlyR]); } if (!node.addChild(this._parseBinaryExpr())) { - return this.finish(node, ParseError.ExpressionExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]); } if (!this.acceptIdent("to") && !this.acceptIdent("through")) { - return this.finish(node, SCSSParseError.ThroughOrToExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, SCSSParseError.ThroughOrToExpected, [TokenType.CurlyR]); } if (!node.addChild(this._parseBinaryExpr())) { - return this.finish(node, ParseError.ExpressionExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]); } return this._parseBody(node, parseStatement); } - public _parseEachStatement( - parseStatement: () => nodes.Node | null, - ): nodes.Node | null { + public _parseEachStatement(parseStatement: () => nodes.Node | null): nodes.Node | null { if (!this.peekKeyword("@each")) { return null; } @@ -554,15 +499,11 @@ export class SCSSParser extends cssParser.Parser { this.consumeToken(); // @each const variables = node.getVariables(); if (!variables.addChild(this._parseVariable())) { - return this.finish(node, ParseError.VariableNameExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.VariableNameExpected, [TokenType.CurlyR]); } while (this.accept(TokenType.Comma)) { if (!variables.addChild(this._parseVariable())) { - return this.finish(node, ParseError.VariableNameExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.VariableNameExpected, [TokenType.CurlyR]); } } this.finish(variables); @@ -570,17 +511,13 @@ export class SCSSParser extends cssParser.Parser { return this.finish(node, SCSSParseError.InExpected, [TokenType.CurlyR]); } if (!node.addChild(this._parseExpr())) { - return this.finish(node, ParseError.ExpressionExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]); } return this._parseBody(node, parseStatement); } - public _parseWhileStatement( - parseStatement: () => nodes.Node | null, - ): nodes.Node | null { + public _parseWhileStatement(parseStatement: () => nodes.Node | null): nodes.Node | null { if (!this.peekKeyword("@while")) { return null; } @@ -588,9 +525,7 @@ export class SCSSParser extends cssParser.Parser { const node = this.create(nodes.WhileStatement); this.consumeToken(); // @while if (!node.addChild(this._parseBinaryExpr())) { - return this.finish(node, ParseError.ExpressionExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]); } return this._parseBody(node, parseStatement); @@ -610,21 +545,15 @@ export class SCSSParser extends cssParser.Parser { return null; } - const node = ( - this.create(nodes.FunctionDeclaration) - ); + const node = this.create(nodes.FunctionDeclaration); this.consumeToken(); // @function if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Function]))) { - return this.finish(node, ParseError.IdentifierExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); } if (!this.accept(TokenType.ParenthesisL)) { - return this.finish(node, ParseError.LeftParenthesisExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.CurlyR]); } if (node.getParameters().addChild(this._parseParameterDeclaration())) { @@ -639,9 +568,7 @@ export class SCSSParser extends cssParser.Parser { } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyR]); } return this._parseBody(node, this._parseFunctionBodyDeclaration.bind(this)); @@ -670,9 +597,7 @@ export class SCSSParser extends cssParser.Parser { this.consumeToken(); if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Mixin]))) { - return this.finish(node, ParseError.IdentifierExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); } if (this.accept(TokenType.ParenthesisL)) { @@ -681,18 +606,14 @@ export class SCSSParser extends cssParser.Parser { if (this.peek(TokenType.ParenthesisR)) { break; } - if ( - !node.getParameters().addChild(this._parseParameterDeclaration()) - ) { + if (!node.getParameters().addChild(this._parseParameterDeclaration())) { return this.finish(node, ParseError.VariableNameExpected); } } } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyR]); } } @@ -712,12 +633,7 @@ export class SCSSParser extends cssParser.Parser { if (this.accept(TokenType.Colon)) { if (!node.setDefaultValue(this._parseExpr(true))) { - return this.finish( - node, - ParseError.VariableValueExpected, - [], - [TokenType.Comma, TokenType.ParenthesisR], - ); + return this.finish(node, ParseError.VariableValueExpected, [], [TokenType.Comma, TokenType.ParenthesisR]); } } return this.finish(node); @@ -759,17 +675,11 @@ export class SCSSParser extends cssParser.Parser { // Could be module or mixin identifier, set as mixin as default. const firstIdent = this._parseIdent([nodes.ReferenceType.Mixin]); if (!node.setIdentifier(firstIdent)) { - return this.finish(node, ParseError.IdentifierExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); } // Is a module accessor. - if ( - !this.hasWhitespace() && - this.acceptDelim(".") && - !this.hasWhitespace() - ) { + if (!this.hasWhitespace() && this.acceptDelim(".") && !this.hasWhitespace()) { const secondIdent = this._parseIdent([nodes.ReferenceType.Mixin]); const moduleToken = this.create(nodes.Module); @@ -782,9 +692,7 @@ export class SCSSParser extends cssParser.Parser { node.addChild(moduleToken); if (!secondIdent) { - return this.finish(node, ParseError.IdentifierExpected, [ - TokenType.CurlyR, - ]); + return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); } } @@ -812,32 +720,24 @@ export class SCSSParser extends cssParser.Parser { } public _parseMixinContentDeclaration() { - const node = ( - this.create(nodes.MixinContentDeclaration) - ); + const node = this.create(nodes.MixinContentDeclaration); if (this.acceptIdent("using")) { if (!this.accept(TokenType.ParenthesisL)) { - return this.finish(node, ParseError.LeftParenthesisExpected, [ - TokenType.CurlyL, - ]); + return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.CurlyL]); } if (node.getParameters().addChild(this._parseParameterDeclaration())) { while (this.accept(TokenType.Comma)) { if (this.peek(TokenType.ParenthesisR)) { break; } - if ( - !node.getParameters().addChild(this._parseParameterDeclaration()) - ) { + if (!node.getParameters().addChild(this._parseParameterDeclaration())) { return this.finish(node, ParseError.VariableNameExpected); } } } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [ - TokenType.CurlyL, - ]); + return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyL]); } } @@ -947,9 +847,7 @@ export class SCSSParser extends cssParser.Parser { } if (this.acceptIdent("as")) { - if ( - !node.setIdentifier(this._parseIdent([nodes.ReferenceType.Module])) - ) { + if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Module]))) { const hasWildcard = this.peekDelim("*"); if (hasWildcard) { const mnode = this.create(nodes.Identifier); @@ -964,15 +862,11 @@ export class SCSSParser extends cssParser.Parser { if (this.acceptIdent("with")) { if (!this.accept(TokenType.ParenthesisL)) { - return this.finish(node, ParseError.LeftParenthesisExpected, [ - TokenType.ParenthesisR, - ]); + return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.ParenthesisR]); } // First variable statement, no comma. - if ( - !node.getParameters().addChild(this._parseModuleConfigDeclaration()) - ) { + if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) { return this.finish(node, ParseError.VariableNameExpected); } @@ -980,9 +874,7 @@ export class SCSSParser extends cssParser.Parser { if (this.peek(TokenType.ParenthesisR)) { break; } - if ( - !node.getParameters().addChild(this._parseModuleConfigDeclaration()) - ) { + if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) { return this.finish(node, ParseError.VariableNameExpected); } } @@ -1001,24 +893,14 @@ export class SCSSParser extends cssParser.Parser { } public _parseModuleConfigDeclaration(): nodes.Node | null { - const node = ( - this.create(nodes.ModuleConfiguration) - ); + const node = this.create(nodes.ModuleConfiguration); if (!node.setIdentifier(this._parseVariable())) { return null; } - if ( - !this.accept(TokenType.Colon) || - !node.setValue(this._parseExpr(true)) - ) { - return this.finish( - node, - ParseError.VariableValueExpected, - [], - [TokenType.Comma, TokenType.ParenthesisR], - ); + if (!this.accept(TokenType.Colon) || !node.setValue(this._parseExpr(true))) { + return this.finish(node, ParseError.VariableValueExpected, [], [TokenType.Comma, TokenType.ParenthesisR]); } if (this.accept(TokenType.Exclamation)) { @@ -1056,15 +938,11 @@ export class SCSSParser extends cssParser.Parser { if (this.acceptIdent("with")) { if (!this.accept(TokenType.ParenthesisL)) { - return this.finish(node, ParseError.LeftParenthesisExpected, [ - TokenType.ParenthesisR, - ]); + return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.ParenthesisR]); } // First variable statement, no comma. - if ( - !node.getParameters().addChild(this._parseModuleConfigDeclaration()) - ) { + if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) { return this.finish(node, ParseError.VariableNameExpected); } @@ -1072,9 +950,7 @@ export class SCSSParser extends cssParser.Parser { if (this.peek(TokenType.ParenthesisR)) { break; } - if ( - !node.getParameters().addChild(this._parseModuleConfigDeclaration()) - ) { + if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) { return this.finish(node, ParseError.VariableNameExpected); } } diff --git a/packages/parser/src/scssScanner.ts b/packages/vscode-css-languageservice/src/parser/scssScanner.ts similarity index 98% rename from packages/parser/src/scssScanner.ts rename to packages/vscode-css-languageservice/src/parser/scssScanner.ts index ec9f0201..bf321136 100644 --- a/packages/parser/src/scssScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/scssScanner.ts @@ -2,6 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +"use strict"; + import { TokenType, Scanner, IToken } from "./cssScanner"; const _FSL = "/".charCodeAt(0); @@ -17,6 +19,7 @@ const _BNG = "!".charCodeAt(0); const _LAN = "<".charCodeAt(0); const _RAN = ">".charCodeAt(0); const _DOT = ".".charCodeAt(0); +const _ATS = "@".charCodeAt(0); let customTokenValue = TokenType.CustomToken; diff --git a/packages/vscode-css-languageservice/src/services/cssCompletion.ts b/packages/vscode-css-languageservice/src/services/cssCompletion.ts index 85954b04..c4fb815b 100644 --- a/packages/vscode-css-languageservice/src/services/cssCompletion.ts +++ b/packages/vscode-css-languageservice/src/services/cssCompletion.ts @@ -1188,7 +1188,7 @@ export class CSSCompletion { } } -function isDeprecated(entry: languageFacts.IEntry): boolean { +function isDeprecated(entry: languageFacts.IEntry2): boolean { if (entry.status && (entry.status === "nonstandard" || entry.status === "obsolete")) { return true; } diff --git a/packages/parser/src/cssParser.test.ts b/packages/vscode-css-languageservice/src/test/css/parser.test.ts similarity index 54% rename from packages/parser/src/cssParser.test.ts rename to packages/vscode-css-languageservice/src/test/css/parser.test.ts index a6e4b090..c87943f7 100644 --- a/packages/parser/src/cssParser.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/parser.test.ts @@ -2,18 +2,15 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from "node:assert"; -import { suite, test } from "vitest"; -import { ParseError } from "./cssErrors.js"; -import * as nodes from "./cssNodes.js"; -import { Parser } from "./cssParser.js"; -import { TokenType } from "./cssScanner.js"; +"use strict"; -export function assertNode( - text: string, - parser: Parser, - f: (...args: any[]) => nodes.Node | null, -): nodes.Node { +import * as assert from "assert"; +import { Parser } from "../../parser/cssParser"; +import { TokenType } from "../../parser/cssScanner"; +import * as nodes from "../../parser/cssNodes"; +import { ParseError } from "../../parser/cssErrors"; + +export function assertNode(text: string, parser: Parser, f: (...args: any[]) => nodes.Node | null): nodes.Node { const node = parser.internalParse(text, f)!; assert.ok(node !== null, "no node returned"); const markers = nodes.ParseErrorCollector.entries(node); @@ -32,29 +29,16 @@ export function assertNode( return node; } -export function assertFunction( - text: string, - parser: Parser, - f: () => nodes.Node | null, -): void { +export function assertFunction(text: string, parser: Parser, f: () => nodes.Node | null): void { assertNode(text, parser, f); } -export function assertNoNode( - text: string, - parser: Parser, - f: () => nodes.Node | null, -): void { +export function assertNoNode(text: string, parser: Parser, f: () => nodes.Node | null): void { const node = parser.internalParse(text, f)!; assert.ok(node === null || !parser.accept(TokenType.EOF)); } -export function assertError( - text: string, - parser: Parser, - f: () => nodes.Node | null, - error: nodes.IRule, -): void { +export function assertError(text: string, parser: Parser, f: () => nodes.Node | null, error: nodes.IRule): void { const node = parser.internalParse(text, f)!; assert.ok(node !== null, "no node returned"); let markers = nodes.ParseErrorCollector.entries(node); @@ -64,84 +48,36 @@ export function assertError( markers = markers.sort((a, b) => { return a.getOffset() - b.getOffset(); }); - assert.equal( - markers[0].getRule().id, - error.id, - "incorrect error returned from parsing: " + text, - ); + assert.equal(markers[0].getRule().id, error.id, "incorrect error returned from parsing: " + text); } } suite("CSS - Parser", () => { test("stylesheet", function () { const parser = new Parser(); - assertNode( - '@charset "demo" ;', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "body { margin: 0px; padding: 3em, 6em; }", - parser, - parser._parseStylesheet.bind(parser), - ); + assertNode('@charset "demo" ;', parser, parser._parseStylesheet.bind(parser)); + assertNode("body { margin: 0px; padding: 3em, 6em; }", parser, parser._parseStylesheet.bind(parser)); assertNode("--> @import "string"; ', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media asdsa { } ", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media screen, projection { }", - parser, - parser._parseStylesheet.bind(parser), - ); + assertNode(' @import "string"; ', parser, parser._parseStylesheet.bind(parser)); + assertNode("@media asdsa { } ", parser, parser._parseStylesheet.bind(parser)); + assertNode("@media screen, projection { }", parser, parser._parseStylesheet.bind(parser)); assertNode( "@media screen and (max-width: 400px) { @-ms-viewport { width: 320px; }}", parser, parser._parseStylesheet.bind(parser), ); - assertNode( - "@-ms-viewport { width: 320px; height: 768px; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "#boo, far {} \n.far boo {}", - parser, - parser._parseStylesheet.bind(parser), - ); + assertNode("@-ms-viewport { width: 320px; height: 768px; }", parser, parser._parseStylesheet.bind(parser)); + assertNode("#boo, far {} \n.far boo {}", parser, parser._parseStylesheet.bind(parser)); assertNode( "@-moz-keyframes darkWordHighlight { from { background-color: inherit; } to { background-color: rgba(83, 83, 83, 0.7); } }", parser, parser._parseStylesheet.bind(parser), ); - assertNode( - "@page { margin: 2.5cm; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@font-face { font-family: "Example Font"; }', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@namespace "http://www.w3.org/1999/xhtml";', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@namespace pref url(http://test);", - parser, - parser._parseStylesheet.bind(parser), - ); + assertNode("@page { margin: 2.5cm; }", parser, parser._parseStylesheet.bind(parser)); + assertNode('@font-face { font-family: "Example Font"; }', parser, parser._parseStylesheet.bind(parser)); + assertNode('@namespace "http://www.w3.org/1999/xhtml";', parser, parser._parseStylesheet.bind(parser)); + assertNode("@namespace pref url(http://test);", parser, parser._parseStylesheet.bind(parser)); assertNode( "@-moz-document url(http://test), url-prefix(http://www.w3.org/Style/) { body { color: purple; background: yellow; } }", parser, @@ -152,11 +88,7 @@ suite("CSS - Parser", () => { parser, parser._parseStylesheet.bind(parser), ); - assertNode( - 'input[type="submit"] {}', - parser, - parser._parseStylesheet.bind(parser), - ); + assertNode('input[type="submit"] {}', parser, parser._parseStylesheet.bind(parser)); assertNode( "E:root E:nth-child(n) E:nth-last-child(n) E:nth-of-type(n) E:nth-last-of-type(n) E:first-child E:last-child {}", parser, @@ -167,77 +99,33 @@ suite("CSS - Parser", () => { parser, parser._parseStylesheet.bind(parser), ); - assertNode( - "E::first-line E::first-letter E::before E::after {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "E.warning E#myid E:not(s) {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertError( - "@namespace;", - parser, - parser._parseStylesheet.bind(parser), - ParseError.URIExpected, - ); + assertNode("E::first-line E::first-letter E::before E::after {}", parser, parser._parseStylesheet.bind(parser)); + assertNode("E.warning E#myid E:not(s) {}", parser, parser._parseStylesheet.bind(parser)); + assertError("@namespace;", parser, parser._parseStylesheet.bind(parser), ParseError.URIExpected); assertError( "@namespace url(http://test)", parser, parser._parseStylesheet.bind(parser), ParseError.SemiColonExpected, ); - assertError( - "@charset;", - parser, - parser._parseStylesheet.bind(parser), - ParseError.IdentifierExpected, - ); - assertError( - "@charset 'utf8'", - parser, - parser._parseStylesheet.bind(parser), - ParseError.SemiColonExpected, - ); + assertError("@charset;", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); + assertError("@charset 'utf8'", parser, parser._parseStylesheet.bind(parser), ParseError.SemiColonExpected); }); test("stylesheet - graceful handling of unknown rules", function () { const parser = new Parser(); assertNode("@unknown-rule;", parser, parser._parseStylesheet.bind(parser)); - assertNode( - `@unknown-rule 'foo';`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@unknown-rule (foo) {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@unknown-rule (foo) { .bar {} }", - parser, - parser._parseStylesheet.bind(parser), - ); + assertNode(`@unknown-rule 'foo';`, parser, parser._parseStylesheet.bind(parser)); + assertNode("@unknown-rule (foo) {}", parser, parser._parseStylesheet.bind(parser)); + assertNode("@unknown-rule (foo) { .bar {} }", parser, parser._parseStylesheet.bind(parser)); assertNode( "@mskeyframes darkWordHighlight { from { background-color: inherit; } to { background-color: rgba(83, 83, 83, 0.7); } }", parser, parser._parseStylesheet.bind(parser), ); - assertNode( - "foo { @unknown-rule; }", - parser, - parser._parseStylesheet.bind(parser), - ); + assertNode("foo { @unknown-rule; }", parser, parser._parseStylesheet.bind(parser)); - assertError( - "@unknown-rule (;", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); + assertError("@unknown-rule (;", parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected); assertError( "@unknown-rule [foo", parser, @@ -250,12 +138,7 @@ suite("CSS - Parser", () => { parser._parseStylesheet.bind(parser), ParseError.RightSquareBracketExpected, ); - assertError( - "@unknown-rule (foo) {", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightCurlyExpected, - ); + assertError("@unknown-rule (foo) {", parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected); assertError( "@unknown-rule (foo) { .bar {}", parser, @@ -266,11 +149,7 @@ suite("CSS - Parser", () => { test("stylesheet - unknown rules node ends properly. Microsoft/vscode#53159", function () { const parser = new Parser(); - const node = assertNode( - "@unknown-rule (foo) {} .foo {}", - parser, - parser._parseStylesheet.bind(parser), - ); + const node = assertNode("@unknown-rule (foo) {} .foo {}", parser, parser._parseStylesheet.bind(parser)); const unknownAtRule = node.getChild(0)!; assert.equal(unknownAtRule.type, nodes.NodeType.UnknownAtRule); @@ -299,32 +178,15 @@ suite("CSS - Parser", () => { parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected, ); - assertError( - '- @import "foo";', - parser, - parser._parseStylesheet.bind(parser), - ParseError.RuleOrSelectorExpected, - ); + assertError('- @import "foo";', parser, parser._parseStylesheet.bind(parser), ParseError.RuleOrSelectorExpected); }); test("@font-face", function () { const parser = new Parser(); assertNode("@font-face {}", parser, parser._parseFontFace.bind(parser)); - assertNode( - "@font-face { src: url(http://test) }", - parser, - parser._parseFontFace.bind(parser), - ); - assertNode( - "@font-face { font-style: normal; font-stretch: normal; }", - parser, - parser._parseFontFace.bind(parser), - ); - assertNode( - "@font-face { unicode-range: U+0021-007F }", - parser, - parser._parseFontFace.bind(parser), - ); + assertNode("@font-face { src: url(http://test) }", parser, parser._parseFontFace.bind(parser)); + assertNode("@font-face { font-style: normal; font-stretch: normal; }", parser, parser._parseFontFace.bind(parser)); + assertNode("@font-face { unicode-range: U+0021-007F }", parser, parser._parseFontFace.bind(parser)); assertError( "@font-face { font-style: normal font-stretch: normal; }", parser, @@ -339,110 +201,30 @@ suite("CSS - Parser", () => { assertNode("to {}", parser, parser._parseKeyframeSelector.bind(parser)); assertNode("0% {}", parser, parser._parseKeyframeSelector.bind(parser)); assertNode("10% {}", parser, parser._parseKeyframeSelector.bind(parser)); - assertNode( - "cover 10% {}", - parser, - parser._parseKeyframeSelector.bind(parser), - ); - assertNode( - "100000% {}", - parser, - parser._parseKeyframeSelector.bind(parser), - ); - assertNode( - "from { width: 100% }", - parser, - parser._parseKeyframeSelector.bind(parser), - ); - assertNode( - "from { width: 100%; to: 10px; }", - parser, - parser._parseKeyframeSelector.bind(parser), - ); - assertNode( - "from, to { width: 10px; }", - parser, - parser._parseKeyframeSelector.bind(parser), - ); - assertNode( - "10%, to { width: 10px; }", - parser, - parser._parseKeyframeSelector.bind(parser), - ); - assertNode( - "from, 20% { width: 10px; }", - parser, - parser._parseKeyframeSelector.bind(parser), - ); - assertNode( - "10%, 20% { width: 10px; }", - parser, - parser._parseKeyframeSelector.bind(parser), - ); - assertNode( - "cover 10% {}", - parser, - parser._parseKeyframeSelector.bind(parser), - ); - assertNode( - "cover 10%, exit 20% {}", - parser, - parser._parseKeyframeSelector.bind(parser), - ); - assertNode( - "10%, exit 20% {}", - parser, - parser._parseKeyframeSelector.bind(parser), - ); - assertNode( - "from, exit 20% {}", - parser, - parser._parseKeyframeSelector.bind(parser), - ); - assertNode( - "cover 10%, to {}", - parser, - parser._parseKeyframeSelector.bind(parser), - ); - assertNode( - "cover 10%, 20% {}", - parser, - parser._parseKeyframeSelector.bind(parser), - ); + assertNode("cover 10% {}", parser, parser._parseKeyframeSelector.bind(parser)); + assertNode("100000% {}", parser, parser._parseKeyframeSelector.bind(parser)); + assertNode("from { width: 100% }", parser, parser._parseKeyframeSelector.bind(parser)); + assertNode("from { width: 100%; to: 10px; }", parser, parser._parseKeyframeSelector.bind(parser)); + assertNode("from, to { width: 10px; }", parser, parser._parseKeyframeSelector.bind(parser)); + assertNode("10%, to { width: 10px; }", parser, parser._parseKeyframeSelector.bind(parser)); + assertNode("from, 20% { width: 10px; }", parser, parser._parseKeyframeSelector.bind(parser)); + assertNode("10%, 20% { width: 10px; }", parser, parser._parseKeyframeSelector.bind(parser)); + assertNode("cover 10% {}", parser, parser._parseKeyframeSelector.bind(parser)); + assertNode("cover 10%, exit 20% {}", parser, parser._parseKeyframeSelector.bind(parser)); + assertNode("10%, exit 20% {}", parser, parser._parseKeyframeSelector.bind(parser)); + assertNode("from, exit 20% {}", parser, parser._parseKeyframeSelector.bind(parser)); + assertNode("cover 10%, to {}", parser, parser._parseKeyframeSelector.bind(parser)); + assertNode("cover 10%, 20% {}", parser, parser._parseKeyframeSelector.bind(parser)); }); test("@keyframe", function () { const parser = new Parser(); - assertNode( - "@keyframes name {}", - parser, - parser._parseKeyframe.bind(parser), - ); - assertNode( - "@-webkit-keyframes name {}", - parser, - parser._parseKeyframe.bind(parser), - ); - assertNode( - "@-o-keyframes name {}", - parser, - parser._parseKeyframe.bind(parser), - ); - assertNode( - "@-moz-keyframes name {}", - parser, - parser._parseKeyframe.bind(parser), - ); - assertNode( - "@keyframes name { from {} to {}}", - parser, - parser._parseKeyframe.bind(parser), - ); - assertNode( - "@keyframes name { from {} 80% {} 100% {}}", - parser, - parser._parseKeyframe.bind(parser), - ); + assertNode("@keyframes name {}", parser, parser._parseKeyframe.bind(parser)); + assertNode("@-webkit-keyframes name {}", parser, parser._parseKeyframe.bind(parser)); + assertNode("@-o-keyframes name {}", parser, parser._parseKeyframe.bind(parser)); + assertNode("@-moz-keyframes name {}", parser, parser._parseKeyframe.bind(parser)); + assertNode("@keyframes name { from {} to {}}", parser, parser._parseKeyframe.bind(parser)); + assertNode("@keyframes name { from {} 80% {} 100% {}}", parser, parser._parseKeyframe.bind(parser)); assertNode( "@keyframes name { from { top: 0px; } 80% { top: 100px; } 100% { top: 50px; }}", parser, @@ -469,12 +251,7 @@ suite("CSS - Parser", () => { parser._parseKeyframe.bind(parser), ParseError.SemiColonExpected, ); - assertError( - "@keyframes )", - parser, - parser._parseKeyframe.bind(parser), - ParseError.IdentifierExpected, - ); + assertError("@keyframes )", parser, parser._parseKeyframe.bind(parser), ParseError.IdentifierExpected); assertError( "@keyframes name { { top: 0px; } }", parser, @@ -514,12 +291,7 @@ suite("CSS - Parser", () => { parser, parser._parseStylesheet.bind(parser), ); - assertError( - `@property { }`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.IdentifierExpected, - ); + assertError(`@property { }`, parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); }); test("@container", function () { @@ -554,54 +326,26 @@ suite("CSS - Parser", () => { const parser = new Parser(); assertNode('@import "asdasdsa"', parser, parser._parseImport.bind(parser)); assertNode('@ImPort "asdsadsa"', parser, parser._parseImport.bind(parser)); - assertNode( - '@import "asdasd" dsfsdf', - parser, - parser._parseImport.bind(parser), - ); + assertNode('@import "asdasd" dsfsdf', parser, parser._parseImport.bind(parser)); assertNode('@import "foo";', parser, parser._parseStylesheet.bind(parser)); - assertNode( - "@import url(/css/screen.css) screen, projection;", - parser, - parser._parseStylesheet.bind(parser), - ); + assertNode("@import url(/css/screen.css) screen, projection;", parser, parser._parseStylesheet.bind(parser)); assertNode( "@import url('landscape.css') screen and (orientation:landscape);", parser, parser._parseStylesheet.bind(parser), ); - assertNode( - '@import url("/inc/Styles/full.css") (min-width: 940px);', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@import url(style.css) screen and (min-width:600px);", - parser, - parser._parseStylesheet.bind(parser), - ); + assertNode('@import url("/inc/Styles/full.css") (min-width: 940px);', parser, parser._parseStylesheet.bind(parser)); + assertNode("@import url(style.css) screen and (min-width:600px);", parser, parser._parseStylesheet.bind(parser)); assertNode( '@import url("./700.css") only screen and (max-width: 700px);', parser, parser._parseStylesheet.bind(parser), ); - assertNode( - '@import url("override.css") layer;', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@import url("tabs.css") layer(framework.component);', - parser, - parser._parseStylesheet.bind(parser), - ); + assertNode('@import url("override.css") layer;', parser, parser._parseStylesheet.bind(parser)); + assertNode('@import url("tabs.css") layer(framework.component);', parser, parser._parseStylesheet.bind(parser)); - assertNode( - '@import "mystyle.css" supports(display: flex);', - parser, - parser._parseStylesheet.bind(parser), - ); + assertNode('@import "mystyle.css" supports(display: flex);', parser, parser._parseStylesheet.bind(parser)); assertNode( '@import url("narrow.css") supports(display: flex) handheld and (max-width: 400px);', @@ -614,12 +358,7 @@ suite("CSS - Parser", () => { parser._parseStylesheet.bind(parser), ); - assertError( - "@import", - parser, - parser._parseStylesheet.bind(parser), - ParseError.URIOrStringExpected, - ); + assertError("@import", parser, parser._parseStylesheet.bind(parser), ParseError.URIOrStringExpected); }); test("@supports", function () { @@ -644,16 +383,8 @@ suite("CSS - Parser", () => { parser, parser._parseSupports.bind(parser), ); - assertNode( - "@supports ((display: flexbox)) { }", - parser, - parser._parseSupports.bind(parser), - ); - assertNode( - "@supports (display: flexbox !important) { }", - parser, - parser._parseSupports.bind(parser), - ); + assertNode("@supports ((display: flexbox)) { }", parser, parser._parseSupports.bind(parser)); + assertNode("@supports (display: flexbox !important) { }", parser, parser._parseSupports.bind(parser)); assertNode( "@supports (grid-area: auto) { @media screen and (min-width: 768px) { .me { } } }", parser, @@ -664,11 +395,7 @@ suite("CSS - Parser", () => { parser, parser._parseSupports.bind(parser), ); // #49288 - assertNode( - "@supports not (--validValue: , 0 ) {}", - parser, - parser._parseSupports.bind(parser), - ); // #82178 + assertNode("@supports not (--validValue: , 0 ) {}", parser, parser._parseSupports.bind(parser)); // #82178 assertError( "@supports (transition-property: color) or (animation-name: foo) and (transform: rotate(10deg)) { }", parser, @@ -687,147 +414,56 @@ suite("CSS - Parser", () => { const parser = new Parser(); assertNode("@media asdsa { }", parser, parser._parseMedia.bind(parser)); assertNode("@meDia sadd{} ", parser, parser._parseMedia.bind(parser)); + assertNode("@media somename, othername2 { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media only screen and (max-width:850px) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media only screen and (max-width:850px) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media all and (min-width:500px) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media screen and (color), projection and (color) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media not screen and (device-aspect-ratio: 16/9) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media print and (min-resolution: 300dpi) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media print and (min-resolution: 118dpcm) { }", parser, parser._parseMedia.bind(parser)); assertNode( - "@media somename, othername2 { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media only screen and (max-width:850px) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media only screen and (max-width:850px) { }", + "@media print { @page { margin: 10% } blockquote, pre { page-break-inside: avoid } }", parser, parser._parseMedia.bind(parser), ); - assertNode( - "@media all and (min-width:500px) { }", + assertNode("@media print { body:before { } }", parser, parser._parseMedia.bind(parser)); + assertNode("@media not (-moz-os-version: windows-win7) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media not (not (-moz-os-version: windows-win7)) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media (height > 600px) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media (height < 600px) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media (height <= 600px) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media (400px <= width <= 700px) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media (400px >= width >= 700px) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media screen and (750px <= width < 900px) { }", parser, parser._parseMedia.bind(parser)); + assertError( + "@media somename othername2 { }", parser, parser._parseMedia.bind(parser), + ParseError.LeftCurlyExpected, ); - assertNode( - "@media screen and (color), projection and (color) { }", + assertError("@media not, screen { }", parser, parser._parseMedia.bind(parser), ParseError.MediaQueryExpected); + assertError( + "@media not screen and foo { }", parser, parser._parseMedia.bind(parser), + ParseError.LeftParenthesisExpected, ); - assertNode( - "@media not screen and (device-aspect-ratio: 16/9) { }", + assertError("@media not screen and () { }", parser, parser._parseMedia.bind(parser), ParseError.IdentifierExpected); + assertError("@media not screen and (color:) { }", parser, parser._parseMedia.bind(parser), ParseError.TermExpected); + assertError( + "@media not screen and (color:#234567 { }", parser, parser._parseMedia.bind(parser), - ); - assertNode( - "@media print and (min-resolution: 300dpi) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media print and (min-resolution: 118dpcm) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media print { @page { margin: 10% } blockquote, pre { page-break-inside: avoid } }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media print { body:before { } }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media not (-moz-os-version: windows-win7) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media not (not (-moz-os-version: windows-win7)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media (height > 600px) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media (height < 600px) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media (height <= 600px) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media (400px <= width <= 700px) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media (400px >= width >= 700px) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (750px <= width < 900px) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertError( - "@media somename othername2 { }", - parser, - parser._parseMedia.bind(parser), - ParseError.LeftCurlyExpected, - ); - assertError( - "@media not, screen { }", - parser, - parser._parseMedia.bind(parser), - ParseError.MediaQueryExpected, - ); - assertError( - "@media not screen and foo { }", - parser, - parser._parseMedia.bind(parser), - ParseError.LeftParenthesisExpected, - ); - assertError( - "@media not screen and () { }", - parser, - parser._parseMedia.bind(parser), - ParseError.IdentifierExpected, - ); - assertError( - "@media not screen and (color:) { }", - parser, - parser._parseMedia.bind(parser), - ParseError.TermExpected, - ); - assertError( - "@media not screen and (color:#234567 { }", - parser, - parser._parseMedia.bind(parser), - ParseError.RightParenthesisExpected, + ParseError.RightParenthesisExpected, ); }); test("media_list", function () { const parser = new Parser(); assertNode("somename", parser, parser._parseMediaQueryList.bind(parser)); - assertNode( - "somename, othername", - parser, - parser._parseMediaQueryList.bind(parser), - ); - assertNode( - "not all and (monochrome)", - parser, - parser._parseMediaQueryList.bind(parser), - ); + assertNode("somename, othername", parser, parser._parseMediaQueryList.bind(parser)); + assertNode("not all and (monochrome)", parser, parser._parseMediaQueryList.bind(parser)); }); test("medium", function () { @@ -840,36 +476,16 @@ suite("CSS - Parser", () => { test("@page", function () { const parser = new Parser(); assertNode("@page : name{ }", parser, parser._parsePage.bind(parser)); - assertNode( - "@page :left, :right { }", - parser, - parser._parsePage.bind(parser), - ); - assertNode( - '@page : name{ some : "asdas" }', - parser, - parser._parsePage.bind(parser), - ); - assertNode( - '@page : name{ some : "asdas" !important }', - parser, - parser._parsePage.bind(parser), - ); + assertNode("@page :left, :right { }", parser, parser._parsePage.bind(parser)); + assertNode('@page : name{ some : "asdas" }', parser, parser._parsePage.bind(parser)); + assertNode('@page : name{ some : "asdas" !important }', parser, parser._parsePage.bind(parser)); assertNode( '@page : name{ some : "asdas" !important; some : "asdas" !important }', parser, parser._parsePage.bind(parser), ); - assertNode( - "@page rotated { size : landscape }", - parser, - parser._parsePage.bind(parser), - ); - assertNode( - "@page :left { margin-left: 4cm; margin-right: 3cm; }", - parser, - parser._parsePage.bind(parser), - ); + assertNode("@page rotated { size : landscape }", parser, parser._parsePage.bind(parser)); + assertNode("@page :left { margin-left: 4cm; margin-right: 3cm; }", parser, parser._parsePage.bind(parser)); assertNode( "@page { @top-right-corner { content: url(foo.png); border: solid green; } }", parser, @@ -893,73 +509,24 @@ suite("CSS - Parser", () => { parser._parsePage.bind(parser), ParseError.SemiColonExpected, ); - assertError( - "@page : { }", - parser, - parser._parsePage.bind(parser), - ParseError.IdentifierExpected, - ); - assertError( - "@page :left, { }", - parser, - parser._parsePage.bind(parser), - ParseError.IdentifierExpected, - ); + assertError("@page : { }", parser, parser._parsePage.bind(parser), ParseError.IdentifierExpected); + assertError("@page :left, { }", parser, parser._parsePage.bind(parser), ParseError.IdentifierExpected); }); test("@layer", function () { const parser = new Parser(); - assertNode( - "@layer utilities { .padding-sm { padding: .5rem; } }", - parser, - parser._parseLayer.bind(parser), - ); + assertNode("@layer utilities { .padding-sm { padding: .5rem; } }", parser, parser._parseLayer.bind(parser)); assertNode("@layer utilities;", parser, parser._parseLayer.bind(parser)); - assertNode( - "@layer theme, layout, utilities;", - parser, - parser._parseLayer.bind(parser), - ); - assertNode( - "@layer utilities { p { margin-block: 1rem; } }", - parser, - parser._parseLayer.bind(parser), - ); - assertNode( - "@layer framework { @layer layout { } }", - parser, - parser._parseLayer.bind(parser), - ); - assertNode( - "@layer framework.layout { @keyframes slide-left {} }", - parser, - parser._parseLayer.bind(parser), - ); + assertNode("@layer theme, layout, utilities;", parser, parser._parseLayer.bind(parser)); + assertNode("@layer utilities { p { margin-block: 1rem; } }", parser, parser._parseLayer.bind(parser)); + assertNode("@layer framework { @layer layout { } }", parser, parser._parseLayer.bind(parser)); + assertNode("@layer framework.layout { @keyframes slide-left {} }", parser, parser._parseLayer.bind(parser)); - assertNode( - "@media (min-width: 30em) { @layer layout { } }", - parser, - parser._parseStylesheet.bind(parser), - ); + assertNode("@media (min-width: 30em) { @layer layout { } }", parser, parser._parseStylesheet.bind(parser)); - assertError( - "@layer theme layout { }", - parser, - parser._parseLayer.bind(parser), - ParseError.SemiColonExpected, - ); - assertError( - "@layer theme, layout { }", - parser, - parser._parseLayer.bind(parser), - ParseError.SemiColonExpected, - ); - assertError( - "@layer framework .layout { }", - parser, - parser._parseLayer.bind(parser), - ParseError.SemiColonExpected, - ); + assertError("@layer theme layout { }", parser, parser._parseLayer.bind(parser), ParseError.SemiColonExpected); + assertError("@layer theme, layout { }", parser, parser._parseLayer.bind(parser), ParseError.SemiColonExpected); + assertError("@layer framework .layout { }", parser, parser._parseLayer.bind(parser), ParseError.SemiColonExpected); assertError( "@layer framework. layout { }", parser, @@ -984,11 +551,7 @@ suite("CSS - Parser", () => { assertNode(">", parser, parser._parseCombinator.bind(parser)); assertNode(">>>", parser, parser._parseCombinator.bind(parser)); assertNode("/deep/", parser, parser._parseCombinator.bind(parser)); - assertNode( - ":host >>> .data-table { width: 100%; }", - parser, - parser._parseStylesheet.bind(parser), - ); + assertNode(":host >>> .data-table { width: 100%; }", parser, parser._parseStylesheet.bind(parser)); assertError( ":host >> .data-table { width: 100%; }", parser, @@ -1019,120 +582,41 @@ suite("CSS - Parser", () => { test("ruleset", function () { const parser = new Parser(); assertNode("name{ }", parser, parser._parseRuleset.bind(parser)); - assertNode( - ' name\n{ some : "asdas" }', - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ' name{ some : "asdas" !important }', - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - 'name{ \n some : "asdas" !important; some : "asdas" }', - parser, - parser._parseRuleset.bind(parser), - ); + assertNode(' name\n{ some : "asdas" }', parser, parser._parseRuleset.bind(parser)); + assertNode(' name{ some : "asdas" !important }', parser, parser._parseRuleset.bind(parser)); + assertNode('name{ \n some : "asdas" !important; some : "asdas" }', parser, parser._parseRuleset.bind(parser)); assertNode("* {}", parser, parser._parseRuleset.bind(parser)); assertNode(".far{}", parser, parser._parseRuleset.bind(parser)); assertNode("boo {}", parser, parser._parseRuleset.bind(parser)); assertNode(".far #boo {}", parser, parser._parseRuleset.bind(parser)); - assertNode( - "boo { prop: value }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "boo { prop: value; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "boo { prop: value; prop: value }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "boo { prop: value; prop: value; }", - parser, - parser._parseRuleset.bind(parser), - ); + assertNode("boo { prop: value }", parser, parser._parseRuleset.bind(parser)); + assertNode("boo { prop: value; }", parser, parser._parseRuleset.bind(parser)); + assertNode("boo { prop: value; prop: value }", parser, parser._parseRuleset.bind(parser)); + assertNode("boo { prop: value; prop: value; }", parser, parser._parseRuleset.bind(parser)); assertNode("boo {--minimal: }", parser, parser._parseRuleset.bind(parser)); assertNode("boo {--minimal: ;}", parser, parser._parseRuleset.bind(parser)); - assertNode( - "boo {--normal-text: red yellow green}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "boo {--normal-text: red yellow green;}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "boo {--important: red !important;}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "boo {--nested: {color: green;}}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "boo {--parens: this()is()ok()}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "boo {--squares: this[]is[]ok[]too[]}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - "boo {--combined: ([{{[]()()}[]{}}])()}", - parser, - parser._parseRuleset.bind(parser), - ); + assertNode("boo {--normal-text: red yellow green}", parser, parser._parseRuleset.bind(parser)); + assertNode("boo {--normal-text: red yellow green;}", parser, parser._parseRuleset.bind(parser)); + assertNode("boo {--important: red !important;}", parser, parser._parseRuleset.bind(parser)); + assertNode("boo {--nested: {color: green;}}", parser, parser._parseRuleset.bind(parser)); + assertNode("boo {--parens: this()is()ok()}", parser, parser._parseRuleset.bind(parser)); + assertNode("boo {--squares: this[]is[]ok[]too[]}", parser, parser._parseRuleset.bind(parser)); + assertNode("boo {--combined: ([{{[]()()}[]{}}])()}", parser, parser._parseRuleset.bind(parser)); assertNode( "boo {--weird-inside-delims: {color: green;;;;;;!important;;}}", parser, parser._parseRuleset.bind(parser), ); - assertNode( - `boo {--validValue: , 0 0}`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `boo {--validValue: , 0 0;}`, - parser, - parser._parseRuleset.bind(parser), - ); - assertError( - "boo, { }", - parser, - parser._parseRuleset.bind(parser), - ParseError.SelectorExpected, - ); + assertNode(`boo {--validValue: , 0 0}`, parser, parser._parseRuleset.bind(parser)); + assertNode(`boo {--validValue: , 0 0;}`, parser, parser._parseRuleset.bind(parser)); + assertError("boo, { }", parser, parser._parseRuleset.bind(parser), ParseError.SelectorExpected); }); test("ruleset /Panic/", function () { const parser = new Parser(); // assertNode('boo { : value }', parser, parser._parseRuleset.bind(parser)); - assertError( - "boo { prop: ; }", - parser, - parser._parseRuleset.bind(parser), - ParseError.PropertyValueExpected, - ); - assertError( - "boo { prop }", - parser, - parser._parseRuleset.bind(parser), - ParseError.ColonExpected, - ); + assertError("boo { prop: ; }", parser, parser._parseRuleset.bind(parser), ParseError.PropertyValueExpected); + assertError("boo { prop }", parser, parser._parseRuleset.bind(parser), ParseError.ColonExpected); assertError( "boo { prop: ; far: 12em; }", parser, @@ -1141,18 +625,8 @@ suite("CSS - Parser", () => { ); // assertNode('boo { prop: ; 1ar: 12em; }', parser, parser._parseRuleset.bind(parser)); - assertError( - "boo { --too-minimal:}", - parser, - parser._parseRuleset.bind(parser), - ParseError.PropertyValueExpected, - ); - assertError( - "boo { --unterminated: ", - parser, - parser._parseRuleset.bind(parser), - ParseError.RightCurlyExpected, - ); + assertError("boo { --too-minimal:}", parser, parser._parseRuleset.bind(parser), ParseError.PropertyValueExpected); + assertError("boo { --unterminated: ", parser, parser._parseRuleset.bind(parser), ParseError.RightCurlyExpected); assertError( "boo { --double-important: red !important !important;}", parser, @@ -1192,75 +666,26 @@ suite("CSS - Parser", () => { }); test("nested ruleset", function () { - const parser = new Parser(); - assertNode( - ".foo { color: red; input { color: blue; } }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".foo { color: red; :focus { color: blue; } }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".foo { color: red; .bar { color: blue; } }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".foo { color: red; &:hover { color: blue; } }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".foo { color: red; + .bar { color: blue; } }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".foo { color: red; foo:hover { color: blue }; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".foo { color: red; @media screen { color: blue }; }", - parser, - parser._parseRuleset.bind(parser), - ); + let parser = new Parser(); + assertNode(".foo { color: red; input { color: blue; } }", parser, parser._parseRuleset.bind(parser)); + assertNode(".foo { color: red; :focus { color: blue; } }", parser, parser._parseRuleset.bind(parser)); + assertNode(".foo { color: red; .bar { color: blue; } }", parser, parser._parseRuleset.bind(parser)); + assertNode(".foo { color: red; &:hover { color: blue; } }", parser, parser._parseRuleset.bind(parser)); + assertNode(".foo { color: red; + .bar { color: blue; } }", parser, parser._parseRuleset.bind(parser)); + assertNode(".foo { color: red; foo:hover { color: blue }; }", parser, parser._parseRuleset.bind(parser)); + assertNode(".foo { color: red; @media screen { color: blue }; }", parser, parser._parseRuleset.bind(parser)); // Top level curly braces are allowed in declaration values if they are for a custom property. - assertNode( - ".foo { --foo: {}; }", - parser, - parser._parseRuleset.bind(parser), - ); + assertNode(".foo { --foo: {}; }", parser, parser._parseRuleset.bind(parser)); // Top level curly braces are not allowed in declaration values. - assertError( - ".foo { foo: {}; }", - parser, - parser._parseRuleset.bind(parser), - ParseError.PropertyValueExpected, - ); + assertError(".foo { foo: {}; }", parser, parser._parseRuleset.bind(parser), ParseError.PropertyValueExpected); }); test("nested ruleset 2", function () { - const parser = new Parser(); - assertNode( - ".foo { .parent & { color: blue; } }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".foo { color: red; & > .bar, > .baz { color: blue; } }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".foo { & .bar & .baz & .qux { color: blue; } }", - parser, - parser._parseRuleset.bind(parser), - ); + let parser = new Parser(); + assertNode(".foo { .parent & { color: blue; } }", parser, parser._parseRuleset.bind(parser)); + assertNode(".foo { color: red; & > .bar, > .baz { color: blue; } }", parser, parser._parseRuleset.bind(parser)); + assertNode(".foo { & .bar & .baz & .qux { color: blue; } }", parser, parser._parseRuleset.bind(parser)); assertNode( ".foo { color: red; :not(&) { color: blue; }; + .bar + & { color: green; } }", parser, @@ -1271,11 +696,7 @@ suite("CSS - Parser", () => { parser, parser._parseRuleset.bind(parser), ); - assertNode( - ".foo { & :is(.bar, &.baz) { color: red; } }", - parser, - parser._parseRuleset.bind(parser), - ); + assertNode(".foo { & :is(.bar, &.baz) { color: red; } }", parser, parser._parseRuleset.bind(parser)); assertNode( "figure { > figcaption { background: hsl(0 0% 0% / 50%); > p { font-size: .9rem; } } }", parser, @@ -1292,51 +713,23 @@ suite("CSS - Parser", () => { const parser = new Parser(); assertNode("asdsa", parser, parser._parseSelector.bind(parser)); assertNode("asdsa + asdas", parser, parser._parseSelector.bind(parser)); - assertNode( - "asdsa + asdas + name", - parser, - parser._parseSelector.bind(parser), - ); - assertNode( - "asdsa + asdas + name", - parser, - parser._parseSelector.bind(parser), - ); - assertNode( - "name #id#anotherid", - parser, - parser._parseSelector.bind(parser), - ); + assertNode("asdsa + asdas + name", parser, parser._parseSelector.bind(parser)); + assertNode("asdsa + asdas + name", parser, parser._parseSelector.bind(parser)); + assertNode("name #id#anotherid", parser, parser._parseSelector.bind(parser)); assertNode("name.far .boo", parser, parser._parseSelector.bind(parser)); - assertNode( - "name .name .zweitername", - parser, - parser._parseSelector.bind(parser), - ); + assertNode("name .name .zweitername", parser, parser._parseSelector.bind(parser)); assertNode("*", parser, parser._parseSelector.bind(parser)); assertNode("#id", parser, parser._parseSelector.bind(parser)); assertNode("far.boo", parser, parser._parseSelector.bind(parser)); - assertNode( - "::slotted(div)::after", - parser, - parser._parseSelector.bind(parser), - ); // 35076 + assertNode("::slotted(div)::after", parser, parser._parseSelector.bind(parser)); // 35076 }); test("simple selector", function () { const parser = new Parser(); assertNode("name", parser, parser._parseSimpleSelector.bind(parser)); - assertNode( - "#id#anotherid", - parser, - parser._parseSimpleSelector.bind(parser), - ); + assertNode("#id#anotherid", parser, parser._parseSimpleSelector.bind(parser)); assertNode("name.far", parser, parser._parseSimpleSelector.bind(parser)); - assertNode( - "name.erstername.zweitername", - parser, - parser._parseSimpleSelector.bind(parser), - ); + assertNode("name.erstername.zweitername", parser, parser._parseSimpleSelector.bind(parser)); }); test("element name", function () { @@ -1356,49 +749,17 @@ suite("CSS - Parser", () => { assertNode("[name ~= name3]", parser, parser._parseAttrib.bind(parser)); assertNode("[name~=name3]", parser, parser._parseAttrib.bind(parser)); assertNode("[name |= name3]", parser, parser._parseAttrib.bind(parser)); - assertNode( - '[name |= "this is a striiiing"]', - parser, - parser._parseAttrib.bind(parser), - ); - assertNode( - '[href*="insensitive" i]', - parser, - parser._parseAttrib.bind(parser), - ); - assertNode( - '[href*="sensitive" S]', - parser, - parser._parseAttrib.bind(parser), - ); + assertNode('[name |= "this is a striiiing"]', parser, parser._parseAttrib.bind(parser)); + assertNode('[href*="insensitive" i]', parser, parser._parseAttrib.bind(parser)); + assertNode('[href*="sensitive" S]', parser, parser._parseAttrib.bind(parser)); // Single namespace assertNode("[namespace|name]", parser, parser._parseAttrib.bind(parser)); - assertNode( - "[name-space|name = name2]", - parser, - parser._parseAttrib.bind(parser), - ); - assertNode( - "[name_space|name ~= name3]", - parser, - parser._parseAttrib.bind(parser), - ); - assertNode( - "[name0spae|name~=name3]", - parser, - parser._parseAttrib.bind(parser), - ); - assertNode( - '[NameSpace|name |= "this is a striiiing"]', - parser, - parser._parseAttrib.bind(parser), - ); - assertNode( - "[name\\*space|name |= name3]", - parser, - parser._parseAttrib.bind(parser), - ); + assertNode("[name-space|name = name2]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name_space|name ~= name3]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name0spae|name~=name3]", parser, parser._parseAttrib.bind(parser)); + assertNode('[NameSpace|name |= "this is a striiiing"]', parser, parser._parseAttrib.bind(parser)); + assertNode("[name\\*space|name |= name3]", parser, parser._parseAttrib.bind(parser)); assertNode("[*|name]", parser, parser._parseAttrib.bind(parser)); }); @@ -1410,93 +771,34 @@ suite("CSS - Parser", () => { assertNode(":nth-child(1n)", parser, parser._parsePseudo.bind(parser)); assertNode(":nth-child(-n+3)", parser, parser._parsePseudo.bind(parser)); assertNode(":nth-child(2n+1)", parser, parser._parsePseudo.bind(parser)); - assertNode( - ":nth-child(2n+1 of .foo)", - parser, - parser._parsePseudo.bind(parser), - ); - assertNode( - ':nth-child(2n+1 of .foo > bar, :not(*) ~ [other="value"])', - parser, - parser._parsePseudo.bind(parser), - ); + assertNode(":nth-child(2n+1 of .foo)", parser, parser._parsePseudo.bind(parser)); + assertNode(':nth-child(2n+1 of .foo > bar, :not(*) ~ [other="value"])', parser, parser._parsePseudo.bind(parser)); assertNode(":lang(it)", parser, parser._parsePseudo.bind(parser)); assertNode(":not(.class)", parser, parser._parsePseudo.bind(parser)); assertNode(":not(:disabled)", parser, parser._parsePseudo.bind(parser)); assertNode(":not(#foo)", parser, parser._parsePseudo.bind(parser)); assertNode("::slotted(*)", parser, parser._parsePseudo.bind(parser)); // #35076 - assertNode( - "::slotted(div:hover)", - parser, - parser._parsePseudo.bind(parser), - ); // #35076 - assertNode( - ":global(.output ::selection)", - parser, - parser._parsePseudo.bind(parser), - ); // #49010 - assertNode( - ":matches(:hover, :focus)", - parser, - parser._parsePseudo.bind(parser), - ); // #49010 - assertNode( - ":host([foo=bar][bar=foo])", - parser, - parser._parsePseudo.bind(parser), - ); // #49589 + assertNode("::slotted(div:hover)", parser, parser._parsePseudo.bind(parser)); // #35076 + assertNode(":global(.output ::selection)", parser, parser._parsePseudo.bind(parser)); // #49010 + assertNode(":matches(:hover, :focus)", parser, parser._parsePseudo.bind(parser)); // #49010 + assertNode(":host([foo=bar][bar=foo])", parser, parser._parsePseudo.bind(parser)); // #49589 assertNode(":has(> .test)", parser, parser._parsePseudo.bind(parser)); // #250 assertNode(":has(~ .test)", parser, parser._parsePseudo.bind(parser)); // #250 assertNode(":has(+ .test)", parser, parser._parsePseudo.bind(parser)); // #250 assertNode(":has(~ div .test)", parser, parser._parsePseudo.bind(parser)); // #250 - assertError( - "::", - parser, - parser._parsePseudo.bind(parser), - ParseError.IdentifierExpected, - ); - assertError( - ":: foo", - parser, - parser._parsePseudo.bind(parser), - ParseError.IdentifierExpected, - ); - assertError( - ":nth-child(1n of)", - parser, - parser._parsePseudo.bind(parser), - ParseError.SelectorExpected, - ); + assertError("::", parser, parser._parsePseudo.bind(parser), ParseError.IdentifierExpected); + assertError(":: foo", parser, parser._parsePseudo.bind(parser), ParseError.IdentifierExpected); + assertError(":nth-child(1n of)", parser, parser._parsePseudo.bind(parser), ParseError.SelectorExpected); }); test("declaration", function () { const parser = new Parser(); - assertNode( - 'name : "this is a string" !important', - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - 'name : "this is a string"', - parser, - parser._parseDeclaration.bind(parser), - ); + assertNode('name : "this is a string" !important', parser, parser._parseDeclaration.bind(parser)); + assertNode('name : "this is a string"', parser, parser._parseDeclaration.bind(parser)); assertNode("property:12", parser, parser._parseDeclaration.bind(parser)); - assertNode( - "-vendor-property: 12", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "font-size: 12px", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "color : #888 /4", - parser, - parser._parseDeclaration.bind(parser), - ); + assertNode("-vendor-property: 12", parser, parser._parseDeclaration.bind(parser)); + assertNode("font-size: 12px", parser, parser._parseDeclaration.bind(parser)); + assertNode("color : #888 /4", parser, parser._parseDeclaration.bind(parser)); assertNode( "filter : progid:DXImageTransform.Microsoft.Shadow(color=#000000,direction=45)", parser, @@ -1507,21 +809,9 @@ suite("CSS - Parser", () => { parser, parser._parseDeclaration.bind(parser), ); - assertNode( - "font-size: 12px", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "*background: #f00 /* IE 7 and below */", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "_background: #f60 /* IE 6 and below */", - parser, - parser._parseDeclaration.bind(parser), - ); + assertNode("font-size: 12px", parser, parser._parseDeclaration.bind(parser)); + assertNode("*background: #f00 /* IE 7 and below */", parser, parser._parseDeclaration.bind(parser)); + assertNode("_background: #f60 /* IE 6 and below */", parser, parser._parseDeclaration.bind(parser)); assertNode( "background-image: linear-gradient(to right, silver, white 50px, white calc(100% - 50px), silver)", parser, @@ -1542,26 +832,14 @@ suite("CSS - Parser", () => { parser, parser._parseDeclaration.bind(parser), ); - assertNode( - "grid-template: [foo] 10px / [bar] 10px", - parser, - parser._parseDeclaration.bind(parser), - ); + assertNode("grid-template: [foo] 10px / [bar] 10px", parser, parser._parseDeclaration.bind(parser)); assertNode( `grid-template: 'left1 footer footer' 1fr [end] / [ini] 1fr [info-start] 2fr 1fr [end]`, parser, parser._parseDeclaration.bind(parser), ); - assertNode( - `content: "("counter(foo) ")"`, - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - `content: 'Hello\\0A''world'`, - parser, - parser._parseDeclaration.bind(parser), - ); + assertNode(`content: "("counter(foo) ")"`, parser, parser._parseDeclaration.bind(parser)); + assertNode(`content: 'Hello\\0A''world'`, parser, parser._parseDeclaration.bind(parser)); }); test("term", function () { @@ -1578,29 +856,17 @@ suite("CSS - Parser", () => { assertNode("-45em", parser, parser._parseTerm.bind(parser)); assertNode('"asdsa"', parser, parser._parseTerm.bind(parser)); assertNode("faa", parser, parser._parseTerm.bind(parser)); - assertNode( - 'url("this is a striiiiing")', - parser, - parser._parseTerm.bind(parser), - ); + assertNode('url("this is a striiiiing")', parser, parser._parseTerm.bind(parser)); assertNode("#FFFFFF", parser, parser._parseTerm.bind(parser)); assertNode("name(asd)", parser, parser._parseTerm.bind(parser)); assertNode("calc(50% + 20px)", parser, parser._parseTerm.bind(parser)); - assertNode( - "calc(50% + (100%/3 - 2*1em - 2*1px))", - parser, - parser._parseTerm.bind(parser), - ); + assertNode("calc(50% + (100%/3 - 2*1em - 2*1px))", parser, parser._parseTerm.bind(parser)); assertNoNode( "%('repetitions: %S file: %S', 1 + 2, \"directory/file.less\")", parser, parser._parseTerm.bind(parser), ); // less syntax - assertNoNode( - '~"ms:alwaysHasItsOwnSyntax.For.Stuff()"', - parser, - parser._parseTerm.bind(parser), - ); // less syntax + assertNoNode('~"ms:alwaysHasItsOwnSyntax.For.Stuff()"', parser, parser._parseTerm.bind(parser)); // less syntax assertNode("U+002?-0199", parser, parser._parseTerm.bind(parser)); assertNoNode("U+002?-01??", parser, parser._parseTerm.bind(parser)); assertNoNode("U+00?0;", parser, parser._parseTerm.bind(parser)); @@ -1620,26 +886,10 @@ suite("CSS - Parser", () => { assertNoNode("% ()", parser, parser._parseFunction.bind(parser)); assertFunction("let(--color)", parser, parser._parseFunction.bind(parser)); - assertFunction( - "let(--color, somevalue)", - parser, - parser._parseFunction.bind(parser), - ); - assertFunction( - "let(--variable1, --variable2)", - parser, - parser._parseFunction.bind(parser), - ); - assertFunction( - "let(--variable1, let(--variable2))", - parser, - parser._parseFunction.bind(parser), - ); - assertFunction( - "fun(value1, value2)", - parser, - parser._parseFunction.bind(parser), - ); + assertFunction("let(--color, somevalue)", parser, parser._parseFunction.bind(parser)); + assertFunction("let(--variable1, --variable2)", parser, parser._parseFunction.bind(parser)); + assertFunction("let(--variable1, let(--variable2))", parser, parser._parseFunction.bind(parser)); + assertFunction("fun(value1, value2)", parser, parser._parseFunction.bind(parser)); assertFunction("fun(value1,)", parser, parser._parseFunction.bind(parser)); }); @@ -1648,11 +898,7 @@ suite("CSS - Parser", () => { assertNode("!important", parser, parser._parsePrio.bind(parser)); assertNode("!/*demo*/important", parser, parser._parsePrio.bind(parser)); assertNode("! /*demo*/ important", parser, parser._parsePrio.bind(parser)); - assertNode( - "! /*dem o*/ important", - parser, - parser._parsePrio.bind(parser), - ); + assertNode("! /*dem o*/ important", parser, parser._parsePrio.bind(parser)); }); test("hexcolor", function () { @@ -1681,64 +927,24 @@ suite("CSS - Parser", () => { assertNode("45,5px", parser, parser._parseExpr.bind(parser)); assertNode(" 45 , 5px ", parser, parser._parseExpr.bind(parser)); assertNode("5/6", parser, parser._parseExpr.bind(parser)); - assertNode( - "36mm, -webkit-calc(100%-10px)", - parser, - parser._parseExpr.bind(parser), - ); + assertNode("36mm, -webkit-calc(100%-10px)", parser, parser._parseExpr.bind(parser)); }); test("url", function () { const parser = new Parser(); - assertNode( - "url(//yourdomain/yourpath.png)", - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode( - "url('http://msft.com')", - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode( - 'url("http://msft.com")', - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode( - 'url( "http://msft.com")', - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode( - 'url(\t"http://msft.com")', - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode( - 'url(\n"http://msft.com")', - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode( - 'url("http://msft.com"\n)', - parser, - parser._parseURILiteral.bind(parser), - ); + assertNode("url(//yourdomain/yourpath.png)", parser, parser._parseURILiteral.bind(parser)); + assertNode("url('http://msft.com')", parser, parser._parseURILiteral.bind(parser)); + assertNode('url("http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url( "http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url(\t"http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url(\n"http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url("http://msft.com"\n)', parser, parser._parseURILiteral.bind(parser)); assertNode('url("")', parser, parser._parseURILiteral.bind(parser)); assertNode('uRL("")', parser, parser._parseURILiteral.bind(parser)); assertNode('URL("")', parser, parser._parseURILiteral.bind(parser)); - assertNode( - "url(http://msft.com)", - parser, - parser._parseURILiteral.bind(parser), - ); + assertNode("url(http://msft.com)", parser, parser._parseURILiteral.bind(parser)); assertNode("url()", parser, parser._parseURILiteral.bind(parser)); - assertNode( - "url('http://msft.com\n)", - parser, - parser._parseURILiteral.bind(parser), - ); + assertNode("url('http://msft.com\n)", parser, parser._parseURILiteral.bind(parser)); assertError( 'url("http://msft.com"', parser, diff --git a/packages/vscode-css-languageservice/src/test/scss/parser.test.ts b/packages/vscode-css-languageservice/src/test/scss/parser.test.ts new file mode 100644 index 00000000..c333c062 --- /dev/null +++ b/packages/vscode-css-languageservice/src/test/scss/parser.test.ts @@ -0,0 +1,1156 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +"use strict"; + +import { SCSSParser } from "../../parser/scssParser"; +import { ParseError } from "../../parser/cssErrors"; +import { SCSSParseError } from "../../parser/scssErrors"; + +import { assertNode, assertError } from "../css/parser.test"; + +suite("SCSS - Parser", () => { + test("Comments", function () { + const parser = new SCSSParser(); + assertNode(" a { b: /* comment */ c }", parser, parser._parseStylesheet.bind(parser)); + assertNode( + " a { b: /* comment \n * is several\n * lines long\n */ c }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(" a { b: // single line comment\n c }", parser, parser._parseStylesheet.bind(parser)); + }); + + test("Variable", function () { + const parser = new SCSSParser(); + assertNode("$color", parser, parser._parseVariable.bind(parser)); + assertNode("$co42lor", parser, parser._parseVariable.bind(parser)); + assertNode("$-co42lor", parser, parser._parseVariable.bind(parser)); + }); + + test("Module variable", function () { + const parser = new SCSSParser(); + assertNode("module.$color", parser, parser._parseModuleMember.bind(parser)); + assertNode("module.$co42lor", parser, parser._parseModuleMember.bind(parser)); + assertNode("module.$-co42lor", parser, parser._parseModuleMember.bind(parser)); + assertNode("module.function()", parser, parser._parseModuleMember.bind(parser)); + + assertError("module.", parser, parser._parseModuleMember.bind(parser), ParseError.IdentifierOrVariableExpected); + }); + + test("VariableDeclaration", function () { + const parser = new SCSSParser(); + assertNode("$color: #F5F5F5", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 0", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 255", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 25.5", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 25px", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 25.5px !default", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$text-color: green !global", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode( + '$_RESOURCES: append($_RESOURCES, "clean") !global', + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode("$footer-height: 40px !default !global", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode( + '$primary-font: "wf_SegoeUI","Segoe UI","Segoe","Segoe WP"', + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode("$color: red !important", parser, parser._parseVariableDeclaration.bind(parser)); + + assertError("$color: red !def", parser, parser._parseVariableDeclaration.bind(parser), ParseError.UnknownKeyword); + assertError( + "$color : !default", + parser, + parser._parseVariableDeclaration.bind(parser), + ParseError.VariableValueExpected, + ); + assertError("$color !default", parser, parser._parseVariableDeclaration.bind(parser), ParseError.ColonExpected); + }); + + test("Expr", function () { + const parser = new SCSSParser(); + assertNode("($const + 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($const - 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($const * 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($const / 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 - $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 * $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 / $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 / 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + $const + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($var1 + $var2)", parser, parser._parseExpr.bind(parser)); + assertNode("(($const + 5) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("(($const + (5 + 2)) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("($const + ((5 + 2) * 2))", parser, parser._parseExpr.bind(parser)); + assertNode("$color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, 42%", parser, parser._parseExpr.bind(parser)); + assertNode("$color, 42%, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color - ($color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("($base + $filler)", parser, parser._parseExpr.bind(parser)); + assertNode("(100% / 2 + $filler)", parser, parser._parseExpr.bind(parser)); + assertNode("100% / 2 + $filler", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and $b) or $c", parser, parser._parseExpr.bind(parser)); + + assertNode("(module.$const + 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const - 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const * 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const / 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 - module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 * module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 / module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + module.$const + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("($var1 + module.$var2)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$var1 + $var2)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$var1 + module.$var2)", parser, parser._parseExpr.bind(parser)); + assertNode("((module.$const + 5) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("((module.$const + (5 + 2)) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const + ((5 + 2) * 2))", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, 42%", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, 42%, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, 42%, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, 42%, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color - ($color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("$color - (module.$color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color - (module.$color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$base + $filler)", parser, parser._parseExpr.bind(parser)); + assertNode("($base + module.$filler)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$base + module.$filler)", parser, parser._parseExpr.bind(parser)); + assertNode("(100% / 2 + module.$filler)", parser, parser._parseExpr.bind(parser)); + assertNode("100% / 2 + module.$filler", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and $b) or $c", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and module.$b) or $c", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and $b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and module.$b) or $c", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and $b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and module.$b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and module.$b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not module.$v", parser, parser._parseExpr.bind(parser)); + + assertError("(20 + 20", parser, parser._parseExpr.bind(parser), ParseError.RightParenthesisExpected); + }); + + test("SCSSOperator", function () { + const parser = new SCSSParser(); + assertNode(">=", parser, parser._parseOperator.bind(parser)); + assertNode(">", parser, parser._parseOperator.bind(parser)); + assertNode("<", parser, parser._parseOperator.bind(parser)); + assertNode("<=", parser, parser._parseOperator.bind(parser)); + assertNode("==", parser, parser._parseOperator.bind(parser)); + assertNode("!=", parser, parser._parseOperator.bind(parser)); + assertNode("and", parser, parser._parseOperator.bind(parser)); + assertNode("+", parser, parser._parseOperator.bind(parser)); + assertNode("-", parser, parser._parseOperator.bind(parser)); + assertNode("*", parser, parser._parseOperator.bind(parser)); + assertNode("/", parser, parser._parseOperator.bind(parser)); + assertNode("%", parser, parser._parseOperator.bind(parser)); + assertNode("not", parser, parser._parseUnaryOperator.bind(parser)); + }); + + test("Interpolation", function () { + const parser = new SCSSParser(); + // assertNode('#{red}', parser, parser._parseIdent.bind(parser)); + // assertNode('#{$color}', parser, parser._parseIdent.bind(parser)); + // assertNode('#{3 + 4}', parser, parser._parseIdent.bind(parser)); + // assertNode('#{3 + #{3 + 4}}', parser, parser._parseIdent.bind(parser)); + // assertNode('#{$d}-style: 0', parser, parser._parseDeclaration.bind(parser)); + // assertNode('foo-#{$d}: 1', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{$d}-bar-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('foo-#{$d}-bar: 1', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('&:nth-child(#{$query}+1) { clear: $opposite-direction; }', parser, parser._parseRuleset.bind(parser)); + // assertNode('--#{$propname}: some-value', parser, parser._parseDeclaration.bind(parser)); + // assertNode('some-property: var(--#{$propname})', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{}', parser, parser._parseIdent.bind(parser)); + // assertError('#{1 + 2', parser, parser._parseIdent.bind(parser), ParseError.RightCurlyExpected); + + // assertNode('#{module.$color}', parser, parser._parseIdent.bind(parser)); + // assertNode('#{module.$d}-style: 0', parser, parser._parseDeclaration.bind(parser)); + // assertNode('foo-#{module.$d}: 1', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{module.$d}-bar-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{$d}-bar-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{module.$d}-bar-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('foo-#{module.$d}-bar: 1', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{module.$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{$d}-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{module.$d}-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('&:nth-child(#{module.$query}+1) { clear: $opposite-direction; }', parser, parser._parseRuleset.bind(parser)); + // assertNode('&:nth-child(#{$query}+1) { clear: module.$opposite-direction; }', parser, parser._parseRuleset.bind(parser)); + // assertNode('&:nth-child(#{module.$query}+1) { clear: module.$opposite-direction; }', parser, parser._parseRuleset.bind(parser)); + assertNode("--#{module.$propname}: some-value", parser, parser._parseDeclaration.bind(parser)); + // assertNode('some-property: var(--#{module.$propname})', parser, parser._parseDeclaration.bind(parser)); + // assertNode('@supports #{$val} { }', parser, parser._parseStylesheet.bind(parser)); // #88283 + // assertNode('.mb-#{$i}0np {} .push-up-#{$i}0 {} .mt-#{$i}0vh {}', parser, parser._parseStylesheet.bind(parser)); + }); + + test("Declaration", function () { + const parser = new SCSSParser(); + assertNode("border: thin solid 1px", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: $color", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: blue", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / $const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / 20 + $const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: func($red)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: func($red) !important", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: desaturate($red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: desaturate(16, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: $base-color + #111", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: 100% / 2 + $ref", parser, parser._parseDeclaration.bind(parser)); + assertNode("border: ($width * 2) solid black", parser, parser._parseDeclaration.bind(parser)); + assertNode("property: $class", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: fnc($t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("width: (1em + 2em) * 3", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: #010203 + #040506", parser, parser._parseDeclaration.bind(parser)); + assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser)); + assertNode("margin: 3px + 4px auto", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: hsl(0, 100%, 50%)", parser, parser._parseDeclaration.bind(parser)); + assertNode( + "color: hsl($hue: 0, $saturation: 100%, $lightness: 50%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode("foo: if($value == 'default', flex-gutter(), $value)", parser, parser._parseDeclaration.bind(parser)); + assertNode("foo: if(true, !important, null)", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: selector-replace(&, 1)", parser, parser._parseDeclaration.bind(parser)); + + assertNode("dummy: module.$color", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / module.$const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / 20 + module.$const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.func($red)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.func($red) !important", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.desaturate($red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: desaturate(module.$red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.desaturate(module.$red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.desaturate(16, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: module.$base-color + #111", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: 100% / 2 + module.$ref", parser, parser._parseDeclaration.bind(parser)); + assertNode("border: (module.$width * 2) solid black", parser, parser._parseDeclaration.bind(parser)); + assertNode("property: module.$class", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: module.fnc($t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: fnc(module.$t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: module.fnc(module.$t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("width: (1em + 2em) * 3", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: #010203 + #040506", parser, parser._parseDeclaration.bind(parser)); + assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser)); + assertNode("margin: 3px + 4px auto", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: color.hsl(0, 100%, 50%)", parser, parser._parseDeclaration.bind(parser)); + assertNode( + "color: color.hsl($hue: 0, $saturation: 100%, $lightness: 50%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if(module.$value == 'default', flex-gutter(), $value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if($value == 'default', module.flex-gutter(), $value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if($value == 'default', flex-gutter(), module.$value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if(module.$value == 'default', module.flex-gutter(), $value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if($value == 'default', module.flex-gutter(), module.$value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if(module.$value == 'default', module.flex-gutter(), module.$value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode("color: selector.replace(&, 1)", parser, parser._parseDeclaration.bind(parser)); + + assertError("fo = 8", parser, parser._parseDeclaration.bind(parser), ParseError.ColonExpected); + assertError("fo:", parser, parser._parseDeclaration.bind(parser), ParseError.PropertyValueExpected); + assertError("color: hsl($hue: 0,", parser, parser._parseDeclaration.bind(parser), ParseError.ExpressionExpected); + assertError( + "color: hsl($hue: 0", + parser, + parser._parseDeclaration.bind(parser), + ParseError.RightParenthesisExpected, + ); + }); + + test("Stylesheet", function () { + const parser = new SCSSParser(); + assertNode("$color: #F5F5F5;", parser, parser._parseStylesheet.bind(parser)); + assertNode("$color: #F5F5F5; $color: #F5F5F5;", parser, parser._parseStylesheet.bind(parser)); + assertNode("$color: #F5F5F5; $color: #F5F5F5; $color: #F5F5F5;", parser, parser._parseStylesheet.bind(parser)); + assertNode("$color: #F5F5F5 !important;", parser, parser._parseStylesheet.bind(parser)); + assertNode( + "#main { width: 97%; p, div { a { font-weight: bold; } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode("a { &:hover { color: red; } }", parser, parser._parseStylesheet.bind(parser)); + assertNode("fo { font: 2px/3px { family: fantasy; } }", parser, parser._parseStylesheet.bind(parser)); + assertNode(".foo { bar: { yoo: fantasy; } }", parser, parser._parseStylesheet.bind(parser)); + assertNode( + "selector { propsuffix: { nested: 1px; } rule: 1px; nested.selector { foo: 1; } nested:selector { foo: 2 }}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:s(1)}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode("@mixin keyframe { @keyframes name { @content; } }", parser, parser._parseStylesheet.bind(parser)); + assertNode("@include keyframe { 10% { top: 3px; } }", parser, parser._parseStylesheet.bind(parser)); + assertNode(".class{&--sub-class-with-ampersand{color: red;}}", parser, parser._parseStylesheet.bind(parser)); + assertError( + "fo { font: 2px/3px { family } }", + parser, + parser._parseStylesheet.bind(parser), + ParseError.ColonExpected, + ); + + assertNode( + "legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:m.s(1)}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode("@include module.keyframe { 10% { top: 3px; } }", parser, parser._parseStylesheet.bind(parser)); + }); + + test("@import", function () { + const parser = new SCSSParser(); + assertNode('@import "test.css"', parser, parser._parseImport.bind(parser)); + assertNode('@import url("test.css")', parser, parser._parseImport.bind(parser)); + assertNode('@import "test.css", "bar.css"', parser, parser._parseImport.bind(parser)); + assertNode('@import "test.css", "bar.css" screen, projection', parser, parser._parseImport.bind(parser)); + assertNode('foo { @import "test.css"; }', parser, parser._parseStylesheet.bind(parser)); + + assertError( + '@import "test.css" "bar.css"', + parser, + parser._parseStylesheet.bind(parser), + ParseError.MediaQueryExpected, + ); + assertError('@import "test.css", screen', parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); + assertError("@import", parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); + assertNode('@import url("override.css") layer;', parser, parser._parseStylesheet.bind(parser)); + }); + + test("@layer", function () { + const parser = new SCSSParser(); + assertNode("@layer #{$layer} { }", parser, parser._parseLayer.bind(parser)); + }); + + test("@container", function () { + const parser = new SCSSParser(); + assertNode( + `@container (min-width: #{$minWidth}) { .scss-interpolation { line-height: 10cqh; } }`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.item-icon { @container (max-height: 100px) { .item-icon { display: none; } } }`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `:root { @container (max-height: 100px) { display: none;} }`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@use", function () { + const parser = new SCSSParser(); + assertNode('@use "test"', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" as foo', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" as *', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" with ($foo: "test", $bar: 1)', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" as foo with ($foo: "test", $bar: 1)', parser, parser._parseUse.bind(parser)); + + assertError("@use", parser, parser._parseUse.bind(parser), ParseError.StringLiteralExpected); + assertError('@use "test" foo', parser, parser._parseUse.bind(parser), ParseError.UnknownKeyword); + assertError('@use "test" as', parser, parser._parseUse.bind(parser), ParseError.IdentifierOrWildcardExpected); + assertError('@use "test" with', parser, parser._parseUse.bind(parser), ParseError.LeftParenthesisExpected); + assertError('@use "test" with ($foo)', parser, parser._parseUse.bind(parser), ParseError.VariableValueExpected); + assertError('@use "test" with ("bar")', parser, parser._parseUse.bind(parser), ParseError.VariableNameExpected); + assertError( + '@use "test" with ($foo: 1, "bar")', + parser, + parser._parseUse.bind(parser), + ParseError.VariableNameExpected, + ); + assertError( + '@use "test" with ($foo: "bar"', + parser, + parser._parseUse.bind(parser), + ParseError.RightParenthesisExpected, + ); + + assertNode('@forward "test"; @use "lib"', parser, parser._parseStylesheet.bind(parser)); + assertNode('@use "test"; @use "lib"', parser, parser._parseStylesheet.bind(parser)); + assertNode('$test: "test"; @use "lib"', parser, parser._parseStylesheet.bind(parser)); + }); + + test("@forward", function () { + const parser = new SCSSParser(); + assertNode('@forward "test"', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" as foo-*', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide this', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide this $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide this, $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "abstracts/functions" show px-to-rem, theme-color', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" show this', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" show $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" show this $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" as foo-* show this $that', parser, parser._parseForward.bind(parser)); + + assertError("@forward", parser, parser._parseForward.bind(parser), ParseError.StringLiteralExpected); + assertError('@forward "test" foo', parser, parser._parseForward.bind(parser), ParseError.SemiColonExpected); + assertError('@forward "test" as', parser, parser._parseForward.bind(parser), ParseError.IdentifierExpected); + assertError('@forward "test" as foo-', parser, parser._parseForward.bind(parser), ParseError.WildcardExpected); + assertError('@forward "test" as foo- *', parser, parser._parseForward.bind(parser), ParseError.WildcardExpected); + assertError( + '@forward "test" show', + parser, + parser._parseForward.bind(parser), + ParseError.IdentifierOrVariableExpected, + ); + assertError( + '@forward "test" hide', + parser, + parser._parseForward.bind(parser), + ParseError.IdentifierOrVariableExpected, + ); + + assertNode( + '@forward "test" with ( $black: #222 !default, $border-radius: 0.1rem !default )', + parser, + parser._parseForward.bind(parser), + ); + assertNode( + '@forward "../forms.scss" as components-* with ( $field-border: false )', + parser, + parser._parseForward.bind(parser), + ); // #145108 + + assertNode('@use "lib"; @forward "test"', parser, parser._parseStylesheet.bind(parser)); + assertNode('@forward "test"; @forward "lib"', parser, parser._parseStylesheet.bind(parser)); + assertNode('$test: "test"; @forward "test"', parser, parser._parseStylesheet.bind(parser)); + }); + + test("@media", function () { + const parser = new SCSSParser(); + assertNode( + "@media screen { .sidebar { @media (orientation: landscape) { width: 500px; } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode("@media #{$media} and ($feature: $value) {}", parser, parser._parseStylesheet.bind(parser)); + assertNode("@media only screen and #{$query} {}", parser, parser._parseStylesheet.bind(parser)); + assertNode( + "foo { bar { @media screen and (orientation: landscape) {}} }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode("@media screen and (nth($query, 1): nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); + assertNode( + ".something { @media (max-width: 760px) { > .test { color: blue; } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".something { @media (max-width: 760px) { ~ div { display: block; } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ".something { @media (max-width: 760px) { + div { display: block; } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode("@media (max-width: 760px) { + div { display: block; } }", parser, parser._parseStylesheet.bind(parser)); + assertNode("@media (height <= 600px) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media (height >= 600px) { }", parser, parser._parseMedia.bind(parser)); + + assertNode("@media #{layout.$media} and ($feature: $value) {}", parser, parser._parseStylesheet.bind(parser)); + assertNode("@media #{$media} and (layout.$feature: $value) {}", parser, parser._parseStylesheet.bind(parser)); + assertNode("@media #{$media} and ($feature: layout.$value) {}", parser, parser._parseStylesheet.bind(parser)); + assertNode( + "@media #{layout.$media} and (layout.$feature: $value) {}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media #{$media} and (layout.$feature: layout.$value) {}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media #{layout.$media} and (layout.$feature: layout.$value) {}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode("@media screen and (list.nth($query, 1): nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media screen and (nth(list.$query, 1): nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media screen and (nth($query, 1): list.nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media screen and (nth($query, 1): nth(list.$query, 2)) { }", parser, parser._parseMedia.bind(parser)); + assertNode( + "@media screen and (list.nth(list.$query, 1): nth($query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (list.nth($query, 1): list.nth($query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (list.nth($query, 1): nth(list.$query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (nth(list.$query, 1): list.nth($query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (nth(list.$query, 1): nth(list.$query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (nth($query, 1): list.nth(list.$query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (list.nth(list.$query, 1): list.nth($query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (nth(list.$query, 1): list.nth(list.$query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + "@media screen and (list.nth(list.$query, 1): list.nth(list.$query, 2)) { }", + parser, + parser._parseMedia.bind(parser), + ); + }); + + test("@keyframe", function () { + const parser = new SCSSParser(); + assertNode("@keyframes name { @content; }", parser, parser._parseKeyframe.bind(parser)); + assertNode( + "@keyframes name { @for $i from 0 through $steps { #{$i * (100%/$steps)} { transform: $rotate $translate; } } }", + parser, + parser._parseKeyframe.bind(parser), + ); // issue 42086 + assertNode( + '@keyframes test-keyframe { @for $i from 1 through 60 { $s: ($i * 100) / 60 + "%"; } }', + parser, + parser._parseKeyframe.bind(parser), + ); + + assertNode( + "@keyframes name { @for $i from 0 through m.$steps { #{$i * (100%/$steps)} { transform: $rotate $translate; } } }", + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode("@keyframes name { @function bar() { } }", parser, parser._parseKeyframe.bind(parser)); // #197742 + assertNode("@keyframes name { @include keyframe-mixin(); }", parser, parser._parseKeyframe.bind(parser)); // #197742 + }); + + test("@extend", function () { + const parser = new SCSSParser(); + assertNode(".themable { @extend %theme; }", parser, parser._parseStylesheet.bind(parser)); + assertNode("foo { @extend .error; border-width: 3px; }", parser, parser._parseStylesheet.bind(parser)); + assertNode("a.important { @extend .notice !optional; }", parser, parser._parseStylesheet.bind(parser)); + assertNode(".hoverlink { @extend a:hover; }", parser, parser._parseStylesheet.bind(parser)); + assertNode(".seriousError { @extend .error; @extend .attention; }", parser, parser._parseStylesheet.bind(parser)); + assertNode( + "#context a%extreme { color: blue; } .notice { @extend %extreme }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media print { .error { } .seriousError { @extend .error; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@mixin error($a: false) { @extend .#{$a}; @extend ##{$a}; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(".foo { @extend .text-center, .uppercase; }", parser, parser._parseStylesheet.bind(parser)); + assertNode(".foo { @extend .text-center, .uppercase, ; }", parser, parser._parseStylesheet.bind(parser)); + assertNode(".foo { @extend .text-center, .uppercase !optional ; }", parser, parser._parseStylesheet.bind(parser)); + assertError(".hoverlink { @extend }", parser, parser._parseStylesheet.bind(parser), ParseError.SelectorExpected); + assertError( + ".hoverlink { @extend %extreme !default }", + parser, + parser._parseStylesheet.bind(parser), + ParseError.UnknownKeyword, + ); + }); + + test("@debug", function () { + const parser = new SCSSParser(); + assertNode("@debug test;", parser, parser._parseStylesheet.bind(parser)); + assertNode("foo { @debug 1 + 4; nested { @warn 1 4; } }", parser, parser._parseStylesheet.bind(parser)); + assertNode("@if $foo == 1 { @debug 1 + 4 }", parser, parser._parseStylesheet.bind(parser)); + assertNode( + '@function setStyle($map, $object, $style) { @warn "The key ´#{$object} is not available in the map."; @return null; }', + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@if", function () { + const parser = new SCSSParser(); + assertNode("@if 1 + 1 == 2 { border: 1px solid; }", parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode("@if 5 < 3 { border: 2px dotted; }", parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode("@if null { border: 3px double; }", parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode( + "@if 1 <= $const { border: 3px; } @else { border: 4px; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "@if 1 >= (1 + $foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "p { @if $i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@if (index($_RESOURCES, "clean") != null) { @error "sdssd"; }', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode("@if $i == 1 { p { x: 3px; } }", parser, parser._parseStylesheet.bind(parser)); + assertError( + "@if { border: 1px solid; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.ExpressionExpected, + ); + assertError("@if 1 }", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.LeftCurlyExpected); + + assertNode( + "@if 1 <= m.$const { border: 3px; } @else { border: 4px; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "@if 1 >= (1 + m.$foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + "p { @if m.$i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "p { @if $i == 1 { x: 3px; } @else if m.$i == 1 { x: 4px; } @else { x: 4px; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "p { @if m.$i == 1 { x: 3px; } @else if m.$i == 1 { x: 4px; } @else { x: 4px; } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@if (list.index($_RESOURCES, "clean") != null) { @error "sdssd"; }', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@if (index(m.$_RESOURCES, "clean") != null) { @error "sdssd"; }', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@if (list.index(m.$_RESOURCES, "clean") != null) { @error "sdssd"; }', + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@for", function () { + const parser = new SCSSParser(); + assertNode( + "@for $i from 1 to 5 { .item-#{$i} { width: 2em * $i; } }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode("@for $k from 1 + $x through 5 + $x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); + assertError( + "@for i from 0 to 4 {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.VariableNameExpected, + ); + assertError("@for $i to 4 {}", parser, parser._parseRuleSetDeclaration.bind(parser), SCSSParseError.FromExpected); + assertError( + "@for $i from 0 by 4 {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + SCSSParseError.ThroughOrToExpected, + ); + assertError("@for $i from {}", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); + assertError( + "@for $i from 0 to {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.ExpressionExpected, + ); + assertNode('@for $i from 1 through 60 { $s: $i + "%"; }', parser, parser._parseRuleSetDeclaration.bind(parser)); + + assertNode("@for $k from 1 + m.$x through 5 + $x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode("@for $k from 1 + $x through 5 + m.$x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode("@for $k from 1 + m.$x through 5 + m.$x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); + }); + + test("@each", function () { + const parser = new SCSSParser(); + assertNode("@each $i in 1, 2, 3 { }", parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode("@each $i in 1 2 3 { }", parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode( + "@each $animal, $color, $cursor in (puma, black, default), (egret, white, move) {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertError( + "@each i in 4 {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.VariableNameExpected, + ); + assertError("@each $i from 4 {}", parser, parser._parseRuleSetDeclaration.bind(parser), SCSSParseError.InExpected); + assertError("@each $i in {}", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); + assertError( + "@each $animal, in (1, 1, 1), (2, 2, 2) {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.VariableNameExpected, + ); + }); + + test("@while", function () { + const parser = new SCSSParser(); + assertNode( + "@while $i < 0 { .item-#{$i} { width: 2em * $i; } $i: $i - 2; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertError("@while {}", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); + assertError("@while $i != 4", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.LeftCurlyExpected); + assertError( + "@while ($i >= 4) {", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.RightCurlyExpected, + ); + }); + + test("@mixin", function () { + const parser = new SCSSParser(); + assertNode( + "@mixin large-text { font: { family: Arial; size: 20px; } color: #ff0000; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@mixin sexy-border($color, $width: 1in) { color: black; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@mixin box-shadow($shadows...) { -moz-box-shadow: $shadows; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode("@mixin apply-to-ie6-only { * html { @content; } }", parser, parser._parseStylesheet.bind(parser)); + assertNode("@mixin #{foo}($color){}", parser, parser._parseStylesheet.bind(parser)); + assertNode("@mixin foo ($i:4) { size: $i; @include wee ($i - 1); }", parser, parser._parseStylesheet.bind(parser)); + assertNode("@mixin foo ($i,) { }", parser, parser._parseStylesheet.bind(parser)); + + assertError("@mixin $1 {}", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); + assertError("@mixin foo() i {}", parser, parser._parseStylesheet.bind(parser), ParseError.LeftCurlyExpected); + assertError("@mixin foo(1) {}", parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected); + assertError( + "@mixin foo($color = 9) {}", + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError("@mixin foo($color)", parser, parser._parseStylesheet.bind(parser), ParseError.LeftCurlyExpected); + assertError("@mixin foo($color){", parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected); + assertError("@mixin foo($color,){", parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected); + }); + + test("@content", function () { + const parser = new SCSSParser(); + assertNode("@content", parser, parser._parseMixinContent.bind(parser)); + assertNode("@content($type)", parser, parser._parseMixinContent.bind(parser)); + }); + + test("@include", function () { + const parser = new SCSSParser(); + assertNode("p { @include sexy-border(blue); }", parser, parser._parseStylesheet.bind(parser)); + assertNode( + ".shadows { @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "$values: #ff0000, #00ff00, #0000ff; .primary { @include colors($values...); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode('@include colors(this("styles")...);', parser, parser._parseStylesheet.bind(parser)); + assertNode(".test { @include fontsize(16px, 21px !important); }", parser, parser._parseStylesheet.bind(parser)); + assertNode( + "p { @include apply-to-ie6-only { #logo { background-image: url(/logo.gif); } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode("p { @include foo($values,) }", parser, parser._parseStylesheet.bind(parser)); + assertNode("p { @include foo($values,); }", parser, parser._parseStylesheet.bind(parser)); + + assertError( + "p { @include sexy-border blue", + parser, + parser._parseStylesheet.bind(parser), + ParseError.SemiColonExpected, + ); + assertError( + "p { @include sexy-border($values blue", + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError("p { @include }", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); + assertError( + "p { @include foo($values }", + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + "p { @include foo($values, }", + parser, + parser._parseStylesheet.bind(parser), + ParseError.ExpressionExpected, + ); + + assertNode("p { @include lib.sexy-border(blue); }", parser, parser._parseStylesheet.bind(parser)); + assertNode( + ".shadows { @include lib.box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "$values: #ff0000, #00ff00, #0000ff; .primary { @include lib.colors($values...); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(".primary { @include colors(lib.$values...); }", parser, parser._parseStylesheet.bind(parser)); + assertNode(".primary { @include lib.colors(lib.$values...); }", parser, parser._parseStylesheet.bind(parser)); + assertNode('@include lib.colors(this("styles")...);', parser, parser._parseStylesheet.bind(parser)); + assertNode('@include colors(lib.this("styles")...);', parser, parser._parseStylesheet.bind(parser)); + assertNode('@include lib.colors(lib.this("styles")...);', parser, parser._parseStylesheet.bind(parser)); + assertNode(".test { @include lib.fontsize(16px, 21px !important); }", parser, parser._parseStylesheet.bind(parser)); + assertNode( + "p { @include lib.apply-to-ie6-only { #logo { background-image: url(/logo.gif); } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode("p { @include lib.foo($values,) }", parser, parser._parseStylesheet.bind(parser)); + assertNode("p { @include foo(lib.$values,) }", parser, parser._parseStylesheet.bind(parser)); + assertNode("p { @include lib.foo(m.$values,); }", parser, parser._parseStylesheet.bind(parser)); + + assertError( + "p { @include foo.($values) }", + parser, + parser._parseStylesheet.bind(parser), + ParseError.IdentifierExpected, + ); + + assertNode( + '@include rtl("left") using ($dir) { margin-#{$dir}: 10px; }', + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@function", function () { + const parser = new SCSSParser(); + assertNode( + "@function grid-width($n) { @return $n * $grid-width + ($n - 1) * $gutter-width; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode("@function grid-width($n: 1, $e) { @return 0; }", parser, parser._parseStylesheet.bind(parser)); + assertNode( + "@function foo($total, $a) { @for $i from 0 to $total { } @return $grid; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + '@function foo() { @if (unit($a) == "%") and ($i == ($total - 1)) { @return 0; } @return 1; }', + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@function is-even($int) { @if $int%2 == 0 { @return true; } @return false }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@function bar ($i) { @if $i > 0 { @return $i * bar($i - 1); } @return 1; }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode("@function foo($a,) {} ", parser, parser._parseStylesheet.bind(parser)); + + assertError("@function foo {} ", parser, parser._parseStylesheet.bind(parser), ParseError.LeftParenthesisExpected); + assertError("@function {} ", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); + assertError( + "@function foo($a $b) {} ", + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + "@function foo($a {} ", + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + "@function foo($a...) { @return; }", + parser, + parser._parseStylesheet.bind(parser), + ParseError.ExpressionExpected, + ); + assertError( + "@function foo($a:) {} ", + parser, + parser._parseStylesheet.bind(parser), + ParseError.VariableValueExpected, + ); + }); + + test("@at-root", function () { + const parser = new SCSSParser(); + assertNode( + "@mixin unify-parent($child) { @at-root #{selector.unify(&, $child)} { }}", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@at-root #main2 .some-class { padding-left: calc( #{$a-variable} + 8px ); }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + "@media print { .page { @at-root (without: media) { } } }", + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode("@media print { .page { @at-root (with: rule) { } } }", parser, parser._parseStylesheet.bind(parser)); + }); + + test("Ruleset", function () { + const parser = new SCSSParser(); + assertNode(".selector { prop: erty $const 1px; }", parser, parser._parseRuleset.bind(parser)); + assertNode(".selector { prop: erty $const 1px m.$foo; }", parser, parser._parseRuleset.bind(parser)); + assertNode("selector:active { property:value; nested:hover {}}", parser, parser._parseRuleset.bind(parser)); + assertNode("selector {}", parser, parser._parseRuleset.bind(parser)); + assertNode("selector { property: declaration }", parser, parser._parseRuleset.bind(parser)); + assertNode("selector { $variable: declaration }", parser, parser._parseRuleset.bind(parser)); + assertNode("selector { nested {}}", parser, parser._parseRuleset.bind(parser)); + assertNode("selector { nested, a, b {}}", parser, parser._parseRuleset.bind(parser)); + assertNode("selector { property: value; property: $value; }", parser, parser._parseRuleset.bind(parser)); + assertNode( + "selector { property: value; @keyframes foo {} @-moz-keyframes foo {}}", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode("foo|bar { }", parser, parser._parseRuleset.bind(parser)); + }); + + test("Nested Ruleset", function () { + const parser = new SCSSParser(); + assertNode( + ".class1 { $const: 1; .class { $const: 2; three: $const; const: 3; } one: $const; }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".class1 { $const: 1; .class { $const: m.$foo; } one: $const; }", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode(".class1 { > .class2 { & > .class4 { rule1: v1; } } }", parser, parser._parseRuleset.bind(parser)); + assertNode("foo { @at-root { display: none; } }", parser, parser._parseRuleset.bind(parser)); + assertNode( + 'th, tr { @at-root #{selector-replace(&, "tr")} { border-bottom: 0; } }', + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ".foo { @supports(display: grid) { .bar { display: none; }}}", + parser, + parser._parseRuleset.bind(parser), + ); + assertNode(".foo { @supports(display: grid) { display: none; }}", parser, parser._parseRuleset.bind(parser)); + assertNode( + ".foo { @supports (position: sticky) { @media (min-width: map-get($grid-breakpoints, md)) { position: sticky; } }}", + parser, + parser._parseRuleset.bind(parser), + ); // issue #152 + }); + + test("Selector Interpolation", function () { + const parser = new SCSSParser(); + assertNode(".#{$name} { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".#{$name}-foo { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".#{$name}-foo-3 { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".#{$name}-1 { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".sc-col#{$postfix}-2-1 { }", parser, parser._parseRuleset.bind(parser)); + assertNode("p.#{$name} { #{$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); + assertNode("sans-#{serif} { a-#{1 + 2}-color-#{$attr}: blue; }", parser, parser._parseRuleset.bind(parser)); + assertNode("##{f} .#{f} #{f}:#{f} { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".foo-#{&} .foo-#{&-sub} { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".-#{$variable} { }", parser, parser._parseRuleset.bind(parser)); + assertNode("#{&}([foo=bar][bar=foo]) { }", parser, parser._parseRuleset.bind(parser)); // #49589 + + assertNode(".#{module.$name} { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".#{module.$name}-foo { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".#{module.$name}-foo-3 { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".#{module.$name}-1 { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".sc-col#{module.$postfix}-2-1 { }", parser, parser._parseRuleset.bind(parser)); + assertNode("p.#{module.$name} { #{$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); + assertNode("p.#{$name} { #{module.$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); + assertNode("p.#{module.$name} { #{module.$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); + assertNode("sans-#{serif} { a-#{1 + 2}-color-#{module.$attr}: blue; }", parser, parser._parseRuleset.bind(parser)); + assertNode(".-#{module.$variable} { }", parser, parser._parseRuleset.bind(parser)); + }); + + test("Parent Selector", function () { + const parser = new SCSSParser(); + assertNode("&:hover", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&.float", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-bar", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-1", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&1", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-foo-1", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&&", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-10-thing", parser, parser._parseSimpleSelector.bind(parser)); + }); + + test("Selector Placeholder", function () { + const parser = new SCSSParser(); + assertNode("%hover", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("a%float", parser, parser._parseSimpleSelector.bind(parser)); + }); + + test("Map", function () { + const parser = new SCSSParser(); + assertNode("(key1: 1px, key2: solid + px, key3: (2+3))", parser, parser._parseExpr.bind(parser)); + assertNode("($key1 + 3: 1px)", parser, parser._parseExpr.bind(parser)); + }); + + test("Url", function () { + const parser = new SCSSParser(); + assertNode("url(foo())", parser, parser._parseURILiteral.bind(parser)); + assertNode( + "url('data:image/svg+xml;utf8,%3Csvg%20fill%3D%22%23' + $color + 'foo')", + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode("url(//yourdomain/yourpath.png)", parser, parser._parseURILiteral.bind(parser)); + assertNode("url('http://msft.com')", parser, parser._parseURILiteral.bind(parser)); + assertNode('url("http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url( "http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url(\t"http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url(\n"http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url("http://msft.com"\n)', parser, parser._parseURILiteral.bind(parser)); + assertNode('url("")', parser, parser._parseURILiteral.bind(parser)); + assertNode('uRL("")', parser, parser._parseURILiteral.bind(parser)); + assertNode('URL("")', parser, parser._parseURILiteral.bind(parser)); + assertNode("url(http://msft.com)", parser, parser._parseURILiteral.bind(parser)); + assertNode("url()", parser, parser._parseURILiteral.bind(parser)); + assertNode("url('http://msft.com\n)", parser, parser._parseURILiteral.bind(parser)); + assertError( + 'url("http://msft.com"', + parser, + parser._parseURILiteral.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + "url(http://msft.com')", + parser, + parser._parseURILiteral.bind(parser), + ParseError.RightParenthesisExpected, + ); + }); + + test("@font-face", function () { + const parser = new SCSSParser(); + assertNode("@font-face {}", parser, parser._parseFontFace.bind(parser)); + assertNode("@font-face { src: url(http://test) }", parser, parser._parseFontFace.bind(parser)); + assertNode("@font-face { font-style: normal; font-stretch: normal; }", parser, parser._parseFontFace.bind(parser)); + assertNode( + "@font-face { unicode-range: U+0021-007F, u+1f49C, U+4??, U+??????; }", + parser, + parser._parseFontFace.bind(parser), + ); + assertError( + "@font-face { font-style: normal font-stretch: normal; }", + parser, + parser._parseFontFace.bind(parser), + ParseError.SemiColonExpected, + ); + }); +}); diff --git a/vitest.workspace.js b/vitest.workspace.js deleted file mode 100644 index 976b4948..00000000 --- a/vitest.workspace.js +++ /dev/null @@ -1,8 +0,0 @@ -import { defineWorkspace } from "vitest/config"; - -export default defineWorkspace([ - "./packages/language-services/vitest.config.mts", - "./packages/language-server/vitest.config.mts", - "./packages/parser/vitest.config.mts", - "./packages/language-facts/vitest.config.mts", -]); From 2c6e6c109e6a7825a71b47dd6610e08a50b18f5e Mon Sep 17 00:00:00 2001 From: William Killerud Date: Mon, 13 May 2024 21:53:01 +0200 Subject: [PATCH 003/138] refactor: use vitest across the board --- docs/src/contributing/automated-tests.md | 3 +- docs/src/contributing/debugging-unit-tests.md | 2 +- package-lock.json | 1227 +++-------------- .../vscode-css-languageservice/.editorconfig | 5 - .../vscode-css-languageservice/.mocharc.json | 6 - .../vscode-css-languageservice/.npmignore | 19 - .../vscode-css-languageservice/package.json | 11 +- .../src/test/css/codeActions.test.ts | 2 +- .../src/test/css/completion.test.ts | 4 +- .../src/test/css/customData.test.ts | 3 +- .../src/test/css/folding.test.ts | 2 +- .../src/test/css/hover.test.ts | 2 +- .../src/test/css/languageFacts.test.ts | 2 +- .../src/test/css/lint.test.ts | 2 +- .../src/test/css/navigation.test.ts | 4 +- .../src/test/css/nodes.test.ts | 2 +- .../src/test/css/parser.test.ts | 2 +- .../src/test/css/scanner.test.ts | 2 +- .../src/test/css/selectionRange.test.ts | 3 +- .../src/test/css/selectorPrinting.test.ts | 9 +- .../src/test/scss/languageFacts.test.ts | 1 + .../src/test/scss/lint.test.ts | 1 + .../src/test/scss/parser.test.ts | 1 + .../src/test/scss/scssCompletion.test.ts | 3 +- .../src/test/scss/scssNavigation.test.ts | 11 +- .../src/test/scss/selectorPrinting.test.ts | 2 +- .../src/test/util.test.ts | 2 +- .../vitest.config.mts | 12 + 28 files changed, 230 insertions(+), 1115 deletions(-) delete mode 100644 packages/vscode-css-languageservice/.editorconfig delete mode 100644 packages/vscode-css-languageservice/.mocharc.json delete mode 100644 packages/vscode-css-languageservice/.npmignore create mode 100644 packages/vscode-css-languageservice/vitest.config.mts diff --git a/docs/src/contributing/automated-tests.md b/docs/src/contributing/automated-tests.md index 02c5178b..89e28c54 100644 --- a/docs/src/contributing/automated-tests.md +++ b/docs/src/contributing/automated-tests.md @@ -10,7 +10,7 @@ All packages in `packages/` have unit tests. To run them: npm run test ``` -The main test runner is [Vitest]. `vscode-css-languageservice` uses [Mocha]. +The est runner is [Vitest]. Unit tests typically cover either a utility function or a language feature such as `doHover`. For language features the tests are typically split in several files, each focusing on part of the functionality of the language feature. @@ -39,4 +39,3 @@ npm run test:all ``` [Vitest]: https://vitest.dev/ -[Mocha]: https://mochajs.org/ diff --git a/docs/src/contributing/debugging-unit-tests.md b/docs/src/contributing/debugging-unit-tests.md index 06af6cab..7890c5aa 100644 --- a/docs/src/contributing/debugging-unit-tests.md +++ b/docs/src/contributing/debugging-unit-tests.md @@ -2,7 +2,7 @@ This document assumes you use Visual Studio Code and have the [Vitest](https://marketplace.visualstudio.com/items?itemName=vitest.explorer) extension. -Open a unit test file (excluding tests in `vscode-css-languageservice`, which use Mocha) and find the test you want to debug. +Open a unit test file and find the test you want to debug. You should see an icon in the gutter. To debug the test, right click and select Debug test. diff --git a/package-lock.json b/package-lock.json index e46e3066..d61d2453 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3097,105 +3097,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -5957,19 +5858,6 @@ "node": ">= 14" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "8.13.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", @@ -6082,24 +5970,6 @@ "node": ">= 8" } }, - "node_modules/append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "dependencies": { - "default-require-extensions": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true - }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -6870,45 +6740,6 @@ "node": ">= 6.0.0" } }, - "node_modules/caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, - "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/caching-transform/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caching-transform/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -6937,15 +6768,6 @@ "node": ">=6" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -7169,15 +6991,6 @@ "node": ">=8" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/cli-cursor": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", @@ -7440,12 +7253,6 @@ "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", "dev": true }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, "node_modules/compare-func": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", @@ -8161,15 +7968,6 @@ } } }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -8336,21 +8134,6 @@ "node": ">=6" } }, - "node_modules/default-require-extensions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", - "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", - "dev": true, - "dependencies": { - "strip-bom": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -8924,12 +8707,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, "node_modules/esbuild": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", @@ -10111,25 +9888,6 @@ "is-callable": "^1.1.3" } }, - "node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, "node_modules/fork-ts-checker-webpack-plugin": { "version": "7.2.13", "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-7.2.13.tgz", @@ -10380,26 +10138,6 @@ "node": ">= 0.6" } }, - "node_modules/fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -10540,15 +10278,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", @@ -10835,34 +10564,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hasha/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -11447,15 +11148,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -11929,12 +11621,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -11965,15 +11651,6 @@ "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", "dev": true }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -12016,128 +11693,18 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "dependencies": { - "append-transform": "^2.0.0" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", - "dev": true, - "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "node": ">=10" } }, "node_modules/istanbul-lib-report/node_modules/make-dir": { @@ -12167,20 +11734,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-reports": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", @@ -13346,12 +12899,6 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "dev": true - }, "node_modules/lodash.identity": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-2.4.1.tgz", @@ -14465,18 +14012,6 @@ "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", "dev": true }, - "node_modules/node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "dependencies": { - "process-on-spawn": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -14756,358 +14291,6 @@ "node": ">=8" } }, - "node_modules/nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, - "dependencies": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "bin": { - "nyc": "bin/nyc.js" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/nyc/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/nyc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/nyc/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/nyc/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/nyc/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/nyc/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/nyc/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/nyc/node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/nyc/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nyc/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nyc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/nyc/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nyc/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nyc/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/nyc/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/nyc/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/nyc/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -15516,18 +14699,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/p-retry": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", @@ -15550,21 +14721,6 @@ "node": ">=6" } }, - "node_modules/package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", @@ -16859,18 +16015,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, - "node_modules/process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, - "dependencies": { - "fromentries": "^1.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -17227,18 +16371,6 @@ "jsesc": "bin/jsesc" } }, - "node_modules/release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", - "dev": true, - "dependencies": { - "es6-error": "^4.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -17257,12 +16389,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -17923,12 +17049,6 @@ "node": ">= 0.8.0" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -18278,110 +17398,6 @@ "source-map": "^0.6.0" } }, - "node_modules/spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, - "dependencies": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/spawn-wrap/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/spawn-wrap/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/spawn-wrap/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/spawn-wrap/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/spawn-wrap/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/spawn-wrap/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/spawn-wrap/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", @@ -18657,15 +17673,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-final-newline": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", @@ -19597,15 +18604,6 @@ "node": ">=4" } }, - "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -19709,15 +18707,6 @@ "underscore": "^1.12.1" } }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, "node_modules/typescript": { "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", @@ -20680,12 +19669,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "dev": true - }, "node_modules/which-typed-array": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", @@ -20884,24 +19867,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, "node_modules/ws": { "version": "8.17.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", @@ -21195,11 +20160,181 @@ "@typescript-eslint/parser": "7.8.0", "@vscode/web-custom-data": "0.4.9", "eslint": "8.57.0", - "mocha": "10.4.0", - "nyc": "15.1.0", "rimraf": "5.0.5", "source-map-support": "0.5.21", - "typescript": "5.4.5" + "typescript": "5.4.5", + "vitest": "1.6.0" + } + }, + "packages/vscode-css-languageservice/node_modules/@vitest/expect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz", + "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==", + "dev": true, + "dependencies": { + "@vitest/spy": "1.6.0", + "@vitest/utils": "1.6.0", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/vscode-css-languageservice/node_modules/@vitest/runner": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz", + "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==", + "dev": true, + "dependencies": { + "@vitest/utils": "1.6.0", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/vscode-css-languageservice/node_modules/@vitest/snapshot": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", + "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/vscode-css-languageservice/node_modules/@vitest/spy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz", + "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==", + "dev": true, + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/vscode-css-languageservice/node_modules/@vitest/utils": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz", + "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/vscode-css-languageservice/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/vscode-css-languageservice/node_modules/vite-node": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz", + "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/vscode-css-languageservice/node_modules/vitest": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz", + "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==", + "dev": true, + "dependencies": { + "@vitest/expect": "1.6.0", + "@vitest/runner": "1.6.0", + "@vitest/snapshot": "1.6.0", + "@vitest/spy": "1.6.0", + "@vitest/utils": "1.6.0", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.0", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.0", + "@vitest/ui": "1.6.0", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } } }, "vscode-extension": { diff --git a/packages/vscode-css-languageservice/.editorconfig b/packages/vscode-css-languageservice/.editorconfig deleted file mode 100644 index 00725063..00000000 --- a/packages/vscode-css-languageservice/.editorconfig +++ /dev/null @@ -1,5 +0,0 @@ -indent_style = tab -indent_size = 2 - -[*.json] -indent_style = space \ No newline at end of file diff --git a/packages/vscode-css-languageservice/.mocharc.json b/packages/vscode-css-languageservice/.mocharc.json deleted file mode 100644 index fbf679e0..00000000 --- a/packages/vscode-css-languageservice/.mocharc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "ui": "tdd", - "color": true, - "spec": "./lib/umd/test/**/*.test.js", - "recursive": true -} \ No newline at end of file diff --git a/packages/vscode-css-languageservice/.npmignore b/packages/vscode-css-languageservice/.npmignore deleted file mode 100644 index f282cfef..00000000 --- a/packages/vscode-css-languageservice/.npmignore +++ /dev/null @@ -1,19 +0,0 @@ -.vscode/ -.github/ -lib/*/test/ -lib/**/*.js.map -lib/*/*/*.d.ts -src/ -build/ -coverage/ -test/ -.eslintrc.json -.gitignore -.travis.yml -gulpfile.js -tslint.json -package-lock.json -azure-pipelines.yml -.editorconfig -.mocharc.json -.nyc_output/ \ No newline at end of file diff --git a/packages/vscode-css-languageservice/package.json b/packages/vscode-css-languageservice/package.json index 972d542e..46d56cf2 100644 --- a/packages/vscode-css-languageservice/package.json +++ b/packages/vscode-css-languageservice/package.json @@ -16,17 +16,15 @@ "url": "https://github.com/wkillerud/some-sass" }, "devDependencies": { - "@types/mocha": "10.0.6", "@types/node": "20.12.8", "@typescript-eslint/eslint-plugin": "7.8.0", "@typescript-eslint/parser": "7.8.0", "@vscode/web-custom-data": "0.4.9", "eslint": "8.57.0", - "mocha": "10.4.0", - "nyc": "15.1.0", "rimraf": "5.0.5", "source-map-support": "0.5.21", - "typescript": "5.4.5" + "typescript": "5.4.5", + "vitest": "1.6.0" }, "dependencies": { "@vscode/l10n": "0.0.18", @@ -40,9 +38,8 @@ "compile-esm": "tsc -p ./src/tsconfig.esm.json", "clean": "rimraf lib", "watch": "tsc -w -p ./src", - "test": "npm run compile && npm run mocha", - "mocha": "mocha --require source-map-support/register", - "coverage": "npm run compile && nyc --reporter=html --reporter=text mocha", + "test": "vitest", + "coverage": "vitest run --coverage", "lint": "eslint src/**/*.ts", "update-data": "npm install @vscode/web-custom-data -D && node ./build/generateData.js", "install-types-next": "npm install vscode-languageserver-types@next -f -S && npm install vscode-languageserver-textdocument@next -f -S" diff --git a/packages/vscode-css-languageservice/src/test/css/codeActions.test.ts b/packages/vscode-css-languageservice/src/test/css/codeActions.test.ts index 4e5c4a91..639d5900 100644 --- a/packages/vscode-css-languageservice/src/test/css/codeActions.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/codeActions.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ "use strict"; -import * as assert from "assert"; +import { suite, test, assert } from "vitest"; import { getCSSLanguageService, diff --git a/packages/vscode-css-languageservice/src/test/css/completion.test.ts b/packages/vscode-css-languageservice/src/test/css/completion.test.ts index 71027ef7..e2f26cef 100644 --- a/packages/vscode-css-languageservice/src/test/css/completion.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/completion.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ "use strict"; -import * as assert from "assert"; +import { suite, test, assert } from "vitest"; import * as path from "path"; import { getCSSLanguageService, @@ -974,7 +974,7 @@ suite("CSS - Completion", () => { assert.ok("d_a2" < "d_fe"); }); - const testFixturesPath = path.join(__dirname, "../../../../test"); + const testFixturesPath = path.join(__dirname, "../../../test"); test("CSS url() Path completion", async function () { const testUri = URI.file(path.resolve(testFixturesPath, "pathCompletionFixtures/about/about.css")).toString(true); diff --git a/packages/vscode-css-languageservice/src/test/css/customData.test.ts b/packages/vscode-css-languageservice/src/test/css/customData.test.ts index ba425397..72608049 100644 --- a/packages/vscode-css-languageservice/src/test/css/customData.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/customData.test.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ "use strict"; -import * as assert from "assert"; - +import { suite, test, assert } from "vitest"; import { testCompletionFor } from "./completion.test"; import { getCSSLanguageService, TextDocument, newCSSDataProvider, LanguageSettings } from "../../cssLanguageService"; diff --git a/packages/vscode-css-languageservice/src/test/css/folding.test.ts b/packages/vscode-css-languageservice/src/test/css/folding.test.ts index 519c6b77..2b92f996 100644 --- a/packages/vscode-css-languageservice/src/test/css/folding.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/folding.test.ts @@ -5,7 +5,7 @@ "use strict"; -import * as assert from "assert"; +import { suite, test, assert } from "vitest"; import { TextDocument, FoldingRange, FoldingRangeKind, getCSSLanguageService } from "../../cssLanguageService"; function assertRanges( diff --git a/packages/vscode-css-languageservice/src/test/css/hover.test.ts b/packages/vscode-css-languageservice/src/test/css/hover.test.ts index 3aef9834..64bb67d3 100644 --- a/packages/vscode-css-languageservice/src/test/css/hover.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/hover.test.ts @@ -5,7 +5,7 @@ "use strict"; -import * as assert from "assert"; +import { suite, test, assert } from "vitest"; import { Hover, TextDocument, getCSSLanguageService, getSCSSLanguageService } from "../../cssLanguageService"; import { HoverSettings } from "../../cssLanguageTypes"; diff --git a/packages/vscode-css-languageservice/src/test/css/languageFacts.test.ts b/packages/vscode-css-languageservice/src/test/css/languageFacts.test.ts index 48d922de..639ed261 100644 --- a/packages/vscode-css-languageservice/src/test/css/languageFacts.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/languageFacts.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ "use strict"; -import * as assert from "assert"; +import { suite, test, assert } from "vitest"; import { isColorValue, getColorValue, diff --git a/packages/vscode-css-languageservice/src/test/css/lint.test.ts b/packages/vscode-css-languageservice/src/test/css/lint.test.ts index 8e0f2de3..91b3ecc9 100644 --- a/packages/vscode-css-languageservice/src/test/css/lint.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/lint.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ "use strict"; -import * as assert from "assert"; +import { suite, test, assert } from "vitest"; import { Node, IRule, Level } from "../../parser/cssNodes"; import { Parser } from "../../parser/cssParser"; import { LintVisitor } from "../../services/lint"; diff --git a/packages/vscode-css-languageservice/src/test/css/navigation.test.ts b/packages/vscode-css-languageservice/src/test/css/navigation.test.ts index d68760ec..d04a6527 100644 --- a/packages/vscode-css-languageservice/src/test/css/navigation.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/navigation.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ "use strict"; -import * as assert from "assert"; +import { suite, test, assert } from "vitest"; import { join } from "path"; import { Scope, GlobalScope, ScopeBuilder } from "../../parser/cssSymbolScope"; import * as nodes from "../../parser/cssNodes"; @@ -188,7 +188,7 @@ export function assertScopeBuilding( } export function getTestResource(path: string) { - return URI.file(join(__dirname, "../../../../test/linksTestFixtures", path)).toString(true); + return URI.file(join(__dirname, "../../../test/linksTestFixtures", path)).toString(true); } function scopeToString(scope: Scope): string { diff --git a/packages/vscode-css-languageservice/src/test/css/nodes.test.ts b/packages/vscode-css-languageservice/src/test/css/nodes.test.ts index 6fe9115d..370d2eeb 100644 --- a/packages/vscode-css-languageservice/src/test/css/nodes.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/nodes.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ "use strict"; -import * as assert from "assert"; +import { suite, test, assert } from "vitest"; import * as nodes from "../../parser/cssNodes"; import { Parser } from "../../parser/cssParser"; diff --git a/packages/vscode-css-languageservice/src/test/css/parser.test.ts b/packages/vscode-css-languageservice/src/test/css/parser.test.ts index c87943f7..0de87fb5 100644 --- a/packages/vscode-css-languageservice/src/test/css/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/parser.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ "use strict"; -import * as assert from "assert"; +import { suite, test, assert } from "vitest"; import { Parser } from "../../parser/cssParser"; import { TokenType } from "../../parser/cssScanner"; import * as nodes from "../../parser/cssNodes"; diff --git a/packages/vscode-css-languageservice/src/test/css/scanner.test.ts b/packages/vscode-css-languageservice/src/test/css/scanner.test.ts index a9833904..a1c7c1f9 100644 --- a/packages/vscode-css-languageservice/src/test/css/scanner.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/scanner.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ "use strict"; -import * as assert from "assert"; +import { suite, test, assert } from "vitest"; import { Scanner, TokenType } from "../../parser/cssScanner"; suite("CSS - Scanner", () => { diff --git a/packages/vscode-css-languageservice/src/test/css/selectionRange.test.ts b/packages/vscode-css-languageservice/src/test/css/selectionRange.test.ts index 3861b8e5..f7fd747b 100644 --- a/packages/vscode-css-languageservice/src/test/css/selectionRange.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/selectionRange.test.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ "use strict"; -import "mocha"; -import * as assert from "assert"; +import { suite, test, assert } from "vitest"; import { getCSSLanguageService, TextDocument, SelectionRange } from "../../cssLanguageService"; function assertRanges(content: string, expected: (number | string)[][]): void { diff --git a/packages/vscode-css-languageservice/src/test/css/selectorPrinting.test.ts b/packages/vscode-css-languageservice/src/test/css/selectorPrinting.test.ts index e132a05c..e4c9771a 100644 --- a/packages/vscode-css-languageservice/src/test/css/selectorPrinting.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/selectorPrinting.test.ts @@ -3,8 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ "use strict"; - -import * as assert from "assert"; +import { suite, test, assert } from "vitest"; import { Parser } from "../../parser/cssParser"; import * as nodes from "../../parser/cssNodes"; import * as selectorPrinting from "../../services/selectorPrinting"; @@ -57,10 +56,10 @@ function doParse(p: Parser, input: string, selectorName: string): nodes.Selector export function assertSelector(p: Parser, input: string, selectorName: string, expected: string): void { let selector = doParse(p, input, selectorName); - assert(selector); + assert.ok(selector); let element = selectorPrinting.selectorToElement(selector!); - assert(element); + assert.ok(element); assert.equal(elementToString(element!), expected); } @@ -74,7 +73,7 @@ function assertElement(p: Parser, input: string, expected: { name: string; value function assertSelectorMarkdown(p: Parser, input: string, selectorName: string, expected: MarkedString[]): void { let selector = doParse(p, input, selectorName); - assert(selector); + assert.ok(selector); const selectorPrinter = new selectorPrinting.SelectorPrinting(cssDataManager); let printedElement = selectorPrinter.selectorToMarkedString(selector!); diff --git a/packages/vscode-css-languageservice/src/test/scss/languageFacts.test.ts b/packages/vscode-css-languageservice/src/test/scss/languageFacts.test.ts index e89cf507..69e8eaa8 100644 --- a/packages/vscode-css-languageservice/src/test/scss/languageFacts.test.ts +++ b/packages/vscode-css-languageservice/src/test/scss/languageFacts.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ "use strict"; +import { suite, test } from "vitest"; import { SCSSParser } from "../../parser/scssParser"; import { assertColor } from "../css/languageFacts.test"; diff --git a/packages/vscode-css-languageservice/src/test/scss/lint.test.ts b/packages/vscode-css-languageservice/src/test/scss/lint.test.ts index a98a9307..6c46a290 100644 --- a/packages/vscode-css-languageservice/src/test/scss/lint.test.ts +++ b/packages/vscode-css-languageservice/src/test/scss/lint.test.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ "use strict"; +import { suite, test } from "vitest"; import { TextDocument } from "../../cssLanguageTypes"; import { SCSSParser } from "../../parser/scssParser"; diff --git a/packages/vscode-css-languageservice/src/test/scss/parser.test.ts b/packages/vscode-css-languageservice/src/test/scss/parser.test.ts index c333c062..022cc455 100644 --- a/packages/vscode-css-languageservice/src/test/scss/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/scss/parser.test.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ "use strict"; +import { suite, test } from "vitest"; import { SCSSParser } from "../../parser/scssParser"; import { ParseError } from "../../parser/cssErrors"; diff --git a/packages/vscode-css-languageservice/src/test/scss/scssCompletion.test.ts b/packages/vscode-css-languageservice/src/test/scss/scssCompletion.test.ts index aed11bc0..2e3260eb 100644 --- a/packages/vscode-css-languageservice/src/test/scss/scssCompletion.test.ts +++ b/packages/vscode-css-languageservice/src/test/scss/scssCompletion.test.ts @@ -5,6 +5,7 @@ "use strict"; import * as path from "path"; +import { suite, test } from "vitest"; import { Position, InsertTextFormat, CompletionItemKind, LanguageSettings } from "../../cssLanguageService"; import { testCompletionFor as testCSSCompletionFor, ExpectedCompetions } from "../css/completion.test"; @@ -283,7 +284,7 @@ suite("SCSS - Completions", () => { }); }); - const testFixturesPath = path.join(__dirname, "../../../../test"); + const testFixturesPath = path.join(__dirname, "../../../test"); /** * For SCSS, `@import 'foo';` can be used for importing partial file `_foo.scss` diff --git a/packages/vscode-css-languageservice/src/test/scss/scssNavigation.test.ts b/packages/vscode-css-languageservice/src/test/scss/scssNavigation.test.ts index f466ab87..00312cb4 100644 --- a/packages/vscode-css-languageservice/src/test/scss/scssNavigation.test.ts +++ b/packages/vscode-css-languageservice/src/test/scss/scssNavigation.test.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ "use strict"; +import { suite, test, assert } from "vitest"; + import * as nodes from "../../parser/cssNodes"; import { assertSymbolsInScope, @@ -22,7 +24,6 @@ import { LanguageSettings, StylesheetDocumentLink, } from "../../cssLanguageService"; -import * as assert from "assert"; import * as path from "path"; import { URI } from "vscode-uri"; import { getFsProvider } from "../testUtil/fsProvider"; @@ -238,7 +239,7 @@ suite("SCSS - Navigation", () => { suite("Links", () => { // For invalid links that have no corresponding file on disk, return no link test("Invalid SCSS partial file links", async () => { - const fixtureRoot = path.resolve(__dirname, "../../../../src/test/scss/linkFixture/non-existent"); + const fixtureRoot = path.resolve(__dirname, "../../../src/test/scss/linkFixture/non-existent"); const getDocumentUri = (relativePath: string) => { return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true); }; @@ -253,7 +254,7 @@ suite("SCSS - Navigation", () => { }); test("SCSS partial file dynamic links", async () => { - const fixtureRoot = path.resolve(__dirname, "../../../../src/test/scss/linkFixture"); + const fixtureRoot = path.resolve(__dirname, "../../../src/test/scss/linkFixture"); const getDocumentUri = (relativePath: string) => { return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true); }; @@ -313,7 +314,7 @@ suite("SCSS - Navigation", () => { }); test("SCSS aliased links", async function () { - const fixtureRoot = path.resolve(__dirname, "../../../../src/test/scss/linkFixture"); + const fixtureRoot = path.resolve(__dirname, "../../../src/test/scss/linkFixture"); const getDocumentUri = (relativePath: string) => { return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true); }; @@ -356,7 +357,7 @@ suite("SCSS - Navigation", () => { }); test("SCSS module file links", async () => { - const fixtureRoot = path.resolve(__dirname, "../../../../src/test/scss/linkFixture/module"); + const fixtureRoot = path.resolve(__dirname, "../../../src/test/scss/linkFixture/module"); const getDocumentUri = (relativePath: string) => { return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true); }; diff --git a/packages/vscode-css-languageservice/src/test/scss/selectorPrinting.test.ts b/packages/vscode-css-languageservice/src/test/scss/selectorPrinting.test.ts index 4109632d..51dc189d 100644 --- a/packages/vscode-css-languageservice/src/test/scss/selectorPrinting.test.ts +++ b/packages/vscode-css-languageservice/src/test/scss/selectorPrinting.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -"use strict"; +import { suite, test } from "vitest"; import { SCSSParser } from "../../parser/scssParser"; import { assertSelector } from "../css/selectorPrinting.test"; diff --git a/packages/vscode-css-languageservice/src/test/util.test.ts b/packages/vscode-css-languageservice/src/test/util.test.ts index 96475351..9f7c8c07 100644 --- a/packages/vscode-css-languageservice/src/test/util.test.ts +++ b/packages/vscode-css-languageservice/src/test/util.test.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from "assert"; +import { suite, test, assert } from "vitest"; import { trim } from "../utils/strings"; suite("Util", () => { diff --git a/packages/vscode-css-languageservice/vitest.config.mts b/packages/vscode-css-languageservice/vitest.config.mts new file mode 100644 index 00000000..4f566d5d --- /dev/null +++ b/packages/vscode-css-languageservice/vitest.config.mts @@ -0,0 +1,12 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + exclude: ["lib/**"], + isolate: false, + coverage: { + provider: "v8", + reporter: ["text", "json", "html"], + }, + }, +}); From 91694a78f5a7fbf9b6ec2d98e4219b2a58c39b40 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Tue, 14 May 2024 20:23:33 +0200 Subject: [PATCH 004/138] refactor: rename to sass* and add dialect option --- .../src/cssLanguageService.ts | 6 +- .../src/parser/cssParser.ts | 10 ++- .../src/parser/cssScanner.ts | 10 +++ .../parser/{scssErrors.ts => sassErrors.ts} | 10 +-- .../parser/{scssParser.ts => sassParser.ts} | 18 ++--- .../parser/{scssScanner.ts => sassScanner.ts} | 2 +- .../src/services/cssFolding.ts | 4 +- .../src/test/css/lint.test.ts | 4 +- .../src/test/sass/parser.test.ts | 39 +++++++++ .../src/test/scss/languageFacts.test.ts | 4 +- .../src/test/scss/lint.test.ts | 6 +- .../src/test/scss/parser.test.ts | 80 +++++++++---------- .../src/test/scss/selectorPrinting.test.ts | 10 +-- 13 files changed, 128 insertions(+), 75 deletions(-) rename packages/vscode-css-languageservice/src/parser/{scssErrors.ts => sassErrors.ts} (70%) rename packages/vscode-css-languageservice/src/parser/{scssParser.ts => sassParser.ts} (98%) rename packages/vscode-css-languageservice/src/parser/{scssScanner.ts => sassScanner.ts} (98%) create mode 100644 packages/vscode-css-languageservice/src/test/sass/parser.test.ts diff --git a/packages/vscode-css-languageservice/src/cssLanguageService.ts b/packages/vscode-css-languageservice/src/cssLanguageService.ts index 123196a4..253e4a01 100644 --- a/packages/vscode-css-languageservice/src/cssLanguageService.ts +++ b/packages/vscode-css-languageservice/src/cssLanguageService.ts @@ -11,7 +11,7 @@ import { CSSNavigation } from "./services/cssNavigation"; import { CSSCodeActions } from "./services/cssCodeActions"; import { CSSValidation } from "./services/cssValidation"; -import { SCSSParser } from "./parser/scssParser"; +import { SassParser } from "./parser/sassParser"; import { SCSSCompletion } from "./services/scssCompletion"; import { getFoldingRanges } from "./services/cssFolding"; @@ -55,7 +55,7 @@ import { cssData } from "./data/webCustomData"; export type Stylesheet = {}; export { TokenType, IToken, Scanner } from "./parser/cssScanner"; -export { SCSSScanner } from "./parser/scssScanner"; +export { SassScanner as SCSSScanner } from "./parser/sassScanner"; export * from "./parser/cssNodes"; export * from "./cssLanguageTypes"; @@ -188,7 +188,7 @@ export function getSCSSLanguageService( ): LanguageService { const cssDataManager = new CSSDataManager(options); return createFacade( - new SCSSParser(), + new SassParser(), new SCSSCompletion(options, cssDataManager), new CSSHover(options && options.clientCapabilities, cssDataManager), new SCSSNavigation(options && options.fileSystemProvider), diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 299a4bcc..e749fa89 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ "use strict"; -import { TokenType, Scanner, IToken } from "./cssScanner"; +import { TokenType, Scanner, IToken, ScannerOptions } from "./cssScanner"; import * as nodes from "./cssNodes"; import { ParseError, CSSIssueType } from "./cssErrors"; import * as languageFacts from "../languageFacts/facts"; @@ -16,6 +16,10 @@ export interface IMark { pos: number; } +export type ParserOptions = ScannerOptions & { + scanner?: Scanner; +}; + /// /// A parser for the css core specification. See for reference: /// https://www.w3.org/TR/CSS21/grammar.html @@ -28,8 +32,8 @@ export class Parser { private lastErrorToken?: IToken; - constructor(scnr: Scanner = new Scanner()) { - this.scanner = scnr; + constructor({ scanner, dialect }: ParserOptions = {}) { + this.scanner = scanner || new Scanner({ dialect }); this.token = { type: TokenType.EOF, offset: -1, len: 0, text: "" }; this.prevToken = undefined!; } diff --git a/packages/vscode-css-languageservice/src/parser/cssScanner.ts b/packages/vscode-css-languageservice/src/parser/cssScanner.ts index fa521baf..6f54a6d7 100644 --- a/packages/vscode-css-languageservice/src/parser/cssScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/cssScanner.ts @@ -219,12 +219,22 @@ staticUnitTable["cqb"] = TokenType.ContainerQueryLength; staticUnitTable["cqmin"] = TokenType.ContainerQueryLength; staticUnitTable["cqmax"] = TokenType.ContainerQueryLength; +export type ScannerOptions = { + dialect?: "indented"; +}; + export class Scanner { public stream: MultiLineStream = new MultiLineStream(""); public ignoreComment = true; public ignoreWhitespace = true; public inURL = false; + dialect; + + constructor({ dialect }: ScannerOptions = {}) { + this.dialect = dialect; + } + public setSource(input: string): void { this.stream = new MultiLineStream(input); } diff --git a/packages/vscode-css-languageservice/src/parser/scssErrors.ts b/packages/vscode-css-languageservice/src/parser/sassErrors.ts similarity index 70% rename from packages/vscode-css-languageservice/src/parser/scssErrors.ts rename to packages/vscode-css-languageservice/src/parser/sassErrors.ts index 83a36d62..d8c747cb 100644 --- a/packages/vscode-css-languageservice/src/parser/scssErrors.ts +++ b/packages/vscode-css-languageservice/src/parser/sassErrors.ts @@ -8,7 +8,7 @@ import * as nodes from "./cssNodes"; import * as l10n from "@vscode/l10n"; -export class SCSSIssueType implements nodes.IRule { +export class SassIssueType implements nodes.IRule { id: string; message: string; @@ -18,8 +18,8 @@ export class SCSSIssueType implements nodes.IRule { } } -export const SCSSParseError = { - FromExpected: new SCSSIssueType("scss-fromexpected", l10n.t("'from' expected")), - ThroughOrToExpected: new SCSSIssueType("scss-throughexpected", l10n.t("'through' or 'to' expected")), - InExpected: new SCSSIssueType("scss-fromexpected", l10n.t("'in' expected")), +export const SassParseError = { + FromExpected: new SassIssueType("scss-fromexpected", l10n.t("'from' expected")), + ThroughOrToExpected: new SassIssueType("scss-throughexpected", l10n.t("'through' or 'to' expected")), + InExpected: new SassIssueType("scss-fromexpected", l10n.t("'in' expected")), }; diff --git a/packages/vscode-css-languageservice/src/parser/scssParser.ts b/packages/vscode-css-languageservice/src/parser/sassParser.ts similarity index 98% rename from packages/vscode-css-languageservice/src/parser/scssParser.ts rename to packages/vscode-css-languageservice/src/parser/sassParser.ts index 384a6515..905ec107 100644 --- a/packages/vscode-css-languageservice/src/parser/scssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/sassParser.ts @@ -3,21 +3,21 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ "use strict"; -import * as scssScanner from "./scssScanner"; -import { TokenType } from "./cssScanner"; +import * as scssScanner from "./sassScanner"; +import { ScannerOptions, TokenType } from "./cssScanner"; import * as cssParser from "./cssParser"; import * as nodes from "./cssNodes"; -import { SCSSParseError } from "./scssErrors"; +import { SassParseError } from "./sassErrors"; import { ParseError } from "./cssErrors"; /// /// A parser for scss /// http://sass-lang.com/documentation/file.SASS_REFERENCE.html /// -export class SCSSParser extends cssParser.Parser { - public constructor() { - super(new scssScanner.SCSSScanner()); +export class SassParser extends cssParser.Parser { + public constructor({ dialect }: ScannerOptions = {}) { + super({ dialect, scanner: new scssScanner.SassScanner({ dialect }) }); } public _parseStylesheetStatement(isNested: boolean = false): nodes.Node | null { @@ -475,13 +475,13 @@ export class SCSSParser extends cssParser.Parser { return this.finish(node, ParseError.VariableNameExpected, [TokenType.CurlyR]); } if (!this.acceptIdent("from")) { - return this.finish(node, SCSSParseError.FromExpected, [TokenType.CurlyR]); + return this.finish(node, SassParseError.FromExpected, [TokenType.CurlyR]); } if (!node.addChild(this._parseBinaryExpr())) { return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]); } if (!this.acceptIdent("to") && !this.acceptIdent("through")) { - return this.finish(node, SCSSParseError.ThroughOrToExpected, [TokenType.CurlyR]); + return this.finish(node, SassParseError.ThroughOrToExpected, [TokenType.CurlyR]); } if (!node.addChild(this._parseBinaryExpr())) { return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]); @@ -508,7 +508,7 @@ export class SCSSParser extends cssParser.Parser { } this.finish(variables); if (!this.acceptIdent("in")) { - return this.finish(node, SCSSParseError.InExpected, [TokenType.CurlyR]); + return this.finish(node, SassParseError.InExpected, [TokenType.CurlyR]); } if (!node.addChild(this._parseExpr())) { return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]); diff --git a/packages/vscode-css-languageservice/src/parser/scssScanner.ts b/packages/vscode-css-languageservice/src/parser/sassScanner.ts similarity index 98% rename from packages/vscode-css-languageservice/src/parser/scssScanner.ts rename to packages/vscode-css-languageservice/src/parser/sassScanner.ts index bf321136..cb931ce0 100644 --- a/packages/vscode-css-languageservice/src/parser/scssScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/sassScanner.ts @@ -33,7 +33,7 @@ export const SmallerEqualsOperator: TokenType = customTokenValue++; export const Ellipsis: TokenType = customTokenValue++; export const Module: TokenType = customTokenValue++; -export class SCSSScanner extends Scanner { +export class SassScanner extends Scanner { protected scanNext(offset: number): IToken { // scss variable if (this.stream.advanceIfChar(_DLR)) { diff --git a/packages/vscode-css-languageservice/src/services/cssFolding.ts b/packages/vscode-css-languageservice/src/services/cssFolding.ts index f2067169..930ed263 100644 --- a/packages/vscode-css-languageservice/src/services/cssFolding.ts +++ b/packages/vscode-css-languageservice/src/services/cssFolding.ts @@ -6,7 +6,7 @@ import { TextDocument, FoldingRange, FoldingRangeKind } from "../cssLanguageTypes"; import { TokenType, Scanner, IToken } from "../parser/cssScanner"; -import { SCSSScanner, InterpolationFunction } from "../parser/scssScanner"; +import { SassScanner, InterpolationFunction } from "../parser/sassScanner"; type DelimiterType = "brace" | "comment"; type Delimiter = { line: number; type: DelimiterType; isStart: boolean }; @@ -26,7 +26,7 @@ function computeFoldingRanges(document: TextDocument): FoldingRange[] { function getScanner() { switch (document.languageId) { case "scss": - return new SCSSScanner(); + return new SassScanner(); default: return new Scanner(); } diff --git a/packages/vscode-css-languageservice/src/test/css/lint.test.ts b/packages/vscode-css-languageservice/src/test/css/lint.test.ts index 91b3ecc9..c9d12741 100644 --- a/packages/vscode-css-languageservice/src/test/css/lint.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/lint.test.ts @@ -10,7 +10,7 @@ import { Parser } from "../../parser/cssParser"; import { LintVisitor } from "../../services/lint"; import { Rule, Rules, LintConfigurationSettings } from "../../services/lintRules"; import { TextDocument } from "../../cssLanguageTypes"; -import { SCSSParser } from "../../parser/scssParser"; +import { SassParser } from "../../parser/sassParser"; import { CSSDataManager } from "../../languageFacts/dataManager"; const cssDataManager = new CSSDataManager({ useDefaultDataProvider: true }); @@ -44,7 +44,7 @@ export function assertEntries( } } } -const parsers = [new Parser(), new SCSSParser()]; +const parsers = [new Parser(), new SassParser()]; function assertStyleSheet(input: string, ...rules: Rule[]): void { for (const p of parsers) { diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts new file mode 100644 index 00000000..22c48a3b --- /dev/null +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -0,0 +1,39 @@ +import { suite, test } from "vitest"; +import { SassParser } from "../../parser/sassParser"; +import { ParseError } from "../../parser/cssErrors"; +import { assertNode, assertError } from "../css/parser.test"; + +suite("Sass - Parser", () => { + test.skip("Comments", function () { + const parser = new SassParser({ dialect: "indented" }); + assertNode( + ` +a + b: /* comment */ c +`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ` +a + // single-line comment + b: c +`, + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + ` +a + b: /* multi +line +comment */ c +`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.WildcardExpected, // TODO: make one for comment closing? How's it done in CSS? + ); + }); +}); diff --git a/packages/vscode-css-languageservice/src/test/scss/languageFacts.test.ts b/packages/vscode-css-languageservice/src/test/scss/languageFacts.test.ts index 69e8eaa8..b6f4c3ac 100644 --- a/packages/vscode-css-languageservice/src/test/scss/languageFacts.test.ts +++ b/packages/vscode-css-languageservice/src/test/scss/languageFacts.test.ts @@ -5,13 +5,13 @@ "use strict"; import { suite, test } from "vitest"; -import { SCSSParser } from "../../parser/scssParser"; +import { SassParser } from "../../parser/sassParser"; import { assertColor } from "../css/languageFacts.test"; import { colorFrom256RGB as newColor } from "../../languageFacts/facts"; suite("SCSS - Language facts", () => { test("is color", function () { - let parser = new SCSSParser(); + let parser = new SassParser(); assertColor(parser, "#main { color: foo(red) }", "red", newColor(0xff, 0, 0)); assertColor(parser, "#main { color: red() }", "red", null); assertColor(parser, "#main { red { nested: 1px } }", "red", null); diff --git a/packages/vscode-css-languageservice/src/test/scss/lint.test.ts b/packages/vscode-css-languageservice/src/test/scss/lint.test.ts index 6c46a290..3cf4144f 100644 --- a/packages/vscode-css-languageservice/src/test/scss/lint.test.ts +++ b/packages/vscode-css-languageservice/src/test/scss/lint.test.ts @@ -6,12 +6,12 @@ import { suite, test } from "vitest"; import { TextDocument } from "../../cssLanguageTypes"; -import { SCSSParser } from "../../parser/scssParser"; +import { SassParser } from "../../parser/sassParser"; import { Rule, Rules } from "../../services/lintRules"; import { assertEntries } from "../css/lint.test"; function assertFontFace(input: string, ...rules: Rule[]): void { - let p = new SCSSParser(); + let p = new SassParser(); let document = TextDocument.create("test://test/test.scss", "scss", 0, input); let node = p.internalParse(input, p._parseFontFace)!; @@ -19,7 +19,7 @@ function assertFontFace(input: string, ...rules: Rule[]): void { } function assertRuleSet(input: string, ...rules: Rule[]): void { - let p = new SCSSParser(); + let p = new SassParser(); let document = TextDocument.create("test://test/test.scss", "scss", 0, input); let node = p.internalParse(input, p._parseRuleset)!; assertEntries(node, document, rules); diff --git a/packages/vscode-css-languageservice/src/test/scss/parser.test.ts b/packages/vscode-css-languageservice/src/test/scss/parser.test.ts index 022cc455..acb9d46b 100644 --- a/packages/vscode-css-languageservice/src/test/scss/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/scss/parser.test.ts @@ -6,15 +6,15 @@ "use strict"; import { suite, test } from "vitest"; -import { SCSSParser } from "../../parser/scssParser"; +import { SassParser } from "../../parser/sassParser"; import { ParseError } from "../../parser/cssErrors"; -import { SCSSParseError } from "../../parser/scssErrors"; +import { SassParseError } from "../../parser/sassErrors"; import { assertNode, assertError } from "../css/parser.test"; suite("SCSS - Parser", () => { test("Comments", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode(" a { b: /* comment */ c }", parser, parser._parseStylesheet.bind(parser)); assertNode( " a { b: /* comment \n * is several\n * lines long\n */ c }", @@ -25,14 +25,14 @@ suite("SCSS - Parser", () => { }); test("Variable", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("$color", parser, parser._parseVariable.bind(parser)); assertNode("$co42lor", parser, parser._parseVariable.bind(parser)); assertNode("$-co42lor", parser, parser._parseVariable.bind(parser)); }); test("Module variable", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("module.$color", parser, parser._parseModuleMember.bind(parser)); assertNode("module.$co42lor", parser, parser._parseModuleMember.bind(parser)); assertNode("module.$-co42lor", parser, parser._parseModuleMember.bind(parser)); @@ -42,7 +42,7 @@ suite("SCSS - Parser", () => { }); test("VariableDeclaration", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("$color: #F5F5F5", parser, parser._parseVariableDeclaration.bind(parser)); assertNode("$color: 0", parser, parser._parseVariableDeclaration.bind(parser)); assertNode("$color: 255", parser, parser._parseVariableDeclaration.bind(parser)); @@ -74,7 +74,7 @@ suite("SCSS - Parser", () => { }); test("Expr", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("($const + 20)", parser, parser._parseExpr.bind(parser)); assertNode("($const - 20)", parser, parser._parseExpr.bind(parser)); assertNode("($const * 20)", parser, parser._parseExpr.bind(parser)); @@ -150,7 +150,7 @@ suite("SCSS - Parser", () => { }); test("SCSSOperator", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode(">=", parser, parser._parseOperator.bind(parser)); assertNode(">", parser, parser._parseOperator.bind(parser)); assertNode("<", parser, parser._parseOperator.bind(parser)); @@ -167,7 +167,7 @@ suite("SCSS - Parser", () => { }); test("Interpolation", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); // assertNode('#{red}', parser, parser._parseIdent.bind(parser)); // assertNode('#{$color}', parser, parser._parseIdent.bind(parser)); // assertNode('#{3 + 4}', parser, parser._parseIdent.bind(parser)); @@ -204,7 +204,7 @@ suite("SCSS - Parser", () => { }); test("Declaration", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("border: thin solid 1px", parser, parser._parseDeclaration.bind(parser)); assertNode("dummy: $color", parser, parser._parseDeclaration.bind(parser)); assertNode("dummy: blue", parser, parser._parseDeclaration.bind(parser)); @@ -303,7 +303,7 @@ suite("SCSS - Parser", () => { }); test("Stylesheet", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("$color: #F5F5F5;", parser, parser._parseStylesheet.bind(parser)); assertNode("$color: #F5F5F5; $color: #F5F5F5;", parser, parser._parseStylesheet.bind(parser)); assertNode("$color: #F5F5F5; $color: #F5F5F5; $color: #F5F5F5;", parser, parser._parseStylesheet.bind(parser)); @@ -345,7 +345,7 @@ suite("SCSS - Parser", () => { }); test("@import", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode('@import "test.css"', parser, parser._parseImport.bind(parser)); assertNode('@import url("test.css")', parser, parser._parseImport.bind(parser)); assertNode('@import "test.css", "bar.css"', parser, parser._parseImport.bind(parser)); @@ -364,12 +364,12 @@ suite("SCSS - Parser", () => { }); test("@layer", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("@layer #{$layer} { }", parser, parser._parseLayer.bind(parser)); }); test("@container", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode( `@container (min-width: #{$minWidth}) { .scss-interpolation { line-height: 10cqh; } }`, parser, @@ -388,7 +388,7 @@ suite("SCSS - Parser", () => { }); test("@use", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode('@use "test"', parser, parser._parseUse.bind(parser)); assertNode('@use "test" as foo', parser, parser._parseUse.bind(parser)); assertNode('@use "test" as *', parser, parser._parseUse.bind(parser)); @@ -420,7 +420,7 @@ suite("SCSS - Parser", () => { }); test("@forward", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode('@forward "test"', parser, parser._parseForward.bind(parser)); assertNode('@forward "test" as foo-*', parser, parser._parseForward.bind(parser)); assertNode('@forward "test" hide this', parser, parser._parseForward.bind(parser)); @@ -468,7 +468,7 @@ suite("SCSS - Parser", () => { }); test("@media", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode( "@media screen { .sidebar { @media (orientation: landscape) { width: 500px; } } }", parser, @@ -571,7 +571,7 @@ suite("SCSS - Parser", () => { }); test("@keyframe", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("@keyframes name { @content; }", parser, parser._parseKeyframe.bind(parser)); assertNode( "@keyframes name { @for $i from 0 through $steps { #{$i * (100%/$steps)} { transform: $rotate $translate; } } }", @@ -594,7 +594,7 @@ suite("SCSS - Parser", () => { }); test("@extend", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode(".themable { @extend %theme; }", parser, parser._parseStylesheet.bind(parser)); assertNode("foo { @extend .error; border-width: 3px; }", parser, parser._parseStylesheet.bind(parser)); assertNode("a.important { @extend .notice !optional; }", parser, parser._parseStylesheet.bind(parser)); @@ -628,7 +628,7 @@ suite("SCSS - Parser", () => { }); test("@debug", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("@debug test;", parser, parser._parseStylesheet.bind(parser)); assertNode("foo { @debug 1 + 4; nested { @warn 1 4; } }", parser, parser._parseStylesheet.bind(parser)); assertNode("@if $foo == 1 { @debug 1 + 4 }", parser, parser._parseStylesheet.bind(parser)); @@ -640,7 +640,7 @@ suite("SCSS - Parser", () => { }); test("@if", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("@if 1 + 1 == 2 { border: 1px solid; }", parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode("@if 5 < 3 { border: 2px dotted; }", parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode("@if null { border: 3px double; }", parser, parser._parseRuleSetDeclaration.bind(parser)); @@ -716,7 +716,7 @@ suite("SCSS - Parser", () => { }); test("@for", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode( "@for $i from 1 to 5 { .item-#{$i} { width: 2em * $i; } }", parser, @@ -729,12 +729,12 @@ suite("SCSS - Parser", () => { parser._parseRuleSetDeclaration.bind(parser), ParseError.VariableNameExpected, ); - assertError("@for $i to 4 {}", parser, parser._parseRuleSetDeclaration.bind(parser), SCSSParseError.FromExpected); + assertError("@for $i to 4 {}", parser, parser._parseRuleSetDeclaration.bind(parser), SassParseError.FromExpected); assertError( "@for $i from 0 by 4 {}", parser, parser._parseRuleSetDeclaration.bind(parser), - SCSSParseError.ThroughOrToExpected, + SassParseError.ThroughOrToExpected, ); assertError("@for $i from {}", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); assertError( @@ -751,7 +751,7 @@ suite("SCSS - Parser", () => { }); test("@each", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("@each $i in 1, 2, 3 { }", parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode("@each $i in 1 2 3 { }", parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode( @@ -765,7 +765,7 @@ suite("SCSS - Parser", () => { parser._parseRuleSetDeclaration.bind(parser), ParseError.VariableNameExpected, ); - assertError("@each $i from 4 {}", parser, parser._parseRuleSetDeclaration.bind(parser), SCSSParseError.InExpected); + assertError("@each $i from 4 {}", parser, parser._parseRuleSetDeclaration.bind(parser), SassParseError.InExpected); assertError("@each $i in {}", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); assertError( "@each $animal, in (1, 1, 1), (2, 2, 2) {}", @@ -776,7 +776,7 @@ suite("SCSS - Parser", () => { }); test("@while", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode( "@while $i < 0 { .item-#{$i} { width: 2em * $i; } $i: $i - 2; }", parser, @@ -793,7 +793,7 @@ suite("SCSS - Parser", () => { }); test("@mixin", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode( "@mixin large-text { font: { family: Arial; size: 20px; } color: #ff0000; }", parser, @@ -829,13 +829,13 @@ suite("SCSS - Parser", () => { }); test("@content", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("@content", parser, parser._parseMixinContent.bind(parser)); assertNode("@content($type)", parser, parser._parseMixinContent.bind(parser)); }); test("@include", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("p { @include sexy-border(blue); }", parser, parser._parseStylesheet.bind(parser)); assertNode( ".shadows { @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }", @@ -924,7 +924,7 @@ suite("SCSS - Parser", () => { }); test("@function", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode( "@function grid-width($n) { @return $n * $grid-width + ($n - 1) * $gutter-width; }", parser, @@ -982,7 +982,7 @@ suite("SCSS - Parser", () => { }); test("@at-root", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode( "@mixin unify-parent($child) { @at-root #{selector.unify(&, $child)} { }}", parser, @@ -1002,7 +1002,7 @@ suite("SCSS - Parser", () => { }); test("Ruleset", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode(".selector { prop: erty $const 1px; }", parser, parser._parseRuleset.bind(parser)); assertNode(".selector { prop: erty $const 1px m.$foo; }", parser, parser._parseRuleset.bind(parser)); assertNode("selector:active { property:value; nested:hover {}}", parser, parser._parseRuleset.bind(parser)); @@ -1021,7 +1021,7 @@ suite("SCSS - Parser", () => { }); test("Nested Ruleset", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode( ".class1 { $const: 1; .class { $const: 2; three: $const; const: 3; } one: $const; }", parser, @@ -1053,7 +1053,7 @@ suite("SCSS - Parser", () => { }); test("Selector Interpolation", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode(".#{$name} { }", parser, parser._parseRuleset.bind(parser)); assertNode(".#{$name}-foo { }", parser, parser._parseRuleset.bind(parser)); assertNode(".#{$name}-foo-3 { }", parser, parser._parseRuleset.bind(parser)); @@ -1079,7 +1079,7 @@ suite("SCSS - Parser", () => { }); test("Parent Selector", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("&:hover", parser, parser._parseSimpleSelector.bind(parser)); assertNode("&.float", parser, parser._parseSimpleSelector.bind(parser)); assertNode("&-bar", parser, parser._parseSimpleSelector.bind(parser)); @@ -1091,19 +1091,19 @@ suite("SCSS - Parser", () => { }); test("Selector Placeholder", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("%hover", parser, parser._parseSimpleSelector.bind(parser)); assertNode("a%float", parser, parser._parseSimpleSelector.bind(parser)); }); test("Map", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("(key1: 1px, key2: solid + px, key3: (2+3))", parser, parser._parseExpr.bind(parser)); assertNode("($key1 + 3: 1px)", parser, parser._parseExpr.bind(parser)); }); test("Url", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("url(foo())", parser, parser._parseURILiteral.bind(parser)); assertNode( "url('data:image/svg+xml;utf8,%3Csvg%20fill%3D%22%23' + $color + 'foo')", @@ -1138,7 +1138,7 @@ suite("SCSS - Parser", () => { }); test("@font-face", function () { - const parser = new SCSSParser(); + const parser = new SassParser(); assertNode("@font-face {}", parser, parser._parseFontFace.bind(parser)); assertNode("@font-face { src: url(http://test) }", parser, parser._parseFontFace.bind(parser)); assertNode("@font-face { font-style: normal; font-stretch: normal; }", parser, parser._parseFontFace.bind(parser)); diff --git a/packages/vscode-css-languageservice/src/test/scss/selectorPrinting.test.ts b/packages/vscode-css-languageservice/src/test/scss/selectorPrinting.test.ts index 51dc189d..1b5b9df6 100644 --- a/packages/vscode-css-languageservice/src/test/scss/selectorPrinting.test.ts +++ b/packages/vscode-css-languageservice/src/test/scss/selectorPrinting.test.ts @@ -5,12 +5,12 @@ import { suite, test } from "vitest"; -import { SCSSParser } from "../../parser/scssParser"; +import { SassParser } from "../../parser/sassParser"; import { assertSelector } from "../css/selectorPrinting.test"; suite("SCSS - Selector Printing", () => { test("simple selector", function () { - let p = new SCSSParser(); + let p = new SassParser(); assertSelector(p, "o1 { }", "o1", "{o1}"); assertSelector(p, ".div { } ", ".div", "{[class=div]}"); assertSelector(p, "#div { } ", "#div", "{[id=div]}"); @@ -21,7 +21,7 @@ suite("SCSS - Selector Printing", () => { }); test("nested selector", function () { - let p = new SCSSParser(); + let p = new SassParser(); assertSelector(p, "o1 { e1 { } }", "e1", "{o1{…{e1}}}"); assertSelector(p, "o1 { e1.div { } }", "e1", "{o1{…{e1[class=div]}}}"); assertSelector(p, "o1 o2 { e1 { } }", "e1", "{o1{…{o2{…{e1}}}}}"); @@ -32,7 +32,7 @@ suite("SCSS - Selector Printing", () => { }); test("referencing selector", function () { - let p = new SCSSParser(); + let p = new SassParser(); assertSelector(p, "o1 { &:hover { }}", "&", "{o1[:hover=]}"); assertSelector(p, "o1 { &:hover & { }}", "&", "{o1[:hover=]{…{o1}}}"); assertSelector(p, "o1 { &__bar {}}", "&", "{o1__bar}"); @@ -41,7 +41,7 @@ suite("SCSS - Selector Printing", () => { }); test("placeholders", function () { - let p = new SCSSParser(); + let p = new SassParser(); assertSelector(p, "%o1 { e1 { } }", "e1", "{%o1{…{e1}}}"); }); }); From 950e82ba96ecf9ce62b44d87c6946e37cbde7bd5 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Tue, 14 May 2024 21:57:42 +0200 Subject: [PATCH 005/138] test: hello, indented First passing test --- .../src/parser/cssErrors.ts | 3 + .../src/parser/cssParser.ts | 38 ++++++++++-- .../src/parser/cssScanner.ts | 59 ++++++++++++++++--- .../src/parser/sassScanner.ts | 25 ++++++++ .../src/test/css/parser.test.ts | 23 +++++--- .../src/test/sass/parser.test.ts | 24 ++++---- 6 files changed, 142 insertions(+), 30 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssErrors.ts b/packages/vscode-css-languageservice/src/parser/cssErrors.ts index 7df6d7a7..0fb3f859 100644 --- a/packages/vscode-css-languageservice/src/parser/cssErrors.ts +++ b/packages/vscode-css-languageservice/src/parser/cssErrors.ts @@ -37,6 +37,9 @@ export const ParseError = { PropertyValueExpected: new CSSIssueType("css-propertyvalueexpected", l10n.t("property value expected")), LeftCurlyExpected: new CSSIssueType("css-lcurlyexpected", l10n.t("{ expected")), RightCurlyExpected: new CSSIssueType("css-rcurlyexpected", l10n.t("} expected")), + IndentExpected: new CSSIssueType("css-indentexpected", l10n.t("indent expected")), + DedentExpected: new CSSIssueType("css-dedentexpected", l10n.t("dedent expected")), + NewlineExpected: new CSSIssueType("css-dedentexpected", l10n.t("new line expected")), LeftSquareBracketExpected: new CSSIssueType("css-rbracketexpected", l10n.t("[ expected")), RightSquareBracketExpected: new CSSIssueType("css-lbracketexpected", l10n.t("] expected")), LeftParenthesisExpected: new CSSIssueType("css-lparentexpected", l10n.t("( expected")), diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index e749fa89..49187de9 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -32,7 +32,10 @@ export class Parser { private lastErrorToken?: IToken; + dialect; + constructor({ scanner, dialect }: ParserOptions = {}) { + this.dialect = dialect; this.scanner = scanner || new Scanner({ dialect }); this.token = { type: TokenType.EOF, offset: -1, len: 0, text: "" }; this.prevToken = undefined!; @@ -418,6 +421,10 @@ export class Parser { } public _needsSemicolonAfter(node: nodes.Node): boolean { + if (this.dialect === "indented") { + return false; + } + switch (node.type) { case nodes.NodeType.Keyframe: case nodes.NodeType.ViewPort: @@ -453,13 +460,13 @@ export class Parser { public _parseDeclarations(parseDeclaration: () => nodes.Node | null): nodes.Declarations | null { const node = this.create(nodes.Declarations); - if (!this.accept(TokenType.CurlyL)) { + if (!this.accept(TokenType.CurlyL) && !(this.accept(TokenType.Newline) && this.accept(TokenType.Indent))) { return null; } let decl = parseDeclaration(); while (node.addChild(decl)) { - if (this.peek(TokenType.CurlyR)) { + if (this.peek(TokenType.CurlyR) || this.peek(TokenType.Dedent)) { break; } if (this._needsSemicolonAfter(decl) && !this.accept(TokenType.SemiColon)) { @@ -472,17 +479,40 @@ export class Parser { while (this.accept(TokenType.SemiColon)) { // accept empty statements } + if (this.dialect === "indented") { + while (this.accept(TokenType.Newline)) { + // accept empty statements + } + } decl = parseDeclaration(); } - if (!this.accept(TokenType.CurlyR)) { - return this.finish(node, ParseError.RightCurlyExpected, [TokenType.CurlyR, TokenType.SemiColon]); + if (this.dialect === "indented") { + if (this.accept(TokenType.EOF)) { + return this.finish(node); + } + if (!this.accept(TokenType.Newline)) { + return this.finish(node, ParseError.NewlineExpected, [TokenType.Dedent]); + } + if (this.accept(TokenType.EOF)) { + return this.finish(node); + } + if (!this.accept(TokenType.Dedent)) { + return this.finish(node, ParseError.DedentExpected, [TokenType.Newline, TokenType.Indent]); + } + } else { + if (!this.accept(TokenType.CurlyR)) { + return this.finish(node, ParseError.RightCurlyExpected, [TokenType.CurlyR, TokenType.SemiColon]); + } } return this.finish(node); } public _parseBody(node: T, parseDeclaration: () => nodes.Node | null): T { if (!node.setDeclarations(this._parseDeclarations(parseDeclaration))) { + if (this.dialect === "indented") { + return this.finish(node, ParseError.IndentExpected, [TokenType.Dedent]); + } return this.finish(node, ParseError.LeftCurlyExpected, [TokenType.CurlyR, TokenType.SemiColon]); } return this.finish(node); diff --git a/packages/vscode-css-languageservice/src/parser/cssScanner.ts b/packages/vscode-css-languageservice/src/parser/cssScanner.ts index 6f54a6d7..e8f7bf73 100644 --- a/packages/vscode-css-languageservice/src/parser/cssScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/cssScanner.ts @@ -21,6 +21,9 @@ export enum TokenType { SemiColon, CurlyL, CurlyR, + Indent, + Dedent, + Newline, // Acts as the end of a statement in the indented syntax ParenthesisL, ParenthesisR, BracketL, @@ -63,6 +66,17 @@ export class MultiLineStream { private len: number; private position: number; + // Used by the indented dialect to keep track of indents and dedents + private _depth: number = 0; + get depth() { + return this._depth; + } + + set depth(value: number) { + console.log(`depth ${value}`); + this._depth = value; + } + constructor(source: string) { this.source = source; this.len = source.length; @@ -229,6 +243,7 @@ export class Scanner { public ignoreWhitespace = true; public inURL = false; + previousToken: IToken | null = null; dialect; constructor({ dialect }: ScannerOptions = {}) { @@ -240,12 +255,14 @@ export class Scanner { } public finishToken(offset: number, type: TokenType, text?: string): IToken { - return { + const token = { offset: offset, len: this.stream.pos() - offset, type: type, text: text || this.stream.substring(offset), }; + this.previousToken = token; + return token; } public substring(offset: number, len: number): string { @@ -418,16 +435,42 @@ export class Scanner { protected trivia(): IToken | null { while (true) { const offset = this.stream.pos(); - if (this._whitespace()) { - if (!this.ignoreWhitespace) { - return this.finishToken(offset, TokenType.Whitespace); + if (this.dialect === "indented") { + // SassScanner needs to know the amount of whitespace right after a newline + // to figure out indents and dedents, so only advance here if the previous + // token is not a Newline. + if (this.previousToken && this.previousToken.type === TokenType.Newline) { + if (this.comment()) { + if (!this.ignoreComment) { + return this.finishToken(offset, TokenType.Comment); + } + } + return null; } - } else if (this.comment()) { - if (!this.ignoreComment) { - return this.finishToken(offset, TokenType.Comment); + const n = this.stream.advanceWhileChar((ch) => { + return ch === _WSP || ch === _TAB; + }); + if (n > 0 && !this.ignoreWhitespace) { + return this.finishToken(offset, TokenType.Whitespace); + } else if (this.comment()) { + if (!this.ignoreComment) { + return this.finishToken(offset, TokenType.Comment); + } + } else { + return null; } } else { - return null; + if (this._whitespace()) { + if (!this.ignoreWhitespace) { + return this.finishToken(offset, TokenType.Whitespace); + } + } else if (this.comment()) { + if (!this.ignoreComment) { + return this.finishToken(offset, TokenType.Comment); + } + } else { + return null; + } } } } diff --git a/packages/vscode-css-languageservice/src/parser/sassScanner.ts b/packages/vscode-css-languageservice/src/parser/sassScanner.ts index cb931ce0..2b28beea 100644 --- a/packages/vscode-css-languageservice/src/parser/sassScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/sassScanner.ts @@ -10,6 +10,8 @@ const _FSL = "/".charCodeAt(0); const _NWL = "\n".charCodeAt(0); const _CAR = "\r".charCodeAt(0); const _LFD = "\f".charCodeAt(0); +const _WSP = " ".charCodeAt(0); +const _TAB = "\t".charCodeAt(0); const _DLR = "$".charCodeAt(0); const _HSH = "#".charCodeAt(0); @@ -81,6 +83,29 @@ export class SassScanner extends Scanner { return this.finishToken(offset, Ellipsis); } + // indents and dedents for the indented syntax + if (this.dialect === "indented") { + let n = this.stream.advanceWhileChar((ch) => { + return ch === _NWL || ch === _LFD || ch === _CAR; + }); + if (n > 0) { + return this.finishToken(offset, TokenType.Newline); + } + if (this.previousToken && this.previousToken.type === TokenType.Newline) { + n = this.stream.advanceWhileChar((ch) => { + return ch === _TAB || ch === _WSP; + }); + + if (n > this.stream.depth) { + this.stream.depth = n; + return this.finishToken(offset, TokenType.Indent); + } else if (n < this.stream.depth) { + this.stream.depth = n; + return this.finishToken(offset, TokenType.Dedent); + } + } + } + return super.scanNext(offset); } diff --git a/packages/vscode-css-languageservice/src/test/css/parser.test.ts b/packages/vscode-css-languageservice/src/test/css/parser.test.ts index 0de87fb5..01b39f46 100644 --- a/packages/vscode-css-languageservice/src/test/css/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/parser.test.ts @@ -15,14 +15,21 @@ export function assertNode(text: string, parser: Parser, f: (...args: any[]) => assert.ok(node !== null, "no node returned"); const markers = nodes.ParseErrorCollector.entries(node); if (markers.length > 0) { - assert.ok( - false, - "node has errors: " + - markers[0].getMessage() + - ", offset: " + - markers[0].getNode().offset + - " when parsing " + - text, + assert.fail( + `${text} has errors: ` + + markers + .map((marker) => { + const offset = marker.getOffset(); + const before = text.substring(0, offset); + const problem = text.charAt(offset); + const after = text.substring(offset + 1); + + return ` +${marker.getMessage()} at offset ${marker.getOffset()} (👉 follow us 👈) + +${before}👉👉👉${problem}👈👈👈${after}`; + }) + .join("\n"), ); } assert.ok(parser.accept(TokenType.EOF), "Expect scanner at EOF"); diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index 22c48a3b..335cc658 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -1,39 +1,43 @@ import { suite, test } from "vitest"; -import { SassParser } from "../../parser/sassParser"; import { ParseError } from "../../parser/cssErrors"; -import { assertNode, assertError } from "../css/parser.test"; +import { SassParser } from "../../parser/sassParser"; +import { assertError, assertNode } from "../css/parser.test"; suite("Sass - Parser", () => { - test.skip("Comments", function () { + test("CSS comment", () => { const parser = new SassParser({ dialect: "indented" }); assertNode( - ` -a + `a b: /* comment */ c `, parser, parser._parseStylesheet.bind(parser), ); + }); + + test("Sass comment", () => { + const parser = new SassParser({ dialect: "indented" }); assertNode( - ` -a + `a // single-line comment b: c `, parser, parser._parseStylesheet.bind(parser), ); + }); + test("Multi-line CSS comment should error", () => { + const parser = new SassParser({ dialect: "indented" }); assertError( - ` -a + `a b: /* multi line comment */ c `, parser, parser._parseStylesheet.bind(parser), - ParseError.WildcardExpected, // TODO: make one for comment closing? How's it done in CSS? + ParseError.WildcardExpected, // TODO: make one for comment closing? How's it done in CSS if it's missing? ); }); }); From 21bcb3622ecc84754a65f7610dc8f3e954070a5c Mon Sep 17 00:00:00 2001 From: William Killerud Date: Mon, 20 May 2024 14:53:36 +0200 Subject: [PATCH 006/138] refactor: fix comment parsing --- .../src/parser/cssParser.ts | 20 ++++---- .../src/parser/cssScanner.ts | 51 ++++++------------- .../src/parser/sassParser.ts | 30 +++++++---- .../src/parser/sassScanner.ts | 40 +++++++++++++++ .../src/test/sass/parser.test.ts | 2 +- 5 files changed, 86 insertions(+), 57 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 49187de9..7935e637 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -371,7 +371,7 @@ export class Parser { while (this.accept(TokenType.Comma) && this._parseSelector(isNested)) { // loop } - if (this.accept(TokenType.CurlyL)) { + if (this.accept(TokenType.CurlyL) || (this.accept(TokenType.Newline) && this.accept(TokenType.Indent))) { this.restoreAtMark(mark); return this._parseRuleset(isNested); } @@ -464,6 +464,12 @@ export class Parser { return null; } + if (this.dialect === "indented") { + while (this.accept(TokenType.Newline)) { + // accept empty statements/starting with comments + } + } + let decl = parseDeclaration(); while (node.addChild(decl)) { if (this.peek(TokenType.CurlyR) || this.peek(TokenType.Dedent)) { @@ -479,20 +485,12 @@ export class Parser { while (this.accept(TokenType.SemiColon)) { // accept empty statements } - if (this.dialect === "indented") { - while (this.accept(TokenType.Newline)) { - // accept empty statements - } - } decl = parseDeclaration(); } if (this.dialect === "indented") { - if (this.accept(TokenType.EOF)) { - return this.finish(node); - } - if (!this.accept(TokenType.Newline)) { - return this.finish(node, ParseError.NewlineExpected, [TokenType.Dedent]); + while (this.accept(TokenType.Newline)) { + // accept empty statements } if (this.accept(TokenType.EOF)) { return this.finish(node); diff --git a/packages/vscode-css-languageservice/src/parser/cssScanner.ts b/packages/vscode-css-languageservice/src/parser/cssScanner.ts index e8f7bf73..81c6fd9c 100644 --- a/packages/vscode-css-languageservice/src/parser/cssScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/cssScanner.ts @@ -73,7 +73,6 @@ export class MultiLineStream { } set depth(value: number) { - console.log(`depth ${value}`); this._depth = value; } @@ -435,42 +434,16 @@ export class Scanner { protected trivia(): IToken | null { while (true) { const offset = this.stream.pos(); - if (this.dialect === "indented") { - // SassScanner needs to know the amount of whitespace right after a newline - // to figure out indents and dedents, so only advance here if the previous - // token is not a Newline. - if (this.previousToken && this.previousToken.type === TokenType.Newline) { - if (this.comment()) { - if (!this.ignoreComment) { - return this.finishToken(offset, TokenType.Comment); - } - } - return null; - } - const n = this.stream.advanceWhileChar((ch) => { - return ch === _WSP || ch === _TAB; - }); - if (n > 0 && !this.ignoreWhitespace) { + if (this.whitespace()) { + if (!this.ignoreWhitespace) { return this.finishToken(offset, TokenType.Whitespace); - } else if (this.comment()) { - if (!this.ignoreComment) { - return this.finishToken(offset, TokenType.Comment); - } - } else { - return null; } - } else { - if (this._whitespace()) { - if (!this.ignoreWhitespace) { - return this.finishToken(offset, TokenType.Whitespace); - } - } else if (this.comment()) { - if (!this.ignoreComment) { - return this.finishToken(offset, TokenType.Comment); - } - } else { - return null; + } else if (this.comment()) { + if (!this.ignoreComment) { + return this.finishToken(offset, TokenType.Comment); } + } else { + return null; } } } @@ -484,6 +457,14 @@ export class Scanner { success = true; return false; } + + if (this.dialect === "indented") { + // in this dialect multiline comments are not allowed + if (ch === _NWL || ch === _LFD || ch === _CAR) { + return false; + } + } + hot = ch === _MUL; return true; }); @@ -632,7 +613,7 @@ export class Scanner { return hasContent; } - private _whitespace(): boolean { + protected whitespace(): boolean { const n = this.stream.advanceWhileChar((ch) => { return ch === _WSP || ch === _TAB || ch === _NWL || ch === _LFD || ch === _CAR; }); diff --git a/packages/vscode-css-languageservice/src/parser/sassParser.ts b/packages/vscode-css-languageservice/src/parser/sassParser.ts index 905ec107..8bd07012 100644 --- a/packages/vscode-css-languageservice/src/parser/sassParser.ts +++ b/packages/vscode-css-languageservice/src/parser/sassParser.ts @@ -284,9 +284,9 @@ export class SassParser extends cssParser.Parser { } public _parseDeclaration(stopTokens?: TokenType[]): nodes.Declaration | null { - const custonProperty = this._tryParseCustomPropertyDeclaration(stopTokens); - if (custonProperty) { - return custonProperty; + const customProperty = this._tryParseCustomPropertyDeclaration(stopTokens); + if (customProperty) { + return customProperty; } const node = this.create(nodes.Declaration); @@ -306,15 +306,25 @@ export class SassParser extends cssParser.Parser { hasContent = true; node.addChild(this._parsePrio()); } - if (this.peek(TokenType.CurlyL)) { - node.setNestedProperties(this._parseNestedProperties()); + if (this.dialect === "indented") { + if (this.peek(TokenType.Indent)) { + node.setNestedProperties(this._parseNestedProperties()); + } else { + if (!hasContent) { + return this.finish(node, ParseError.PropertyValueExpected); + } + } } else { - if (!hasContent) { - return this.finish(node, ParseError.PropertyValueExpected); + if (this.peek(TokenType.CurlyL)) { + node.setNestedProperties(this._parseNestedProperties()); + } else { + if (!hasContent) { + return this.finish(node, ParseError.PropertyValueExpected); + } + } + if (this.peek(TokenType.SemiColon)) { + node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist } - } - if (this.peek(TokenType.SemiColon)) { - node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist } return this.finish(node); } diff --git a/packages/vscode-css-languageservice/src/parser/sassScanner.ts b/packages/vscode-css-languageservice/src/parser/sassScanner.ts index 2b28beea..dccb8351 100644 --- a/packages/vscode-css-languageservice/src/parser/sassScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/sassScanner.ts @@ -109,6 +109,46 @@ export class SassScanner extends Scanner { return super.scanNext(offset); } + protected trivia(): IToken | null { + while (true) { + const offset = this.stream.pos(); + if (this.whitespace()) { + if (!this.ignoreWhitespace) { + return this.finishToken(offset, TokenType.Whitespace); + } + } else if (this.comment()) { + if (!this.ignoreComment) { + return this.finishToken(offset, TokenType.Comment); + } + } else { + return null; + } + } + } + + protected whitespace(): boolean { + if (this.dialect === "indented") { + // Whitespace is only considered trivial in this dialect if: + // - it is not a newline + // - it is not at the beginning of a line (i. e. is indentation) + // Only advance the stream for _WSP and _TAB if the previous token is not a _NWL, _LFD or _CAR. + + const prevChar = this.stream.lookbackChar(1); + if (prevChar === _NWL || prevChar === _LFD || prevChar === _CAR) { + return false; + } + + const n = this.stream.advanceWhileChar((ch) => { + return ch === _WSP || ch === _TAB; + }); + return n > 0; + } + const n = this.stream.advanceWhileChar((ch) => { + return ch === _WSP || ch === _TAB || ch === _NWL || ch === _LFD || ch === _CAR; + }); + return n > 0; + } + protected comment(): boolean { if (super.comment()) { return true; diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index 335cc658..abd5fc6a 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -37,7 +37,7 @@ comment */ c `, parser, parser._parseStylesheet.bind(parser), - ParseError.WildcardExpected, // TODO: make one for comment closing? How's it done in CSS if it's missing? + ParseError.ColonExpected, // TODO: make one for comment closing? How's it done in CSS if it's missing? ); }); }); From 55e9485867ad3a4e523e9b1e6643638b4ba22ef8 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Mon, 20 May 2024 20:49:19 +0200 Subject: [PATCH 007/138] refactor: consume the preceeding whitespace also when not indent or dedent --- .../src/parser/cssErrors.ts | 1 + .../src/parser/cssParser.ts | 103 +++++++++++----- .../src/parser/cssScanner.ts | 43 +++++-- .../src/parser/sassParser.ts | 6 +- .../src/parser/sassScanner.ts | 25 +--- .../src/test/sass/parser.test.ts | 112 ++++++++++++++++-- 6 files changed, 214 insertions(+), 76 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssErrors.ts b/packages/vscode-css-languageservice/src/parser/cssErrors.ts index 0fb3f859..ff5f4ec3 100644 --- a/packages/vscode-css-languageservice/src/parser/cssErrors.ts +++ b/packages/vscode-css-languageservice/src/parser/cssErrors.ts @@ -58,4 +58,5 @@ export const ParseError = { IdentifierOrWildcardExpected: new CSSIssueType("css-idorwildcardexpected", l10n.t("identifier or wildcard expected")), WildcardExpected: new CSSIssueType("css-wildcardexpected", l10n.t("wildcard expected")), IdentifierOrVariableExpected: new CSSIssueType("css-idorvarexpected", l10n.t("identifier or variable expected")), + UnexpectedSemicolon: new CSSIssueType("css-nosemi", l10n.t("';' is not allowed in indented syntax")), }; diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 7935e637..c4f2f7bf 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -12,6 +12,7 @@ import { isDefined } from "../utils/objects"; export interface IMark { prev?: IToken; + depth: number; curr: IToken; pos: number; } @@ -32,11 +33,11 @@ export class Parser { private lastErrorToken?: IToken; - dialect; + syntax; - constructor({ scanner, dialect }: ParserOptions = {}) { - this.dialect = dialect; - this.scanner = scanner || new Scanner({ dialect }); + constructor({ scanner, syntax: dialect }: ParserOptions = {}) { + this.syntax = dialect; + this.scanner = scanner || new Scanner({ syntax: dialect }); this.token = { type: TokenType.EOF, offset: -1, len: 0, text: "" }; this.prevToken = undefined!; } @@ -99,6 +100,7 @@ export class Parser { return { prev: this.prevToken, curr: this.token, + depth: this.scanner.stream.depth, pos: this.scanner.pos(), }; } @@ -293,6 +295,12 @@ export class Parser { public _parseStylesheet(): nodes.Stylesheet { const node = this.create(nodes.Stylesheet); + if (this.syntax === "indented") { + while (this.accept(TokenType.Newline)) { + // allow empty statements + } + } + while (node.addChild(this._parseStylesheetStart())) { // Parse statements only valid at the beginning of stylesheets. } @@ -366,14 +374,21 @@ export class Parser { } public _tryParseRuleset(isNested: boolean): nodes.RuleSet | null { - const mark = this.mark(); + let mark = this.mark(); if (this._parseSelector(isNested)) { while (this.accept(TokenType.Comma) && this._parseSelector(isNested)) { // loop } - if (this.accept(TokenType.CurlyL) || (this.accept(TokenType.Newline) && this.accept(TokenType.Indent))) { - this.restoreAtMark(mark); - return this._parseRuleset(isNested); + if (this.syntax === "indented") { + if (this.accept(TokenType.Indent)) { + this.restoreAtMark(mark); + return this._parseRuleset(isNested); + } + } else { + if (this.accept(TokenType.CurlyL)) { + this.restoreAtMark(mark); + return this._parseRuleset(isNested); + } } } this.restoreAtMark(mark); @@ -421,10 +436,6 @@ export class Parser { } public _needsSemicolonAfter(node: nodes.Node): boolean { - if (this.dialect === "indented") { - return false; - } - switch (node.type) { case nodes.NodeType.Keyframe: case nodes.NodeType.ViewPort: @@ -460,38 +471,62 @@ export class Parser { public _parseDeclarations(parseDeclaration: () => nodes.Node | null): nodes.Declarations | null { const node = this.create(nodes.Declarations); - if (!this.accept(TokenType.CurlyL) && !(this.accept(TokenType.Newline) && this.accept(TokenType.Indent))) { - return null; - } - if (this.dialect === "indented") { + if (this.syntax === "indented") { + if (!this.accept(TokenType.Indent)) { + return null; + } while (this.accept(TokenType.Newline)) { - // accept empty statements/starting with comments + // accept empty statements + } + } else { + if (!this.accept(TokenType.CurlyL)) { + return null; } } let decl = parseDeclaration(); while (node.addChild(decl)) { - if (this.peek(TokenType.CurlyR) || this.peek(TokenType.Dedent)) { - break; - } - if (this._needsSemicolonAfter(decl) && !this.accept(TokenType.SemiColon)) { - return this.finish(node, ParseError.SemiColonExpected, [TokenType.SemiColon, TokenType.CurlyR]); - } - // We accepted semicolon token. Link it to declaration. - if (decl && this.prevToken && this.prevToken.type === TokenType.SemiColon) { - (decl as nodes.Declaration).semicolonPosition = this.prevToken.offset; - } - while (this.accept(TokenType.SemiColon)) { - // accept empty statements + if (this.syntax === "indented") { + if (this.peek(TokenType.Dedent)) { + break; + } + if (this._needsSemicolonAfter(decl) && !this.accept(TokenType.Newline)) { + if (!this.accept(TokenType.EOF)) { + return this.finish(node, ParseError.NewlineExpected, [TokenType.Newline, TokenType.Dedent]); + } + } + // We accepted newline token. Link it to declaration. + if (decl && this.prevToken && this.prevToken.type === TokenType.Newline) { + (decl as nodes.Declaration).semicolonPosition = this.prevToken.offset; + } + while (this.accept(TokenType.Newline)) { + // accept empty statements + } + } else { + if (this.peek(TokenType.CurlyR)) { + break; + } + if (this._needsSemicolonAfter(decl) && !this.accept(TokenType.SemiColon)) { + return this.finish(node, ParseError.SemiColonExpected, [TokenType.SemiColon, TokenType.CurlyR]); + } + // We accepted semicolon token. Link it to declaration. + if (decl && this.prevToken && this.prevToken.type === TokenType.SemiColon) { + (decl as nodes.Declaration).semicolonPosition = this.prevToken.offset; + } + while (this.accept(TokenType.SemiColon)) { + // accept empty statements + } } + decl = parseDeclaration(); } - if (this.dialect === "indented") { + if (this.syntax === "indented") { while (this.accept(TokenType.Newline)) { // accept empty statements } + // don't raise an error of dedent expected if this is the end of the file if (this.accept(TokenType.EOF)) { return this.finish(node); } @@ -508,7 +543,7 @@ export class Parser { public _parseBody(node: T, parseDeclaration: () => nodes.Node | null): T { if (!node.setDeclarations(this._parseDeclarations(parseDeclaration))) { - if (this.dialect === "indented") { + if (this.syntax === "indented") { return this.finish(node, ParseError.IndentExpected, [TokenType.Dedent]); } return this.finish(node, ParseError.LeftCurlyExpected, [TokenType.CurlyR, TokenType.SemiColon]); @@ -744,6 +779,12 @@ export class Parser { if (!this.accept(TokenType.String)) { return this.finish(node, ParseError.IdentifierExpected); } + if (this.syntax === "indented") { + if (this.accept(TokenType.SemiColon)) { + return this.finish(node, ParseError.UnexpectedSemicolon); + } + return this.finish(node); + } if (!this.accept(TokenType.SemiColon)) { return this.finish(node, ParseError.SemiColonExpected); } diff --git a/packages/vscode-css-languageservice/src/parser/cssScanner.ts b/packages/vscode-css-languageservice/src/parser/cssScanner.ts index 81c6fd9c..f4098e58 100644 --- a/packages/vscode-css-languageservice/src/parser/cssScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/cssScanner.ts @@ -94,8 +94,9 @@ export class MultiLineStream { return this.position; } - public goBackTo(pos: number): void { + public goBackTo(pos: number, depth = 0): void { this.position = pos; + this.depth = depth; } public goBack(n: number): void { @@ -233,7 +234,7 @@ staticUnitTable["cqmin"] = TokenType.ContainerQueryLength; staticUnitTable["cqmax"] = TokenType.ContainerQueryLength; export type ScannerOptions = { - dialect?: "indented"; + syntax?: "indented"; }; export class Scanner { @@ -242,11 +243,10 @@ export class Scanner { public ignoreWhitespace = true; public inURL = false; - previousToken: IToken | null = null; - dialect; + syntax; - constructor({ dialect }: ScannerOptions = {}) { - this.dialect = dialect; + constructor({ syntax }: ScannerOptions = {}) { + this.syntax = syntax; } public setSource(input: string): void { @@ -260,7 +260,6 @@ export class Scanner { type: type, text: text || this.stream.substring(offset), }; - this.previousToken = token; return token; } @@ -272,8 +271,8 @@ export class Scanner { return this.stream.pos(); } - public goBackTo(pos: number): void { - this.stream.goBackTo(pos); + public goBackTo(pos: number, depth = 0): void { + this.stream.goBackTo(pos, depth); } public scanUnquotedString(): IToken | null { @@ -426,6 +425,28 @@ export class Scanner { return this.finishToken(offset, TokenType.SuffixOperator); } + // indents and dedents for the indented syntax + if (this.syntax === "indented") { + let n = this.stream.advanceWhileChar((ch) => { + return ch === _NWL || ch === _LFD || ch === _CAR; + }); + if (n > 0) { + n = this.stream.advanceWhileChar((ch) => { + return ch === _TAB || ch === _WSP; + }); + + if (n > this.stream.depth) { + this.stream.depth = n; + return this.finishToken(offset, TokenType.Indent); + } else if (n < this.stream.depth) { + this.stream.depth = n; + return this.finishToken(offset, TokenType.Dedent); + } else { + return this.finishToken(offset, TokenType.Newline); + } + } + } + // Delim this.stream.nextChar(); return this.finishToken(offset, TokenType.Delim); @@ -458,8 +479,8 @@ export class Scanner { return false; } - if (this.dialect === "indented") { - // in this dialect multiline comments are not allowed + if (this.syntax === "indented") { + // in this dialect multiline comments are notallowed if (ch === _NWL || ch === _LFD || ch === _CAR) { return false; } diff --git a/packages/vscode-css-languageservice/src/parser/sassParser.ts b/packages/vscode-css-languageservice/src/parser/sassParser.ts index 8bd07012..154e4287 100644 --- a/packages/vscode-css-languageservice/src/parser/sassParser.ts +++ b/packages/vscode-css-languageservice/src/parser/sassParser.ts @@ -16,8 +16,8 @@ import { ParseError } from "./cssErrors"; /// http://sass-lang.com/documentation/file.SASS_REFERENCE.html /// export class SassParser extends cssParser.Parser { - public constructor({ dialect }: ScannerOptions = {}) { - super({ dialect, scanner: new scssScanner.SassScanner({ dialect }) }); + public constructor({ syntax: dialect }: ScannerOptions = {}) { + super({ syntax: dialect, scanner: new scssScanner.SassScanner({ syntax: dialect }) }); } public _parseStylesheetStatement(isNested: boolean = false): nodes.Node | null { @@ -306,7 +306,7 @@ export class SassParser extends cssParser.Parser { hasContent = true; node.addChild(this._parsePrio()); } - if (this.dialect === "indented") { + if (this.syntax === "indented") { if (this.peek(TokenType.Indent)) { node.setNestedProperties(this._parseNestedProperties()); } else { diff --git a/packages/vscode-css-languageservice/src/parser/sassScanner.ts b/packages/vscode-css-languageservice/src/parser/sassScanner.ts index dccb8351..e768f82a 100644 --- a/packages/vscode-css-languageservice/src/parser/sassScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/sassScanner.ts @@ -83,29 +83,6 @@ export class SassScanner extends Scanner { return this.finishToken(offset, Ellipsis); } - // indents and dedents for the indented syntax - if (this.dialect === "indented") { - let n = this.stream.advanceWhileChar((ch) => { - return ch === _NWL || ch === _LFD || ch === _CAR; - }); - if (n > 0) { - return this.finishToken(offset, TokenType.Newline); - } - if (this.previousToken && this.previousToken.type === TokenType.Newline) { - n = this.stream.advanceWhileChar((ch) => { - return ch === _TAB || ch === _WSP; - }); - - if (n > this.stream.depth) { - this.stream.depth = n; - return this.finishToken(offset, TokenType.Indent); - } else if (n < this.stream.depth) { - this.stream.depth = n; - return this.finishToken(offset, TokenType.Dedent); - } - } - } - return super.scanNext(offset); } @@ -127,7 +104,7 @@ export class SassScanner extends Scanner { } protected whitespace(): boolean { - if (this.dialect === "indented") { + if (this.syntax === "indented") { // Whitespace is only considered trivial in this dialect if: // - it is not a newline // - it is not at the beginning of a line (i. e. is indentation) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index abd5fc6a..e07c0c30 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -1,34 +1,64 @@ import { suite, test } from "vitest"; import { ParseError } from "../../parser/cssErrors"; +import { SassParseError } from "../../parser/sassErrors"; import { SassParser } from "../../parser/sassParser"; import { assertError, assertNode } from "../css/parser.test"; suite("Sass - Parser", () => { + const parser = new SassParser({ syntax: "indented" }); + const parseStylesheet = parser._parseStylesheet.bind(parser); + + test("empty stylesheet", () => { + assertNode("", parser, parseStylesheet); + }); + + test("@charset", () => { + assertNode('@charset "demo"', parser, parseStylesheet); + assertError('@charset "demo";', parser, parseStylesheet, ParseError.UnexpectedSemicolon); + }); + + test("newline before at-rule", () => { + assertNode( + ` +@charset "demo"`, + parser, + parseStylesheet, + ); + }); + + test("declarations", () => { + assertNode( + `body + margin: 0px + padding: 3em, 6em +`, + parser, + parseStylesheet, + ); + }); + test("CSS comment", () => { - const parser = new SassParser({ dialect: "indented" }); assertNode( `a b: /* comment */ c `, parser, - parser._parseStylesheet.bind(parser), + parseStylesheet, ); }); test("Sass comment", () => { - const parser = new SassParser({ dialect: "indented" }); assertNode( `a // single-line comment b: c `, parser, - parser._parseStylesheet.bind(parser), + parseStylesheet, ); }); test("Multi-line CSS comment should error", () => { - const parser = new SassParser({ dialect: "indented" }); assertError( `a b: /* multi @@ -36,8 +66,76 @@ line comment */ c `, parser, - parser._parseStylesheet.bind(parser), - ParseError.ColonExpected, // TODO: make one for comment closing? How's it done in CSS if it's missing? + parseStylesheet, + ParseError.ColonExpected, + ); + }); + + test("@media", () => { + assertNode( + `@media screen, projection + a + b: c`, + parser, + parseStylesheet, + ); + assertNode( + `@media screen and (max-width: 400px) + @-ms-viewport + width: 320px`, + parser, + parseStylesheet, + ); + assertNode( + `@-ms-viewport + width: 320px + height: 720px`, + parser, + parseStylesheet, + ); + }); + + test("selectors", () => { + assertNode( + ` +#boo, far + a: b + +.far boo + c: d +`, + parser, + parseStylesheet, + ); + }); + + test("nested selectors", () => { + assertNode( + ` +#boo, far + a: b + + &:hover + a: c + + d: e +`, + parser, + parseStylesheet, + ); + }); + + test("@-moz-keyframes", () => { + assertNode( + ` +@-moz-keyframes darkWordHighlight + from + background-color: inherit + to + background-color: rgba(83, 83, 83, 0.7) +`, + parser, + parseStylesheet, ); }); }); From 7987b5c099f1c34757f2c845560d82dc186100d7 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Wed, 22 May 2024 19:18:08 +0200 Subject: [PATCH 008/138] refactor: restore the depth when going back in the stream --- .../src/parser/cssParser.ts | 37 +++++++++++-------- .../src/parser/cssScanner.ts | 10 +++-- .../src/parser/sassParser.ts | 8 ++-- .../src/parser/sassScanner.ts | 3 +- .../src/services/cssFolding.ts | 1 + .../src/test/sass/parser.test.ts | 2 +- 6 files changed, 35 insertions(+), 26 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index c4f2f7bf..99e1e647 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -108,7 +108,7 @@ export class Parser { public restoreAtMark(mark: IMark): void { this.prevToken = mark.prev; this.token = mark.curr; - this.scanner.goBackTo(mark.pos); + this.scanner.goBackTo(mark.pos, mark.depth); } public try(func: () => nodes.Node | null): nodes.Node | null { @@ -181,14 +181,15 @@ export class Parser { protected acceptUnquotedString(): boolean { const pos = this.scanner.pos(); - this.scanner.goBackTo(this.token.offset); + const depth = this.scanner.stream.depth; + this.scanner.goBackTo(this.token.offset, depth); const unquoted = this.scanner.scanUnquotedString(); if (unquoted) { this.token = unquoted; this.consumeToken(); return true; } - this.scanner.goBackTo(pos); + this.scanner.goBackTo(pos, depth); return false; } @@ -476,6 +477,10 @@ export class Parser { if (!this.accept(TokenType.Indent)) { return null; } + // What causes this extra redundant Indent token? + // while (this.accept(TokenType.Indent)) { + // // accept empty statements + // } while (this.accept(TokenType.Newline)) { // accept empty statements } @@ -614,7 +619,7 @@ export class Parser { } const mark = this.mark(); - if (this.peek(TokenType.CurlyL)) { + if (this.peek(TokenType.CurlyL) || this.peek(TokenType.Indent)) { // try to parse it as nested declaration const propertySet = this.create(nodes.CustomPropertySet); const declarations = this._parseDeclarations(this._parseRuleSetDeclaration.bind(this)); @@ -978,7 +983,7 @@ export class Parser { } } - if (!this.peek(TokenType.CurlyL)) { + if (!this.peek(TokenType.CurlyL) && !this.peek(TokenType.Indent)) { this.restoreAtMark(pos); return null; } @@ -1019,7 +1024,7 @@ export class Parser { if (names) { node.setNames(names); } - if ((!names || names.getChildren().length === 1) && this.peek(TokenType.CurlyL)) { + if ((!names || names.getChildren().length === 1) && (this.peek(TokenType.CurlyL) || this.peek(TokenType.Indent))) { return this._parseBody(node, this._parseLayerDeclaration.bind(this, isNested)); } if (!this.accept(TokenType.SemiColon)) { @@ -1235,7 +1240,7 @@ export class Parser { while (parseExpression) { if (!this.accept(TokenType.ParenthesisL)) { - return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL, TokenType.Indent]); } if (this.peek(TokenType.ParenthesisL) || this.peekIdent("not")) { // @@ -1245,7 +1250,7 @@ export class Parser { } // not yet implemented: general enclosed if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL, TokenType.Indent]); } parseExpression = this.acceptIdent("and") || this.acceptIdent("or"); } @@ -1358,7 +1363,7 @@ export class Parser { const node = this.create(nodes.PageBoxMarginBox); if (!this.acceptOneKeyword(languageFacts.pageBoxDirectives)) { - this.markError(node, ParseError.UnknownAtRule, [], [TokenType.CurlyL]); + this.markError(node, ParseError.UnknownAtRule, [], [TokenType.CurlyL, TokenType.Indent]); } return this._parseBody(node, this._parseRuleSetDeclaration.bind(this)); @@ -1390,7 +1395,7 @@ export class Parser { const node = this.create(nodes.Document); this.consumeToken(); // @-moz-document - this.resync([], [TokenType.CurlyL]); // ignore all the rules + this.resync([], [TokenType.CurlyL, TokenType.Indent]); // ignore all the rules return this._parseBody(node, this._parseStylesheetStatement.bind(this)); } @@ -1449,18 +1454,18 @@ export class Parser { node.addChild(this._parseMediaFeature()); } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL, TokenType.Indent]); } } else if (this.acceptIdent("style")) { if (this.hasWhitespace() || !this.accept(TokenType.ParenthesisL)) { - return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL, TokenType.Indent]); } node.addChild(this._parseStyleQuery()); if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL, TokenType.Indent]); } } else { - return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL, TokenType.Indent]); } return this.finish(node); } @@ -1498,10 +1503,10 @@ export class Parser { if (this.accept(TokenType.ParenthesisL)) { node.addChild(this._parseStyleQuery()); if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish(node, ParseError.RightParenthesisExpected, [], [TokenType.CurlyL, TokenType.Indent]); } } else { - return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL]); + return this.finish(node, ParseError.LeftParenthesisExpected, [], [TokenType.CurlyL, TokenType.Indent]); } return this.finish(node); } diff --git a/packages/vscode-css-languageservice/src/parser/cssScanner.ts b/packages/vscode-css-languageservice/src/parser/cssScanner.ts index f4098e58..a6dabe23 100644 --- a/packages/vscode-css-languageservice/src/parser/cssScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/cssScanner.ts @@ -94,7 +94,7 @@ export class MultiLineStream { return this.position; } - public goBackTo(pos: number, depth = 0): void { + public goBackTo(pos: number, depth: number): void { this.position = pos; this.depth = depth; } @@ -271,7 +271,7 @@ export class Scanner { return this.stream.pos(); } - public goBackTo(pos: number, depth = 0): void { + public goBackTo(pos: number, depth: number): void { this.stream.goBackTo(pos, depth); } @@ -307,10 +307,11 @@ export class Scanner { */ public tryScanUnicode(): IToken | undefined { const offset = this.stream.pos(); + const depth = this.stream.depth; if (!this.stream.eos() && this._unicodeRange()) { return this.finishToken(offset, TokenType.UnicodeRange); } - this.stream.goBackTo(offset); + this.stream.goBackTo(offset, depth); return undefined; } @@ -651,6 +652,7 @@ export class Scanner { protected ident(result: string[]): boolean { const pos = this.stream.pos(); + const depth = this.stream.depth; const hasMinus = this._minus(result); if (hasMinus) { if (this._minus(result) /* -- */ || this._identFirstChar(result) || this._escape(result)) { @@ -665,7 +667,7 @@ export class Scanner { } return true; } - this.stream.goBackTo(pos); + this.stream.goBackTo(pos, depth); return false; } diff --git a/packages/vscode-css-languageservice/src/parser/sassParser.ts b/packages/vscode-css-languageservice/src/parser/sassParser.ts index 154e4287..81b461e9 100644 --- a/packages/vscode-css-languageservice/src/parser/sassParser.ts +++ b/packages/vscode-css-languageservice/src/parser/sassParser.ts @@ -465,7 +465,7 @@ export class SassParser extends cssParser.Parser { if (this.acceptKeyword("@else")) { if (this.peekIdent("if")) { node.setElseClause(this._internalParseIfStatement(parseStatement)); - } else if (this.peek(TokenType.CurlyL)) { + } else if (this.peek(TokenType.CurlyL) || this.peek(TokenType.Indent)) { const elseNode = this.create(nodes.ElseStatement); this._parseBody(elseNode, parseStatement); node.setElseClause(elseNode); @@ -733,7 +733,7 @@ export class SassParser extends cssParser.Parser { const node = this.create(nodes.MixinContentDeclaration); if (this.acceptIdent("using")) { if (!this.accept(TokenType.ParenthesisL)) { - return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.CurlyL]); + return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.CurlyL, TokenType.Indent]); } if (node.getParameters().addChild(this._parseParameterDeclaration())) { while (this.accept(TokenType.Comma)) { @@ -747,11 +747,11 @@ export class SassParser extends cssParser.Parser { } if (!this.accept(TokenType.ParenthesisR)) { - return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyL]); + return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyL, TokenType.Indent]); } } - if (this.peek(TokenType.CurlyL)) { + if (this.peek(TokenType.CurlyL) || this.peek(TokenType.Indent)) { this._parseBody(node, this._parseMixinReferenceBodyStatement.bind(this)); } diff --git a/packages/vscode-css-languageservice/src/parser/sassScanner.ts b/packages/vscode-css-languageservice/src/parser/sassScanner.ts index e768f82a..6f6891dc 100644 --- a/packages/vscode-css-languageservice/src/parser/sassScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/sassScanner.ts @@ -37,13 +37,14 @@ export const Module: TokenType = customTokenValue++; export class SassScanner extends Scanner { protected scanNext(offset: number): IToken { + const depth = this.stream.depth; // scss variable if (this.stream.advanceIfChar(_DLR)) { const content = ["$"]; if (this.ident(content)) { return this.finishToken(offset, VariableName, content.join("")); } else { - this.stream.goBackTo(offset); + this.stream.goBackTo(offset, depth); } } diff --git a/packages/vscode-css-languageservice/src/services/cssFolding.ts b/packages/vscode-css-languageservice/src/services/cssFolding.ts index 930ed263..1769e03e 100644 --- a/packages/vscode-css-languageservice/src/services/cssFolding.ts +++ b/packages/vscode-css-languageservice/src/services/cssFolding.ts @@ -59,6 +59,7 @@ function computeFoldingRanges(document: TextDocument): FoldingRange[] { while (token.type !== TokenType.EOF) { switch (token.type) { case TokenType.CurlyL: + case TokenType.Indent: case InterpolationFunction: { delimiterStack.push({ line: getStartLine(token), type: "brace", isStart: true }); break; diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index e07c0c30..3c8228d5 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -67,7 +67,7 @@ comment */ c `, parser, parseStylesheet, - ParseError.ColonExpected, + ParseError.PropertyValueExpected, ); }); From 9992b7527d59f07f37d3584aa89343b2c6eeb9cf Mon Sep 17 00:00:00 2001 From: William Killerud Date: Wed, 22 May 2024 19:33:31 +0200 Subject: [PATCH 009/138] refactor: no semicolon errors in indented --- .../src/parser/cssParser.ts | 29 ++++--- .../src/parser/sassParser.ts | 20 ++++- .../src/test/sass/parser.test.ts | 85 ++++++++++++++++++- 3 files changed, 118 insertions(+), 16 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 99e1e647..cc9e462d 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -316,7 +316,12 @@ export class Parser { node.addChild(statement); hasMatch = true; inRecovery = false; - if (!this.peek(TokenType.EOF) && this._needsSemicolonAfter(statement) && !this.accept(TokenType.SemiColon)) { + if ( + this.syntax !== "indented" && + !this.peek(TokenType.EOF) && + this._needsSemicolonAfter(statement) && + !this.accept(TokenType.SemiColon) + ) { this.markError(node, ParseError.SemiColonExpected); } } @@ -512,7 +517,7 @@ export class Parser { if (this.peek(TokenType.CurlyR)) { break; } - if (this._needsSemicolonAfter(decl) && !this.accept(TokenType.SemiColon)) { + if (this.syntax !== "indented" && this._needsSemicolonAfter(decl) && !this.accept(TokenType.SemiColon)) { return this.finish(node, ParseError.SemiColonExpected, [TokenType.SemiColon, TokenType.CurlyR]); } // We accepted semicolon token. Link it to declaration. @@ -784,15 +789,13 @@ export class Parser { if (!this.accept(TokenType.String)) { return this.finish(node, ParseError.IdentifierExpected); } - if (this.syntax === "indented") { - if (this.accept(TokenType.SemiColon)) { - return this.finish(node, ParseError.UnexpectedSemicolon); - } - return this.finish(node); - } - if (!this.accept(TokenType.SemiColon)) { + + if (this.syntax === "indented" && this.accept(TokenType.SemiColon)) { + return this.finish(node, ParseError.UnexpectedSemicolon); + } else if (this.syntax !== "indented" && !this.accept(TokenType.SemiColon)) { return this.finish(node, ParseError.SemiColonExpected); } + return this.finish(node); } @@ -862,7 +865,9 @@ export class Parser { } } - if (!this.accept(TokenType.SemiColon)) { + if (this.syntax === "indented" && this.accept(TokenType.SemiColon)) { + return this.finish(node, ParseError.UnexpectedSemicolon); + } else if (this.syntax !== "indented" && !this.accept(TokenType.SemiColon)) { return this.finish(node, ParseError.SemiColonExpected); } @@ -1027,7 +1032,9 @@ export class Parser { if ((!names || names.getChildren().length === 1) && (this.peek(TokenType.CurlyL) || this.peek(TokenType.Indent))) { return this._parseBody(node, this._parseLayerDeclaration.bind(this, isNested)); } - if (!this.accept(TokenType.SemiColon)) { + if (this.syntax === "indented" && this.accept(TokenType.SemiColon)) { + return this.finish(node, ParseError.UnexpectedSemicolon); + } else if (this.syntax !== "indented" && !this.accept(TokenType.SemiColon)) { return this.finish(node, ParseError.SemiColonExpected); } return this.finish(node); diff --git a/packages/vscode-css-languageservice/src/parser/sassParser.ts b/packages/vscode-css-languageservice/src/parser/sassParser.ts index 81b461e9..fc0e1737 100644 --- a/packages/vscode-css-languageservice/src/parser/sassParser.ts +++ b/packages/vscode-css-languageservice/src/parser/sassParser.ts @@ -895,8 +895,14 @@ export class SassParser extends cssParser.Parser { } } - if (!this.accept(TokenType.SemiColon) && !this.accept(TokenType.EOF)) { - return this.finish(node, ParseError.SemiColonExpected); + if (this.syntax === "indented") { + if (this.accept(TokenType.SemiColon)) { + return this.finish(node, ParseError.UnexpectedSemicolon); + } + } else { + if (!this.accept(TokenType.SemiColon) && !this.accept(TokenType.EOF)) { + return this.finish(node, ParseError.SemiColonExpected); + } } return this.finish(node); @@ -974,8 +980,14 @@ export class SassParser extends cssParser.Parser { } } - if (!this.accept(TokenType.SemiColon) && !this.accept(TokenType.EOF)) { - return this.finish(node, ParseError.SemiColonExpected); + if (this.syntax === "indented") { + if (this.accept(TokenType.SemiColon)) { + return this.finish(node, ParseError.UnexpectedSemicolon); + } + } else { + if (!this.accept(TokenType.SemiColon) && !this.accept(TokenType.EOF)) { + return this.finish(node, ParseError.SemiColonExpected); + } } return this.finish(node); diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index 3c8228d5..6935caef 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -1,6 +1,5 @@ import { suite, test } from "vitest"; import { ParseError } from "../../parser/cssErrors"; -import { SassParseError } from "../../parser/sassErrors"; import { SassParser } from "../../parser/sassParser"; import { assertError, assertNode } from "../css/parser.test"; @@ -14,6 +13,7 @@ suite("Sass - Parser", () => { test("@charset", () => { assertNode('@charset "demo"', parser, parseStylesheet); + assertError("@charset", parser, parseStylesheet, ParseError.IdentifierExpected); assertError('@charset "demo";', parser, parseStylesheet, ParseError.UnexpectedSemicolon); }); @@ -138,4 +138,87 @@ comment */ c parseStylesheet, ); }); + + test("@page", () => { + assertNode( + ` +@page + margin: 2.5cm +`, + parser, + parseStylesheet, + ); + }); + + test("@font-face", () => { + assertNode( + ` +@font-face + font-family: "Example Font" +`, + parser, + parseStylesheet, + ); + }); + + test("@namespace", () => { + assertNode(`@namespace "http://www.w3.org/1999/xhtml"`, parser, parseStylesheet); + assertNode(`@namespace pref url(http://test)`, parser, parseStylesheet); + assertError("@charset", parser, parseStylesheet, ParseError.IdentifierExpected); + }); + + test("@-moz-document", () => { + assertNode( + ` +@-moz-document url(http://test), url-prefix(http://www.w3.org/Style/) + body + color: purple + background: yellow +`, + parser, + parseStylesheet, + ); + }); + + test("attribute selectors", () => { + assertNode( + `E E[foo] E[foo="bar"] E[foo~="bar"] E[foo^="bar"] E[foo$="bar"] E[foo*="bar"] E[foo|="en"] + color: limegreen`, + parser, + parseStylesheet, + ); + assertNode( + `input[type="submit"] + color: limegreen`, + parser, + parseStylesheet, + ); + }); + + test("pseudo-class selectors", () => { + assertNode( + `E:root E:nth-child(n) E:nth-last-child(n) E:nth-of-type(n) E:nth-last-of-type(n) E:first-child E:last-child + color: limegreen`, + parser, + parseStylesheet, + ); + assertNode( + `E:first-of-type E:last-of-type E:only-child E:only-of-type E:empty E:link E:visited E:active E:hover E:focus E:target E:lang(fr) E:enabled E:disabled E:checked + color: limegreen`, + parser, + parseStylesheet, + ); + assertNode( + `E::first-line E::first-letter E::before E::after + color: limegreen`, + parser, + parseStylesheet, + ); + assertNode( + `E.warning E#myid E:not(s) + color: limegreen`, + parser, + parseStylesheet, + ); + }); }); From a0232141b16a88bb20e81cba1eb580265dd47939 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Wed, 22 May 2024 20:41:55 +0200 Subject: [PATCH 010/138] refactor: adapt to not necesarily matching amount of dedent --- .../src/parser/cssParser.ts | 27 +++++++- .../src/test/sass/parser.test.ts | 69 ++++++++++++++++++- 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index cc9e462d..fa104dc2 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -325,7 +325,12 @@ export class Parser { this.markError(node, ParseError.SemiColonExpected); } } - while (this.accept(TokenType.SemiColon) || this.accept(TokenType.CDO) || this.accept(TokenType.CDC)) { + while ( + this.accept(TokenType.Newline) || + this.accept(TokenType.SemiColon) || + this.accept(TokenType.CDO) || + this.accept(TokenType.CDC) + ) { // accept empty statements hasMatch = true; inRecovery = false; @@ -731,6 +736,9 @@ export class Parser { case TokenType.BadString: // fall through break done; case TokenType.EOF: + if (this.syntax === "indented") { + break; + } // We shouldn't have reached the end of input, something is // unterminated. let error = ParseError.RightCurlyExpected; @@ -1540,7 +1548,7 @@ export class Parser { } break; case TokenType.EOF: - if (curlyDepth > 0) { + if (curlyDepth > 0 && this.syntax !== "indented") { return this.finish(node, ParseError.RightCurlyExpected); } else if (bracketsDepth > 0) { return this.finish(node, ParseError.RightSquareBracketExpected); @@ -1550,16 +1558,26 @@ export class Parser { return this.finish(node); } case TokenType.CurlyL: + case TokenType.Indent: curlyLCount++; curlyDepth++; break; + case TokenType.Dedent: case TokenType.CurlyR: - curlyDepth--; + if (this.token.type === TokenType.Dedent) { + curlyDepth = this.scanner.stream.depth; + } else { + curlyDepth--; + } + // End of at-rule, consume CurlyR and return node if (curlyLCount > 0 && curlyDepth === 0) { this.consumeToken(); if (bracketsDepth > 0) { + if (this.syntax === "indented" && !this.accept(TokenType.EOF)) { + return this.finish(node, ParseError.DedentExpected); + } return this.finish(node, ParseError.RightSquareBracketExpected); } else if (parensDepth > 0) { return this.finish(node, ParseError.RightParenthesisExpected); @@ -1572,6 +1590,9 @@ export class Parser { if (parensDepth === 0 && bracketsDepth === 0) { break done; } + if (this.syntax === "indented") { + return this.finish(node, ParseError.IndentExpected); + } return this.finish(node, ParseError.LeftCurlyExpected); } break; diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index 6935caef..1d590b67 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -1,6 +1,7 @@ -import { suite, test } from "vitest"; +import { suite, test, assert } from "vitest"; import { ParseError } from "../../parser/cssErrors"; import { SassParser } from "../../parser/sassParser"; +import * as nodes from "../../parser/cssNodes"; import { assertError, assertNode } from "../css/parser.test"; suite("Sass - Parser", () => { @@ -221,4 +222,70 @@ comment */ c parseStylesheet, ); }); + + test("graceful handling of unknown rules", () => { + assertNode(`@unknown-rule`, parser, parseStylesheet); + assertNode(`@unknown-rule 'foo'`, parser, parseStylesheet); + assertNode(`@unknown-rule (foo)`, parser, parseStylesheet); + assertNode( + `@unknown-rule (foo) + .bar`, + parser, + parseStylesheet, + ); + assertNode( + `@mskeyframes darkWordHighlight + from + background-color: inherit + + to + background-color: rgba(83, 83, 83, 0.7)`, + parser, + parseStylesheet, + ); + assertNode( + `foo + @unknown-rule`, + parser, + parseStylesheet, + ); + + assertError(`@unknown-rule (`, parser, parseStylesheet, ParseError.RightParenthesisExpected); + assertError(`@unknown-rule [foo`, parser, parseStylesheet, ParseError.RightSquareBracketExpected); + assertError( + `@unknown-rule + [foo`, + parser, + parseStylesheet, + ParseError.RightSquareBracketExpected, + ); + }); + + test("unknown rules node ends properly", () => { + const node = assertNode( + `@unknown-rule (foo) + .bar + color: limegreen + +.foo + color: red`, + parser, + parseStylesheet, + ); + + const unknownAtRule = node.getChild(0)!; + assert.equal(unknownAtRule.type, nodes.NodeType.UnknownAtRule); + assert.equal(unknownAtRule.offset, 0); + assert.equal(node.getChild(0)!.length, 13); + + assertNode( + ` +.foo + @apply p-4 bg-neutral-50 + min-height: var(--space-14) +`, + parser, + parseStylesheet, + ); + }); }); From 92f0cee2efd643903dd2379caf97a05181283bfd Mon Sep 17 00:00:00 2001 From: William Killerud Date: Wed, 22 May 2024 21:24:07 +0200 Subject: [PATCH 011/138] test: test coverage --- .../src/test/sass/parser.test.ts | 253 ++++++++++++++++++ 1 file changed, 253 insertions(+) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index 1d590b67..016d8891 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -7,6 +7,8 @@ import { assertError, assertNode } from "../css/parser.test"; suite("Sass - Parser", () => { const parser = new SassParser({ syntax: "indented" }); const parseStylesheet = parser._parseStylesheet.bind(parser); + const parseKeyframeSelector = parser._parseKeyframeSelector.bind(parser); + const parseKeyframe = parser._parseKeyframe.bind(parser); test("empty stylesheet", () => { assertNode("", parser, parseStylesheet); @@ -156,6 +158,31 @@ comment */ c ` @font-face font-family: "Example Font" +`, + parser, + parseStylesheet, + ); + assertNode( + ` +@font-face + src: url(http://test) +`, + parser, + parseStylesheet, + ); + assertNode( + ` +@font-face + font-style: normal + font-stretch: normal +`, + parser, + parseStylesheet, + ); + assertNode( + ` +@font-face + unicode-range: U+0021-007F `, parser, parseStylesheet, @@ -288,4 +315,230 @@ comment */ c parseStylesheet, ); }); + + test("stylesheet /panic/", () => { + assertError('- @import "foo"', parser, parseStylesheet, ParseError.RuleOrSelectorExpected); + }); + + test("@keyframe selector", () => { + assertNode( + `from + color: red`, + parser, + parseKeyframeSelector, + ); + assertNode( + `to + color: blue`, + parser, + parseKeyframeSelector, + ); + assertNode( + `0% + color: blue`, + parser, + parseKeyframeSelector, + ); + assertNode( + `10% + color: purple`, + parser, + parseKeyframeSelector, + ); + assertNode( + `cover 10% + color: purple`, + parser, + parseKeyframeSelector, + ); + assertNode( + `100000% + color: purple`, + parser, + parseKeyframeSelector, + ); + assertNode( + `from + width: 100 + to: 10px`, + parser, + parseKeyframeSelector, + ); + assertNode( + `from, to + width: 10px`, + parser, + parseKeyframeSelector, + ); + assertNode( + `10%, to + width: 10px`, + parser, + parseKeyframeSelector, + ); + assertNode( + `from, 20% + width: 10px`, + parser, + parseKeyframeSelector, + ); + assertNode( + `10%, 20% + width: 10px`, + parser, + parseKeyframeSelector, + ); + assertNode( + `cover 10%, exit 20% + width: 10px`, + parser, + parseKeyframeSelector, + ); + assertNode( + `10%, exit 20% + width: 10px`, + parser, + parseKeyframeSelector, + ); + assertNode( + `from, exit 20% + width: 10px`, + parser, + parseKeyframeSelector, + ); + assertNode( + `cover 10%, to + width: 10px`, + parser, + parseKeyframeSelector, + ); + assertNode( + `cover 10%, 20% + width: 10px`, + parser, + parseKeyframeSelector, + ); + }); + + test("@keyframe", () => { + assertNode( + `@keyframes name + //`, + parser, + parseKeyframe, + ); + assertNode( + `@-webkit-keyframes name + //`, + parser, + parseKeyframe, + ); + assertNode( + `@-o-keyframes name + //`, + parser, + parseKeyframe, + ); + assertNode( + `@-moz-keyframes name + //`, + parser, + parseKeyframe, + ); + assertNode( + `@keyframes name + from + // + to + //`, + parser, + parseKeyframe, + ); + assertNode( + `@keyframes name + from + // + 80% + // + 100% + //`, + parser, + parseKeyframe, + ); + assertNode( + `@keyframes name + from + top: 0px + 80% + top: 100px + 100% + top: 50px`, + parser, + parseKeyframe, + ); + assertNode( + `@keyframes name + from + top: 0px + 70%, 80% + top: 100px + 100% + top: 50px`, + parser, + parseKeyframe, + ); + assertNode( + `@keyframes name + from + top: 0px + left: 1px + right: 2px`, + parser, + parseKeyframe, + ); + assertNode( + `@keyframes name + exit 50% + top: 0px + left: 1px + right: 2px`, + parser, + parseKeyframe, + ); + + assertError("@keyframes )", parser, parseKeyframe, ParseError.IdentifierExpected); + assertError( + `@keyframes name + from, #123`, + parser, + parseKeyframe, + ParseError.DedentExpected, // Not as accurate as SCSS, but good enough + ); + assertError( + `@keyframes name + 10% from + top: 0px`, + parser, + parseKeyframe, + ParseError.DedentExpected, + ); + assertError( + `@keyframes name + 10% 20% + top: 0px`, + parser, + parseKeyframe, + ParseError.DedentExpected, + ); + assertError( + `@keyframes name + from to + top: 0px`, + parser, + parseKeyframe, + ParseError.DedentExpected, + ); + }); + + test.todo("@property"); }); From 7b01be86b1b179be0836593ea89dd52fd38896e7 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Thu, 23 May 2024 19:31:03 +0200 Subject: [PATCH 012/138] test: property --- .../src/test/sass/parser.test.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index 016d8891..0368894e 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -540,5 +540,24 @@ comment */ c ); }); - test.todo("@property"); + test("@property", () => { + assertNode( + `@property --my-color + syntax: '' + inherits: false + initial-value: #c0ffee`, + parser, + parseStylesheet, + ); + + assertError( + `@property + syntax: '' + inherits: false + initial-value: #c0ffee`, + parser, + parseStylesheet, + ParseError.IdentifierExpected, + ); + }); }); From 80ba2112e4298d32ca7526a19f79f7abf7595339 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Thu, 23 May 2024 19:39:54 +0200 Subject: [PATCH 013/138] test: import --- .../src/test/sass/parser.test.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index 0368894e..c8048fb6 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -9,6 +9,7 @@ suite("Sass - Parser", () => { const parseStylesheet = parser._parseStylesheet.bind(parser); const parseKeyframeSelector = parser._parseKeyframeSelector.bind(parser); const parseKeyframe = parser._parseKeyframe.bind(parser); + const parseImport = parser._parseImport.bind(parser); test("empty stylesheet", () => { assertNode("", parser, parseStylesheet); @@ -560,4 +561,59 @@ comment */ c ParseError.IdentifierExpected, ); }); + + test("@container", () => { + assertNode( + `@container (width <= 150px) + #inner + background-color: skyblue`, + parser, + parseStylesheet, + ); + assertNode( + `@container card (inline-size > 30em) and style(--responsive: true) + #inner + background-color: skyblue`, + parser, + parseStylesheet, + ); + assertNode( + ` +@container card (inline-size > 30em) + @container style(--responsive: true) + #inner + background-color: skyblue`, + parser, + parseStylesheet, + ); + assertNode( + ` +@container (min-width: 700px) + .card h2 + font-size: max(1.5em, 1.23em + 2cqi)`, + parser, + parseStylesheet, + ); + }); + + test("@import", () => { + assertNode(`@import "asdfasdf"`, parser, parseImport); + assertNode(`@ImPort "asdfasdf"`, parser, parseImport); + assertNode(`@import url(/css/screen.css) screen, projection`, parser, parseImport); + assertNode(`@import url('landscape.css') screen and (orientation:landscape)`, parser, parseImport); + assertNode(`@import url("/inc/Styles/full.css") (min-width: 940px)`, parser, parseImport); + assertNode(`@import url(style.css) screen and (min-width:600px)`, parser, parseImport); + assertNode(`@import url("./700.css") only screen and (max-width: 700px)`, parser, parseImport); + assertNode(`@import url("override.css") layer`, parser, parseImport); + assertNode(`@import url("tabs.css") layer(framework.component)`, parser, parseImport); + assertNode(`@import "mystyle.css" supports(display: flex)`, parser, parseImport); + assertNode( + `@import url("narrow.css") supports(display: flex) handheld and (max-width: 400px)`, + parser, + parseImport, + ); + assertNode(`@import url("fallback-layout.css") supports(not (display: flex))`, parser, parseImport); + + assertError(`@import`, parser, parseImport, ParseError.URIOrStringExpected); + }); }); From b3db600b8adcfa536d039b916fb72bbcd639bff6 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Thu, 23 May 2024 19:47:20 +0200 Subject: [PATCH 014/138] test: supports --- .../src/test/sass/parser.test.ts | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index c8048fb6..b272f6f8 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -10,6 +10,7 @@ suite("Sass - Parser", () => { const parseKeyframeSelector = parser._parseKeyframeSelector.bind(parser); const parseKeyframe = parser._parseKeyframe.bind(parser); const parseImport = parser._parseImport.bind(parser); + const parseSupports = parser._parseSupports.bind(parser); test("empty stylesheet", () => { assertNode("", parser, parseStylesheet); @@ -616,4 +617,72 @@ comment */ c assertError(`@import`, parser, parseImport, ParseError.URIOrStringExpected); }); + + test("@supports", () => { + assertNode( + `@supports ( display: flexbox ) + body + display: flexbox`, + parser, + parseSupports, + ); + assertNode( + `@supports not (display: flexbox) + .outline + box-shadow: 2px 2px 2px black /* unprefixed last */`, + parser, + parseSupports, + ); + assertNode( + `@supports ( box-shadow: 2px 2px 2px black ) or ( -moz-box-shadow: 2px 2px 2px black ) or ( -webkit-box-shadow: 2px 2px 2px black ) + .foo + color: red`, + parser, + parseSupports, + ); + assertNode( + `@supports ((transition-property: color) or (animation-name: foo)) and (transform: rotate(10deg)) + .foo + color: red`, + parser, + parseSupports, + ); + assertNode( + `@supports ((display: flexbox)) + .foo + color: red`, + parser, + parseSupports, + ); + assertNode( + `@supports (display: flexbox !important) + .foo + color: red`, + parser, + parseSupports, + ); + assertNode( + `@supports (column-width: 1rem) OR (-moz-column-width: 1rem) OR (-webkit-column-width: 1rem) oR (-x-column-width: 1rem) + .foo + color: limegreen`, + parser, + parseSupports, + ); + assertNode( + `@supports not (--validValue: , 0 ) + .foo + color: limegreen`, + parser, + parseSupports, + ); + + assertError( + `@supports display: flexbox + .foo + color: limegreen`, + parser, + parseSupports, + ParseError.LeftParenthesisExpected, + ); + }); }); From dba9158369b815c67e432553e199152570ee1f6f Mon Sep 17 00:00:00 2001 From: William Killerud Date: Thu, 23 May 2024 20:17:19 +0200 Subject: [PATCH 015/138] test: media --- .../src/test/sass/parser.test.ts | 419 +++++++++++++----- 1 file changed, 313 insertions(+), 106 deletions(-) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index b272f6f8..f45c81ad 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -6,20 +6,15 @@ import { assertError, assertNode } from "../css/parser.test"; suite("Sass - Parser", () => { const parser = new SassParser({ syntax: "indented" }); - const parseStylesheet = parser._parseStylesheet.bind(parser); - const parseKeyframeSelector = parser._parseKeyframeSelector.bind(parser); - const parseKeyframe = parser._parseKeyframe.bind(parser); - const parseImport = parser._parseImport.bind(parser); - const parseSupports = parser._parseSupports.bind(parser); test("empty stylesheet", () => { - assertNode("", parser, parseStylesheet); + assertNode("", parser, parser._parseStylesheet.bind(parser)); }); test("@charset", () => { - assertNode('@charset "demo"', parser, parseStylesheet); - assertError("@charset", parser, parseStylesheet, ParseError.IdentifierExpected); - assertError('@charset "demo";', parser, parseStylesheet, ParseError.UnexpectedSemicolon); + assertNode('@charset "demo"', parser, parser._parseStylesheet.bind(parser)); + assertError("@charset", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); + assertError('@charset "demo";', parser, parser._parseStylesheet.bind(parser), ParseError.UnexpectedSemicolon); }); test("newline before at-rule", () => { @@ -27,7 +22,7 @@ suite("Sass - Parser", () => { ` @charset "demo"`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); }); @@ -38,7 +33,7 @@ suite("Sass - Parser", () => { padding: 3em, 6em `, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); }); @@ -48,7 +43,7 @@ suite("Sass - Parser", () => { b: /* comment */ c `, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); }); @@ -59,7 +54,7 @@ suite("Sass - Parser", () => { b: c `, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); }); @@ -71,7 +66,7 @@ line comment */ c `, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ParseError.PropertyValueExpected, ); }); @@ -82,21 +77,21 @@ comment */ c a b: c`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); assertNode( `@media screen and (max-width: 400px) @-ms-viewport width: 320px`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); assertNode( `@-ms-viewport width: 320px height: 720px`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); }); @@ -110,7 +105,7 @@ comment */ c c: d `, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); }); @@ -126,7 +121,7 @@ comment */ c d: e `, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); }); @@ -140,7 +135,7 @@ comment */ c background-color: rgba(83, 83, 83, 0.7) `, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); }); @@ -151,7 +146,7 @@ comment */ c margin: 2.5cm `, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); }); @@ -162,7 +157,7 @@ comment */ c font-family: "Example Font" `, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); assertNode( ` @@ -170,7 +165,7 @@ comment */ c src: url(http://test) `, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); assertNode( ` @@ -179,7 +174,7 @@ comment */ c font-stretch: normal `, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); assertNode( ` @@ -187,14 +182,14 @@ comment */ c unicode-range: U+0021-007F `, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); }); test("@namespace", () => { - assertNode(`@namespace "http://www.w3.org/1999/xhtml"`, parser, parseStylesheet); - assertNode(`@namespace pref url(http://test)`, parser, parseStylesheet); - assertError("@charset", parser, parseStylesheet, ParseError.IdentifierExpected); + assertNode(`@namespace "http://www.w3.org/1999/xhtml"`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@namespace pref url(http://test)`, parser, parser._parseStylesheet.bind(parser)); + assertError("@charset", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); }); test("@-moz-document", () => { @@ -206,7 +201,7 @@ comment */ c background: yellow `, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); }); @@ -215,13 +210,13 @@ comment */ c `E E[foo] E[foo="bar"] E[foo~="bar"] E[foo^="bar"] E[foo$="bar"] E[foo*="bar"] E[foo|="en"] color: limegreen`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); assertNode( `input[type="submit"] color: limegreen`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); }); @@ -230,37 +225,37 @@ comment */ c `E:root E:nth-child(n) E:nth-last-child(n) E:nth-of-type(n) E:nth-last-of-type(n) E:first-child E:last-child color: limegreen`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); assertNode( `E:first-of-type E:last-of-type E:only-child E:only-of-type E:empty E:link E:visited E:active E:hover E:focus E:target E:lang(fr) E:enabled E:disabled E:checked color: limegreen`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); assertNode( `E::first-line E::first-letter E::before E::after color: limegreen`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); assertNode( `E.warning E#myid E:not(s) color: limegreen`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); }); test("graceful handling of unknown rules", () => { - assertNode(`@unknown-rule`, parser, parseStylesheet); - assertNode(`@unknown-rule 'foo'`, parser, parseStylesheet); - assertNode(`@unknown-rule (foo)`, parser, parseStylesheet); + assertNode(`@unknown-rule`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@unknown-rule 'foo'`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@unknown-rule (foo)`, parser, parser._parseStylesheet.bind(parser)); assertNode( `@unknown-rule (foo) .bar`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); assertNode( `@mskeyframes darkWordHighlight @@ -270,22 +265,27 @@ comment */ c to background-color: rgba(83, 83, 83, 0.7)`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); assertNode( `foo @unknown-rule`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); - assertError(`@unknown-rule (`, parser, parseStylesheet, ParseError.RightParenthesisExpected); - assertError(`@unknown-rule [foo`, parser, parseStylesheet, ParseError.RightSquareBracketExpected); + assertError(`@unknown-rule (`, parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected); + assertError( + `@unknown-rule [foo`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightSquareBracketExpected, + ); assertError( `@unknown-rule [foo`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ParseError.RightSquareBracketExpected, ); }); @@ -299,7 +299,7 @@ comment */ c .foo color: red`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); const unknownAtRule = node.getChild(0)!; @@ -314,12 +314,12 @@ comment */ c min-height: var(--space-14) `, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); }); test("stylesheet /panic/", () => { - assertError('- @import "foo"', parser, parseStylesheet, ParseError.RuleOrSelectorExpected); + assertError('- @import "foo"', parser, parser._parseStylesheet.bind(parser), ParseError.RuleOrSelectorExpected); }); test("@keyframe selector", () => { @@ -327,98 +327,98 @@ comment */ c `from color: red`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); assertNode( `to color: blue`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); assertNode( `0% color: blue`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); assertNode( `10% color: purple`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); assertNode( `cover 10% color: purple`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); assertNode( `100000% color: purple`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); assertNode( `from width: 100 to: 10px`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); assertNode( `from, to width: 10px`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); assertNode( `10%, to width: 10px`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); assertNode( `from, 20% width: 10px`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); assertNode( `10%, 20% width: 10px`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); assertNode( `cover 10%, exit 20% width: 10px`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); assertNode( `10%, exit 20% width: 10px`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); assertNode( `from, exit 20% width: 10px`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); assertNode( `cover 10%, to width: 10px`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); assertNode( `cover 10%, 20% width: 10px`, parser, - parseKeyframeSelector, + parser._parseKeyframeSelector.bind(parser), ); }); @@ -427,25 +427,25 @@ comment */ c `@keyframes name //`, parser, - parseKeyframe, + parser._parseKeyframe.bind(parser), ); assertNode( `@-webkit-keyframes name //`, parser, - parseKeyframe, + parser._parseKeyframe.bind(parser), ); assertNode( `@-o-keyframes name //`, parser, - parseKeyframe, + parser._parseKeyframe.bind(parser), ); assertNode( `@-moz-keyframes name //`, parser, - parseKeyframe, + parser._parseKeyframe.bind(parser), ); assertNode( `@keyframes name @@ -454,7 +454,7 @@ comment */ c to //`, parser, - parseKeyframe, + parser._parseKeyframe.bind(parser), ); assertNode( `@keyframes name @@ -465,7 +465,7 @@ comment */ c 100% //`, parser, - parseKeyframe, + parser._parseKeyframe.bind(parser), ); assertNode( `@keyframes name @@ -476,7 +476,7 @@ comment */ c 100% top: 50px`, parser, - parseKeyframe, + parser._parseKeyframe.bind(parser), ); assertNode( `@keyframes name @@ -487,7 +487,7 @@ comment */ c 100% top: 50px`, parser, - parseKeyframe, + parser._parseKeyframe.bind(parser), ); assertNode( `@keyframes name @@ -496,7 +496,7 @@ comment */ c left: 1px right: 2px`, parser, - parseKeyframe, + parser._parseKeyframe.bind(parser), ); assertNode( `@keyframes name @@ -505,15 +505,15 @@ comment */ c left: 1px right: 2px`, parser, - parseKeyframe, + parser._parseKeyframe.bind(parser), ); - assertError("@keyframes )", parser, parseKeyframe, ParseError.IdentifierExpected); + assertError("@keyframes )", parser, parser._parseKeyframe.bind(parser), ParseError.IdentifierExpected); assertError( `@keyframes name from, #123`, parser, - parseKeyframe, + parser._parseKeyframe.bind(parser), ParseError.DedentExpected, // Not as accurate as SCSS, but good enough ); assertError( @@ -521,7 +521,7 @@ comment */ c 10% from top: 0px`, parser, - parseKeyframe, + parser._parseKeyframe.bind(parser), ParseError.DedentExpected, ); assertError( @@ -529,7 +529,7 @@ comment */ c 10% 20% top: 0px`, parser, - parseKeyframe, + parser._parseKeyframe.bind(parser), ParseError.DedentExpected, ); assertError( @@ -537,7 +537,7 @@ comment */ c from to top: 0px`, parser, - parseKeyframe, + parser._parseKeyframe.bind(parser), ParseError.DedentExpected, ); }); @@ -549,7 +549,7 @@ comment */ c inherits: false initial-value: #c0ffee`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); assertError( @@ -558,7 +558,7 @@ comment */ c inherits: false initial-value: #c0ffee`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected, ); }); @@ -569,14 +569,14 @@ comment */ c #inner background-color: skyblue`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); assertNode( `@container card (inline-size > 30em) and style(--responsive: true) #inner background-color: skyblue`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); assertNode( ` @@ -585,7 +585,7 @@ comment */ c #inner background-color: skyblue`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); assertNode( ` @@ -593,29 +593,37 @@ comment */ c .card h2 font-size: max(1.5em, 1.23em + 2cqi)`, parser, - parseStylesheet, + parser._parseStylesheet.bind(parser), ); }); test("@import", () => { - assertNode(`@import "asdfasdf"`, parser, parseImport); - assertNode(`@ImPort "asdfasdf"`, parser, parseImport); - assertNode(`@import url(/css/screen.css) screen, projection`, parser, parseImport); - assertNode(`@import url('landscape.css') screen and (orientation:landscape)`, parser, parseImport); - assertNode(`@import url("/inc/Styles/full.css") (min-width: 940px)`, parser, parseImport); - assertNode(`@import url(style.css) screen and (min-width:600px)`, parser, parseImport); - assertNode(`@import url("./700.css") only screen and (max-width: 700px)`, parser, parseImport); - assertNode(`@import url("override.css") layer`, parser, parseImport); - assertNode(`@import url("tabs.css") layer(framework.component)`, parser, parseImport); - assertNode(`@import "mystyle.css" supports(display: flex)`, parser, parseImport); + assertNode(`@import "asdfasdf"`, parser, parser._parseImport.bind(parser)); + assertNode(`@ImPort "asdfasdf"`, parser, parser._parseImport.bind(parser)); + assertNode(`@import url(/css/screen.css) screen, projection`, parser, parser._parseImport.bind(parser)); + assertNode( + `@import url('landscape.css') screen and (orientation:landscape)`, + parser, + parser._parseImport.bind(parser), + ); + assertNode(`@import url("/inc/Styles/full.css") (min-width: 940px)`, parser, parser._parseImport.bind(parser)); + assertNode(`@import url(style.css) screen and (min-width:600px)`, parser, parser._parseImport.bind(parser)); + assertNode(`@import url("./700.css") only screen and (max-width: 700px)`, parser, parser._parseImport.bind(parser)); + assertNode(`@import url("override.css") layer`, parser, parser._parseImport.bind(parser)); + assertNode(`@import url("tabs.css") layer(framework.component)`, parser, parser._parseImport.bind(parser)); + assertNode(`@import "mystyle.css" supports(display: flex)`, parser, parser._parseImport.bind(parser)); assertNode( `@import url("narrow.css") supports(display: flex) handheld and (max-width: 400px)`, parser, - parseImport, + parser._parseImport.bind(parser), + ); + assertNode( + `@import url("fallback-layout.css") supports(not (display: flex))`, + parser, + parser._parseImport.bind(parser), ); - assertNode(`@import url("fallback-layout.css") supports(not (display: flex))`, parser, parseImport); - assertError(`@import`, parser, parseImport, ParseError.URIOrStringExpected); + assertError(`@import`, parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); }); test("@supports", () => { @@ -624,56 +632,56 @@ comment */ c body display: flexbox`, parser, - parseSupports, + parser._parseSupports.bind(parser), ); assertNode( `@supports not (display: flexbox) .outline box-shadow: 2px 2px 2px black /* unprefixed last */`, parser, - parseSupports, + parser._parseSupports.bind(parser), ); assertNode( `@supports ( box-shadow: 2px 2px 2px black ) or ( -moz-box-shadow: 2px 2px 2px black ) or ( -webkit-box-shadow: 2px 2px 2px black ) .foo color: red`, parser, - parseSupports, + parser._parseSupports.bind(parser), ); assertNode( `@supports ((transition-property: color) or (animation-name: foo)) and (transform: rotate(10deg)) .foo color: red`, parser, - parseSupports, + parser._parseSupports.bind(parser), ); assertNode( `@supports ((display: flexbox)) .foo color: red`, parser, - parseSupports, + parser._parseSupports.bind(parser), ); assertNode( `@supports (display: flexbox !important) .foo color: red`, parser, - parseSupports, + parser._parseSupports.bind(parser), ); assertNode( `@supports (column-width: 1rem) OR (-moz-column-width: 1rem) OR (-webkit-column-width: 1rem) oR (-x-column-width: 1rem) .foo color: limegreen`, parser, - parseSupports, + parser._parseSupports.bind(parser), ); assertNode( `@supports not (--validValue: , 0 ) .foo color: limegreen`, parser, - parseSupports, + parser._parseSupports.bind(parser), ); assertError( @@ -681,8 +689,207 @@ comment */ c .foo color: limegreen`, parser, - parseSupports, + parser._parseSupports.bind(parser), + ParseError.LeftParenthesisExpected, + ); + }); + + test("@media", () => { + assertNode( + `@media asdsa + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@meDia asdsa + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@meDia somename, othername2 + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media only screen and (max-width:850px) + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media all and (min-width:500px) + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (color), projection and (color) + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media not screen and (device-aspect-ratio: 16/9) + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media print and (min-resolution: 300dpi) + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media print and (min-resolution: 118dpcm) + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media print + @page + margin: 10% + blockquote, pre + page-break-inside: avoid`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media print + body:before + page-break-inside: avoid`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media not (-moz-os-version: windows-win7) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media not (not (-moz-os-version: windows-win7)) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media (height > 600px) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media (height < 600px) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media (height <= 600px) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media (400px <= width <= 700px) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media (400px >= width >= 700px) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (750px <= width < 900px) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + + assertError( + `@media somename othername2 + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ParseError.IndentExpected, + ); + assertError( + `@media not, screen + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ParseError.MediaQueryExpected, + ); + assertError( + `@media not screen and foo + body + color: black`, + parser, + parser._parseMedia.bind(parser), ParseError.LeftParenthesisExpected, ); + assertError( + `@media not screen and () + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + `@media not screen and (color:) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ParseError.TermExpected, + ); + assertError( + `@media not screen and (color:#fff + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ParseError.RightParenthesisExpected, + ); + }); + + test("media query list", () => { + assertNode("somename", parser, parser._parseMediaQueryList.bind(parser)); + assertNode("somename, othername", parser, parser._parseMediaQueryList.bind(parser)); + assertNode("not all and (monochrome)", parser, parser._parseMediaQueryList.bind(parser)); + }); + + test("medium", function () { + assertNode("somename", parser, parser._parseMedium.bind(parser)); + assertNode("-asdas", parser, parser._parseMedium.bind(parser)); + assertNode("-asda34s", parser, parser._parseMedium.bind(parser)); }); }); From a16a91a9eb016eda3bd077052fa8bb9256234292 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Thu, 23 May 2024 21:19:01 +0200 Subject: [PATCH 016/138] test: bunch of stuff --- .../src/test/sass/parser.test.ts | 172 +++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index f45c81ad..df4869e2 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -887,9 +887,179 @@ comment */ c assertNode("not all and (monochrome)", parser, parser._parseMediaQueryList.bind(parser)); }); - test("medium", function () { + test("medium", () => { assertNode("somename", parser, parser._parseMedium.bind(parser)); assertNode("-asdas", parser, parser._parseMedium.bind(parser)); assertNode("-asda34s", parser, parser._parseMedium.bind(parser)); }); + + test("@page", () => { + assertNode( + `@page : name + some: "asdf"`, + parser, + parser._parsePage.bind(parser), + ); + assertNode( + `@page :left, :right + some: "asdf"`, + parser, + parser._parsePage.bind(parser), + ); + assertNode( + `@page : name + some: "asdf" !important + some: "asdf" !important`, + parser, + parser._parsePage.bind(parser), + ); + assertNode( + `@page rotated + size: landscape`, + parser, + parser._parsePage.bind(parser), + ); + assertNode( + `@page :left + margin-left: 4cm + margin-right: 3cm`, + parser, + parser._parsePage.bind(parser), + ); + assertNode( + `@page + @top-right-corner + content: url(foo.png) + border: solid green`, + parser, + parser._parsePage.bind(parser), + ); + assertNode( + `@page + @top-left-corner + content: " " + border: solid green + @bottom-right-corner + content: counter(page) + border: solid green`, + parser, + parser._parsePage.bind(parser), + ); + + assertError( + `@page + @top-left-corner foo + content: " " + border: solid green`, + parser, + parser._parsePage.bind(parser), + ParseError.IndentExpected, + ); + assertError( + `@page : + @top-left-corner foo + content: " " + border: solid green`, + parser, + parser._parsePage.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + `@page :left, + @top-left-corner foo + content: " " + border: solid green`, + parser, + parser._parsePage.bind(parser), + ParseError.IdentifierExpected, + ); + }); + + test("@layer", () => { + assertNode( + `@layer utilities + .padding-sm + padding: .5rem`, + parser, + parser._parseLayer.bind(parser), + ); + assertNode(`@layer utilities`, parser, parser._parseLayer.bind(parser)); + assertNode(`@layer theme, layout, utilities`, parser, parser._parseLayer.bind(parser)); + assertNode( + `@layer framework + @layer layout + .padding-sm + padding: .5rem`, + parser, + parser._parseLayer.bind(parser), + ); + assertNode( + `@layer framework.layout + @keyframes slide-left + from + foo: bar + to + foo: baz`, + parser, + parser._parseLayer.bind(parser), + ); + + assertNode( + `@media (min-width: 30em) + @layer layout + .padding-sm + padding: .5rem`, + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError(`@layer theme. layout`, parser, parser._parseLayer.bind(parser), ParseError.IdentifierExpected); + }); + + test("operator", () => { + assertNode("/", parser, parser._parseOperator.bind(parser)); + assertNode("*", parser, parser._parseOperator.bind(parser)); + assertNode("+", parser, parser._parseOperator.bind(parser)); + assertNode("-", parser, parser._parseOperator.bind(parser)); + }); + + test("combinator", () => { + assertNode("+", parser, parser._parseCombinator.bind(parser)); + assertNode("+ ", parser, parser._parseCombinator.bind(parser)); + assertNode("> ", parser, parser._parseCombinator.bind(parser)); + assertNode(">", parser, parser._parseCombinator.bind(parser)); + assertNode(">>>", parser, parser._parseCombinator.bind(parser)); + assertNode("/deep/", parser, parser._parseCombinator.bind(parser)); + assertNode( + `:host >>> .data-table + width: 100%`, + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + `:host >> .data-table + width: 100%`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.IndentExpected, + ); + }); + + test("unary_operator", () => { + assertNode("-", parser, parser._parseUnaryOperator.bind(parser)); + assertNode("+", parser, parser._parseUnaryOperator.bind(parser)); + }); + + test("property", () => { + assertNode("asdsa", parser, parser._parseProperty.bind(parser)); + assertNode("asdsa334", parser, parser._parseProperty.bind(parser)); + + assertNode("--color", parser, parser._parseProperty.bind(parser)); + assertNode("--primary-font", parser, parser._parseProperty.bind(parser)); + assertNode("-color", parser, parser._parseProperty.bind(parser)); + assertNode("somevar", parser, parser._parseProperty.bind(parser)); + assertNode("some--let", parser, parser._parseProperty.bind(parser)); + assertNode("somevar--", parser, parser._parseProperty.bind(parser)); + }); }); From ee2ce729a684aa25e82c79fd12b68ce87335e9d9 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Thu, 23 May 2024 22:31:04 +0200 Subject: [PATCH 017/138] refactor: ruleset parsing --- .../src/parser/cssParser.ts | 10 +- .../src/test/sass/parser.test.ts | 127 ++++++++++++++++++ 2 files changed, 132 insertions(+), 5 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index fa104dc2..756d0636 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -410,6 +410,10 @@ export class Parser { const node = this.create(nodes.RuleSet); const selectors = node.getSelectors(); + while (this.accept(TokenType.Newline)) { + // accept empty statements + } + if (!selectors.addChild(this._parseSelector(isNested))) { return null; } @@ -487,10 +491,6 @@ export class Parser { if (!this.accept(TokenType.Indent)) { return null; } - // What causes this extra redundant Indent token? - // while (this.accept(TokenType.Indent)) { - // // accept empty statements - // } while (this.accept(TokenType.Newline)) { // accept empty statements } @@ -737,7 +737,7 @@ export class Parser { break done; case TokenType.EOF: if (this.syntax === "indented") { - break; + break done; } // We shouldn't have reached the end of input, something is // unterminated. diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index df4869e2..dae88cad 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -1062,4 +1062,131 @@ comment */ c assertNode("some--let", parser, parser._parseProperty.bind(parser)); assertNode("somevar--", parser, parser._parseProperty.bind(parser)); }); + + test("ruleset", () => { + assertNode( + `name + foo: bar`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` + +name + foo: "asdfasdf"`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` + +name + foo : "asdfasdf" !important`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `* + foo: bar`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `.far + foo: bar`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `boo + foo: bar`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `.far #boo + foo: bar`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `name + foo: bar + baz: bar`, + parser, + parser._parseRuleset.bind(parser), + ); + + assertError( + `name + --minimal:`, + parser, + parser._parseRuleset.bind(parser), + ParseError.PropertyValueExpected, + ); + assertNode( + `name + --minimal: + + other + padding: 1rem`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `name + --normal-text: red yellow green`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `name + --normal-text: red !important`, + parser, + parser._parseRuleset.bind(parser), + ); + + assertError( + `name + --nested: + color: green`, // not supported in indented + parser, + parser._parseRuleset.bind(parser), + ParseError.IdentifierExpected, + ); + + assertNode( + `name + --normal-text: this()is()ok()`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `name + --normal-text: this[]is[]ok[]`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `name + --normal-text: ([{{[]()()}[]{}}])()`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `name + --normal-text: , 0 0`, + parser, + parser._parseRuleset.bind(parser), + ); + + assertError( + `boo, + `, + parser, + parser._parseRuleset.bind(parser), + ParseError.SelectorExpected, + ); + }); }); From 1ba8977a39c38a51073d0bfdf3cf4123a290dddd Mon Sep 17 00:00:00 2001 From: William Killerud Date: Thu, 23 May 2024 22:36:11 +0200 Subject: [PATCH 018/138] test: ruleset errors --- .../src/test/sass/parser.test.ts | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index dae88cad..b3f5831b 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -1189,4 +1189,64 @@ name ParseError.SelectorExpected, ); }); + + test("ruleset /Panic/", () => { + assertError( + ` +foo + bar:`, + parser, + parser._parseRuleset.bind(parser), + ParseError.PropertyValueExpected, + ); + assertError( + ` +foo + bar: + far: 12em`, + parser, + parser._parseRuleset.bind(parser), + ParseError.PropertyValueExpected, + ); + assertError( + ` +foo + bar`, + parser, + parser._parseRuleset.bind(parser), + ParseError.ColonExpected, + ); + assertError( + ` +foo + --too-minimal:`, + parser, + parser._parseRuleset.bind(parser), + ParseError.PropertyValueExpected, + ); + assertError( + ` +foo + --double-important: red !important !important`, + parser, + parser._parseRuleset.bind(parser), + ParseError.NewlineExpected, + ); + assertError( + ` +foo + --unbalanced-parens: not)()(cool`, + parser, + parser._parseRuleset.bind(parser), + ParseError.LeftParenthesisExpected, + ); + assertError( + ` +foo + --unbalanced-parens: not][][cool`, + parser, + parser._parseRuleset.bind(parser), + ParseError.LeftSquareBracketExpected, + ); + }); }); From 79e595e1253b94a7c137e557b54768a43d8940a3 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Thu, 23 May 2024 22:45:54 +0200 Subject: [PATCH 019/138] test: nested ruleset --- .../src/test/sass/parser.test.ts | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index b3f5831b..2adfed0b 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -1180,7 +1180,20 @@ name parser, parser._parseRuleset.bind(parser), ); + assertNode( + `name + --normal-text: {}`, + parser, + parser._parseRuleset.bind(parser), + ); + assertError( + `name + font-size: {}`, + parser, + parser._parseRuleset.bind(parser), + ParseError.PropertyValueExpected, + ); assertError( `boo, `, @@ -1249,4 +1262,147 @@ foo ParseError.LeftSquareBracketExpected, ); }); + + test("nested ruleset", () => { + assertNode( + ` +.foo + color: red + input + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + :focus + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + .bar + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + &:hover + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + + .bar + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + foo:hover + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + @media screen + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + }); + + test("nested ruleset 2", () => { + assertNode( + ` +.foo + .parent & + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + & > .bar, > .baz + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + & .bar & .baz & .hmm + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + :not(&) + color: blue + + .bar + & + color: green`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + & + color: blue + && + color: green`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + & :is(.bar, &.baz) + color: red`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +figure + > figcaption + background: hsl(0 0% 0% / 50%) + > p + font-size: .9rem`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +@layer base + html + & body + min-block-size: 100%`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); }); From 56df451e92f8138a23143908c83cf58860a5a640 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Thu, 23 May 2024 22:48:27 +0200 Subject: [PATCH 020/138] test: all the rest of CSS test suite --- .../src/test/sass/parser.test.ts | 219 +++++++++++++++++- 1 file changed, 218 insertions(+), 1 deletion(-) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index 2adfed0b..16ab491e 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -2,7 +2,7 @@ import { suite, test, assert } from "vitest"; import { ParseError } from "../../parser/cssErrors"; import { SassParser } from "../../parser/sassParser"; import * as nodes from "../../parser/cssNodes"; -import { assertError, assertNode } from "../css/parser.test"; +import { assertError, assertFunction, assertNoNode, assertNode } from "../css/parser.test"; suite("Sass - Parser", () => { const parser = new SassParser({ syntax: "indented" }); @@ -1405,4 +1405,221 @@ figure parser._parseStylesheet.bind(parser), ); }); + + test("selector", () => { + assertNode("asdsa", parser, parser._parseSelector.bind(parser)); + assertNode("asdsa + asdas", parser, parser._parseSelector.bind(parser)); + assertNode("asdsa + asdas + name", parser, parser._parseSelector.bind(parser)); + assertNode("asdsa + asdas + name", parser, parser._parseSelector.bind(parser)); + assertNode("name #id#anotherid", parser, parser._parseSelector.bind(parser)); + assertNode("name.far .boo", parser, parser._parseSelector.bind(parser)); + assertNode("name .name .zweitername", parser, parser._parseSelector.bind(parser)); + assertNode("*", parser, parser._parseSelector.bind(parser)); + assertNode("#id", parser, parser._parseSelector.bind(parser)); + assertNode("far.boo", parser, parser._parseSelector.bind(parser)); + assertNode("::slotted(div)::after", parser, parser._parseSelector.bind(parser)); // 35076 + }); + + test("attrib", () => { + assertNode("[name]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name = name2]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name ~= name3]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name~=name3]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name |= name3]", parser, parser._parseAttrib.bind(parser)); + assertNode('[name |= "this is a striiiing"]', parser, parser._parseAttrib.bind(parser)); + assertNode('[href*="insensitive" i]', parser, parser._parseAttrib.bind(parser)); + assertNode('[href*="sensitive" S]', parser, parser._parseAttrib.bind(parser)); + + // Single namespace + assertNode("[namespace|name]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name-space|name = name2]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name_space|name ~= name3]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name0spae|name~=name3]", parser, parser._parseAttrib.bind(parser)); + assertNode('[NameSpace|name |= "this is a striiiing"]', parser, parser._parseAttrib.bind(parser)); + assertNode("[name\\*space|name |= name3]", parser, parser._parseAttrib.bind(parser)); + assertNode("[*|name]", parser, parser._parseAttrib.bind(parser)); + }); + + test("pseudo", () => { + assertNode(":some", parser, parser._parsePseudo.bind(parser)); + assertNode(":some(thing)", parser, parser._parsePseudo.bind(parser)); + assertNode(":nth-child(12)", parser, parser._parsePseudo.bind(parser)); + assertNode(":nth-child(1n)", parser, parser._parsePseudo.bind(parser)); + assertNode(":nth-child(-n+3)", parser, parser._parsePseudo.bind(parser)); + assertNode(":nth-child(2n+1)", parser, parser._parsePseudo.bind(parser)); + assertNode(":nth-child(2n+1 of .foo)", parser, parser._parsePseudo.bind(parser)); + assertNode(':nth-child(2n+1 of .foo > bar, :not(*) ~ [other="value"])', parser, parser._parsePseudo.bind(parser)); + assertNode(":lang(it)", parser, parser._parsePseudo.bind(parser)); + assertNode(":not(.class)", parser, parser._parsePseudo.bind(parser)); + assertNode(":not(:disabled)", parser, parser._parsePseudo.bind(parser)); + assertNode(":not(#foo)", parser, parser._parsePseudo.bind(parser)); + assertNode("::slotted(*)", parser, parser._parsePseudo.bind(parser)); // #35076 + assertNode("::slotted(div:hover)", parser, parser._parsePseudo.bind(parser)); // #35076 + assertNode(":global(.output ::selection)", parser, parser._parsePseudo.bind(parser)); // #49010 + assertNode(":matches(:hover, :focus)", parser, parser._parsePseudo.bind(parser)); // #49010 + assertNode(":host([foo=bar][bar=foo])", parser, parser._parsePseudo.bind(parser)); // #49589 + assertNode(":has(> .test)", parser, parser._parsePseudo.bind(parser)); // #250 + assertNode(":has(~ .test)", parser, parser._parsePseudo.bind(parser)); // #250 + assertNode(":has(+ .test)", parser, parser._parsePseudo.bind(parser)); // #250 + assertNode(":has(~ div .test)", parser, parser._parsePseudo.bind(parser)); // #250 + assertError("::", parser, parser._parsePseudo.bind(parser), ParseError.IdentifierExpected); + assertError(":: foo", parser, parser._parsePseudo.bind(parser), ParseError.IdentifierExpected); + assertError(":nth-child(1n of)", parser, parser._parsePseudo.bind(parser), ParseError.SelectorExpected); + }); + + test("declaration", () => { + assertNode('name : "this is a string" !important', parser, parser._parseDeclaration.bind(parser)); + assertNode('name : "this is a string"', parser, parser._parseDeclaration.bind(parser)); + assertNode("property:12", parser, parser._parseDeclaration.bind(parser)); + assertNode("-vendor-property: 12", parser, parser._parseDeclaration.bind(parser)); + assertNode("font-size: 12px", parser, parser._parseDeclaration.bind(parser)); + assertNode("color : #888 /4", parser, parser._parseDeclaration.bind(parser)); + assertNode( + "filter : progid:DXImageTransform.Microsoft.Shadow(color=#000000,direction=45)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "filter : progid: DXImageTransform.Microsoft.DropShadow(offx=2, offy=1, color=#000000)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode("font-size: 12px", parser, parser._parseDeclaration.bind(parser)); + assertNode("*background: #f00 /* IE 7 and below */", parser, parser._parseDeclaration.bind(parser)); + assertNode("_background: #f60 /* IE 6 and below */", parser, parser._parseDeclaration.bind(parser)); + assertNode( + "background-image: linear-gradient(to right, silver, white 50px, white calc(100% - 50px), silver)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "grid-template-columns: [first nav-start] 150px [main-start] 1fr [last]", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "grid-template-columns: repeat(4, 10px [col-start] 250px [col-end]) 10px", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "grid-template-columns: [a] auto [b] minmax(min-content, 1fr) [b c d] repeat(2, [e] 40px)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode("grid-template: [foo] 10px / [bar] 10px", parser, parser._parseDeclaration.bind(parser)); + assertNode( + `grid-template: 'left1 footer footer' 1fr [end] / [ini] 1fr [info-start] 2fr 1fr [end]`, + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode(`content: "("counter(foo) ")"`, parser, parser._parseDeclaration.bind(parser)); + assertNode(`content: 'Hello\\0A''world'`, parser, parser._parseDeclaration.bind(parser)); + }); + + test("term", () => { + assertNode('"asdasd"', parser, parser._parseTerm.bind(parser)); + assertNode("name", parser, parser._parseTerm.bind(parser)); + assertNode("#FFFFFF", parser, parser._parseTerm.bind(parser)); + assertNode('url("this is a url")', parser, parser._parseTerm.bind(parser)); + assertNode("+324", parser, parser._parseTerm.bind(parser)); + assertNode("-45", parser, parser._parseTerm.bind(parser)); + assertNode("+45", parser, parser._parseTerm.bind(parser)); + assertNode("-45%", parser, parser._parseTerm.bind(parser)); + assertNode("-45mm", parser, parser._parseTerm.bind(parser)); + assertNode("-45em", parser, parser._parseTerm.bind(parser)); + assertNode('"asdsa"', parser, parser._parseTerm.bind(parser)); + assertNode("faa", parser, parser._parseTerm.bind(parser)); + assertNode('url("this is a striiiiing")', parser, parser._parseTerm.bind(parser)); + assertNode("#FFFFFF", parser, parser._parseTerm.bind(parser)); + assertNode("name(asd)", parser, parser._parseTerm.bind(parser)); + assertNode("calc(50% + 20px)", parser, parser._parseTerm.bind(parser)); + assertNode("calc(50% + (100%/3 - 2*1em - 2*1px))", parser, parser._parseTerm.bind(parser)); + assertNoNode( + "%('repetitions: %S file: %S', 1 + 2, \"directory/file.less\")", + parser, + parser._parseTerm.bind(parser), + ); // less syntax + assertNoNode('~"ms:alwaysHasItsOwnSyntax.For.Stuff()"', parser, parser._parseTerm.bind(parser)); // less syntax + assertNode("U+002?-0199", parser, parser._parseTerm.bind(parser)); + assertNoNode("U+002?-01??", parser, parser._parseTerm.bind(parser)); + assertNoNode("U+00?0;", parser, parser._parseTerm.bind(parser)); + assertNoNode("U+0XFF;", parser, parser._parseTerm.bind(parser)); + }); + + test("function", () => { + assertNode('name( "bla" )', parser, parser._parseFunction.bind(parser)); + assertNode("name( name )", parser, parser._parseFunction.bind(parser)); + assertNode("name( -500mm )", parser, parser._parseFunction.bind(parser)); + assertNode("\u060frf()", parser, parser._parseFunction.bind(parser)); + assertNode("über()", parser, parser._parseFunction.bind(parser)); + + assertNoNode("über ()", parser, parser._parseFunction.bind(parser)); + assertNoNode("%()", parser, parser._parseFunction.bind(parser)); + assertNoNode("% ()", parser, parser._parseFunction.bind(parser)); + + assertFunction("let(--color)", parser, parser._parseFunction.bind(parser)); + assertFunction("let(--color, somevalue)", parser, parser._parseFunction.bind(parser)); + assertFunction("let(--variable1, --variable2)", parser, parser._parseFunction.bind(parser)); + assertFunction("let(--variable1, let(--variable2))", parser, parser._parseFunction.bind(parser)); + assertFunction("fun(value1, value2)", parser, parser._parseFunction.bind(parser)); + assertFunction("fun(value1,)", parser, parser._parseFunction.bind(parser)); + }); + + test("test token prio", () => { + assertNode("!important", parser, parser._parsePrio.bind(parser)); + assertNode("!/*demo*/important", parser, parser._parsePrio.bind(parser)); + assertNode("! /*demo*/ important", parser, parser._parsePrio.bind(parser)); + assertNode("! /*dem o*/ important", parser, parser._parsePrio.bind(parser)); + }); + + test("hexcolor", () => { + assertNode("#FFF", parser, parser._parseHexColor.bind(parser)); + assertNode("#FFFF", parser, parser._parseHexColor.bind(parser)); + assertNode("#FFFFFF", parser, parser._parseHexColor.bind(parser)); + assertNode("#FFFFFFFF", parser, parser._parseHexColor.bind(parser)); + }); + + test("test class", () => { + assertNode(".faa", parser, parser._parseClass.bind(parser)); + assertNode("faa", parser, parser._parseElementName.bind(parser)); + assertNode("*", parser, parser._parseElementName.bind(parser)); + assertNode(".faa42", parser, parser._parseClass.bind(parser)); + }); + + test("prio", () => { + assertNode("!important", parser, parser._parsePrio.bind(parser)); + }); + + test("expr", () => { + assertNode("45,5px", parser, parser._parseExpr.bind(parser)); + assertNode(" 45 , 5px ", parser, parser._parseExpr.bind(parser)); + assertNode("5/6", parser, parser._parseExpr.bind(parser)); + assertNode("36mm, -webkit-calc(100%-10px)", parser, parser._parseExpr.bind(parser)); + }); + + test("url", () => { + assertNode("url(//yourdomain/yourpath.png)", parser, parser._parseURILiteral.bind(parser)); + assertNode("url('http://msft.com')", parser, parser._parseURILiteral.bind(parser)); + assertNode('url("http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url( "http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url(\t"http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url("")', parser, parser._parseURILiteral.bind(parser)); + assertNode('uRL("")', parser, parser._parseURILiteral.bind(parser)); + assertNode('URL("")', parser, parser._parseURILiteral.bind(parser)); + assertNode("url(http://msft.com)", parser, parser._parseURILiteral.bind(parser)); + assertNode("url()", parser, parser._parseURILiteral.bind(parser)); + assertError( + 'url("http://msft.com"', + parser, + parser._parseURILiteral.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + "url(http://msft.com')", + parser, + parser._parseURILiteral.bind(parser), + ParseError.RightParenthesisExpected, + ); + }); }); From 680e89f8926332cf8251a84833d68e4f568bebec Mon Sep 17 00:00:00 2001 From: William Killerud Date: Thu, 23 May 2024 23:14:25 +0200 Subject: [PATCH 021/138] test: tests from SCSS parser --- .../src/parser/cssParser.ts | 4 +- .../src/test/sass/parser.test.ts | 230 +++++++++++++++--- 2 files changed, 200 insertions(+), 34 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 756d0636..b1a2ddd1 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -541,12 +541,12 @@ export class Parser { while (this.accept(TokenType.Newline)) { // accept empty statements } - // don't raise an error of dedent expected if this is the end of the file if (this.accept(TokenType.EOF)) { return this.finish(node); } if (!this.accept(TokenType.Dedent)) { - return this.finish(node, ParseError.DedentExpected, [TokenType.Newline, TokenType.Indent]); + // TODO: figure out if/when we should raise this + // return this.finish(node, ParseError.DedentExpected, [TokenType.Newline, TokenType.Indent]); } } else { if (!this.accept(TokenType.CurlyR)) { diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index 16ab491e..926e8152 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -179,7 +179,7 @@ comment */ c assertNode( ` @font-face - unicode-range: U+0021-007F + unicode-range: U+0021-007F, u+1f49C, U+4??, U+?????? `, parser, parser._parseStylesheet.bind(parser), @@ -509,37 +509,6 @@ comment */ c ); assertError("@keyframes )", parser, parser._parseKeyframe.bind(parser), ParseError.IdentifierExpected); - assertError( - `@keyframes name - from, #123`, - parser, - parser._parseKeyframe.bind(parser), - ParseError.DedentExpected, // Not as accurate as SCSS, but good enough - ); - assertError( - `@keyframes name - 10% from - top: 0px`, - parser, - parser._parseKeyframe.bind(parser), - ParseError.DedentExpected, - ); - assertError( - `@keyframes name - 10% 20% - top: 0px`, - parser, - parser._parseKeyframe.bind(parser), - ParseError.DedentExpected, - ); - assertError( - `@keyframes name - from to - top: 0px`, - parser, - parser._parseKeyframe.bind(parser), - ParseError.DedentExpected, - ); }); test("@property", () => { @@ -1064,6 +1033,23 @@ comment */ c }); test("ruleset", () => { + assertNode( + `selector + property: value + @keyframes foo + from + top: 0 + 100% + top: 100% + @-moz-keyframes foo + from + top: 0 + 100% + top: 100%`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( `name foo: bar`, @@ -1187,6 +1173,46 @@ name parser._parseRuleset.bind(parser), ); + assertNode( + `.selector + prop: erty $const 1px`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `.selector + prop: erty $const 1px m.$foo`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `selector:active + property: value + nested: hover + property: value`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `selector + property: declaration`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `selector + $variable: declaration`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `selector + property: value + property: $value`, + parser, + parser._parseRuleset.bind(parser), + ); + assertError( `name font-size: {}`, @@ -1327,6 +1353,69 @@ foo parser, parser._parseRuleset.bind(parser), ); + + assertNode( + ` +.foo + $const: 1 + .class + $const: 2 + $const: 3 + one: $const`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.class1 + > .class2 + & > .class4 + rule1: v1`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +foo + @at-root + display: none`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +th, tr + @at-root #{selector-replace(&, "tr")} + border-bottom: 0`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +foo + @supports(display: grid) + .bar + display: none`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +foo + @supports(display: grid) + display: none`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +foo + @supports(position: sticky) + @media (min-width: map-get($grid-breakpoints, medium)) + position: sticky`, + parser, + parser._parseRuleset.bind(parser), + ); }); test("nested ruleset 2", () => { @@ -1599,6 +1688,12 @@ figure }); test("url", () => { + assertNode("url(foo())", parser, parser._parseURILiteral.bind(parser)); + assertNode( + "url('data:image/svg+xml;utf8,%3Csvg%20fill%3D%22%23' + $color + 'foo')", + parser, + parser._parseURILiteral.bind(parser), + ); assertNode("url(//yourdomain/yourpath.png)", parser, parser._parseURILiteral.bind(parser)); assertNode("url('http://msft.com')", parser, parser._parseURILiteral.bind(parser)); assertNode('url("http://msft.com")', parser, parser._parseURILiteral.bind(parser)); @@ -1622,4 +1717,75 @@ figure ParseError.RightParenthesisExpected, ); }); + + test("map", () => { + assertNode("(key1: 1px, key2: solid + px, key3: (2+3))", parser, parser._parseExpr.bind(parser)); + assertNode("($key1 + 3: 1px)", parser, parser._parseExpr.bind(parser)); + }); + + test("parent selector", () => { + assertNode("&:hover", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&.float", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-bar", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-1", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&1", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-foo-1", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&&", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-10-thing", parser, parser._parseSimpleSelector.bind(parser)); + }); + + test("placeholder selector", () => { + assertNode("%hover", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("a%float", parser, parser._parseSimpleSelector.bind(parser)); + }); + + test("selector interpolation", function () { + assertNode(`.#{$name}\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.#{$name}-foo\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.#{$name}-foo-3\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.#{$name}-1\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.sc-col#{$postfix}-2-1\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`p.#{$name}\n\t#{$attr}-color: blue`, parser, parser._parseRuleset.bind(parser)); + assertNode( + `sans-#{serif} + a-#{1 + 2}-color-#{$attr}: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode(`##{f} .#{f} #{f}:#{f}\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.foo-#{&} .foo-#{&-sub}\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.-#{$variable}\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`#{&}([foo=bar][bar=foo])\n\t//`, parser, parser._parseRuleset.bind(parser)); + + assertNode(`.#{module.$name}\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.#{module.$name}-foo\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.#{module.$name}-foo-3\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.#{module.$name}-1\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.sc-col#{module.$postfix}-2-1\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode( + `p.#{module.$name} + #{$attr}-color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `p.#{$name} + #{module.$attr}-color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `p.#{module.$name} + #{module.$attr}-color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `sans-#{serif} + a-#{1 + 2}-color-#{module.$attr}: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode(`.-#{module.$variable}\n\t//`, parser, parser._parseRuleset.bind(parser)); + }); }); From 46544e62de79f5f3252d4d9b5ea2b09b281d1b6b Mon Sep 17 00:00:00 2001 From: William Killerud Date: Thu, 23 May 2024 23:21:28 +0200 Subject: [PATCH 022/138] test: at-root --- .../src/test/sass/parser.test.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index 926e8152..b1d1838f 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -1050,6 +1050,8 @@ comment */ c parser._parseRuleset.bind(parser), ); + assertNode("foo|bar\n\t//", parser, parser._parseRuleset.bind(parser)); + assertNode( `name foo: bar`, @@ -1788,4 +1790,36 @@ figure ); assertNode(`.-#{module.$variable}\n\t//`, parser, parser._parseRuleset.bind(parser)); }); + + test("@at-root", () => { + assertNode( + `@mixin unify-parent($child) + @at-root f#{selector.unify(&, $child)} + color: f`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@at-root #main2 .some-class + padding-left: calc( #{$a-variable} + 8px)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@media print + .page + @at-root (without: media) + foo: bar`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@media print + .page + @at-root (with: rule) + foo: bar`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); }); From 02e390d818214b394f349adc585170b9d4981a54 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Fri, 24 May 2024 20:27:09 +0200 Subject: [PATCH 023/138] test: function --- .../src/test/sass/parser.test.ts | 96 +++++++++++++++++++ .../src/test/scss/parser.test.ts | 1 + 2 files changed, 97 insertions(+) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index b1d1838f..a745e08f 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -1822,4 +1822,100 @@ figure parser._parseStylesheet.bind(parser), ); }); + + test("@function", () => { + assertNode( + `@function grid-width($n) + @return $n * $grid-width + ($n - 1) * $gutter-width`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@function grid-width($n: 1, $e) + @return 0`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@function foo($total, $a) + @for $i from 0 to $total + // + @return $grid`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@function foo() + @if (unit($a) == "%") and ($i == ($total - 1)) + @return 0 + @return 1`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@function is-even($int) + @if $int%2 == 0 + @return true + @return false`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@function bar ($i) + @if $i > 0 + @return $i * bar($i - 1) + @return 1`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@function foo($a,) + //`, + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + `@function foo + //`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.LeftParenthesisExpected, + ); + assertError( + `@function + //`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + `@function foo($a $b) + //`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + `@function foo($a + //`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + `@function foo($a...) + @return`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.ExpressionExpected, + ); + assertError( + `@function foo($a:) + //`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.VariableValueExpected, + ); + }); }); diff --git a/packages/vscode-css-languageservice/src/test/scss/parser.test.ts b/packages/vscode-css-languageservice/src/test/scss/parser.test.ts index acb9d46b..7eb2f029 100644 --- a/packages/vscode-css-languageservice/src/test/scss/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/scss/parser.test.ts @@ -924,6 +924,7 @@ suite("SCSS - Parser", () => { }); test("@function", function () { + // 👆👆👆 you are here, going up 👆👆👆 const parser = new SassParser(); assertNode( "@function grid-width($n) { @return $n * $grid-width + ($n - 1) * $gutter-width; }", From e06271e0eb975886810ebe69d7b66597402eb88c Mon Sep 17 00:00:00 2001 From: William Killerud Date: Fri, 24 May 2024 20:46:48 +0200 Subject: [PATCH 024/138] refactor: include --- .../src/parser/sassParser.ts | 2 +- .../src/test/sass/parser.test.ts | 161 ++++++++++++++++++ .../src/test/scss/parser.test.ts | 1 - 3 files changed, 162 insertions(+), 2 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/sassParser.ts b/packages/vscode-css-languageservice/src/parser/sassParser.ts index fc0e1737..728a1765 100644 --- a/packages/vscode-css-languageservice/src/parser/sassParser.ts +++ b/packages/vscode-css-languageservice/src/parser/sassParser.ts @@ -723,7 +723,7 @@ export class SassParser extends cssParser.Parser { } } - if (this.peekIdent("using") || this.peek(TokenType.CurlyL)) { + if (this.peekIdent("using") || this.peek(TokenType.CurlyL) || this.peek(TokenType.Indent)) { node.setContent(this._parseMixinContentDeclaration()); } return this.finish(node); diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index a745e08f..9c15ae2c 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -1918,4 +1918,165 @@ figure ParseError.VariableValueExpected, ); }); + + test("@include", () => { + assertNode( + `p + @include double-border(blue)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.shadows + @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `$values: #ff0000, #00ff00, #0000ff + +.primary + @include colors($values...)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@include colors(this("styles")...)`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `.test + @include fontsize(16px, 21px !important)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @include apply-to-ie6-only + #logo + background-image: url(/logo.gif)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @include foo($values,)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @include foo($values,)`, + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + `p + @include double-border($values blue`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + `p + @include`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + `p + @include foo($values`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + `p + @include foo($values,`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.ExpressionExpected, + ); + + assertNode( + `p + @include lib.double-border(blue)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.shadows + @include lib.box-shadow(0px 4px 5px #666, 2px 6px 10px #999)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `$values: #ff0000, #00ff00, #0000ff +.primary + @include lib.colors($values...)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.primary + @include colors(lib.$values...)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.primary + @include lib.colors(lib.$values...)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@include lib.colors(this("styles")...)`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@include colors(lib.this("styles")...)`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@include lib.colors(lib.this("styles")...)`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `.test + @include lib.fontsize(16px, 21px !important)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @include lib.apply-to-ie6-only + #logo + background-image: url(/logo.gif)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @include lib.foo($values,)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @include foo(lib.$values,)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @include lib.foo(m.$values,)`, + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + `p + @include foo.($values)`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.IdentifierExpected, + ); + + assertNode( + `@include rtl("left") using ($dir) + margin-#{$dir}: 10px`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); }); diff --git a/packages/vscode-css-languageservice/src/test/scss/parser.test.ts b/packages/vscode-css-languageservice/src/test/scss/parser.test.ts index 7eb2f029..acb9d46b 100644 --- a/packages/vscode-css-languageservice/src/test/scss/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/scss/parser.test.ts @@ -924,7 +924,6 @@ suite("SCSS - Parser", () => { }); test("@function", function () { - // 👆👆👆 you are here, going up 👆👆👆 const parser = new SassParser(); assertNode( "@function grid-width($n) { @return $n * $grid-width + ($n - 1) * $gutter-width; }", From 1495d4701d723481a97a0b82687c1c769654cc48 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Fri, 24 May 2024 21:27:01 +0200 Subject: [PATCH 025/138] refactor: nested declaration, at mixin --- .../src/parser/cssParser.ts | 2 +- .../src/test/sass/parser.test.ts | 78 +++++++++++++++++-- 2 files changed, 74 insertions(+), 6 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index b1a2ddd1..32a804fb 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -1861,7 +1861,7 @@ export class Parser { const pos = this.mark(); const node = this.createNode(nodes.NodeType.PseudoSelector); this.consumeToken(); // Colon - if (this.hasWhitespace()) { + if (this.hasWhitespace() || this.peek(TokenType.Indent)) { this.restoreAtMark(pos); return null; } diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index 9c15ae2c..7a0ead56 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -1033,6 +1033,16 @@ comment */ c }); test("ruleset", () => { + assertNode( + ` +.foo + font: + family: Arial + size: 20px + color: #ff0000`, + parser, + parser._parseRuleset.bind(parser), + ); assertNode( `selector property: value @@ -1134,16 +1144,13 @@ name parser, parser._parseRuleset.bind(parser), ); - - assertError( + assertNode( `name --nested: - color: green`, // not supported in indented + color: green`, parser, parser._parseRuleset.bind(parser), - ParseError.IdentifierExpected, ); - assertNode( `name --normal-text: this()is()ok()`, @@ -2079,4 +2086,65 @@ figure parser._parseStylesheet.bind(parser), ); }); + + test("@content", () => { + assertNode("@content", parser, parser._parseMixinContent.bind(parser)); + assertNode("@content($type)", parser, parser._parseMixinContent.bind(parser)); + }); + + test("@mixin", () => { + assertNode( + `@mixin large-text + font: + family: Arial + size: 20px + color: #ff0000`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@mixin double-border($color, $width: 1in) + color: black`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@mixin box-shadow($shadows...) + -moz-box-shadow: $shadows`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@mixin apply-to-ie6-only + * html + @content`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@mixin #{foo}($color)\n\t//`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `@mixin foo ($i:4) + size: $i + @include wee ($i - 1)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@mixin foo ($i,)\n\t//`, parser, parser._parseStylesheet.bind(parser)); + + assertError(`@mixin $1\n\t//`, parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); + assertError(`@mixin foo() i\n\t//`, parser, parser._parseStylesheet.bind(parser), ParseError.IndentExpected); + assertError( + `@mixin foo(1)\n\t//`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + `@mixin foo($color = 9)\n\t//`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError(`@mixin foo($color)`, parser, parser._parseStylesheet.bind(parser), ParseError.IndentExpected); + }); }); From 8eab530962782e5b862e436c1512bb0476ae1625 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Fri, 24 May 2024 21:32:22 +0200 Subject: [PATCH 026/138] test: at-for --- .../src/test/sass/parser.test.ts | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index 7a0ead56..93e75696 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -3,6 +3,7 @@ import { ParseError } from "../../parser/cssErrors"; import { SassParser } from "../../parser/sassParser"; import * as nodes from "../../parser/cssNodes"; import { assertError, assertFunction, assertNoNode, assertNode } from "../css/parser.test"; +import { SassParseError } from "../../parser/sassErrors"; suite("Sass - Parser", () => { const parser = new SassParser({ syntax: "indented" }); @@ -2147,4 +2148,87 @@ figure ); assertError(`@mixin foo($color)`, parser, parser._parseStylesheet.bind(parser), ParseError.IndentExpected); }); + + test("@while", () => { + assertNode( + `@while $i < 0 + .item-#{$i} + width: 2em * $i + $i: $i - 2`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertError(`@while\n\t//`, parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); + assertError(`@while $i != 4`, parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.IndentExpected); + }); + + test("@each", () => { + assertNode(`@each $i in 1, 2, 3\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode(`@each $i in 1 2 3\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode( + `@each $animal, $color, $cursor in (puma, black, default), (egret, white, move)\n\t`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertError( + `@each i in 4\n\t`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.VariableNameExpected, + ); + assertError(`@each $i from 4\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser), SassParseError.InExpected); + assertError(`@each $i in\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); + assertError( + `@each $animal, in (1, 1, 1), (2, 2, 2)\n\t`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.VariableNameExpected, + ); + }); + + test("@for", () => { + assertNode( + `@for $i from 1 to 5 + .item-#{$i} + width: 2em * $i`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode(`@for $k from 1 + $x through 5 + $x\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); + assertError( + `@for i from 0 to 4\n\t`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.VariableNameExpected, + ); + assertError(`@for $i to 4\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser), SassParseError.FromExpected); + assertError( + `@for $i from 0 by 4\n\t`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + SassParseError.ThroughOrToExpected, + ); + assertError( + `@for $i from\n\t`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.ExpressionExpected, + ); + assertError( + `@for $i from 0 to\n\t`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.ExpressionExpected, + ); + assertNode( + `@for $i from 1 through 60 + $s: $i + "%"`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + + assertNode(`@for $k from 1 + m.$x through 5 + $x\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode(`@for $k from 1 + $x through 5 + m.$x\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode(`@for $k from 1 + m.$x through 5 + m.$x\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); + }); }); From 47192d058d86b2e48532c56654e44fb364a3f5c9 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Fri, 24 May 2024 21:38:34 +0200 Subject: [PATCH 027/138] test: at-if --- .../src/test/sass/parser.test.ts | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index 93e75696..c9d0a7d0 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -2231,4 +2231,140 @@ figure assertNode(`@for $k from 1 + $x through 5 + m.$x\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode(`@for $k from 1 + m.$x through 5 + m.$x\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); }); + + test("@if", () => { + assertNode( + `@if 1 + 1 == 2 + border: 1px solid`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + `@if 5 < 3 + border: 2px dotted`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + `@if null + border: 3px double`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + `@if 1 <= $const + border: 3px +@else + border: 4px`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + `@if 1 >= (1 + $foo) + border: 3px +@else if 1 + 1 == 2 + border: 4px`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + `p + @if $i == 1 + x: 3px + @else if $i == 1 + x: 4px + @else + x: 4px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@if (index($_RESOURCES, "clean") != null) + @error "sdssd"`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@if $i == 1 + p + x: 3px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertError( + `@if + border: 1px solid`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.ExpressionExpected, + ); + + assertNode( + `@if 1 <= m.$const + border: 3px +@else + border: 4px`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + `@if 1 >= (1 + m.$foo) + border: 3px +@else if 1 + 1 == 2 + border: 4px`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + `p + @if m.$i == 1 + x: 3px + @else if $i == 1 + x: 4px + @else + x: 4px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @if $i == 1 + x: 3px + @else if m.$i == 1 + x: 4px + @else + x: 4px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @if m.$i == 1 + x: 3px + @else if m.$i == 1 + x: 4px + @else + x: 4px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@if (list.index($_RESOURCES, "clean") != null) + @error "sdssd"`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@if (index(m.$_RESOURCES, "clean") != null) + @error "sdssd"`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@if (list.index(m.$_RESOURCES, "clean") != null) + @error "sdssd"`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); }); From 4669154d87eb3ca137718b471b0605f54aa8c892 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Fri, 24 May 2024 21:51:43 +0200 Subject: [PATCH 028/138] test: at-media --- .../src/test/sass/parser.test.ts | 285 ++++++++++++++++++ 1 file changed, 285 insertions(+) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index c9d0a7d0..c443781e 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -512,6 +512,52 @@ comment */ c assertError("@keyframes )", parser, parser._parseKeyframe.bind(parser), ParseError.IdentifierExpected); }); + test("@keyframes sass", () => { + assertNode( + `@keyframes name + @content`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@keyframes name + @for $i from 0 through $steps + #{$i * (100%/$steps)} + transform: $rotate $translate`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@keyframes test-keyframe + @for $i from 1 through 60 + $s: ($i * 100) / 60 + "%"`, + parser, + parser._parseKeyframe.bind(parser), + ); + + assertNode( + `@keyframes name + @for $i from 0 through m.$steps + #{$i * (100%/$steps)} + transform: $rotate $translate`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@keyframes name + @function bar() + @return 1`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@keyframes name + @include keyframe-mixin()`, + parser, + parser._parseKeyframe.bind(parser), + ); + }); + test("@property", () => { assertNode( `@property --my-color @@ -851,6 +897,129 @@ comment */ c ); }); + test("@media sass", () => { + assertNode( + `@media screen + .sidebar + @media (orientation: landscape) + width: 500px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@media #{$media} and ($feature: $value)\n\t`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@media only screen and #{$query}\n\t`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `foo + bar + @media screen and (orientation: landscape) + color: red`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@media screen and (nth($query, 1): nth($query, 2))\n\t`, parser, parser._parseMedia.bind(parser)); + assertNode( + `.something + @media (max-width: 760px) + > .test + color: blue`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.something + @media (max-width: 760px) + ~ div + display: block`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.something + @media (max-width: 760px) + + div + display: block`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@media (max-width: 760px) + + div + display: block`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@media (height <= 600px)\n\t`, parser, parser._parseMedia.bind(parser)); + assertNode(`@media (height >= 600px)\n\t`, parser, parser._parseMedia.bind(parser)); + + assertNode(`@media #{layout.$media} and ($feature: $value)\n\t`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@media #{$media} and (layout.$feature: $value)\n\t`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@media #{$media} and ($feature: layout.$value)\n\t`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `@media #{layout.$media} and (layout.$feature: $value)\n\t`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@media #{$media} and (layout.$feature: layout.$value)\n\t`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@media #{layout.$media} and (layout.$feature: layout.$value)\n\t`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@media screen and (list.nth($query, 1): nth($query, 2))\n\t`, parser, parser._parseMedia.bind(parser)); + assertNode(`@media screen and (nth(list.$query, 1): nth($query, 2))\n\t`, parser, parser._parseMedia.bind(parser)); + assertNode(`@media screen and (nth($query, 1): list.nth($query, 2))\n\t`, parser, parser._parseMedia.bind(parser)); + assertNode(`@media screen and (nth($query, 1): nth(list.$query, 2))\n\t`, parser, parser._parseMedia.bind(parser)); + assertNode( + `@media screen and (list.nth(list.$query, 1): nth($query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (list.nth($query, 1): list.nth($query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (list.nth($query, 1): nth(list.$query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (nth(list.$query, 1): list.nth($query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (nth(list.$query, 1): nth(list.$query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (nth($query, 1): list.nth(list.$query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (list.nth(list.$query, 1): list.nth($query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (nth(list.$query, 1): list.nth(list.$query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (list.nth(list.$query, 1): list.nth(list.$query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + }); + test("media query list", () => { assertNode("somename", parser, parser._parseMediaQueryList.bind(parser)); assertNode("somename, othername", parser, parser._parseMediaQueryList.bind(parser)); @@ -2367,4 +2536,120 @@ figure parser._parseStylesheet.bind(parser), ); }); + + test("@debug", () => { + assertNode(`@debug test`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `foo + @debug 1 + 4 + nested + @warn 1 4`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@if $foo == 1 + @debug 1 + 4`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@function setStyle($map, $object, $style) + @warn "The key ´#{$object} is not available in the map." + @return null`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@extend", () => { + assertNode( + `.themable + @extend %theme`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `foo + @extend .error + border-width: 3px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `a.important + @extend .notice !optional`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.hoverlink + @extend a:hover`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.seriousError + @extend .error + @extend .attention`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `#context a%extreme + color: blue +.notice + @extend %extreme`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@media print + .error + color: red + .seriousError + @extend .error`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@mixin error($a: false) + @extend .#{$a} + @extend ##{$a}`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.foo + @extend .text-center, .uppercase`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.foo + @extend .text-center, .uppercase, `, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.foo + @extend .text-center, .uppercase !optional `, + parser, + parser._parseStylesheet.bind(parser), + ); + assertError( + `.hoverlink + @extend`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.SelectorExpected, + ); + assertError( + `.hoverlink + @extend %extreme !default`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.UnknownKeyword, + ); + }); }); From a8a214d54e1a55936daded6e701da7cfb0c7b270 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Fri, 24 May 2024 22:15:58 +0200 Subject: [PATCH 029/138] refactor: all parser tests passing --- .../src/parser/sassParser.ts | 2 +- .../src/test/sass/parser.test.ts | 515 ++++++++++++++++++ 2 files changed, 516 insertions(+), 1 deletion(-) diff --git a/packages/vscode-css-languageservice/src/parser/sassParser.ts b/packages/vscode-css-languageservice/src/parser/sassParser.ts index 728a1765..4b0c0ef7 100644 --- a/packages/vscode-css-languageservice/src/parser/sassParser.ts +++ b/packages/vscode-css-languageservice/src/parser/sassParser.ts @@ -851,7 +851,7 @@ export class SassParser extends cssParser.Parser { return this.finish(node, ParseError.StringLiteralExpected); } - if (!this.peek(TokenType.SemiColon) && !this.peek(TokenType.EOF)) { + if (!this.peek(TokenType.SemiColon) && !this.peek(TokenType.Newline) && !this.peek(TokenType.EOF)) { if (!this.peekRegExp(TokenType.Ident, /as|with/)) { return this.finish(node, ParseError.UnknownKeyword); } diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index c443781e..78873af9 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -72,6 +72,123 @@ comment */ c ); }); + test("stylesheet", () => { + assertNode(`$color: #F5F5F5`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `$color: #F5F5F5 +$color: #F5F5F5`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `$color: #F5F5F5 +$color: #F5F5F5 +$color: #F5F5F5`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`$color: #F5F5F5 !important`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `#main + width: 97% + p, div + a + font-weight: bold`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `a + &:hover + color: red`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `fo + font: 2px/3px + family: fantasy`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.foo + bar: + yoo: fantasy`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `selector + propsuffix: + nested: 1px + rule: 1px + nested.selector + foo: 1 + nested:selector + foo: 2`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `legend + foo + a:s + margin-top: 0 + margin-bottom: #123 + margin-top:s(1)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@mixin keyframe + @keyframes name + @content`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@include keyframe + 10% + top: 3px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.class + &--sub-class-with-ampersand + color: red`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertError( + `fo + font: 2px/3px + family`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.ColonExpected, + ); + + assertNode( + `legend + foo + a:s + margin-top:0 + margin-bottom:#123 + margin-top:m.s(1)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@include module.keyframe + 10% + top: 3px`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + test("@media", () => { assertNode( `@media screen, projection @@ -2652,4 +2769,402 @@ figure ParseError.UnknownKeyword, ); }); + + test("@forward", () => { + assertNode('@forward "test"', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" as foo-*', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide this', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide this $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide this, $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "abstracts/functions" show px-to-rem, theme-color', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" show this', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" show $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" show this $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" as foo-* show this $that', parser, parser._parseForward.bind(parser)); + + assertError("@forward", parser, parser._parseForward.bind(parser), ParseError.StringLiteralExpected); + assertError('@forward "test" as', parser, parser._parseForward.bind(parser), ParseError.IdentifierExpected); + assertError('@forward "test" as foo-', parser, parser._parseForward.bind(parser), ParseError.WildcardExpected); + assertError('@forward "test" as foo- *', parser, parser._parseForward.bind(parser), ParseError.WildcardExpected); + assertError( + '@forward "test" show', + parser, + parser._parseForward.bind(parser), + ParseError.IdentifierOrVariableExpected, + ); + assertError( + '@forward "test" hide', + parser, + parser._parseForward.bind(parser), + ParseError.IdentifierOrVariableExpected, + ); + + assertNode( + '@forward "test" with ( $black: #222 !default, $border-radius: 0.1rem !default )', + parser, + parser._parseForward.bind(parser), + ); + assertNode( + '@forward "../forms.scss" as components-* with ( $field-border: false )', + parser, + parser._parseForward.bind(parser), + ); // #145108 + + assertNode( + `@use "lib" +@forward "test"`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@forward "test" +@forward "lib"`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `$test: "test" +@forward "test"`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@use", () => { + assertNode('@use "test"', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" as foo', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" as *', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" with ($foo: "test", $bar: 1)', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" as foo with ($foo: "test", $bar: 1)', parser, parser._parseUse.bind(parser)); + + assertError("@use", parser, parser._parseUse.bind(parser), ParseError.StringLiteralExpected); + assertError('@use "test" foo', parser, parser._parseUse.bind(parser), ParseError.UnknownKeyword); + assertError('@use "test" as', parser, parser._parseUse.bind(parser), ParseError.IdentifierOrWildcardExpected); + assertError('@use "test" with', parser, parser._parseUse.bind(parser), ParseError.LeftParenthesisExpected); + assertError('@use "test" with ($foo)', parser, parser._parseUse.bind(parser), ParseError.VariableValueExpected); + assertError('@use "test" with ("bar")', parser, parser._parseUse.bind(parser), ParseError.VariableNameExpected); + assertError( + '@use "test" with ($foo: 1, "bar")', + parser, + parser._parseUse.bind(parser), + ParseError.VariableNameExpected, + ); + assertError( + '@use "test" with ($foo: "bar"', + parser, + parser._parseUse.bind(parser), + ParseError.RightParenthesisExpected, + ); + + assertNode( + `@forward "test" +@use "lib"`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@use "test" +@use "lib"`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `$test: "test" +@use "lib"`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@container", () => { + assertNode( + `@container (min-width: #{$minWidth}) + .scss-interpolation + line-height: 10cqh`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.item-icon + @container (max-height: 100px) + .item-icon + display: none`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `:root + @container (max-height: 100px) + display: none`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@layer", () => { + assertNode("@layer #{$layer}\n\t", parser, parser._parseLayer.bind(parser)); + }); + + test("@import", () => { + assertNode('@import "test.css"', parser, parser._parseImport.bind(parser)); + assertNode('@import url("test.css")', parser, parser._parseImport.bind(parser)); + assertNode('@import "test.css", "bar.css"', parser, parser._parseImport.bind(parser)); + assertNode('@import "test.css", "bar.css" screen, projection', parser, parser._parseImport.bind(parser)); + assertNode( + `foo + @import "test.css"`, + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + '@import "test.css" "bar.css"', + parser, + parser._parseStylesheet.bind(parser), + ParseError.MediaQueryExpected, + ); + assertError('@import "test.css", screen', parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); + assertError("@import", parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); + assertNode('@import url("override.css") layer', parser, parser._parseStylesheet.bind(parser)); + }); + + test("declaration", () => { + assertNode("border: thin solid 1px", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: $color", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: blue", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / $const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / 20 + $const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: func($red)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: func($red) !important", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: desaturate($red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: desaturate(16, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: $base-color + #111", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: 100% / 2 + $ref", parser, parser._parseDeclaration.bind(parser)); + assertNode("border: ($width * 2) solid black", parser, parser._parseDeclaration.bind(parser)); + assertNode("property: $class", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: fnc($t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("width: (1em + 2em) * 3", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: #010203 + #040506", parser, parser._parseDeclaration.bind(parser)); + assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser)); + assertNode("margin: 3px + 4px auto", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: hsl(0, 100%, 50%)", parser, parser._parseDeclaration.bind(parser)); + assertNode( + "color: hsl($hue: 0, $saturation: 100%, $lightness: 50%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode("foo: if($value == 'default', flex-gutter(), $value)", parser, parser._parseDeclaration.bind(parser)); + assertNode("foo: if(true, !important, null)", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: selector-replace(&, 1)", parser, parser._parseDeclaration.bind(parser)); + + assertNode("dummy: module.$color", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / module.$const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / 20 + module.$const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.func($red)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.func($red) !important", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.desaturate($red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: desaturate(module.$red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.desaturate(module.$red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.desaturate(16, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: module.$base-color + #111", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: 100% / 2 + module.$ref", parser, parser._parseDeclaration.bind(parser)); + assertNode("border: (module.$width * 2) solid black", parser, parser._parseDeclaration.bind(parser)); + assertNode("property: module.$class", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: module.fnc($t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: fnc(module.$t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: module.fnc(module.$t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("width: (1em + 2em) * 3", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: #010203 + #040506", parser, parser._parseDeclaration.bind(parser)); + assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser)); + assertNode("margin: 3px + 4px auto", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: color.hsl(0, 100%, 50%)", parser, parser._parseDeclaration.bind(parser)); + assertNode( + "color: color.hsl($hue: 0, $saturation: 100%, $lightness: 50%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if(module.$value == 'default', flex-gutter(), $value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if($value == 'default', module.flex-gutter(), $value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if($value == 'default', flex-gutter(), module.$value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if(module.$value == 'default', module.flex-gutter(), $value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if($value == 'default', module.flex-gutter(), module.$value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if(module.$value == 'default', module.flex-gutter(), module.$value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode("color: selector.replace(&, 1)", parser, parser._parseDeclaration.bind(parser)); + + assertError("fo = 8", parser, parser._parseDeclaration.bind(parser), ParseError.ColonExpected); + assertError("fo:", parser, parser._parseDeclaration.bind(parser), ParseError.PropertyValueExpected); + assertError("color: hsl($hue: 0,", parser, parser._parseDeclaration.bind(parser), ParseError.ExpressionExpected); + assertError( + "color: hsl($hue: 0", + parser, + parser._parseDeclaration.bind(parser), + ParseError.RightParenthesisExpected, + ); + }); + + test("interpolation", () => { + assertNode("--#{module.$propname}: some-value", parser, parser._parseDeclaration.bind(parser)); + }); + + test("operators", () => { + assertNode(">=", parser, parser._parseOperator.bind(parser)); + assertNode(">", parser, parser._parseOperator.bind(parser)); + assertNode("<", parser, parser._parseOperator.bind(parser)); + assertNode("<=", parser, parser._parseOperator.bind(parser)); + assertNode("==", parser, parser._parseOperator.bind(parser)); + assertNode("!=", parser, parser._parseOperator.bind(parser)); + assertNode("and", parser, parser._parseOperator.bind(parser)); + assertNode("+", parser, parser._parseOperator.bind(parser)); + assertNode("-", parser, parser._parseOperator.bind(parser)); + assertNode("*", parser, parser._parseOperator.bind(parser)); + assertNode("/", parser, parser._parseOperator.bind(parser)); + assertNode("%", parser, parser._parseOperator.bind(parser)); + assertNode("not", parser, parser._parseUnaryOperator.bind(parser)); + }); + + test("expressions", () => { + assertNode("($const + 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($const - 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($const * 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($const / 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 - $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 * $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 / $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 / 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + $const + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($var1 + $var2)", parser, parser._parseExpr.bind(parser)); + assertNode("(($const + 5) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("(($const + (5 + 2)) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("($const + ((5 + 2) * 2))", parser, parser._parseExpr.bind(parser)); + assertNode("$color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, 42%", parser, parser._parseExpr.bind(parser)); + assertNode("$color, 42%, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color - ($color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("($base + $filler)", parser, parser._parseExpr.bind(parser)); + assertNode("(100% / 2 + $filler)", parser, parser._parseExpr.bind(parser)); + assertNode("100% / 2 + $filler", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and $b) or $c", parser, parser._parseExpr.bind(parser)); + + assertNode("(module.$const + 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const - 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const * 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const / 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 - module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 * module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 / module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + module.$const + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("($var1 + module.$var2)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$var1 + $var2)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$var1 + module.$var2)", parser, parser._parseExpr.bind(parser)); + assertNode("((module.$const + 5) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("((module.$const + (5 + 2)) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const + ((5 + 2) * 2))", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, 42%", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, 42%, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, 42%, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, 42%, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color - ($color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("$color - (module.$color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color - (module.$color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$base + $filler)", parser, parser._parseExpr.bind(parser)); + assertNode("($base + module.$filler)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$base + module.$filler)", parser, parser._parseExpr.bind(parser)); + assertNode("(100% / 2 + module.$filler)", parser, parser._parseExpr.bind(parser)); + assertNode("100% / 2 + module.$filler", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and $b) or $c", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and module.$b) or $c", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and $b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and module.$b) or $c", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and $b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and module.$b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and module.$b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not module.$v", parser, parser._parseExpr.bind(parser)); + + assertError("(20 + 20", parser, parser._parseExpr.bind(parser), ParseError.RightParenthesisExpected); + }); + + test("variable declaration", () => { + assertNode("$color: #F5F5F5", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 0", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 255", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 25.5", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 25px", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 25.5px !default", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$text-color: green !global", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode( + '$_RESOURCES: append($_RESOURCES, "clean") !global', + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode("$footer-height: 40px !default !global", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode( + '$primary-font: "wf_SegoeUI","Segoe UI","Segoe","Segoe WP"', + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode("$color: red !important", parser, parser._parseVariableDeclaration.bind(parser)); + + assertError("$color: red !def", parser, parser._parseVariableDeclaration.bind(parser), ParseError.UnknownKeyword); + assertError( + "$color : !default", + parser, + parser._parseVariableDeclaration.bind(parser), + ParseError.VariableValueExpected, + ); + assertError("$color !default", parser, parser._parseVariableDeclaration.bind(parser), ParseError.ColonExpected); + }); + + test("variable", () => { + assertNode("$color", parser, parser._parseVariable.bind(parser)); + assertNode("$co42lor", parser, parser._parseVariable.bind(parser)); + assertNode("$-co42lor", parser, parser._parseVariable.bind(parser)); + }); + + test("module variable", () => { + assertNode("module.$color", parser, parser._parseModuleMember.bind(parser)); + assertNode("module.$co42lor", parser, parser._parseModuleMember.bind(parser)); + assertNode("module.$-co42lor", parser, parser._parseModuleMember.bind(parser)); + assertNode("module.function()", parser, parser._parseModuleMember.bind(parser)); + + assertError("module.", parser, parser._parseModuleMember.bind(parser), ParseError.IdentifierOrVariableExpected); + }); }); From c341f32a8147463a73cdd63b82eb58207a729cc3 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Fri, 24 May 2024 22:27:09 +0200 Subject: [PATCH 030/138] refactor: rename test folder --- .../src/test/{scss => sass}/example.scss | 0 .../test/{scss => sass}/languageFacts.test.ts | 2 +- .../{scss => sass}/linkFixture/both/_foo.scss | 0 .../{scss => sass}/linkFixture/both/foo.scss | 0 .../linkFixture/index/bar/_index.scss | 0 .../linkFixture/index/foo/index.scss | 0 .../linkFixture/module/foo.scss | 0 .../linkFixture/noUnderscore/foo.scss | 0 .../linkFixture/underscore/_foo.scss | 0 .../src/test/{scss => sass}/lint.test.ts | 2 +- .../src/test/sass/parser-indented.test.ts | 3170 +++++++++++++++ .../src/test/sass/parser.test.ts | 3559 ++++------------- .../{scss => sass}/scssCompletion.test.ts | 0 .../{scss => sass}/scssNavigation.test.ts | 8 +- .../{scss => sass}/selectorPrinting.test.ts | 38 + .../src/test/scss/parser.test.ts | 1157 ------ 16 files changed, 3987 insertions(+), 3949 deletions(-) rename packages/vscode-css-languageservice/src/test/{scss => sass}/example.scss (100%) rename packages/vscode-css-languageservice/src/test/{scss => sass}/languageFacts.test.ts (97%) rename packages/vscode-css-languageservice/src/test/{scss => sass}/linkFixture/both/_foo.scss (100%) rename packages/vscode-css-languageservice/src/test/{scss => sass}/linkFixture/both/foo.scss (100%) rename packages/vscode-css-languageservice/src/test/{scss => sass}/linkFixture/index/bar/_index.scss (100%) rename packages/vscode-css-languageservice/src/test/{scss => sass}/linkFixture/index/foo/index.scss (100%) rename packages/vscode-css-languageservice/src/test/{scss => sass}/linkFixture/module/foo.scss (100%) rename packages/vscode-css-languageservice/src/test/{scss => sass}/linkFixture/noUnderscore/foo.scss (100%) rename packages/vscode-css-languageservice/src/test/{scss => sass}/linkFixture/underscore/_foo.scss (100%) rename packages/vscode-css-languageservice/src/test/{scss => sass}/lint.test.ts (99%) create mode 100644 packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts rename packages/vscode-css-languageservice/src/test/{scss => sass}/scssCompletion.test.ts (100%) rename packages/vscode-css-languageservice/src/test/{scss => sass}/scssNavigation.test.ts (99%) rename packages/vscode-css-languageservice/src/test/{scss => sass}/selectorPrinting.test.ts (56%) delete mode 100644 packages/vscode-css-languageservice/src/test/scss/parser.test.ts diff --git a/packages/vscode-css-languageservice/src/test/scss/example.scss b/packages/vscode-css-languageservice/src/test/sass/example.scss similarity index 100% rename from packages/vscode-css-languageservice/src/test/scss/example.scss rename to packages/vscode-css-languageservice/src/test/sass/example.scss diff --git a/packages/vscode-css-languageservice/src/test/scss/languageFacts.test.ts b/packages/vscode-css-languageservice/src/test/sass/languageFacts.test.ts similarity index 97% rename from packages/vscode-css-languageservice/src/test/scss/languageFacts.test.ts rename to packages/vscode-css-languageservice/src/test/sass/languageFacts.test.ts index b6f4c3ac..27b1e891 100644 --- a/packages/vscode-css-languageservice/src/test/scss/languageFacts.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/languageFacts.test.ts @@ -9,7 +9,7 @@ import { SassParser } from "../../parser/sassParser"; import { assertColor } from "../css/languageFacts.test"; import { colorFrom256RGB as newColor } from "../../languageFacts/facts"; -suite("SCSS - Language facts", () => { +suite("Sass - Language facts", () => { test("is color", function () { let parser = new SassParser(); assertColor(parser, "#main { color: foo(red) }", "red", newColor(0xff, 0, 0)); diff --git a/packages/vscode-css-languageservice/src/test/scss/linkFixture/both/_foo.scss b/packages/vscode-css-languageservice/src/test/sass/linkFixture/both/_foo.scss similarity index 100% rename from packages/vscode-css-languageservice/src/test/scss/linkFixture/both/_foo.scss rename to packages/vscode-css-languageservice/src/test/sass/linkFixture/both/_foo.scss diff --git a/packages/vscode-css-languageservice/src/test/scss/linkFixture/both/foo.scss b/packages/vscode-css-languageservice/src/test/sass/linkFixture/both/foo.scss similarity index 100% rename from packages/vscode-css-languageservice/src/test/scss/linkFixture/both/foo.scss rename to packages/vscode-css-languageservice/src/test/sass/linkFixture/both/foo.scss diff --git a/packages/vscode-css-languageservice/src/test/scss/linkFixture/index/bar/_index.scss b/packages/vscode-css-languageservice/src/test/sass/linkFixture/index/bar/_index.scss similarity index 100% rename from packages/vscode-css-languageservice/src/test/scss/linkFixture/index/bar/_index.scss rename to packages/vscode-css-languageservice/src/test/sass/linkFixture/index/bar/_index.scss diff --git a/packages/vscode-css-languageservice/src/test/scss/linkFixture/index/foo/index.scss b/packages/vscode-css-languageservice/src/test/sass/linkFixture/index/foo/index.scss similarity index 100% rename from packages/vscode-css-languageservice/src/test/scss/linkFixture/index/foo/index.scss rename to packages/vscode-css-languageservice/src/test/sass/linkFixture/index/foo/index.scss diff --git a/packages/vscode-css-languageservice/src/test/scss/linkFixture/module/foo.scss b/packages/vscode-css-languageservice/src/test/sass/linkFixture/module/foo.scss similarity index 100% rename from packages/vscode-css-languageservice/src/test/scss/linkFixture/module/foo.scss rename to packages/vscode-css-languageservice/src/test/sass/linkFixture/module/foo.scss diff --git a/packages/vscode-css-languageservice/src/test/scss/linkFixture/noUnderscore/foo.scss b/packages/vscode-css-languageservice/src/test/sass/linkFixture/noUnderscore/foo.scss similarity index 100% rename from packages/vscode-css-languageservice/src/test/scss/linkFixture/noUnderscore/foo.scss rename to packages/vscode-css-languageservice/src/test/sass/linkFixture/noUnderscore/foo.scss diff --git a/packages/vscode-css-languageservice/src/test/scss/linkFixture/underscore/_foo.scss b/packages/vscode-css-languageservice/src/test/sass/linkFixture/underscore/_foo.scss similarity index 100% rename from packages/vscode-css-languageservice/src/test/scss/linkFixture/underscore/_foo.scss rename to packages/vscode-css-languageservice/src/test/sass/linkFixture/underscore/_foo.scss diff --git a/packages/vscode-css-languageservice/src/test/scss/lint.test.ts b/packages/vscode-css-languageservice/src/test/sass/lint.test.ts similarity index 99% rename from packages/vscode-css-languageservice/src/test/scss/lint.test.ts rename to packages/vscode-css-languageservice/src/test/sass/lint.test.ts index 3cf4144f..604dd7b7 100644 --- a/packages/vscode-css-languageservice/src/test/scss/lint.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/lint.test.ts @@ -25,7 +25,7 @@ function assertRuleSet(input: string, ...rules: Rule[]): void { assertEntries(node, document, rules); } -suite("SCSS - Lint", () => { +suite("Sass - Lint", () => { test("empty ruleset", function () { assertRuleSet("selector { color: red; nested {} }", Rules.EmptyRuleSet); }); diff --git a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts new file mode 100644 index 00000000..78873af9 --- /dev/null +++ b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts @@ -0,0 +1,3170 @@ +import { suite, test, assert } from "vitest"; +import { ParseError } from "../../parser/cssErrors"; +import { SassParser } from "../../parser/sassParser"; +import * as nodes from "../../parser/cssNodes"; +import { assertError, assertFunction, assertNoNode, assertNode } from "../css/parser.test"; +import { SassParseError } from "../../parser/sassErrors"; + +suite("Sass - Parser", () => { + const parser = new SassParser({ syntax: "indented" }); + + test("empty stylesheet", () => { + assertNode("", parser, parser._parseStylesheet.bind(parser)); + }); + + test("@charset", () => { + assertNode('@charset "demo"', parser, parser._parseStylesheet.bind(parser)); + assertError("@charset", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); + assertError('@charset "demo";', parser, parser._parseStylesheet.bind(parser), ParseError.UnexpectedSemicolon); + }); + + test("newline before at-rule", () => { + assertNode( + ` +@charset "demo"`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("declarations", () => { + assertNode( + `body + margin: 0px + padding: 3em, 6em +`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("CSS comment", () => { + assertNode( + `a + b: /* comment */ c +`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("Sass comment", () => { + assertNode( + `a + // single-line comment + b: c +`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("Multi-line CSS comment should error", () => { + assertError( + `a + b: /* multi +line +comment */ c +`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.PropertyValueExpected, + ); + }); + + test("stylesheet", () => { + assertNode(`$color: #F5F5F5`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `$color: #F5F5F5 +$color: #F5F5F5`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `$color: #F5F5F5 +$color: #F5F5F5 +$color: #F5F5F5`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`$color: #F5F5F5 !important`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `#main + width: 97% + p, div + a + font-weight: bold`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `a + &:hover + color: red`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `fo + font: 2px/3px + family: fantasy`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.foo + bar: + yoo: fantasy`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `selector + propsuffix: + nested: 1px + rule: 1px + nested.selector + foo: 1 + nested:selector + foo: 2`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `legend + foo + a:s + margin-top: 0 + margin-bottom: #123 + margin-top:s(1)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@mixin keyframe + @keyframes name + @content`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@include keyframe + 10% + top: 3px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.class + &--sub-class-with-ampersand + color: red`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertError( + `fo + font: 2px/3px + family`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.ColonExpected, + ); + + assertNode( + `legend + foo + a:s + margin-top:0 + margin-bottom:#123 + margin-top:m.s(1)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@include module.keyframe + 10% + top: 3px`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@media", () => { + assertNode( + `@media screen, projection + a + b: c`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@media screen and (max-width: 400px) + @-ms-viewport + width: 320px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@-ms-viewport + width: 320px + height: 720px`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("selectors", () => { + assertNode( + ` +#boo, far + a: b + +.far boo + c: d +`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("nested selectors", () => { + assertNode( + ` +#boo, far + a: b + + &:hover + a: c + + d: e +`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@-moz-keyframes", () => { + assertNode( + ` +@-moz-keyframes darkWordHighlight + from + background-color: inherit + to + background-color: rgba(83, 83, 83, 0.7) +`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@page", () => { + assertNode( + ` +@page + margin: 2.5cm +`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@font-face", () => { + assertNode( + ` +@font-face + font-family: "Example Font" +`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ` +@font-face + src: url(http://test) +`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ` +@font-face + font-style: normal + font-stretch: normal +`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ` +@font-face + unicode-range: U+0021-007F, u+1f49C, U+4??, U+?????? +`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@namespace", () => { + assertNode(`@namespace "http://www.w3.org/1999/xhtml"`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@namespace pref url(http://test)`, parser, parser._parseStylesheet.bind(parser)); + assertError("@charset", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); + }); + + test("@-moz-document", () => { + assertNode( + ` +@-moz-document url(http://test), url-prefix(http://www.w3.org/Style/) + body + color: purple + background: yellow +`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("attribute selectors", () => { + assertNode( + `E E[foo] E[foo="bar"] E[foo~="bar"] E[foo^="bar"] E[foo$="bar"] E[foo*="bar"] E[foo|="en"] + color: limegreen`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `input[type="submit"] + color: limegreen`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("pseudo-class selectors", () => { + assertNode( + `E:root E:nth-child(n) E:nth-last-child(n) E:nth-of-type(n) E:nth-last-of-type(n) E:first-child E:last-child + color: limegreen`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `E:first-of-type E:last-of-type E:only-child E:only-of-type E:empty E:link E:visited E:active E:hover E:focus E:target E:lang(fr) E:enabled E:disabled E:checked + color: limegreen`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `E::first-line E::first-letter E::before E::after + color: limegreen`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `E.warning E#myid E:not(s) + color: limegreen`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("graceful handling of unknown rules", () => { + assertNode(`@unknown-rule`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@unknown-rule 'foo'`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@unknown-rule (foo)`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `@unknown-rule (foo) + .bar`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@mskeyframes darkWordHighlight + from + background-color: inherit + + to + background-color: rgba(83, 83, 83, 0.7)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `foo + @unknown-rule`, + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError(`@unknown-rule (`, parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected); + assertError( + `@unknown-rule [foo`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightSquareBracketExpected, + ); + assertError( + `@unknown-rule + [foo`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightSquareBracketExpected, + ); + }); + + test("unknown rules node ends properly", () => { + const node = assertNode( + `@unknown-rule (foo) + .bar + color: limegreen + +.foo + color: red`, + parser, + parser._parseStylesheet.bind(parser), + ); + + const unknownAtRule = node.getChild(0)!; + assert.equal(unknownAtRule.type, nodes.NodeType.UnknownAtRule); + assert.equal(unknownAtRule.offset, 0); + assert.equal(node.getChild(0)!.length, 13); + + assertNode( + ` +.foo + @apply p-4 bg-neutral-50 + min-height: var(--space-14) +`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("stylesheet /panic/", () => { + assertError('- @import "foo"', parser, parser._parseStylesheet.bind(parser), ParseError.RuleOrSelectorExpected); + }); + + test("@keyframe selector", () => { + assertNode( + `from + color: red`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + `to + color: blue`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + `0% + color: blue`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + `10% + color: purple`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + `cover 10% + color: purple`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + `100000% + color: purple`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + `from + width: 100 + to: 10px`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + `from, to + width: 10px`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + `10%, to + width: 10px`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + `from, 20% + width: 10px`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + `10%, 20% + width: 10px`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + `cover 10%, exit 20% + width: 10px`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + `10%, exit 20% + width: 10px`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + `from, exit 20% + width: 10px`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + `cover 10%, to + width: 10px`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + assertNode( + `cover 10%, 20% + width: 10px`, + parser, + parser._parseKeyframeSelector.bind(parser), + ); + }); + + test("@keyframe", () => { + assertNode( + `@keyframes name + //`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@-webkit-keyframes name + //`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@-o-keyframes name + //`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@-moz-keyframes name + //`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@keyframes name + from + // + to + //`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@keyframes name + from + // + 80% + // + 100% + //`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@keyframes name + from + top: 0px + 80% + top: 100px + 100% + top: 50px`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@keyframes name + from + top: 0px + 70%, 80% + top: 100px + 100% + top: 50px`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@keyframes name + from + top: 0px + left: 1px + right: 2px`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@keyframes name + exit 50% + top: 0px + left: 1px + right: 2px`, + parser, + parser._parseKeyframe.bind(parser), + ); + + assertError("@keyframes )", parser, parser._parseKeyframe.bind(parser), ParseError.IdentifierExpected); + }); + + test("@keyframes sass", () => { + assertNode( + `@keyframes name + @content`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@keyframes name + @for $i from 0 through $steps + #{$i * (100%/$steps)} + transform: $rotate $translate`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@keyframes test-keyframe + @for $i from 1 through 60 + $s: ($i * 100) / 60 + "%"`, + parser, + parser._parseKeyframe.bind(parser), + ); + + assertNode( + `@keyframes name + @for $i from 0 through m.$steps + #{$i * (100%/$steps)} + transform: $rotate $translate`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@keyframes name + @function bar() + @return 1`, + parser, + parser._parseKeyframe.bind(parser), + ); + assertNode( + `@keyframes name + @include keyframe-mixin()`, + parser, + parser._parseKeyframe.bind(parser), + ); + }); + + test("@property", () => { + assertNode( + `@property --my-color + syntax: '' + inherits: false + initial-value: #c0ffee`, + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + `@property + syntax: '' + inherits: false + initial-value: #c0ffee`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.IdentifierExpected, + ); + }); + + test("@container", () => { + assertNode( + `@container (width <= 150px) + #inner + background-color: skyblue`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@container card (inline-size > 30em) and style(--responsive: true) + #inner + background-color: skyblue`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ` +@container card (inline-size > 30em) + @container style(--responsive: true) + #inner + background-color: skyblue`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + ` +@container (min-width: 700px) + .card h2 + font-size: max(1.5em, 1.23em + 2cqi)`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@import", () => { + assertNode(`@import "asdfasdf"`, parser, parser._parseImport.bind(parser)); + assertNode(`@ImPort "asdfasdf"`, parser, parser._parseImport.bind(parser)); + assertNode(`@import url(/css/screen.css) screen, projection`, parser, parser._parseImport.bind(parser)); + assertNode( + `@import url('landscape.css') screen and (orientation:landscape)`, + parser, + parser._parseImport.bind(parser), + ); + assertNode(`@import url("/inc/Styles/full.css") (min-width: 940px)`, parser, parser._parseImport.bind(parser)); + assertNode(`@import url(style.css) screen and (min-width:600px)`, parser, parser._parseImport.bind(parser)); + assertNode(`@import url("./700.css") only screen and (max-width: 700px)`, parser, parser._parseImport.bind(parser)); + assertNode(`@import url("override.css") layer`, parser, parser._parseImport.bind(parser)); + assertNode(`@import url("tabs.css") layer(framework.component)`, parser, parser._parseImport.bind(parser)); + assertNode(`@import "mystyle.css" supports(display: flex)`, parser, parser._parseImport.bind(parser)); + assertNode( + `@import url("narrow.css") supports(display: flex) handheld and (max-width: 400px)`, + parser, + parser._parseImport.bind(parser), + ); + assertNode( + `@import url("fallback-layout.css") supports(not (display: flex))`, + parser, + parser._parseImport.bind(parser), + ); + + assertError(`@import`, parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); + }); + + test("@supports", () => { + assertNode( + `@supports ( display: flexbox ) + body + display: flexbox`, + parser, + parser._parseSupports.bind(parser), + ); + assertNode( + `@supports not (display: flexbox) + .outline + box-shadow: 2px 2px 2px black /* unprefixed last */`, + parser, + parser._parseSupports.bind(parser), + ); + assertNode( + `@supports ( box-shadow: 2px 2px 2px black ) or ( -moz-box-shadow: 2px 2px 2px black ) or ( -webkit-box-shadow: 2px 2px 2px black ) + .foo + color: red`, + parser, + parser._parseSupports.bind(parser), + ); + assertNode( + `@supports ((transition-property: color) or (animation-name: foo)) and (transform: rotate(10deg)) + .foo + color: red`, + parser, + parser._parseSupports.bind(parser), + ); + assertNode( + `@supports ((display: flexbox)) + .foo + color: red`, + parser, + parser._parseSupports.bind(parser), + ); + assertNode( + `@supports (display: flexbox !important) + .foo + color: red`, + parser, + parser._parseSupports.bind(parser), + ); + assertNode( + `@supports (column-width: 1rem) OR (-moz-column-width: 1rem) OR (-webkit-column-width: 1rem) oR (-x-column-width: 1rem) + .foo + color: limegreen`, + parser, + parser._parseSupports.bind(parser), + ); + assertNode( + `@supports not (--validValue: , 0 ) + .foo + color: limegreen`, + parser, + parser._parseSupports.bind(parser), + ); + + assertError( + `@supports display: flexbox + .foo + color: limegreen`, + parser, + parser._parseSupports.bind(parser), + ParseError.LeftParenthesisExpected, + ); + }); + + test("@media", () => { + assertNode( + `@media asdsa + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@meDia asdsa + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@meDia somename, othername2 + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media only screen and (max-width:850px) + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media all and (min-width:500px) + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (color), projection and (color) + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media not screen and (device-aspect-ratio: 16/9) + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media print and (min-resolution: 300dpi) + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media print and (min-resolution: 118dpcm) + .foo + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media print + @page + margin: 10% + blockquote, pre + page-break-inside: avoid`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media print + body:before + page-break-inside: avoid`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media not (-moz-os-version: windows-win7) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media not (not (-moz-os-version: windows-win7)) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media (height > 600px) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media (height < 600px) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media (height <= 600px) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media (400px <= width <= 700px) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media (400px >= width >= 700px) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (750px <= width < 900px) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ); + + assertError( + `@media somename othername2 + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ParseError.IndentExpected, + ); + assertError( + `@media not, screen + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ParseError.MediaQueryExpected, + ); + assertError( + `@media not screen and foo + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ParseError.LeftParenthesisExpected, + ); + assertError( + `@media not screen and () + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + `@media not screen and (color:) + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ParseError.TermExpected, + ); + assertError( + `@media not screen and (color:#fff + body + color: black`, + parser, + parser._parseMedia.bind(parser), + ParseError.RightParenthesisExpected, + ); + }); + + test("@media sass", () => { + assertNode( + `@media screen + .sidebar + @media (orientation: landscape) + width: 500px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@media #{$media} and ($feature: $value)\n\t`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@media only screen and #{$query}\n\t`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `foo + bar + @media screen and (orientation: landscape) + color: red`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@media screen and (nth($query, 1): nth($query, 2))\n\t`, parser, parser._parseMedia.bind(parser)); + assertNode( + `.something + @media (max-width: 760px) + > .test + color: blue`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.something + @media (max-width: 760px) + ~ div + display: block`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.something + @media (max-width: 760px) + + div + display: block`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@media (max-width: 760px) + + div + display: block`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@media (height <= 600px)\n\t`, parser, parser._parseMedia.bind(parser)); + assertNode(`@media (height >= 600px)\n\t`, parser, parser._parseMedia.bind(parser)); + + assertNode(`@media #{layout.$media} and ($feature: $value)\n\t`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@media #{$media} and (layout.$feature: $value)\n\t`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@media #{$media} and ($feature: layout.$value)\n\t`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `@media #{layout.$media} and (layout.$feature: $value)\n\t`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@media #{$media} and (layout.$feature: layout.$value)\n\t`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@media #{layout.$media} and (layout.$feature: layout.$value)\n\t`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@media screen and (list.nth($query, 1): nth($query, 2))\n\t`, parser, parser._parseMedia.bind(parser)); + assertNode(`@media screen and (nth(list.$query, 1): nth($query, 2))\n\t`, parser, parser._parseMedia.bind(parser)); + assertNode(`@media screen and (nth($query, 1): list.nth($query, 2))\n\t`, parser, parser._parseMedia.bind(parser)); + assertNode(`@media screen and (nth($query, 1): nth(list.$query, 2))\n\t`, parser, parser._parseMedia.bind(parser)); + assertNode( + `@media screen and (list.nth(list.$query, 1): nth($query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (list.nth($query, 1): list.nth($query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (list.nth($query, 1): nth(list.$query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (nth(list.$query, 1): list.nth($query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (nth(list.$query, 1): nth(list.$query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (nth($query, 1): list.nth(list.$query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (list.nth(list.$query, 1): list.nth($query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (nth(list.$query, 1): list.nth(list.$query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + assertNode( + `@media screen and (list.nth(list.$query, 1): list.nth(list.$query, 2))\n\t`, + parser, + parser._parseMedia.bind(parser), + ); + }); + + test("media query list", () => { + assertNode("somename", parser, parser._parseMediaQueryList.bind(parser)); + assertNode("somename, othername", parser, parser._parseMediaQueryList.bind(parser)); + assertNode("not all and (monochrome)", parser, parser._parseMediaQueryList.bind(parser)); + }); + + test("medium", () => { + assertNode("somename", parser, parser._parseMedium.bind(parser)); + assertNode("-asdas", parser, parser._parseMedium.bind(parser)); + assertNode("-asda34s", parser, parser._parseMedium.bind(parser)); + }); + + test("@page", () => { + assertNode( + `@page : name + some: "asdf"`, + parser, + parser._parsePage.bind(parser), + ); + assertNode( + `@page :left, :right + some: "asdf"`, + parser, + parser._parsePage.bind(parser), + ); + assertNode( + `@page : name + some: "asdf" !important + some: "asdf" !important`, + parser, + parser._parsePage.bind(parser), + ); + assertNode( + `@page rotated + size: landscape`, + parser, + parser._parsePage.bind(parser), + ); + assertNode( + `@page :left + margin-left: 4cm + margin-right: 3cm`, + parser, + parser._parsePage.bind(parser), + ); + assertNode( + `@page + @top-right-corner + content: url(foo.png) + border: solid green`, + parser, + parser._parsePage.bind(parser), + ); + assertNode( + `@page + @top-left-corner + content: " " + border: solid green + @bottom-right-corner + content: counter(page) + border: solid green`, + parser, + parser._parsePage.bind(parser), + ); + + assertError( + `@page + @top-left-corner foo + content: " " + border: solid green`, + parser, + parser._parsePage.bind(parser), + ParseError.IndentExpected, + ); + assertError( + `@page : + @top-left-corner foo + content: " " + border: solid green`, + parser, + parser._parsePage.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + `@page :left, + @top-left-corner foo + content: " " + border: solid green`, + parser, + parser._parsePage.bind(parser), + ParseError.IdentifierExpected, + ); + }); + + test("@layer", () => { + assertNode( + `@layer utilities + .padding-sm + padding: .5rem`, + parser, + parser._parseLayer.bind(parser), + ); + assertNode(`@layer utilities`, parser, parser._parseLayer.bind(parser)); + assertNode(`@layer theme, layout, utilities`, parser, parser._parseLayer.bind(parser)); + assertNode( + `@layer framework + @layer layout + .padding-sm + padding: .5rem`, + parser, + parser._parseLayer.bind(parser), + ); + assertNode( + `@layer framework.layout + @keyframes slide-left + from + foo: bar + to + foo: baz`, + parser, + parser._parseLayer.bind(parser), + ); + + assertNode( + `@media (min-width: 30em) + @layer layout + .padding-sm + padding: .5rem`, + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError(`@layer theme. layout`, parser, parser._parseLayer.bind(parser), ParseError.IdentifierExpected); + }); + + test("operator", () => { + assertNode("/", parser, parser._parseOperator.bind(parser)); + assertNode("*", parser, parser._parseOperator.bind(parser)); + assertNode("+", parser, parser._parseOperator.bind(parser)); + assertNode("-", parser, parser._parseOperator.bind(parser)); + }); + + test("combinator", () => { + assertNode("+", parser, parser._parseCombinator.bind(parser)); + assertNode("+ ", parser, parser._parseCombinator.bind(parser)); + assertNode("> ", parser, parser._parseCombinator.bind(parser)); + assertNode(">", parser, parser._parseCombinator.bind(parser)); + assertNode(">>>", parser, parser._parseCombinator.bind(parser)); + assertNode("/deep/", parser, parser._parseCombinator.bind(parser)); + assertNode( + `:host >>> .data-table + width: 100%`, + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + `:host >> .data-table + width: 100%`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.IndentExpected, + ); + }); + + test("unary_operator", () => { + assertNode("-", parser, parser._parseUnaryOperator.bind(parser)); + assertNode("+", parser, parser._parseUnaryOperator.bind(parser)); + }); + + test("property", () => { + assertNode("asdsa", parser, parser._parseProperty.bind(parser)); + assertNode("asdsa334", parser, parser._parseProperty.bind(parser)); + + assertNode("--color", parser, parser._parseProperty.bind(parser)); + assertNode("--primary-font", parser, parser._parseProperty.bind(parser)); + assertNode("-color", parser, parser._parseProperty.bind(parser)); + assertNode("somevar", parser, parser._parseProperty.bind(parser)); + assertNode("some--let", parser, parser._parseProperty.bind(parser)); + assertNode("somevar--", parser, parser._parseProperty.bind(parser)); + }); + + test("ruleset", () => { + assertNode( + ` +.foo + font: + family: Arial + size: 20px + color: #ff0000`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `selector + property: value + @keyframes foo + from + top: 0 + 100% + top: 100% + @-moz-keyframes foo + from + top: 0 + 100% + top: 100%`, + parser, + parser._parseRuleset.bind(parser), + ); + + assertNode("foo|bar\n\t//", parser, parser._parseRuleset.bind(parser)); + + assertNode( + `name + foo: bar`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` + +name + foo: "asdfasdf"`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` + +name + foo : "asdfasdf" !important`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `* + foo: bar`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `.far + foo: bar`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `boo + foo: bar`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `.far #boo + foo: bar`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `name + foo: bar + baz: bar`, + parser, + parser._parseRuleset.bind(parser), + ); + + assertError( + `name + --minimal:`, + parser, + parser._parseRuleset.bind(parser), + ParseError.PropertyValueExpected, + ); + assertNode( + `name + --minimal: + + other + padding: 1rem`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `name + --normal-text: red yellow green`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `name + --normal-text: red !important`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `name + --nested: + color: green`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `name + --normal-text: this()is()ok()`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `name + --normal-text: this[]is[]ok[]`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `name + --normal-text: ([{{[]()()}[]{}}])()`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `name + --normal-text: , 0 0`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `name + --normal-text: {}`, + parser, + parser._parseRuleset.bind(parser), + ); + + assertNode( + `.selector + prop: erty $const 1px`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `.selector + prop: erty $const 1px m.$foo`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `selector:active + property: value + nested: hover + property: value`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `selector + property: declaration`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `selector + $variable: declaration`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `selector + property: value + property: $value`, + parser, + parser._parseRuleset.bind(parser), + ); + + assertError( + `name + font-size: {}`, + parser, + parser._parseRuleset.bind(parser), + ParseError.PropertyValueExpected, + ); + assertError( + `boo, + `, + parser, + parser._parseRuleset.bind(parser), + ParseError.SelectorExpected, + ); + }); + + test("ruleset /Panic/", () => { + assertError( + ` +foo + bar:`, + parser, + parser._parseRuleset.bind(parser), + ParseError.PropertyValueExpected, + ); + assertError( + ` +foo + bar: + far: 12em`, + parser, + parser._parseRuleset.bind(parser), + ParseError.PropertyValueExpected, + ); + assertError( + ` +foo + bar`, + parser, + parser._parseRuleset.bind(parser), + ParseError.ColonExpected, + ); + assertError( + ` +foo + --too-minimal:`, + parser, + parser._parseRuleset.bind(parser), + ParseError.PropertyValueExpected, + ); + assertError( + ` +foo + --double-important: red !important !important`, + parser, + parser._parseRuleset.bind(parser), + ParseError.NewlineExpected, + ); + assertError( + ` +foo + --unbalanced-parens: not)()(cool`, + parser, + parser._parseRuleset.bind(parser), + ParseError.LeftParenthesisExpected, + ); + assertError( + ` +foo + --unbalanced-parens: not][][cool`, + parser, + parser._parseRuleset.bind(parser), + ParseError.LeftSquareBracketExpected, + ); + }); + + test("nested ruleset", () => { + assertNode( + ` +.foo + color: red + input + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + :focus + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + .bar + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + &:hover + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + + .bar + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + foo:hover + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + @media screen + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + + assertNode( + ` +.foo + $const: 1 + .class + $const: 2 + $const: 3 + one: $const`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.class1 + > .class2 + & > .class4 + rule1: v1`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +foo + @at-root + display: none`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +th, tr + @at-root #{selector-replace(&, "tr")} + border-bottom: 0`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +foo + @supports(display: grid) + .bar + display: none`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +foo + @supports(display: grid) + display: none`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +foo + @supports(position: sticky) + @media (min-width: map-get($grid-breakpoints, medium)) + position: sticky`, + parser, + parser._parseRuleset.bind(parser), + ); + }); + + test("nested ruleset 2", () => { + assertNode( + ` +.foo + .parent & + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + & > .bar, > .baz + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + & .bar & .baz & .hmm + color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + :not(&) + color: blue + + .bar + & + color: green`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + color: red + & + color: blue + && + color: green`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +.foo + & :is(.bar, &.baz) + color: red`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +figure + > figcaption + background: hsl(0 0% 0% / 50%) + > p + font-size: .9rem`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + ` +@layer base + html + & body + min-block-size: 100%`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("selector", () => { + assertNode("asdsa", parser, parser._parseSelector.bind(parser)); + assertNode("asdsa + asdas", parser, parser._parseSelector.bind(parser)); + assertNode("asdsa + asdas + name", parser, parser._parseSelector.bind(parser)); + assertNode("asdsa + asdas + name", parser, parser._parseSelector.bind(parser)); + assertNode("name #id#anotherid", parser, parser._parseSelector.bind(parser)); + assertNode("name.far .boo", parser, parser._parseSelector.bind(parser)); + assertNode("name .name .zweitername", parser, parser._parseSelector.bind(parser)); + assertNode("*", parser, parser._parseSelector.bind(parser)); + assertNode("#id", parser, parser._parseSelector.bind(parser)); + assertNode("far.boo", parser, parser._parseSelector.bind(parser)); + assertNode("::slotted(div)::after", parser, parser._parseSelector.bind(parser)); // 35076 + }); + + test("attrib", () => { + assertNode("[name]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name = name2]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name ~= name3]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name~=name3]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name |= name3]", parser, parser._parseAttrib.bind(parser)); + assertNode('[name |= "this is a striiiing"]', parser, parser._parseAttrib.bind(parser)); + assertNode('[href*="insensitive" i]', parser, parser._parseAttrib.bind(parser)); + assertNode('[href*="sensitive" S]', parser, parser._parseAttrib.bind(parser)); + + // Single namespace + assertNode("[namespace|name]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name-space|name = name2]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name_space|name ~= name3]", parser, parser._parseAttrib.bind(parser)); + assertNode("[name0spae|name~=name3]", parser, parser._parseAttrib.bind(parser)); + assertNode('[NameSpace|name |= "this is a striiiing"]', parser, parser._parseAttrib.bind(parser)); + assertNode("[name\\*space|name |= name3]", parser, parser._parseAttrib.bind(parser)); + assertNode("[*|name]", parser, parser._parseAttrib.bind(parser)); + }); + + test("pseudo", () => { + assertNode(":some", parser, parser._parsePseudo.bind(parser)); + assertNode(":some(thing)", parser, parser._parsePseudo.bind(parser)); + assertNode(":nth-child(12)", parser, parser._parsePseudo.bind(parser)); + assertNode(":nth-child(1n)", parser, parser._parsePseudo.bind(parser)); + assertNode(":nth-child(-n+3)", parser, parser._parsePseudo.bind(parser)); + assertNode(":nth-child(2n+1)", parser, parser._parsePseudo.bind(parser)); + assertNode(":nth-child(2n+1 of .foo)", parser, parser._parsePseudo.bind(parser)); + assertNode(':nth-child(2n+1 of .foo > bar, :not(*) ~ [other="value"])', parser, parser._parsePseudo.bind(parser)); + assertNode(":lang(it)", parser, parser._parsePseudo.bind(parser)); + assertNode(":not(.class)", parser, parser._parsePseudo.bind(parser)); + assertNode(":not(:disabled)", parser, parser._parsePseudo.bind(parser)); + assertNode(":not(#foo)", parser, parser._parsePseudo.bind(parser)); + assertNode("::slotted(*)", parser, parser._parsePseudo.bind(parser)); // #35076 + assertNode("::slotted(div:hover)", parser, parser._parsePseudo.bind(parser)); // #35076 + assertNode(":global(.output ::selection)", parser, parser._parsePseudo.bind(parser)); // #49010 + assertNode(":matches(:hover, :focus)", parser, parser._parsePseudo.bind(parser)); // #49010 + assertNode(":host([foo=bar][bar=foo])", parser, parser._parsePseudo.bind(parser)); // #49589 + assertNode(":has(> .test)", parser, parser._parsePseudo.bind(parser)); // #250 + assertNode(":has(~ .test)", parser, parser._parsePseudo.bind(parser)); // #250 + assertNode(":has(+ .test)", parser, parser._parsePseudo.bind(parser)); // #250 + assertNode(":has(~ div .test)", parser, parser._parsePseudo.bind(parser)); // #250 + assertError("::", parser, parser._parsePseudo.bind(parser), ParseError.IdentifierExpected); + assertError(":: foo", parser, parser._parsePseudo.bind(parser), ParseError.IdentifierExpected); + assertError(":nth-child(1n of)", parser, parser._parsePseudo.bind(parser), ParseError.SelectorExpected); + }); + + test("declaration", () => { + assertNode('name : "this is a string" !important', parser, parser._parseDeclaration.bind(parser)); + assertNode('name : "this is a string"', parser, parser._parseDeclaration.bind(parser)); + assertNode("property:12", parser, parser._parseDeclaration.bind(parser)); + assertNode("-vendor-property: 12", parser, parser._parseDeclaration.bind(parser)); + assertNode("font-size: 12px", parser, parser._parseDeclaration.bind(parser)); + assertNode("color : #888 /4", parser, parser._parseDeclaration.bind(parser)); + assertNode( + "filter : progid:DXImageTransform.Microsoft.Shadow(color=#000000,direction=45)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "filter : progid: DXImageTransform.Microsoft.DropShadow(offx=2, offy=1, color=#000000)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode("font-size: 12px", parser, parser._parseDeclaration.bind(parser)); + assertNode("*background: #f00 /* IE 7 and below */", parser, parser._parseDeclaration.bind(parser)); + assertNode("_background: #f60 /* IE 6 and below */", parser, parser._parseDeclaration.bind(parser)); + assertNode( + "background-image: linear-gradient(to right, silver, white 50px, white calc(100% - 50px), silver)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "grid-template-columns: [first nav-start] 150px [main-start] 1fr [last]", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "grid-template-columns: repeat(4, 10px [col-start] 250px [col-end]) 10px", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "grid-template-columns: [a] auto [b] minmax(min-content, 1fr) [b c d] repeat(2, [e] 40px)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode("grid-template: [foo] 10px / [bar] 10px", parser, parser._parseDeclaration.bind(parser)); + assertNode( + `grid-template: 'left1 footer footer' 1fr [end] / [ini] 1fr [info-start] 2fr 1fr [end]`, + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode(`content: "("counter(foo) ")"`, parser, parser._parseDeclaration.bind(parser)); + assertNode(`content: 'Hello\\0A''world'`, parser, parser._parseDeclaration.bind(parser)); + }); + + test("term", () => { + assertNode('"asdasd"', parser, parser._parseTerm.bind(parser)); + assertNode("name", parser, parser._parseTerm.bind(parser)); + assertNode("#FFFFFF", parser, parser._parseTerm.bind(parser)); + assertNode('url("this is a url")', parser, parser._parseTerm.bind(parser)); + assertNode("+324", parser, parser._parseTerm.bind(parser)); + assertNode("-45", parser, parser._parseTerm.bind(parser)); + assertNode("+45", parser, parser._parseTerm.bind(parser)); + assertNode("-45%", parser, parser._parseTerm.bind(parser)); + assertNode("-45mm", parser, parser._parseTerm.bind(parser)); + assertNode("-45em", parser, parser._parseTerm.bind(parser)); + assertNode('"asdsa"', parser, parser._parseTerm.bind(parser)); + assertNode("faa", parser, parser._parseTerm.bind(parser)); + assertNode('url("this is a striiiiing")', parser, parser._parseTerm.bind(parser)); + assertNode("#FFFFFF", parser, parser._parseTerm.bind(parser)); + assertNode("name(asd)", parser, parser._parseTerm.bind(parser)); + assertNode("calc(50% + 20px)", parser, parser._parseTerm.bind(parser)); + assertNode("calc(50% + (100%/3 - 2*1em - 2*1px))", parser, parser._parseTerm.bind(parser)); + assertNoNode( + "%('repetitions: %S file: %S', 1 + 2, \"directory/file.less\")", + parser, + parser._parseTerm.bind(parser), + ); // less syntax + assertNoNode('~"ms:alwaysHasItsOwnSyntax.For.Stuff()"', parser, parser._parseTerm.bind(parser)); // less syntax + assertNode("U+002?-0199", parser, parser._parseTerm.bind(parser)); + assertNoNode("U+002?-01??", parser, parser._parseTerm.bind(parser)); + assertNoNode("U+00?0;", parser, parser._parseTerm.bind(parser)); + assertNoNode("U+0XFF;", parser, parser._parseTerm.bind(parser)); + }); + + test("function", () => { + assertNode('name( "bla" )', parser, parser._parseFunction.bind(parser)); + assertNode("name( name )", parser, parser._parseFunction.bind(parser)); + assertNode("name( -500mm )", parser, parser._parseFunction.bind(parser)); + assertNode("\u060frf()", parser, parser._parseFunction.bind(parser)); + assertNode("über()", parser, parser._parseFunction.bind(parser)); + + assertNoNode("über ()", parser, parser._parseFunction.bind(parser)); + assertNoNode("%()", parser, parser._parseFunction.bind(parser)); + assertNoNode("% ()", parser, parser._parseFunction.bind(parser)); + + assertFunction("let(--color)", parser, parser._parseFunction.bind(parser)); + assertFunction("let(--color, somevalue)", parser, parser._parseFunction.bind(parser)); + assertFunction("let(--variable1, --variable2)", parser, parser._parseFunction.bind(parser)); + assertFunction("let(--variable1, let(--variable2))", parser, parser._parseFunction.bind(parser)); + assertFunction("fun(value1, value2)", parser, parser._parseFunction.bind(parser)); + assertFunction("fun(value1,)", parser, parser._parseFunction.bind(parser)); + }); + + test("test token prio", () => { + assertNode("!important", parser, parser._parsePrio.bind(parser)); + assertNode("!/*demo*/important", parser, parser._parsePrio.bind(parser)); + assertNode("! /*demo*/ important", parser, parser._parsePrio.bind(parser)); + assertNode("! /*dem o*/ important", parser, parser._parsePrio.bind(parser)); + }); + + test("hexcolor", () => { + assertNode("#FFF", parser, parser._parseHexColor.bind(parser)); + assertNode("#FFFF", parser, parser._parseHexColor.bind(parser)); + assertNode("#FFFFFF", parser, parser._parseHexColor.bind(parser)); + assertNode("#FFFFFFFF", parser, parser._parseHexColor.bind(parser)); + }); + + test("test class", () => { + assertNode(".faa", parser, parser._parseClass.bind(parser)); + assertNode("faa", parser, parser._parseElementName.bind(parser)); + assertNode("*", parser, parser._parseElementName.bind(parser)); + assertNode(".faa42", parser, parser._parseClass.bind(parser)); + }); + + test("prio", () => { + assertNode("!important", parser, parser._parsePrio.bind(parser)); + }); + + test("expr", () => { + assertNode("45,5px", parser, parser._parseExpr.bind(parser)); + assertNode(" 45 , 5px ", parser, parser._parseExpr.bind(parser)); + assertNode("5/6", parser, parser._parseExpr.bind(parser)); + assertNode("36mm, -webkit-calc(100%-10px)", parser, parser._parseExpr.bind(parser)); + }); + + test("url", () => { + assertNode("url(foo())", parser, parser._parseURILiteral.bind(parser)); + assertNode( + "url('data:image/svg+xml;utf8,%3Csvg%20fill%3D%22%23' + $color + 'foo')", + parser, + parser._parseURILiteral.bind(parser), + ); + assertNode("url(//yourdomain/yourpath.png)", parser, parser._parseURILiteral.bind(parser)); + assertNode("url('http://msft.com')", parser, parser._parseURILiteral.bind(parser)); + assertNode('url("http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url( "http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url(\t"http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url("")', parser, parser._parseURILiteral.bind(parser)); + assertNode('uRL("")', parser, parser._parseURILiteral.bind(parser)); + assertNode('URL("")', parser, parser._parseURILiteral.bind(parser)); + assertNode("url(http://msft.com)", parser, parser._parseURILiteral.bind(parser)); + assertNode("url()", parser, parser._parseURILiteral.bind(parser)); + assertError( + 'url("http://msft.com"', + parser, + parser._parseURILiteral.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + "url(http://msft.com')", + parser, + parser._parseURILiteral.bind(parser), + ParseError.RightParenthesisExpected, + ); + }); + + test("map", () => { + assertNode("(key1: 1px, key2: solid + px, key3: (2+3))", parser, parser._parseExpr.bind(parser)); + assertNode("($key1 + 3: 1px)", parser, parser._parseExpr.bind(parser)); + }); + + test("parent selector", () => { + assertNode("&:hover", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&.float", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-bar", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-1", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&1", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-foo-1", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&&", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-10-thing", parser, parser._parseSimpleSelector.bind(parser)); + }); + + test("placeholder selector", () => { + assertNode("%hover", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("a%float", parser, parser._parseSimpleSelector.bind(parser)); + }); + + test("selector interpolation", function () { + assertNode(`.#{$name}\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.#{$name}-foo\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.#{$name}-foo-3\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.#{$name}-1\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.sc-col#{$postfix}-2-1\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`p.#{$name}\n\t#{$attr}-color: blue`, parser, parser._parseRuleset.bind(parser)); + assertNode( + `sans-#{serif} + a-#{1 + 2}-color-#{$attr}: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode(`##{f} .#{f} #{f}:#{f}\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.foo-#{&} .foo-#{&-sub}\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.-#{$variable}\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`#{&}([foo=bar][bar=foo])\n\t//`, parser, parser._parseRuleset.bind(parser)); + + assertNode(`.#{module.$name}\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.#{module.$name}-foo\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.#{module.$name}-foo-3\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.#{module.$name}-1\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode(`.sc-col#{module.$postfix}-2-1\n\t//`, parser, parser._parseRuleset.bind(parser)); + assertNode( + `p.#{module.$name} + #{$attr}-color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `p.#{$name} + #{module.$attr}-color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `p.#{module.$name} + #{module.$attr}-color: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode( + `sans-#{serif} + a-#{1 + 2}-color-#{module.$attr}: blue`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode(`.-#{module.$variable}\n\t//`, parser, parser._parseRuleset.bind(parser)); + }); + + test("@at-root", () => { + assertNode( + `@mixin unify-parent($child) + @at-root f#{selector.unify(&, $child)} + color: f`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@at-root #main2 .some-class + padding-left: calc( #{$a-variable} + 8px)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@media print + .page + @at-root (without: media) + foo: bar`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@media print + .page + @at-root (with: rule) + foo: bar`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@function", () => { + assertNode( + `@function grid-width($n) + @return $n * $grid-width + ($n - 1) * $gutter-width`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@function grid-width($n: 1, $e) + @return 0`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@function foo($total, $a) + @for $i from 0 to $total + // + @return $grid`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@function foo() + @if (unit($a) == "%") and ($i == ($total - 1)) + @return 0 + @return 1`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@function is-even($int) + @if $int%2 == 0 + @return true + @return false`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@function bar ($i) + @if $i > 0 + @return $i * bar($i - 1) + @return 1`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@function foo($a,) + //`, + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + `@function foo + //`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.LeftParenthesisExpected, + ); + assertError( + `@function + //`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + `@function foo($a $b) + //`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + `@function foo($a + //`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + `@function foo($a...) + @return`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.ExpressionExpected, + ); + assertError( + `@function foo($a:) + //`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.VariableValueExpected, + ); + }); + + test("@include", () => { + assertNode( + `p + @include double-border(blue)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.shadows + @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `$values: #ff0000, #00ff00, #0000ff + +.primary + @include colors($values...)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@include colors(this("styles")...)`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `.test + @include fontsize(16px, 21px !important)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @include apply-to-ie6-only + #logo + background-image: url(/logo.gif)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @include foo($values,)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @include foo($values,)`, + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + `p + @include double-border($values blue`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + `p + @include`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.IdentifierExpected, + ); + assertError( + `p + @include foo($values`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + `p + @include foo($values,`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.ExpressionExpected, + ); + + assertNode( + `p + @include lib.double-border(blue)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.shadows + @include lib.box-shadow(0px 4px 5px #666, 2px 6px 10px #999)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `$values: #ff0000, #00ff00, #0000ff +.primary + @include lib.colors($values...)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.primary + @include colors(lib.$values...)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.primary + @include lib.colors(lib.$values...)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@include lib.colors(this("styles")...)`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@include colors(lib.this("styles")...)`, parser, parser._parseStylesheet.bind(parser)); + assertNode(`@include lib.colors(lib.this("styles")...)`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `.test + @include lib.fontsize(16px, 21px !important)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @include lib.apply-to-ie6-only + #logo + background-image: url(/logo.gif)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @include lib.foo($values,)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @include foo(lib.$values,)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @include lib.foo(m.$values,)`, + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + `p + @include foo.($values)`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.IdentifierExpected, + ); + + assertNode( + `@include rtl("left") using ($dir) + margin-#{$dir}: 10px`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@content", () => { + assertNode("@content", parser, parser._parseMixinContent.bind(parser)); + assertNode("@content($type)", parser, parser._parseMixinContent.bind(parser)); + }); + + test("@mixin", () => { + assertNode( + `@mixin large-text + font: + family: Arial + size: 20px + color: #ff0000`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@mixin double-border($color, $width: 1in) + color: black`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@mixin box-shadow($shadows...) + -moz-box-shadow: $shadows`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@mixin apply-to-ie6-only + * html + @content`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@mixin #{foo}($color)\n\t//`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `@mixin foo ($i:4) + size: $i + @include wee ($i - 1)`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode(`@mixin foo ($i,)\n\t//`, parser, parser._parseStylesheet.bind(parser)); + + assertError(`@mixin $1\n\t//`, parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); + assertError(`@mixin foo() i\n\t//`, parser, parser._parseStylesheet.bind(parser), ParseError.IndentExpected); + assertError( + `@mixin foo(1)\n\t//`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError( + `@mixin foo($color = 9)\n\t//`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, + ); + assertError(`@mixin foo($color)`, parser, parser._parseStylesheet.bind(parser), ParseError.IndentExpected); + }); + + test("@while", () => { + assertNode( + `@while $i < 0 + .item-#{$i} + width: 2em * $i + $i: $i - 2`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertError(`@while\n\t//`, parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); + assertError(`@while $i != 4`, parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.IndentExpected); + }); + + test("@each", () => { + assertNode(`@each $i in 1, 2, 3\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode(`@each $i in 1 2 3\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode( + `@each $animal, $color, $cursor in (puma, black, default), (egret, white, move)\n\t`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertError( + `@each i in 4\n\t`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.VariableNameExpected, + ); + assertError(`@each $i from 4\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser), SassParseError.InExpected); + assertError(`@each $i in\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); + assertError( + `@each $animal, in (1, 1, 1), (2, 2, 2)\n\t`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.VariableNameExpected, + ); + }); + + test("@for", () => { + assertNode( + `@for $i from 1 to 5 + .item-#{$i} + width: 2em * $i`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode(`@for $k from 1 + $x through 5 + $x\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); + assertError( + `@for i from 0 to 4\n\t`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.VariableNameExpected, + ); + assertError(`@for $i to 4\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser), SassParseError.FromExpected); + assertError( + `@for $i from 0 by 4\n\t`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + SassParseError.ThroughOrToExpected, + ); + assertError( + `@for $i from\n\t`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.ExpressionExpected, + ); + assertError( + `@for $i from 0 to\n\t`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.ExpressionExpected, + ); + assertNode( + `@for $i from 1 through 60 + $s: $i + "%"`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + + assertNode(`@for $k from 1 + m.$x through 5 + $x\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode(`@for $k from 1 + $x through 5 + m.$x\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode(`@for $k from 1 + m.$x through 5 + m.$x\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); + }); + + test("@if", () => { + assertNode( + `@if 1 + 1 == 2 + border: 1px solid`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + `@if 5 < 3 + border: 2px dotted`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + `@if null + border: 3px double`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + `@if 1 <= $const + border: 3px +@else + border: 4px`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + `@if 1 >= (1 + $foo) + border: 3px +@else if 1 + 1 == 2 + border: 4px`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + `p + @if $i == 1 + x: 3px + @else if $i == 1 + x: 4px + @else + x: 4px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@if (index($_RESOURCES, "clean") != null) + @error "sdssd"`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@if $i == 1 + p + x: 3px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertError( + `@if + border: 1px solid`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.ExpressionExpected, + ); + + assertNode( + `@if 1 <= m.$const + border: 3px +@else + border: 4px`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + `@if 1 >= (1 + m.$foo) + border: 3px +@else if 1 + 1 == 2 + border: 4px`, + parser, + parser._parseRuleSetDeclaration.bind(parser), + ); + assertNode( + `p + @if m.$i == 1 + x: 3px + @else if $i == 1 + x: 4px + @else + x: 4px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @if $i == 1 + x: 3px + @else if m.$i == 1 + x: 4px + @else + x: 4px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `p + @if m.$i == 1 + x: 3px + @else if m.$i == 1 + x: 4px + @else + x: 4px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@if (list.index($_RESOURCES, "clean") != null) + @error "sdssd"`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@if (index(m.$_RESOURCES, "clean") != null) + @error "sdssd"`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@if (list.index(m.$_RESOURCES, "clean") != null) + @error "sdssd"`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@debug", () => { + assertNode(`@debug test`, parser, parser._parseStylesheet.bind(parser)); + assertNode( + `foo + @debug 1 + 4 + nested + @warn 1 4`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@if $foo == 1 + @debug 1 + 4`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@function setStyle($map, $object, $style) + @warn "The key ´#{$object} is not available in the map." + @return null`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@extend", () => { + assertNode( + `.themable + @extend %theme`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `foo + @extend .error + border-width: 3px`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `a.important + @extend .notice !optional`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.hoverlink + @extend a:hover`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.seriousError + @extend .error + @extend .attention`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `#context a%extreme + color: blue +.notice + @extend %extreme`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@media print + .error + color: red + .seriousError + @extend .error`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@mixin error($a: false) + @extend .#{$a} + @extend ##{$a}`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.foo + @extend .text-center, .uppercase`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.foo + @extend .text-center, .uppercase, `, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.foo + @extend .text-center, .uppercase !optional `, + parser, + parser._parseStylesheet.bind(parser), + ); + assertError( + `.hoverlink + @extend`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.SelectorExpected, + ); + assertError( + `.hoverlink + @extend %extreme !default`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.UnknownKeyword, + ); + }); + + test("@forward", () => { + assertNode('@forward "test"', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" as foo-*', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide this', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide this $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide this, $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "abstracts/functions" show px-to-rem, theme-color', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" show this', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" show $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" show this $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" as foo-* show this $that', parser, parser._parseForward.bind(parser)); + + assertError("@forward", parser, parser._parseForward.bind(parser), ParseError.StringLiteralExpected); + assertError('@forward "test" as', parser, parser._parseForward.bind(parser), ParseError.IdentifierExpected); + assertError('@forward "test" as foo-', parser, parser._parseForward.bind(parser), ParseError.WildcardExpected); + assertError('@forward "test" as foo- *', parser, parser._parseForward.bind(parser), ParseError.WildcardExpected); + assertError( + '@forward "test" show', + parser, + parser._parseForward.bind(parser), + ParseError.IdentifierOrVariableExpected, + ); + assertError( + '@forward "test" hide', + parser, + parser._parseForward.bind(parser), + ParseError.IdentifierOrVariableExpected, + ); + + assertNode( + '@forward "test" with ( $black: #222 !default, $border-radius: 0.1rem !default )', + parser, + parser._parseForward.bind(parser), + ); + assertNode( + '@forward "../forms.scss" as components-* with ( $field-border: false )', + parser, + parser._parseForward.bind(parser), + ); // #145108 + + assertNode( + `@use "lib" +@forward "test"`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@forward "test" +@forward "lib"`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `$test: "test" +@forward "test"`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@use", () => { + assertNode('@use "test"', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" as foo', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" as *', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" with ($foo: "test", $bar: 1)', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" as foo with ($foo: "test", $bar: 1)', parser, parser._parseUse.bind(parser)); + + assertError("@use", parser, parser._parseUse.bind(parser), ParseError.StringLiteralExpected); + assertError('@use "test" foo', parser, parser._parseUse.bind(parser), ParseError.UnknownKeyword); + assertError('@use "test" as', parser, parser._parseUse.bind(parser), ParseError.IdentifierOrWildcardExpected); + assertError('@use "test" with', parser, parser._parseUse.bind(parser), ParseError.LeftParenthesisExpected); + assertError('@use "test" with ($foo)', parser, parser._parseUse.bind(parser), ParseError.VariableValueExpected); + assertError('@use "test" with ("bar")', parser, parser._parseUse.bind(parser), ParseError.VariableNameExpected); + assertError( + '@use "test" with ($foo: 1, "bar")', + parser, + parser._parseUse.bind(parser), + ParseError.VariableNameExpected, + ); + assertError( + '@use "test" with ($foo: "bar"', + parser, + parser._parseUse.bind(parser), + ParseError.RightParenthesisExpected, + ); + + assertNode( + `@forward "test" +@use "lib"`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `@use "test" +@use "lib"`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `$test: "test" +@use "lib"`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@container", () => { + assertNode( + `@container (min-width: #{$minWidth}) + .scss-interpolation + line-height: 10cqh`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `.item-icon + @container (max-height: 100px) + .item-icon + display: none`, + parser, + parser._parseStylesheet.bind(parser), + ); + assertNode( + `:root + @container (max-height: 100px) + display: none`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@layer", () => { + assertNode("@layer #{$layer}\n\t", parser, parser._parseLayer.bind(parser)); + }); + + test("@import", () => { + assertNode('@import "test.css"', parser, parser._parseImport.bind(parser)); + assertNode('@import url("test.css")', parser, parser._parseImport.bind(parser)); + assertNode('@import "test.css", "bar.css"', parser, parser._parseImport.bind(parser)); + assertNode('@import "test.css", "bar.css" screen, projection', parser, parser._parseImport.bind(parser)); + assertNode( + `foo + @import "test.css"`, + parser, + parser._parseStylesheet.bind(parser), + ); + + assertError( + '@import "test.css" "bar.css"', + parser, + parser._parseStylesheet.bind(parser), + ParseError.MediaQueryExpected, + ); + assertError('@import "test.css", screen', parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); + assertError("@import", parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); + assertNode('@import url("override.css") layer', parser, parser._parseStylesheet.bind(parser)); + }); + + test("declaration", () => { + assertNode("border: thin solid 1px", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: $color", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: blue", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / $const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / 20 + $const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: func($red)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: func($red) !important", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: desaturate($red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: desaturate(16, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: $base-color + #111", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: 100% / 2 + $ref", parser, parser._parseDeclaration.bind(parser)); + assertNode("border: ($width * 2) solid black", parser, parser._parseDeclaration.bind(parser)); + assertNode("property: $class", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: fnc($t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("width: (1em + 2em) * 3", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: #010203 + #040506", parser, parser._parseDeclaration.bind(parser)); + assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser)); + assertNode("margin: 3px + 4px auto", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: hsl(0, 100%, 50%)", parser, parser._parseDeclaration.bind(parser)); + assertNode( + "color: hsl($hue: 0, $saturation: 100%, $lightness: 50%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode("foo: if($value == 'default', flex-gutter(), $value)", parser, parser._parseDeclaration.bind(parser)); + assertNode("foo: if(true, !important, null)", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: selector-replace(&, 1)", parser, parser._parseDeclaration.bind(parser)); + + assertNode("dummy: module.$color", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / module.$const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / 20 + module.$const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.func($red)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.func($red) !important", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.desaturate($red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: desaturate(module.$red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.desaturate(module.$red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.desaturate(16, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: module.$base-color + #111", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: 100% / 2 + module.$ref", parser, parser._parseDeclaration.bind(parser)); + assertNode("border: (module.$width * 2) solid black", parser, parser._parseDeclaration.bind(parser)); + assertNode("property: module.$class", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: module.fnc($t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: fnc(module.$t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: module.fnc(module.$t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("width: (1em + 2em) * 3", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: #010203 + #040506", parser, parser._parseDeclaration.bind(parser)); + assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser)); + assertNode("margin: 3px + 4px auto", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: color.hsl(0, 100%, 50%)", parser, parser._parseDeclaration.bind(parser)); + assertNode( + "color: color.hsl($hue: 0, $saturation: 100%, $lightness: 50%)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if(module.$value == 'default', flex-gutter(), $value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if($value == 'default', module.flex-gutter(), $value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if($value == 'default', flex-gutter(), module.$value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if(module.$value == 'default', module.flex-gutter(), $value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if($value == 'default', module.flex-gutter(), module.$value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode( + "foo: if(module.$value == 'default', module.flex-gutter(), module.$value)", + parser, + parser._parseDeclaration.bind(parser), + ); + assertNode("color: selector.replace(&, 1)", parser, parser._parseDeclaration.bind(parser)); + + assertError("fo = 8", parser, parser._parseDeclaration.bind(parser), ParseError.ColonExpected); + assertError("fo:", parser, parser._parseDeclaration.bind(parser), ParseError.PropertyValueExpected); + assertError("color: hsl($hue: 0,", parser, parser._parseDeclaration.bind(parser), ParseError.ExpressionExpected); + assertError( + "color: hsl($hue: 0", + parser, + parser._parseDeclaration.bind(parser), + ParseError.RightParenthesisExpected, + ); + }); + + test("interpolation", () => { + assertNode("--#{module.$propname}: some-value", parser, parser._parseDeclaration.bind(parser)); + }); + + test("operators", () => { + assertNode(">=", parser, parser._parseOperator.bind(parser)); + assertNode(">", parser, parser._parseOperator.bind(parser)); + assertNode("<", parser, parser._parseOperator.bind(parser)); + assertNode("<=", parser, parser._parseOperator.bind(parser)); + assertNode("==", parser, parser._parseOperator.bind(parser)); + assertNode("!=", parser, parser._parseOperator.bind(parser)); + assertNode("and", parser, parser._parseOperator.bind(parser)); + assertNode("+", parser, parser._parseOperator.bind(parser)); + assertNode("-", parser, parser._parseOperator.bind(parser)); + assertNode("*", parser, parser._parseOperator.bind(parser)); + assertNode("/", parser, parser._parseOperator.bind(parser)); + assertNode("%", parser, parser._parseOperator.bind(parser)); + assertNode("not", parser, parser._parseUnaryOperator.bind(parser)); + }); + + test("expressions", () => { + assertNode("($const + 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($const - 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($const * 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($const / 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 - $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 * $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 / $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 / 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + $const + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($var1 + $var2)", parser, parser._parseExpr.bind(parser)); + assertNode("(($const + 5) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("(($const + (5 + 2)) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("($const + ((5 + 2) * 2))", parser, parser._parseExpr.bind(parser)); + assertNode("$color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, 42%", parser, parser._parseExpr.bind(parser)); + assertNode("$color, 42%, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color - ($color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("($base + $filler)", parser, parser._parseExpr.bind(parser)); + assertNode("(100% / 2 + $filler)", parser, parser._parseExpr.bind(parser)); + assertNode("100% / 2 + $filler", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and $b) or $c", parser, parser._parseExpr.bind(parser)); + + assertNode("(module.$const + 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const - 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const * 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const / 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 - module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 * module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 / module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + module.$const + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("($var1 + module.$var2)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$var1 + $var2)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$var1 + module.$var2)", parser, parser._parseExpr.bind(parser)); + assertNode("((module.$const + 5) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("((module.$const + (5 + 2)) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const + ((5 + 2) * 2))", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, 42%", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, 42%, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, 42%, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, 42%, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color - ($color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("$color - (module.$color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color - (module.$color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$base + $filler)", parser, parser._parseExpr.bind(parser)); + assertNode("($base + module.$filler)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$base + module.$filler)", parser, parser._parseExpr.bind(parser)); + assertNode("(100% / 2 + module.$filler)", parser, parser._parseExpr.bind(parser)); + assertNode("100% / 2 + module.$filler", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and $b) or $c", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and module.$b) or $c", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and $b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and module.$b) or $c", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and $b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and module.$b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and module.$b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not module.$v", parser, parser._parseExpr.bind(parser)); + + assertError("(20 + 20", parser, parser._parseExpr.bind(parser), ParseError.RightParenthesisExpected); + }); + + test("variable declaration", () => { + assertNode("$color: #F5F5F5", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 0", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 255", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 25.5", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 25px", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 25.5px !default", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$text-color: green !global", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode( + '$_RESOURCES: append($_RESOURCES, "clean") !global', + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode("$footer-height: 40px !default !global", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode( + '$primary-font: "wf_SegoeUI","Segoe UI","Segoe","Segoe WP"', + parser, + parser._parseVariableDeclaration.bind(parser), + ); + assertNode("$color: red !important", parser, parser._parseVariableDeclaration.bind(parser)); + + assertError("$color: red !def", parser, parser._parseVariableDeclaration.bind(parser), ParseError.UnknownKeyword); + assertError( + "$color : !default", + parser, + parser._parseVariableDeclaration.bind(parser), + ParseError.VariableValueExpected, + ); + assertError("$color !default", parser, parser._parseVariableDeclaration.bind(parser), ParseError.ColonExpected); + }); + + test("variable", () => { + assertNode("$color", parser, parser._parseVariable.bind(parser)); + assertNode("$co42lor", parser, parser._parseVariable.bind(parser)); + assertNode("$-co42lor", parser, parser._parseVariable.bind(parser)); + }); + + test("module variable", () => { + assertNode("module.$color", parser, parser._parseModuleMember.bind(parser)); + assertNode("module.$co42lor", parser, parser._parseModuleMember.bind(parser)); + assertNode("module.$-co42lor", parser, parser._parseModuleMember.bind(parser)); + assertNode("module.function()", parser, parser._parseModuleMember.bind(parser)); + + assertError("module.", parser, parser._parseModuleMember.bind(parser), ParseError.IdentifierOrVariableExpected); + }); +}); diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index 78873af9..acb9d46b 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -1,3170 +1,1157 @@ -import { suite, test, assert } from "vitest"; -import { ParseError } from "../../parser/cssErrors"; -import { SassParser } from "../../parser/sassParser"; -import * as nodes from "../../parser/cssNodes"; -import { assertError, assertFunction, assertNoNode, assertNode } from "../css/parser.test"; -import { SassParseError } from "../../parser/sassErrors"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ -suite("Sass - Parser", () => { - const parser = new SassParser({ syntax: "indented" }); +"use strict"; +import { suite, test } from "vitest"; - test("empty stylesheet", () => { - assertNode("", parser, parser._parseStylesheet.bind(parser)); - }); +import { SassParser } from "../../parser/sassParser"; +import { ParseError } from "../../parser/cssErrors"; +import { SassParseError } from "../../parser/sassErrors"; - test("@charset", () => { - assertNode('@charset "demo"', parser, parser._parseStylesheet.bind(parser)); - assertError("@charset", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); - assertError('@charset "demo";', parser, parser._parseStylesheet.bind(parser), ParseError.UnexpectedSemicolon); - }); +import { assertNode, assertError } from "../css/parser.test"; - test("newline before at-rule", () => { +suite("SCSS - Parser", () => { + test("Comments", function () { + const parser = new SassParser(); + assertNode(" a { b: /* comment */ c }", parser, parser._parseStylesheet.bind(parser)); assertNode( - ` -@charset "demo"`, + " a { b: /* comment \n * is several\n * lines long\n */ c }", parser, parser._parseStylesheet.bind(parser), ); + assertNode(" a { b: // single line comment\n c }", parser, parser._parseStylesheet.bind(parser)); }); - test("declarations", () => { - assertNode( - `body - margin: 0px - padding: 3em, 6em -`, - parser, - parser._parseStylesheet.bind(parser), - ); + test("Variable", function () { + const parser = new SassParser(); + assertNode("$color", parser, parser._parseVariable.bind(parser)); + assertNode("$co42lor", parser, parser._parseVariable.bind(parser)); + assertNode("$-co42lor", parser, parser._parseVariable.bind(parser)); + }); + + test("Module variable", function () { + const parser = new SassParser(); + assertNode("module.$color", parser, parser._parseModuleMember.bind(parser)); + assertNode("module.$co42lor", parser, parser._parseModuleMember.bind(parser)); + assertNode("module.$-co42lor", parser, parser._parseModuleMember.bind(parser)); + assertNode("module.function()", parser, parser._parseModuleMember.bind(parser)); + + assertError("module.", parser, parser._parseModuleMember.bind(parser), ParseError.IdentifierOrVariableExpected); }); - test("CSS comment", () => { + test("VariableDeclaration", function () { + const parser = new SassParser(); + assertNode("$color: #F5F5F5", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 0", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 255", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 25.5", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 25px", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$color: 25.5px !default", parser, parser._parseVariableDeclaration.bind(parser)); + assertNode("$text-color: green !global", parser, parser._parseVariableDeclaration.bind(parser)); assertNode( - `a - b: /* comment */ c -`, + '$_RESOURCES: append($_RESOURCES, "clean") !global', parser, - parser._parseStylesheet.bind(parser), + parser._parseVariableDeclaration.bind(parser), ); - }); - - test("Sass comment", () => { + assertNode("$footer-height: 40px !default !global", parser, parser._parseVariableDeclaration.bind(parser)); assertNode( - `a - // single-line comment - b: c -`, + '$primary-font: "wf_SegoeUI","Segoe UI","Segoe","Segoe WP"', parser, - parser._parseStylesheet.bind(parser), + parser._parseVariableDeclaration.bind(parser), ); - }); + assertNode("$color: red !important", parser, parser._parseVariableDeclaration.bind(parser)); - test("Multi-line CSS comment should error", () => { + assertError("$color: red !def", parser, parser._parseVariableDeclaration.bind(parser), ParseError.UnknownKeyword); assertError( - `a - b: /* multi -line -comment */ c -`, + "$color : !default", parser, - parser._parseStylesheet.bind(parser), - ParseError.PropertyValueExpected, + parser._parseVariableDeclaration.bind(parser), + ParseError.VariableValueExpected, ); + assertError("$color !default", parser, parser._parseVariableDeclaration.bind(parser), ParseError.ColonExpected); }); - test("stylesheet", () => { - assertNode(`$color: #F5F5F5`, parser, parser._parseStylesheet.bind(parser)); - assertNode( - `$color: #F5F5F5 -$color: #F5F5F5`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `$color: #F5F5F5 -$color: #F5F5F5 -$color: #F5F5F5`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode(`$color: #F5F5F5 !important`, parser, parser._parseStylesheet.bind(parser)); - assertNode( - `#main - width: 97% - p, div - a - font-weight: bold`, - parser, - parser._parseStylesheet.bind(parser), - ); + test("Expr", function () { + const parser = new SassParser(); + assertNode("($const + 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($const - 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($const * 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($const / 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 - $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 * $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 / $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 / 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + $const + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20)", parser, parser._parseExpr.bind(parser)); + assertNode("($var1 + $var2)", parser, parser._parseExpr.bind(parser)); + assertNode("(($const + 5) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("(($const + (5 + 2)) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("($const + ((5 + 2) * 2))", parser, parser._parseExpr.bind(parser)); + assertNode("$color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, 42%", parser, parser._parseExpr.bind(parser)); + assertNode("$color, 42%, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color - ($color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("($base + $filler)", parser, parser._parseExpr.bind(parser)); + assertNode("(100% / 2 + $filler)", parser, parser._parseExpr.bind(parser)); + assertNode("100% / 2 + $filler", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and $b) or $c", parser, parser._parseExpr.bind(parser)); + + assertNode("(module.$const + 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const - 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const * 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const / 20)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 - module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 * module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 / module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("(20 + 20 + module.$const + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); + assertNode("($var1 + module.$var2)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$var1 + $var2)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$var1 + module.$var2)", parser, parser._parseExpr.bind(parser)); + assertNode("((module.$const + 5) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("((module.$const + (5 + 2)) * 2)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$const + ((5 + 2) * 2))", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, 42%", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, 42%, $color", parser, parser._parseExpr.bind(parser)); + assertNode("$color, 42%, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color, 42%, module.$color", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color - ($color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("$color - (module.$color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("module.$color - (module.$color + 10%)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$base + $filler)", parser, parser._parseExpr.bind(parser)); + assertNode("($base + module.$filler)", parser, parser._parseExpr.bind(parser)); + assertNode("(module.$base + module.$filler)", parser, parser._parseExpr.bind(parser)); + assertNode("(100% / 2 + module.$filler)", parser, parser._parseExpr.bind(parser)); + assertNode("100% / 2 + module.$filler", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and $b) or $c", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and module.$b) or $c", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and $b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and module.$b) or $c", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and $b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not ($v and module.$b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not (module.$v and module.$b) or module.$c", parser, parser._parseExpr.bind(parser)); + assertNode("not module.$v", parser, parser._parseExpr.bind(parser)); + + assertError("(20 + 20", parser, parser._parseExpr.bind(parser), ParseError.RightParenthesisExpected); + }); + + test("SCSSOperator", function () { + const parser = new SassParser(); + assertNode(">=", parser, parser._parseOperator.bind(parser)); + assertNode(">", parser, parser._parseOperator.bind(parser)); + assertNode("<", parser, parser._parseOperator.bind(parser)); + assertNode("<=", parser, parser._parseOperator.bind(parser)); + assertNode("==", parser, parser._parseOperator.bind(parser)); + assertNode("!=", parser, parser._parseOperator.bind(parser)); + assertNode("and", parser, parser._parseOperator.bind(parser)); + assertNode("+", parser, parser._parseOperator.bind(parser)); + assertNode("-", parser, parser._parseOperator.bind(parser)); + assertNode("*", parser, parser._parseOperator.bind(parser)); + assertNode("/", parser, parser._parseOperator.bind(parser)); + assertNode("%", parser, parser._parseOperator.bind(parser)); + assertNode("not", parser, parser._parseUnaryOperator.bind(parser)); + }); + + test("Interpolation", function () { + const parser = new SassParser(); + // assertNode('#{red}', parser, parser._parseIdent.bind(parser)); + // assertNode('#{$color}', parser, parser._parseIdent.bind(parser)); + // assertNode('#{3 + 4}', parser, parser._parseIdent.bind(parser)); + // assertNode('#{3 + #{3 + 4}}', parser, parser._parseIdent.bind(parser)); + // assertNode('#{$d}-style: 0', parser, parser._parseDeclaration.bind(parser)); + // assertNode('foo-#{$d}: 1', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{$d}-bar-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('foo-#{$d}-bar: 1', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('&:nth-child(#{$query}+1) { clear: $opposite-direction; }', parser, parser._parseRuleset.bind(parser)); + // assertNode('--#{$propname}: some-value', parser, parser._parseDeclaration.bind(parser)); + // assertNode('some-property: var(--#{$propname})', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{}', parser, parser._parseIdent.bind(parser)); + // assertError('#{1 + 2', parser, parser._parseIdent.bind(parser), ParseError.RightCurlyExpected); + + // assertNode('#{module.$color}', parser, parser._parseIdent.bind(parser)); + // assertNode('#{module.$d}-style: 0', parser, parser._parseDeclaration.bind(parser)); + // assertNode('foo-#{module.$d}: 1', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{module.$d}-bar-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{$d}-bar-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{module.$d}-bar-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('foo-#{module.$d}-bar: 1', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{module.$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{$d}-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('#{module.$d}-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); + // assertNode('&:nth-child(#{module.$query}+1) { clear: $opposite-direction; }', parser, parser._parseRuleset.bind(parser)); + // assertNode('&:nth-child(#{$query}+1) { clear: module.$opposite-direction; }', parser, parser._parseRuleset.bind(parser)); + // assertNode('&:nth-child(#{module.$query}+1) { clear: module.$opposite-direction; }', parser, parser._parseRuleset.bind(parser)); + assertNode("--#{module.$propname}: some-value", parser, parser._parseDeclaration.bind(parser)); + // assertNode('some-property: var(--#{module.$propname})', parser, parser._parseDeclaration.bind(parser)); + // assertNode('@supports #{$val} { }', parser, parser._parseStylesheet.bind(parser)); // #88283 + // assertNode('.mb-#{$i}0np {} .push-up-#{$i}0 {} .mt-#{$i}0vh {}', parser, parser._parseStylesheet.bind(parser)); + }); + + test("Declaration", function () { + const parser = new SassParser(); + assertNode("border: thin solid 1px", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: $color", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: blue", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / $const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / 20 + $const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: func($red)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: func($red) !important", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: desaturate($red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: desaturate(16, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: $base-color + #111", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: 100% / 2 + $ref", parser, parser._parseDeclaration.bind(parser)); + assertNode("border: ($width * 2) solid black", parser, parser._parseDeclaration.bind(parser)); + assertNode("property: $class", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: fnc($t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("width: (1em + 2em) * 3", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: #010203 + #040506", parser, parser._parseDeclaration.bind(parser)); + assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser)); + assertNode("margin: 3px + 4px auto", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: hsl(0, 100%, 50%)", parser, parser._parseDeclaration.bind(parser)); assertNode( - `a - &:hover - color: red`, + "color: hsl($hue: 0, $saturation: 100%, $lightness: 50%)", parser, - parser._parseStylesheet.bind(parser), + parser._parseDeclaration.bind(parser), ); + assertNode("foo: if($value == 'default', flex-gutter(), $value)", parser, parser._parseDeclaration.bind(parser)); + assertNode("foo: if(true, !important, null)", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: selector-replace(&, 1)", parser, parser._parseDeclaration.bind(parser)); + + assertNode("dummy: module.$color", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / module.$const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: (20 / 20 + module.$const)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.func($red)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.func($red) !important", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.desaturate($red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: desaturate(module.$red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.desaturate(module.$red, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("dummy: module.desaturate(16, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: module.$base-color + #111", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: 100% / 2 + module.$ref", parser, parser._parseDeclaration.bind(parser)); + assertNode("border: (module.$width * 2) solid black", parser, parser._parseDeclaration.bind(parser)); + assertNode("property: module.$class", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: module.fnc($t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: fnc(module.$t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("prop-erty: module.fnc(module.$t, 10%)", parser, parser._parseDeclaration.bind(parser)); + assertNode("width: (1em + 2em) * 3", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: #010203 + #040506", parser, parser._parseDeclaration.bind(parser)); + assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser)); + assertNode("margin: 3px + 4px auto", parser, parser._parseDeclaration.bind(parser)); + assertNode("color: color.hsl(0, 100%, 50%)", parser, parser._parseDeclaration.bind(parser)); assertNode( - `fo - font: 2px/3px - family: fantasy`, + "color: color.hsl($hue: 0, $saturation: 100%, $lightness: 50%)", parser, - parser._parseStylesheet.bind(parser), + parser._parseDeclaration.bind(parser), ); assertNode( - `.foo - bar: - yoo: fantasy`, + "foo: if(module.$value == 'default', flex-gutter(), $value)", parser, - parser._parseStylesheet.bind(parser), + parser._parseDeclaration.bind(parser), ); assertNode( - `selector - propsuffix: - nested: 1px - rule: 1px - nested.selector - foo: 1 - nested:selector - foo: 2`, + "foo: if($value == 'default', module.flex-gutter(), $value)", parser, - parser._parseStylesheet.bind(parser), + parser._parseDeclaration.bind(parser), ); assertNode( - `legend - foo - a:s - margin-top: 0 - margin-bottom: #123 - margin-top:s(1)`, + "foo: if($value == 'default', flex-gutter(), module.$value)", parser, - parser._parseStylesheet.bind(parser), + parser._parseDeclaration.bind(parser), ); assertNode( - `@mixin keyframe - @keyframes name - @content`, + "foo: if(module.$value == 'default', module.flex-gutter(), $value)", parser, - parser._parseStylesheet.bind(parser), + parser._parseDeclaration.bind(parser), ); assertNode( - `@include keyframe - 10% - top: 3px`, + "foo: if($value == 'default', module.flex-gutter(), module.$value)", parser, - parser._parseStylesheet.bind(parser), + parser._parseDeclaration.bind(parser), ); assertNode( - `.class - &--sub-class-with-ampersand - color: red`, + "foo: if(module.$value == 'default', module.flex-gutter(), module.$value)", parser, - parser._parseStylesheet.bind(parser), + parser._parseDeclaration.bind(parser), ); + assertNode("color: selector.replace(&, 1)", parser, parser._parseDeclaration.bind(parser)); + + assertError("fo = 8", parser, parser._parseDeclaration.bind(parser), ParseError.ColonExpected); + assertError("fo:", parser, parser._parseDeclaration.bind(parser), ParseError.PropertyValueExpected); + assertError("color: hsl($hue: 0,", parser, parser._parseDeclaration.bind(parser), ParseError.ExpressionExpected); assertError( - `fo - font: 2px/3px - family`, + "color: hsl($hue: 0", parser, - parser._parseStylesheet.bind(parser), - ParseError.ColonExpected, + parser._parseDeclaration.bind(parser), + ParseError.RightParenthesisExpected, ); + }); + test("Stylesheet", function () { + const parser = new SassParser(); + assertNode("$color: #F5F5F5;", parser, parser._parseStylesheet.bind(parser)); + assertNode("$color: #F5F5F5; $color: #F5F5F5;", parser, parser._parseStylesheet.bind(parser)); + assertNode("$color: #F5F5F5; $color: #F5F5F5; $color: #F5F5F5;", parser, parser._parseStylesheet.bind(parser)); + assertNode("$color: #F5F5F5 !important;", parser, parser._parseStylesheet.bind(parser)); assertNode( - `legend - foo - a:s - margin-top:0 - margin-bottom:#123 - margin-top:m.s(1)`, + "#main { width: 97%; p, div { a { font-weight: bold; } } }", parser, parser._parseStylesheet.bind(parser), ); + assertNode("a { &:hover { color: red; } }", parser, parser._parseStylesheet.bind(parser)); + assertNode("fo { font: 2px/3px { family: fantasy; } }", parser, parser._parseStylesheet.bind(parser)); + assertNode(".foo { bar: { yoo: fantasy; } }", parser, parser._parseStylesheet.bind(parser)); assertNode( - `@include module.keyframe - 10% - top: 3px`, + "selector { propsuffix: { nested: 1px; } rule: 1px; nested.selector { foo: 1; } nested:selector { foo: 2 }}", parser, parser._parseStylesheet.bind(parser), ); - }); - - test("@media", () => { assertNode( - `@media screen, projection - a - b: c`, + "legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:s(1)}", parser, parser._parseStylesheet.bind(parser), ); - assertNode( - `@media screen and (max-width: 400px) - @-ms-viewport - width: 320px`, + assertNode("@mixin keyframe { @keyframes name { @content; } }", parser, parser._parseStylesheet.bind(parser)); + assertNode("@include keyframe { 10% { top: 3px; } }", parser, parser._parseStylesheet.bind(parser)); + assertNode(".class{&--sub-class-with-ampersand{color: red;}}", parser, parser._parseStylesheet.bind(parser)); + assertError( + "fo { font: 2px/3px { family } }", parser, parser._parseStylesheet.bind(parser), + ParseError.ColonExpected, ); + assertNode( - `@-ms-viewport - width: 320px - height: 720px`, + "legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:m.s(1)}", parser, parser._parseStylesheet.bind(parser), ); + assertNode("@include module.keyframe { 10% { top: 3px; } }", parser, parser._parseStylesheet.bind(parser)); }); - test("selectors", () => { - assertNode( - ` -#boo, far - a: b + test("@import", function () { + const parser = new SassParser(); + assertNode('@import "test.css"', parser, parser._parseImport.bind(parser)); + assertNode('@import url("test.css")', parser, parser._parseImport.bind(parser)); + assertNode('@import "test.css", "bar.css"', parser, parser._parseImport.bind(parser)); + assertNode('@import "test.css", "bar.css" screen, projection', parser, parser._parseImport.bind(parser)); + assertNode('foo { @import "test.css"; }', parser, parser._parseStylesheet.bind(parser)); -.far boo - c: d -`, + assertError( + '@import "test.css" "bar.css"', parser, parser._parseStylesheet.bind(parser), + ParseError.MediaQueryExpected, ); + assertError('@import "test.css", screen', parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); + assertError("@import", parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); + assertNode('@import url("override.css") layer;', parser, parser._parseStylesheet.bind(parser)); }); - test("nested selectors", () => { - assertNode( - ` -#boo, far - a: b - - &:hover - a: c - - d: e -`, - parser, - parser._parseStylesheet.bind(parser), - ); + test("@layer", function () { + const parser = new SassParser(); + assertNode("@layer #{$layer} { }", parser, parser._parseLayer.bind(parser)); }); - test("@-moz-keyframes", () => { + test("@container", function () { + const parser = new SassParser(); assertNode( - ` -@-moz-keyframes darkWordHighlight - from - background-color: inherit - to - background-color: rgba(83, 83, 83, 0.7) -`, + `@container (min-width: #{$minWidth}) { .scss-interpolation { line-height: 10cqh; } }`, parser, parser._parseStylesheet.bind(parser), ); - }); - - test("@page", () => { assertNode( - ` -@page - margin: 2.5cm -`, + `.item-icon { @container (max-height: 100px) { .item-icon { display: none; } } }`, parser, parser._parseStylesheet.bind(parser), ); - }); - - test("@font-face", () => { assertNode( - ` -@font-face - font-family: "Example Font" -`, + `:root { @container (max-height: 100px) { display: none;} }`, parser, parser._parseStylesheet.bind(parser), ); - assertNode( - ` -@font-face - src: url(http://test) -`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ` -@font-face - font-style: normal - font-stretch: normal -`, + }); + + test("@use", function () { + const parser = new SassParser(); + assertNode('@use "test"', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" as foo', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" as *', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" with ($foo: "test", $bar: 1)', parser, parser._parseUse.bind(parser)); + assertNode('@use "test" as foo with ($foo: "test", $bar: 1)', parser, parser._parseUse.bind(parser)); + + assertError("@use", parser, parser._parseUse.bind(parser), ParseError.StringLiteralExpected); + assertError('@use "test" foo', parser, parser._parseUse.bind(parser), ParseError.UnknownKeyword); + assertError('@use "test" as', parser, parser._parseUse.bind(parser), ParseError.IdentifierOrWildcardExpected); + assertError('@use "test" with', parser, parser._parseUse.bind(parser), ParseError.LeftParenthesisExpected); + assertError('@use "test" with ($foo)', parser, parser._parseUse.bind(parser), ParseError.VariableValueExpected); + assertError('@use "test" with ("bar")', parser, parser._parseUse.bind(parser), ParseError.VariableNameExpected); + assertError( + '@use "test" with ($foo: 1, "bar")', parser, - parser._parseStylesheet.bind(parser), + parser._parseUse.bind(parser), + ParseError.VariableNameExpected, ); - assertNode( - ` -@font-face - unicode-range: U+0021-007F, u+1f49C, U+4??, U+?????? -`, + assertError( + '@use "test" with ($foo: "bar"', parser, - parser._parseStylesheet.bind(parser), + parser._parseUse.bind(parser), + ParseError.RightParenthesisExpected, ); - }); - test("@namespace", () => { - assertNode(`@namespace "http://www.w3.org/1999/xhtml"`, parser, parser._parseStylesheet.bind(parser)); - assertNode(`@namespace pref url(http://test)`, parser, parser._parseStylesheet.bind(parser)); - assertError("@charset", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); + assertNode('@forward "test"; @use "lib"', parser, parser._parseStylesheet.bind(parser)); + assertNode('@use "test"; @use "lib"', parser, parser._parseStylesheet.bind(parser)); + assertNode('$test: "test"; @use "lib"', parser, parser._parseStylesheet.bind(parser)); }); - test("@-moz-document", () => { - assertNode( - ` -@-moz-document url(http://test), url-prefix(http://www.w3.org/Style/) - body - color: purple - background: yellow -`, + test("@forward", function () { + const parser = new SassParser(); + assertNode('@forward "test"', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" as foo-*', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide this', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide this $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" hide this, $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "abstracts/functions" show px-to-rem, theme-color', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" show this', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" show $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" show this $that', parser, parser._parseForward.bind(parser)); + assertNode('@forward "test" as foo-* show this $that', parser, parser._parseForward.bind(parser)); + + assertError("@forward", parser, parser._parseForward.bind(parser), ParseError.StringLiteralExpected); + assertError('@forward "test" foo', parser, parser._parseForward.bind(parser), ParseError.SemiColonExpected); + assertError('@forward "test" as', parser, parser._parseForward.bind(parser), ParseError.IdentifierExpected); + assertError('@forward "test" as foo-', parser, parser._parseForward.bind(parser), ParseError.WildcardExpected); + assertError('@forward "test" as foo- *', parser, parser._parseForward.bind(parser), ParseError.WildcardExpected); + assertError( + '@forward "test" show', parser, - parser._parseStylesheet.bind(parser), + parser._parseForward.bind(parser), + ParseError.IdentifierOrVariableExpected, + ); + assertError( + '@forward "test" hide', + parser, + parser._parseForward.bind(parser), + ParseError.IdentifierOrVariableExpected, ); - }); - test("attribute selectors", () => { assertNode( - `E E[foo] E[foo="bar"] E[foo~="bar"] E[foo^="bar"] E[foo$="bar"] E[foo*="bar"] E[foo|="en"] - color: limegreen`, + '@forward "test" with ( $black: #222 !default, $border-radius: 0.1rem !default )', parser, - parser._parseStylesheet.bind(parser), + parser._parseForward.bind(parser), ); assertNode( - `input[type="submit"] - color: limegreen`, + '@forward "../forms.scss" as components-* with ( $field-border: false )', parser, - parser._parseStylesheet.bind(parser), - ); + parser._parseForward.bind(parser), + ); // #145108 + + assertNode('@use "lib"; @forward "test"', parser, parser._parseStylesheet.bind(parser)); + assertNode('@forward "test"; @forward "lib"', parser, parser._parseStylesheet.bind(parser)); + assertNode('$test: "test"; @forward "test"', parser, parser._parseStylesheet.bind(parser)); }); - test("pseudo-class selectors", () => { + test("@media", function () { + const parser = new SassParser(); assertNode( - `E:root E:nth-child(n) E:nth-last-child(n) E:nth-of-type(n) E:nth-last-of-type(n) E:first-child E:last-child - color: limegreen`, + "@media screen { .sidebar { @media (orientation: landscape) { width: 500px; } } }", parser, parser._parseStylesheet.bind(parser), ); + assertNode("@media #{$media} and ($feature: $value) {}", parser, parser._parseStylesheet.bind(parser)); + assertNode("@media only screen and #{$query} {}", parser, parser._parseStylesheet.bind(parser)); assertNode( - `E:first-of-type E:last-of-type E:only-child E:only-of-type E:empty E:link E:visited E:active E:hover E:focus E:target E:lang(fr) E:enabled E:disabled E:checked - color: limegreen`, + "foo { bar { @media screen and (orientation: landscape) {}} }", parser, parser._parseStylesheet.bind(parser), ); + assertNode("@media screen and (nth($query, 1): nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); assertNode( - `E::first-line E::first-letter E::before E::after - color: limegreen`, + ".something { @media (max-width: 760px) { > .test { color: blue; } } }", parser, parser._parseStylesheet.bind(parser), ); assertNode( - `E.warning E#myid E:not(s) - color: limegreen`, + ".something { @media (max-width: 760px) { ~ div { display: block; } } }", parser, parser._parseStylesheet.bind(parser), ); - }); - - test("graceful handling of unknown rules", () => { - assertNode(`@unknown-rule`, parser, parser._parseStylesheet.bind(parser)); - assertNode(`@unknown-rule 'foo'`, parser, parser._parseStylesheet.bind(parser)); - assertNode(`@unknown-rule (foo)`, parser, parser._parseStylesheet.bind(parser)); assertNode( - `@unknown-rule (foo) - .bar`, + ".something { @media (max-width: 760px) { + div { display: block; } } }", parser, parser._parseStylesheet.bind(parser), ); - assertNode( - `@mskeyframes darkWordHighlight - from - background-color: inherit + assertNode("@media (max-width: 760px) { + div { display: block; } }", parser, parser._parseStylesheet.bind(parser)); + assertNode("@media (height <= 600px) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media (height >= 600px) { }", parser, parser._parseMedia.bind(parser)); - to - background-color: rgba(83, 83, 83, 0.7)`, + assertNode("@media #{layout.$media} and ($feature: $value) {}", parser, parser._parseStylesheet.bind(parser)); + assertNode("@media #{$media} and (layout.$feature: $value) {}", parser, parser._parseStylesheet.bind(parser)); + assertNode("@media #{$media} and ($feature: layout.$value) {}", parser, parser._parseStylesheet.bind(parser)); + assertNode( + "@media #{layout.$media} and (layout.$feature: $value) {}", parser, parser._parseStylesheet.bind(parser), ); assertNode( - `foo - @unknown-rule`, + "@media #{$media} and (layout.$feature: layout.$value) {}", parser, parser._parseStylesheet.bind(parser), ); - - assertError(`@unknown-rule (`, parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected); - assertError( - `@unknown-rule [foo`, + assertNode( + "@media #{layout.$media} and (layout.$feature: layout.$value) {}", parser, parser._parseStylesheet.bind(parser), - ParseError.RightSquareBracketExpected, ); - assertError( - `@unknown-rule - [foo`, + assertNode("@media screen and (list.nth($query, 1): nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media screen and (nth(list.$query, 1): nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media screen and (nth($query, 1): list.nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); + assertNode("@media screen and (nth($query, 1): nth(list.$query, 2)) { }", parser, parser._parseMedia.bind(parser)); + assertNode( + "@media screen and (list.nth(list.$query, 1): nth($query, 2)) { }", parser, - parser._parseStylesheet.bind(parser), - ParseError.RightSquareBracketExpected, + parser._parseMedia.bind(parser), ); - }); - - test("unknown rules node ends properly", () => { - const node = assertNode( - `@unknown-rule (foo) - .bar - color: limegreen - -.foo - color: red`, + assertNode( + "@media screen and (list.nth($query, 1): list.nth($query, 2)) { }", parser, - parser._parseStylesheet.bind(parser), + parser._parseMedia.bind(parser), ); - - const unknownAtRule = node.getChild(0)!; - assert.equal(unknownAtRule.type, nodes.NodeType.UnknownAtRule); - assert.equal(unknownAtRule.offset, 0); - assert.equal(node.getChild(0)!.length, 13); - assertNode( - ` -.foo - @apply p-4 bg-neutral-50 - min-height: var(--space-14) -`, + "@media screen and (list.nth($query, 1): nth(list.$query, 2)) { }", parser, - parser._parseStylesheet.bind(parser), + parser._parseMedia.bind(parser), ); - }); - - test("stylesheet /panic/", () => { - assertError('- @import "foo"', parser, parser._parseStylesheet.bind(parser), ParseError.RuleOrSelectorExpected); - }); - - test("@keyframe selector", () => { assertNode( - `from - color: red`, + "@media screen and (nth(list.$query, 1): list.nth($query, 2)) { }", parser, - parser._parseKeyframeSelector.bind(parser), + parser._parseMedia.bind(parser), ); assertNode( - `to - color: blue`, + "@media screen and (nth(list.$query, 1): nth(list.$query, 2)) { }", parser, - parser._parseKeyframeSelector.bind(parser), + parser._parseMedia.bind(parser), ); assertNode( - `0% - color: blue`, + "@media screen and (nth($query, 1): list.nth(list.$query, 2)) { }", parser, - parser._parseKeyframeSelector.bind(parser), + parser._parseMedia.bind(parser), ); assertNode( - `10% - color: purple`, + "@media screen and (list.nth(list.$query, 1): list.nth($query, 2)) { }", parser, - parser._parseKeyframeSelector.bind(parser), + parser._parseMedia.bind(parser), ); assertNode( - `cover 10% - color: purple`, + "@media screen and (nth(list.$query, 1): list.nth(list.$query, 2)) { }", parser, - parser._parseKeyframeSelector.bind(parser), + parser._parseMedia.bind(parser), ); assertNode( - `100000% - color: purple`, + "@media screen and (list.nth(list.$query, 1): list.nth(list.$query, 2)) { }", parser, - parser._parseKeyframeSelector.bind(parser), + parser._parseMedia.bind(parser), ); + }); + + test("@keyframe", function () { + const parser = new SassParser(); + assertNode("@keyframes name { @content; }", parser, parser._parseKeyframe.bind(parser)); assertNode( - `from - width: 100 - to: 10px`, + "@keyframes name { @for $i from 0 through $steps { #{$i * (100%/$steps)} { transform: $rotate $translate; } } }", parser, - parser._parseKeyframeSelector.bind(parser), - ); + parser._parseKeyframe.bind(parser), + ); // issue 42086 assertNode( - `from, to - width: 10px`, + '@keyframes test-keyframe { @for $i from 1 through 60 { $s: ($i * 100) / 60 + "%"; } }', parser, - parser._parseKeyframeSelector.bind(parser), + parser._parseKeyframe.bind(parser), ); + assertNode( - `10%, to - width: 10px`, + "@keyframes name { @for $i from 0 through m.$steps { #{$i * (100%/$steps)} { transform: $rotate $translate; } } }", parser, - parser._parseKeyframeSelector.bind(parser), + parser._parseKeyframe.bind(parser), ); + assertNode("@keyframes name { @function bar() { } }", parser, parser._parseKeyframe.bind(parser)); // #197742 + assertNode("@keyframes name { @include keyframe-mixin(); }", parser, parser._parseKeyframe.bind(parser)); // #197742 + }); + + test("@extend", function () { + const parser = new SassParser(); + assertNode(".themable { @extend %theme; }", parser, parser._parseStylesheet.bind(parser)); + assertNode("foo { @extend .error; border-width: 3px; }", parser, parser._parseStylesheet.bind(parser)); + assertNode("a.important { @extend .notice !optional; }", parser, parser._parseStylesheet.bind(parser)); + assertNode(".hoverlink { @extend a:hover; }", parser, parser._parseStylesheet.bind(parser)); + assertNode(".seriousError { @extend .error; @extend .attention; }", parser, parser._parseStylesheet.bind(parser)); assertNode( - `from, 20% - width: 10px`, + "#context a%extreme { color: blue; } .notice { @extend %extreme }", parser, - parser._parseKeyframeSelector.bind(parser), + parser._parseStylesheet.bind(parser), ); assertNode( - `10%, 20% - width: 10px`, + "@media print { .error { } .seriousError { @extend .error; } }", parser, - parser._parseKeyframeSelector.bind(parser), + parser._parseStylesheet.bind(parser), ); assertNode( - `cover 10%, exit 20% - width: 10px`, + "@mixin error($a: false) { @extend .#{$a}; @extend ##{$a}; }", parser, - parser._parseKeyframeSelector.bind(parser), + parser._parseStylesheet.bind(parser), ); - assertNode( - `10%, exit 20% - width: 10px`, + assertNode(".foo { @extend .text-center, .uppercase; }", parser, parser._parseStylesheet.bind(parser)); + assertNode(".foo { @extend .text-center, .uppercase, ; }", parser, parser._parseStylesheet.bind(parser)); + assertNode(".foo { @extend .text-center, .uppercase !optional ; }", parser, parser._parseStylesheet.bind(parser)); + assertError(".hoverlink { @extend }", parser, parser._parseStylesheet.bind(parser), ParseError.SelectorExpected); + assertError( + ".hoverlink { @extend %extreme !default }", parser, - parser._parseKeyframeSelector.bind(parser), + parser._parseStylesheet.bind(parser), + ParseError.UnknownKeyword, ); + }); + + test("@debug", function () { + const parser = new SassParser(); + assertNode("@debug test;", parser, parser._parseStylesheet.bind(parser)); + assertNode("foo { @debug 1 + 4; nested { @warn 1 4; } }", parser, parser._parseStylesheet.bind(parser)); + assertNode("@if $foo == 1 { @debug 1 + 4 }", parser, parser._parseStylesheet.bind(parser)); assertNode( - `from, exit 20% - width: 10px`, + '@function setStyle($map, $object, $style) { @warn "The key ´#{$object} is not available in the map."; @return null; }', parser, - parser._parseKeyframeSelector.bind(parser), + parser._parseStylesheet.bind(parser), ); + }); + + test("@if", function () { + const parser = new SassParser(); + assertNode("@if 1 + 1 == 2 { border: 1px solid; }", parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode("@if 5 < 3 { border: 2px dotted; }", parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode("@if null { border: 3px double; }", parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode( - `cover 10%, to - width: 10px`, + "@if 1 <= $const { border: 3px; } @else { border: 4px; }", parser, - parser._parseKeyframeSelector.bind(parser), + parser._parseRuleSetDeclaration.bind(parser), ); assertNode( - `cover 10%, 20% - width: 10px`, + "@if 1 >= (1 + $foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }", parser, - parser._parseKeyframeSelector.bind(parser), + parser._parseRuleSetDeclaration.bind(parser), ); - }); - - test("@keyframe", () => { assertNode( - `@keyframes name - //`, + "p { @if $i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }", parser, - parser._parseKeyframe.bind(parser), + parser._parseStylesheet.bind(parser), ); assertNode( - `@-webkit-keyframes name - //`, + '@if (index($_RESOURCES, "clean") != null) { @error "sdssd"; }', parser, - parser._parseKeyframe.bind(parser), + parser._parseStylesheet.bind(parser), + ); + assertNode("@if $i == 1 { p { x: 3px; } }", parser, parser._parseStylesheet.bind(parser)); + assertError( + "@if { border: 1px solid; }", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.ExpressionExpected, ); + assertError("@if 1 }", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.LeftCurlyExpected); + assertNode( - `@-o-keyframes name - //`, + "@if 1 <= m.$const { border: 3px; } @else { border: 4px; }", parser, - parser._parseKeyframe.bind(parser), + parser._parseRuleSetDeclaration.bind(parser), ); assertNode( - `@-moz-keyframes name - //`, + "@if 1 >= (1 + m.$foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }", parser, - parser._parseKeyframe.bind(parser), + parser._parseRuleSetDeclaration.bind(parser), ); assertNode( - `@keyframes name - from - // - to - //`, + "p { @if m.$i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }", parser, - parser._parseKeyframe.bind(parser), + parser._parseStylesheet.bind(parser), ); assertNode( - `@keyframes name - from - // - 80% - // - 100% - //`, + "p { @if $i == 1 { x: 3px; } @else if m.$i == 1 { x: 4px; } @else { x: 4px; } }", parser, - parser._parseKeyframe.bind(parser), + parser._parseStylesheet.bind(parser), ); assertNode( - `@keyframes name - from - top: 0px - 80% - top: 100px - 100% - top: 50px`, + "p { @if m.$i == 1 { x: 3px; } @else if m.$i == 1 { x: 4px; } @else { x: 4px; } }", parser, - parser._parseKeyframe.bind(parser), + parser._parseStylesheet.bind(parser), ); assertNode( - `@keyframes name - from - top: 0px - 70%, 80% - top: 100px - 100% - top: 50px`, + '@if (list.index($_RESOURCES, "clean") != null) { @error "sdssd"; }', parser, - parser._parseKeyframe.bind(parser), + parser._parseStylesheet.bind(parser), ); assertNode( - `@keyframes name - from - top: 0px - left: 1px - right: 2px`, + '@if (index(m.$_RESOURCES, "clean") != null) { @error "sdssd"; }', parser, - parser._parseKeyframe.bind(parser), + parser._parseStylesheet.bind(parser), ); assertNode( - `@keyframes name - exit 50% - top: 0px - left: 1px - right: 2px`, + '@if (list.index(m.$_RESOURCES, "clean") != null) { @error "sdssd"; }', parser, - parser._parseKeyframe.bind(parser), + parser._parseStylesheet.bind(parser), ); - - assertError("@keyframes )", parser, parser._parseKeyframe.bind(parser), ParseError.IdentifierExpected); }); - test("@keyframes sass", () => { + test("@for", function () { + const parser = new SassParser(); assertNode( - `@keyframes name - @content`, + "@for $i from 1 to 5 { .item-#{$i} { width: 2em * $i; } }", parser, - parser._parseKeyframe.bind(parser), + parser._parseRuleSetDeclaration.bind(parser), ); - assertNode( - `@keyframes name - @for $i from 0 through $steps - #{$i * (100%/$steps)} - transform: $rotate $translate`, + assertNode("@for $k from 1 + $x through 5 + $x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); + assertError( + "@for i from 0 to 4 {}", parser, - parser._parseKeyframe.bind(parser), + parser._parseRuleSetDeclaration.bind(parser), + ParseError.VariableNameExpected, ); - assertNode( - `@keyframes test-keyframe - @for $i from 1 through 60 - $s: ($i * 100) / 60 + "%"`, + assertError("@for $i to 4 {}", parser, parser._parseRuleSetDeclaration.bind(parser), SassParseError.FromExpected); + assertError( + "@for $i from 0 by 4 {}", parser, - parser._parseKeyframe.bind(parser), + parser._parseRuleSetDeclaration.bind(parser), + SassParseError.ThroughOrToExpected, + ); + assertError("@for $i from {}", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); + assertError( + "@for $i from 0 to {}", + parser, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.ExpressionExpected, ); + assertNode('@for $i from 1 through 60 { $s: $i + "%"; }', parser, parser._parseRuleSetDeclaration.bind(parser)); + + assertNode("@for $k from 1 + m.$x through 5 + $x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode("@for $k from 1 + $x through 5 + m.$x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode("@for $k from 1 + m.$x through 5 + m.$x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); + }); + test("@each", function () { + const parser = new SassParser(); + assertNode("@each $i in 1, 2, 3 { }", parser, parser._parseRuleSetDeclaration.bind(parser)); + assertNode("@each $i in 1 2 3 { }", parser, parser._parseRuleSetDeclaration.bind(parser)); assertNode( - `@keyframes name - @for $i from 0 through m.$steps - #{$i * (100%/$steps)} - transform: $rotate $translate`, + "@each $animal, $color, $cursor in (puma, black, default), (egret, white, move) {}", parser, - parser._parseKeyframe.bind(parser), + parser._parseRuleSetDeclaration.bind(parser), ); - assertNode( - `@keyframes name - @function bar() - @return 1`, + assertError( + "@each i in 4 {}", parser, - parser._parseKeyframe.bind(parser), + parser._parseRuleSetDeclaration.bind(parser), + ParseError.VariableNameExpected, ); - assertNode( - `@keyframes name - @include keyframe-mixin()`, + assertError("@each $i from 4 {}", parser, parser._parseRuleSetDeclaration.bind(parser), SassParseError.InExpected); + assertError("@each $i in {}", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); + assertError( + "@each $animal, in (1, 1, 1), (2, 2, 2) {}", parser, - parser._parseKeyframe.bind(parser), + parser._parseRuleSetDeclaration.bind(parser), + ParseError.VariableNameExpected, ); }); - test("@property", () => { + test("@while", function () { + const parser = new SassParser(); assertNode( - `@property --my-color - syntax: '' - inherits: false - initial-value: #c0ffee`, + "@while $i < 0 { .item-#{$i} { width: 2em * $i; } $i: $i - 2; }", parser, - parser._parseStylesheet.bind(parser), + parser._parseRuleSetDeclaration.bind(parser), ); - + assertError("@while {}", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); + assertError("@while $i != 4", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.LeftCurlyExpected); assertError( - `@property - syntax: '' - inherits: false - initial-value: #c0ffee`, + "@while ($i >= 4) {", parser, - parser._parseStylesheet.bind(parser), - ParseError.IdentifierExpected, + parser._parseRuleSetDeclaration.bind(parser), + ParseError.RightCurlyExpected, ); }); - test("@container", () => { + test("@mixin", function () { + const parser = new SassParser(); assertNode( - `@container (width <= 150px) - #inner - background-color: skyblue`, + "@mixin large-text { font: { family: Arial; size: 20px; } color: #ff0000; }", parser, parser._parseStylesheet.bind(parser), ); assertNode( - `@container card (inline-size > 30em) and style(--responsive: true) - #inner - background-color: skyblue`, + "@mixin sexy-border($color, $width: 1in) { color: black; }", parser, parser._parseStylesheet.bind(parser), ); assertNode( - ` -@container card (inline-size > 30em) - @container style(--responsive: true) - #inner - background-color: skyblue`, + "@mixin box-shadow($shadows...) { -moz-box-shadow: $shadows; }", parser, parser._parseStylesheet.bind(parser), ); - assertNode( - ` -@container (min-width: 700px) - .card h2 - font-size: max(1.5em, 1.23em + 2cqi)`, + assertNode("@mixin apply-to-ie6-only { * html { @content; } }", parser, parser._parseStylesheet.bind(parser)); + assertNode("@mixin #{foo}($color){}", parser, parser._parseStylesheet.bind(parser)); + assertNode("@mixin foo ($i:4) { size: $i; @include wee ($i - 1); }", parser, parser._parseStylesheet.bind(parser)); + assertNode("@mixin foo ($i,) { }", parser, parser._parseStylesheet.bind(parser)); + + assertError("@mixin $1 {}", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); + assertError("@mixin foo() i {}", parser, parser._parseStylesheet.bind(parser), ParseError.LeftCurlyExpected); + assertError("@mixin foo(1) {}", parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected); + assertError( + "@mixin foo($color = 9) {}", parser, parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, ); + assertError("@mixin foo($color)", parser, parser._parseStylesheet.bind(parser), ParseError.LeftCurlyExpected); + assertError("@mixin foo($color){", parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected); + assertError("@mixin foo($color,){", parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected); + }); + + test("@content", function () { + const parser = new SassParser(); + assertNode("@content", parser, parser._parseMixinContent.bind(parser)); + assertNode("@content($type)", parser, parser._parseMixinContent.bind(parser)); }); - test("@import", () => { - assertNode(`@import "asdfasdf"`, parser, parser._parseImport.bind(parser)); - assertNode(`@ImPort "asdfasdf"`, parser, parser._parseImport.bind(parser)); - assertNode(`@import url(/css/screen.css) screen, projection`, parser, parser._parseImport.bind(parser)); + test("@include", function () { + const parser = new SassParser(); + assertNode("p { @include sexy-border(blue); }", parser, parser._parseStylesheet.bind(parser)); assertNode( - `@import url('landscape.css') screen and (orientation:landscape)`, + ".shadows { @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }", parser, - parser._parseImport.bind(parser), + parser._parseStylesheet.bind(parser), ); - assertNode(`@import url("/inc/Styles/full.css") (min-width: 940px)`, parser, parser._parseImport.bind(parser)); - assertNode(`@import url(style.css) screen and (min-width:600px)`, parser, parser._parseImport.bind(parser)); - assertNode(`@import url("./700.css") only screen and (max-width: 700px)`, parser, parser._parseImport.bind(parser)); - assertNode(`@import url("override.css") layer`, parser, parser._parseImport.bind(parser)); - assertNode(`@import url("tabs.css") layer(framework.component)`, parser, parser._parseImport.bind(parser)); - assertNode(`@import "mystyle.css" supports(display: flex)`, parser, parser._parseImport.bind(parser)); assertNode( - `@import url("narrow.css") supports(display: flex) handheld and (max-width: 400px)`, + "$values: #ff0000, #00ff00, #0000ff; .primary { @include colors($values...); }", parser, - parser._parseImport.bind(parser), + parser._parseStylesheet.bind(parser), ); + assertNode('@include colors(this("styles")...);', parser, parser._parseStylesheet.bind(parser)); + assertNode(".test { @include fontsize(16px, 21px !important); }", parser, parser._parseStylesheet.bind(parser)); assertNode( - `@import url("fallback-layout.css") supports(not (display: flex))`, + "p { @include apply-to-ie6-only { #logo { background-image: url(/logo.gif); } } }", parser, - parser._parseImport.bind(parser), + parser._parseStylesheet.bind(parser), ); + assertNode("p { @include foo($values,) }", parser, parser._parseStylesheet.bind(parser)); + assertNode("p { @include foo($values,); }", parser, parser._parseStylesheet.bind(parser)); - assertError(`@import`, parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); - }); - - test("@supports", () => { - assertNode( - `@supports ( display: flexbox ) - body - display: flexbox`, - parser, - parser._parseSupports.bind(parser), - ); - assertNode( - `@supports not (display: flexbox) - .outline - box-shadow: 2px 2px 2px black /* unprefixed last */`, + assertError( + "p { @include sexy-border blue", parser, - parser._parseSupports.bind(parser), + parser._parseStylesheet.bind(parser), + ParseError.SemiColonExpected, ); - assertNode( - `@supports ( box-shadow: 2px 2px 2px black ) or ( -moz-box-shadow: 2px 2px 2px black ) or ( -webkit-box-shadow: 2px 2px 2px black ) - .foo - color: red`, + assertError( + "p { @include sexy-border($values blue", parser, - parser._parseSupports.bind(parser), + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, ); - assertNode( - `@supports ((transition-property: color) or (animation-name: foo)) and (transform: rotate(10deg)) - .foo - color: red`, + assertError("p { @include }", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); + assertError( + "p { @include foo($values }", parser, - parser._parseSupports.bind(parser), + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, ); - assertNode( - `@supports ((display: flexbox)) - .foo - color: red`, + assertError( + "p { @include foo($values, }", parser, - parser._parseSupports.bind(parser), + parser._parseStylesheet.bind(parser), + ParseError.ExpressionExpected, ); + + assertNode("p { @include lib.sexy-border(blue); }", parser, parser._parseStylesheet.bind(parser)); assertNode( - `@supports (display: flexbox !important) - .foo - color: red`, + ".shadows { @include lib.box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }", parser, - parser._parseSupports.bind(parser), + parser._parseStylesheet.bind(parser), ); assertNode( - `@supports (column-width: 1rem) OR (-moz-column-width: 1rem) OR (-webkit-column-width: 1rem) oR (-x-column-width: 1rem) - .foo - color: limegreen`, + "$values: #ff0000, #00ff00, #0000ff; .primary { @include lib.colors($values...); }", parser, - parser._parseSupports.bind(parser), + parser._parseStylesheet.bind(parser), ); + assertNode(".primary { @include colors(lib.$values...); }", parser, parser._parseStylesheet.bind(parser)); + assertNode(".primary { @include lib.colors(lib.$values...); }", parser, parser._parseStylesheet.bind(parser)); + assertNode('@include lib.colors(this("styles")...);', parser, parser._parseStylesheet.bind(parser)); + assertNode('@include colors(lib.this("styles")...);', parser, parser._parseStylesheet.bind(parser)); + assertNode('@include lib.colors(lib.this("styles")...);', parser, parser._parseStylesheet.bind(parser)); + assertNode(".test { @include lib.fontsize(16px, 21px !important); }", parser, parser._parseStylesheet.bind(parser)); assertNode( - `@supports not (--validValue: , 0 ) - .foo - color: limegreen`, + "p { @include lib.apply-to-ie6-only { #logo { background-image: url(/logo.gif); } } }", parser, - parser._parseSupports.bind(parser), + parser._parseStylesheet.bind(parser), ); + assertNode("p { @include lib.foo($values,) }", parser, parser._parseStylesheet.bind(parser)); + assertNode("p { @include foo(lib.$values,) }", parser, parser._parseStylesheet.bind(parser)); + assertNode("p { @include lib.foo(m.$values,); }", parser, parser._parseStylesheet.bind(parser)); assertError( - `@supports display: flexbox - .foo - color: limegreen`, + "p { @include foo.($values) }", parser, - parser._parseSupports.bind(parser), - ParseError.LeftParenthesisExpected, + parser._parseStylesheet.bind(parser), + ParseError.IdentifierExpected, ); - }); - test("@media", () => { assertNode( - `@media asdsa - .foo - color: black`, + '@include rtl("left") using ($dir) { margin-#{$dir}: 10px; }', parser, - parser._parseMedia.bind(parser), + parser._parseStylesheet.bind(parser), ); + }); + + test("@function", function () { + const parser = new SassParser(); assertNode( - `@meDia asdsa - .foo - color: black`, + "@function grid-width($n) { @return $n * $grid-width + ($n - 1) * $gutter-width; }", parser, - parser._parseMedia.bind(parser), + parser._parseStylesheet.bind(parser), ); + assertNode("@function grid-width($n: 1, $e) { @return 0; }", parser, parser._parseStylesheet.bind(parser)); assertNode( - `@meDia somename, othername2 - .foo - color: black`, + "@function foo($total, $a) { @for $i from 0 to $total { } @return $grid; }", parser, - parser._parseMedia.bind(parser), + parser._parseStylesheet.bind(parser), ); assertNode( - `@media only screen and (max-width:850px) - .foo - color: black`, + '@function foo() { @if (unit($a) == "%") and ($i == ($total - 1)) { @return 0; } @return 1; }', parser, - parser._parseMedia.bind(parser), + parser._parseStylesheet.bind(parser), ); assertNode( - `@media all and (min-width:500px) - .foo - color: black`, + "@function is-even($int) { @if $int%2 == 0 { @return true; } @return false }", parser, - parser._parseMedia.bind(parser), + parser._parseStylesheet.bind(parser), ); assertNode( - `@media screen and (color), projection and (color) - .foo - color: black`, + "@function bar ($i) { @if $i > 0 { @return $i * bar($i - 1); } @return 1; }", parser, - parser._parseMedia.bind(parser), + parser._parseStylesheet.bind(parser), ); - assertNode( - `@media not screen and (device-aspect-ratio: 16/9) - .foo - color: black`, + assertNode("@function foo($a,) {} ", parser, parser._parseStylesheet.bind(parser)); + + assertError("@function foo {} ", parser, parser._parseStylesheet.bind(parser), ParseError.LeftParenthesisExpected); + assertError("@function {} ", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); + assertError( + "@function foo($a $b) {} ", parser, - parser._parseMedia.bind(parser), + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, ); - assertNode( - `@media print and (min-resolution: 300dpi) - .foo - color: black`, + assertError( + "@function foo($a {} ", parser, - parser._parseMedia.bind(parser), + parser._parseStylesheet.bind(parser), + ParseError.RightParenthesisExpected, ); - assertNode( - `@media print and (min-resolution: 118dpcm) - .foo - color: black`, + assertError( + "@function foo($a...) { @return; }", parser, - parser._parseMedia.bind(parser), - ); - assertNode( - `@media print - @page - margin: 10% - blockquote, pre - page-break-inside: avoid`, - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - `@media print - body:before - page-break-inside: avoid`, - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - `@media not (-moz-os-version: windows-win7) - body - color: black`, - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - `@media not (not (-moz-os-version: windows-win7)) - body - color: black`, - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - `@media (height > 600px) - body - color: black`, - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - `@media (height < 600px) - body - color: black`, - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - `@media (height <= 600px) - body - color: black`, - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - `@media (400px <= width <= 700px) - body - color: black`, - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - `@media (400px >= width >= 700px) - body - color: black`, - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - `@media screen and (750px <= width < 900px) - body - color: black`, - parser, - parser._parseMedia.bind(parser), - ); - - assertError( - `@media somename othername2 - body - color: black`, - parser, - parser._parseMedia.bind(parser), - ParseError.IndentExpected, - ); - assertError( - `@media not, screen - body - color: black`, - parser, - parser._parseMedia.bind(parser), - ParseError.MediaQueryExpected, - ); - assertError( - `@media not screen and foo - body - color: black`, - parser, - parser._parseMedia.bind(parser), - ParseError.LeftParenthesisExpected, - ); - assertError( - `@media not screen and () - body - color: black`, - parser, - parser._parseMedia.bind(parser), - ParseError.IdentifierExpected, - ); - assertError( - `@media not screen and (color:) - body - color: black`, - parser, - parser._parseMedia.bind(parser), - ParseError.TermExpected, + parser._parseStylesheet.bind(parser), + ParseError.ExpressionExpected, ); assertError( - `@media not screen and (color:#fff - body - color: black`, + "@function foo($a:) {} ", parser, - parser._parseMedia.bind(parser), - ParseError.RightParenthesisExpected, + parser._parseStylesheet.bind(parser), + ParseError.VariableValueExpected, ); }); - test("@media sass", () => { - assertNode( - `@media screen - .sidebar - @media (orientation: landscape) - width: 500px`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode(`@media #{$media} and ($feature: $value)\n\t`, parser, parser._parseStylesheet.bind(parser)); - assertNode(`@media only screen and #{$query}\n\t`, parser, parser._parseStylesheet.bind(parser)); - assertNode( - `foo - bar - @media screen and (orientation: landscape) - color: red`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode(`@media screen and (nth($query, 1): nth($query, 2))\n\t`, parser, parser._parseMedia.bind(parser)); - assertNode( - `.something - @media (max-width: 760px) - > .test - color: blue`, - parser, - parser._parseStylesheet.bind(parser), - ); + test("@at-root", function () { + const parser = new SassParser(); assertNode( - `.something - @media (max-width: 760px) - ~ div - display: block`, + "@mixin unify-parent($child) { @at-root #{selector.unify(&, $child)} { }}", parser, parser._parseStylesheet.bind(parser), ); assertNode( - `.something - @media (max-width: 760px) - + div - display: block`, + "@at-root #main2 .some-class { padding-left: calc( #{$a-variable} + 8px ); }", parser, parser._parseStylesheet.bind(parser), ); assertNode( - `@media (max-width: 760px) - + div - display: block`, + "@media print { .page { @at-root (without: media) { } } }", parser, parser._parseStylesheet.bind(parser), ); - assertNode(`@media (height <= 600px)\n\t`, parser, parser._parseMedia.bind(parser)); - assertNode(`@media (height >= 600px)\n\t`, parser, parser._parseMedia.bind(parser)); + assertNode("@media print { .page { @at-root (with: rule) { } } }", parser, parser._parseStylesheet.bind(parser)); + }); - assertNode(`@media #{layout.$media} and ($feature: $value)\n\t`, parser, parser._parseStylesheet.bind(parser)); - assertNode(`@media #{$media} and (layout.$feature: $value)\n\t`, parser, parser._parseStylesheet.bind(parser)); - assertNode(`@media #{$media} and ($feature: layout.$value)\n\t`, parser, parser._parseStylesheet.bind(parser)); - assertNode( - `@media #{layout.$media} and (layout.$feature: $value)\n\t`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@media #{$media} and (layout.$feature: layout.$value)\n\t`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@media #{layout.$media} and (layout.$feature: layout.$value)\n\t`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode(`@media screen and (list.nth($query, 1): nth($query, 2))\n\t`, parser, parser._parseMedia.bind(parser)); - assertNode(`@media screen and (nth(list.$query, 1): nth($query, 2))\n\t`, parser, parser._parseMedia.bind(parser)); - assertNode(`@media screen and (nth($query, 1): list.nth($query, 2))\n\t`, parser, parser._parseMedia.bind(parser)); - assertNode(`@media screen and (nth($query, 1): nth(list.$query, 2))\n\t`, parser, parser._parseMedia.bind(parser)); - assertNode( - `@media screen and (list.nth(list.$query, 1): nth($query, 2))\n\t`, - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - `@media screen and (list.nth($query, 1): list.nth($query, 2))\n\t`, - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - `@media screen and (list.nth($query, 1): nth(list.$query, 2))\n\t`, - parser, - parser._parseMedia.bind(parser), - ); + test("Ruleset", function () { + const parser = new SassParser(); + assertNode(".selector { prop: erty $const 1px; }", parser, parser._parseRuleset.bind(parser)); + assertNode(".selector { prop: erty $const 1px m.$foo; }", parser, parser._parseRuleset.bind(parser)); + assertNode("selector:active { property:value; nested:hover {}}", parser, parser._parseRuleset.bind(parser)); + assertNode("selector {}", parser, parser._parseRuleset.bind(parser)); + assertNode("selector { property: declaration }", parser, parser._parseRuleset.bind(parser)); + assertNode("selector { $variable: declaration }", parser, parser._parseRuleset.bind(parser)); + assertNode("selector { nested {}}", parser, parser._parseRuleset.bind(parser)); + assertNode("selector { nested, a, b {}}", parser, parser._parseRuleset.bind(parser)); + assertNode("selector { property: value; property: $value; }", parser, parser._parseRuleset.bind(parser)); assertNode( - `@media screen and (nth(list.$query, 1): list.nth($query, 2))\n\t`, + "selector { property: value; @keyframes foo {} @-moz-keyframes foo {}}", parser, - parser._parseMedia.bind(parser), + parser._parseRuleset.bind(parser), ); + assertNode("foo|bar { }", parser, parser._parseRuleset.bind(parser)); + }); + + test("Nested Ruleset", function () { + const parser = new SassParser(); assertNode( - `@media screen and (nth(list.$query, 1): nth(list.$query, 2))\n\t`, + ".class1 { $const: 1; .class { $const: 2; three: $const; const: 3; } one: $const; }", parser, - parser._parseMedia.bind(parser), + parser._parseRuleset.bind(parser), ); assertNode( - `@media screen and (nth($query, 1): list.nth(list.$query, 2))\n\t`, + ".class1 { $const: 1; .class { $const: m.$foo; } one: $const; }", parser, - parser._parseMedia.bind(parser), + parser._parseRuleset.bind(parser), ); + assertNode(".class1 { > .class2 { & > .class4 { rule1: v1; } } }", parser, parser._parseRuleset.bind(parser)); + assertNode("foo { @at-root { display: none; } }", parser, parser._parseRuleset.bind(parser)); assertNode( - `@media screen and (list.nth(list.$query, 1): list.nth($query, 2))\n\t`, + 'th, tr { @at-root #{selector-replace(&, "tr")} { border-bottom: 0; } }', parser, - parser._parseMedia.bind(parser), + parser._parseRuleset.bind(parser), ); assertNode( - `@media screen and (nth(list.$query, 1): list.nth(list.$query, 2))\n\t`, + ".foo { @supports(display: grid) { .bar { display: none; }}}", parser, - parser._parseMedia.bind(parser), + parser._parseRuleset.bind(parser), ); + assertNode(".foo { @supports(display: grid) { display: none; }}", parser, parser._parseRuleset.bind(parser)); assertNode( - `@media screen and (list.nth(list.$query, 1): list.nth(list.$query, 2))\n\t`, + ".foo { @supports (position: sticky) { @media (min-width: map-get($grid-breakpoints, md)) { position: sticky; } }}", parser, - parser._parseMedia.bind(parser), - ); + parser._parseRuleset.bind(parser), + ); // issue #152 + }); + + test("Selector Interpolation", function () { + const parser = new SassParser(); + assertNode(".#{$name} { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".#{$name}-foo { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".#{$name}-foo-3 { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".#{$name}-1 { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".sc-col#{$postfix}-2-1 { }", parser, parser._parseRuleset.bind(parser)); + assertNode("p.#{$name} { #{$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); + assertNode("sans-#{serif} { a-#{1 + 2}-color-#{$attr}: blue; }", parser, parser._parseRuleset.bind(parser)); + assertNode("##{f} .#{f} #{f}:#{f} { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".foo-#{&} .foo-#{&-sub} { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".-#{$variable} { }", parser, parser._parseRuleset.bind(parser)); + assertNode("#{&}([foo=bar][bar=foo]) { }", parser, parser._parseRuleset.bind(parser)); // #49589 + + assertNode(".#{module.$name} { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".#{module.$name}-foo { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".#{module.$name}-foo-3 { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".#{module.$name}-1 { }", parser, parser._parseRuleset.bind(parser)); + assertNode(".sc-col#{module.$postfix}-2-1 { }", parser, parser._parseRuleset.bind(parser)); + assertNode("p.#{module.$name} { #{$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); + assertNode("p.#{$name} { #{module.$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); + assertNode("p.#{module.$name} { #{module.$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); + assertNode("sans-#{serif} { a-#{1 + 2}-color-#{module.$attr}: blue; }", parser, parser._parseRuleset.bind(parser)); + assertNode(".-#{module.$variable} { }", parser, parser._parseRuleset.bind(parser)); + }); + + test("Parent Selector", function () { + const parser = new SassParser(); + assertNode("&:hover", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&.float", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-bar", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-1", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&1", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-foo-1", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&&", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("&-10-thing", parser, parser._parseSimpleSelector.bind(parser)); }); - test("media query list", () => { - assertNode("somename", parser, parser._parseMediaQueryList.bind(parser)); - assertNode("somename, othername", parser, parser._parseMediaQueryList.bind(parser)); - assertNode("not all and (monochrome)", parser, parser._parseMediaQueryList.bind(parser)); + test("Selector Placeholder", function () { + const parser = new SassParser(); + assertNode("%hover", parser, parser._parseSimpleSelector.bind(parser)); + assertNode("a%float", parser, parser._parseSimpleSelector.bind(parser)); }); - test("medium", () => { - assertNode("somename", parser, parser._parseMedium.bind(parser)); - assertNode("-asdas", parser, parser._parseMedium.bind(parser)); - assertNode("-asda34s", parser, parser._parseMedium.bind(parser)); + test("Map", function () { + const parser = new SassParser(); + assertNode("(key1: 1px, key2: solid + px, key3: (2+3))", parser, parser._parseExpr.bind(parser)); + assertNode("($key1 + 3: 1px)", parser, parser._parseExpr.bind(parser)); }); - test("@page", () => { - assertNode( - `@page : name - some: "asdf"`, - parser, - parser._parsePage.bind(parser), - ); - assertNode( - `@page :left, :right - some: "asdf"`, - parser, - parser._parsePage.bind(parser), - ); - assertNode( - `@page : name - some: "asdf" !important - some: "asdf" !important`, - parser, - parser._parsePage.bind(parser), - ); - assertNode( - `@page rotated - size: landscape`, - parser, - parser._parsePage.bind(parser), - ); - assertNode( - `@page :left - margin-left: 4cm - margin-right: 3cm`, - parser, - parser._parsePage.bind(parser), - ); - assertNode( - `@page - @top-right-corner - content: url(foo.png) - border: solid green`, - parser, - parser._parsePage.bind(parser), - ); + test("Url", function () { + const parser = new SassParser(); + assertNode("url(foo())", parser, parser._parseURILiteral.bind(parser)); assertNode( - `@page - @top-left-corner - content: " " - border: solid green - @bottom-right-corner - content: counter(page) - border: solid green`, - parser, - parser._parsePage.bind(parser), - ); - - assertError( - `@page - @top-left-corner foo - content: " " - border: solid green`, + "url('data:image/svg+xml;utf8,%3Csvg%20fill%3D%22%23' + $color + 'foo')", parser, - parser._parsePage.bind(parser), - ParseError.IndentExpected, + parser._parseURILiteral.bind(parser), ); + assertNode("url(//yourdomain/yourpath.png)", parser, parser._parseURILiteral.bind(parser)); + assertNode("url('http://msft.com')", parser, parser._parseURILiteral.bind(parser)); + assertNode('url("http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url( "http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url(\t"http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url(\n"http://msft.com")', parser, parser._parseURILiteral.bind(parser)); + assertNode('url("http://msft.com"\n)', parser, parser._parseURILiteral.bind(parser)); + assertNode('url("")', parser, parser._parseURILiteral.bind(parser)); + assertNode('uRL("")', parser, parser._parseURILiteral.bind(parser)); + assertNode('URL("")', parser, parser._parseURILiteral.bind(parser)); + assertNode("url(http://msft.com)", parser, parser._parseURILiteral.bind(parser)); + assertNode("url()", parser, parser._parseURILiteral.bind(parser)); + assertNode("url('http://msft.com\n)", parser, parser._parseURILiteral.bind(parser)); assertError( - `@page : - @top-left-corner foo - content: " " - border: solid green`, + 'url("http://msft.com"', parser, - parser._parsePage.bind(parser), - ParseError.IdentifierExpected, + parser._parseURILiteral.bind(parser), + ParseError.RightParenthesisExpected, ); assertError( - `@page :left, - @top-left-corner foo - content: " " - border: solid green`, - parser, - parser._parsePage.bind(parser), - ParseError.IdentifierExpected, - ); - }); - - test("@layer", () => { - assertNode( - `@layer utilities - .padding-sm - padding: .5rem`, - parser, - parser._parseLayer.bind(parser), - ); - assertNode(`@layer utilities`, parser, parser._parseLayer.bind(parser)); - assertNode(`@layer theme, layout, utilities`, parser, parser._parseLayer.bind(parser)); - assertNode( - `@layer framework - @layer layout - .padding-sm - padding: .5rem`, - parser, - parser._parseLayer.bind(parser), - ); - assertNode( - `@layer framework.layout - @keyframes slide-left - from - foo: bar - to - foo: baz`, - parser, - parser._parseLayer.bind(parser), - ); - - assertNode( - `@media (min-width: 30em) - @layer layout - .padding-sm - padding: .5rem`, + "url(http://msft.com')", parser, - parser._parseStylesheet.bind(parser), + parser._parseURILiteral.bind(parser), + ParseError.RightParenthesisExpected, ); - - assertError(`@layer theme. layout`, parser, parser._parseLayer.bind(parser), ParseError.IdentifierExpected); }); - test("operator", () => { - assertNode("/", parser, parser._parseOperator.bind(parser)); - assertNode("*", parser, parser._parseOperator.bind(parser)); - assertNode("+", parser, parser._parseOperator.bind(parser)); - assertNode("-", parser, parser._parseOperator.bind(parser)); - }); - - test("combinator", () => { - assertNode("+", parser, parser._parseCombinator.bind(parser)); - assertNode("+ ", parser, parser._parseCombinator.bind(parser)); - assertNode("> ", parser, parser._parseCombinator.bind(parser)); - assertNode(">", parser, parser._parseCombinator.bind(parser)); - assertNode(">>>", parser, parser._parseCombinator.bind(parser)); - assertNode("/deep/", parser, parser._parseCombinator.bind(parser)); + test("@font-face", function () { + const parser = new SassParser(); + assertNode("@font-face {}", parser, parser._parseFontFace.bind(parser)); + assertNode("@font-face { src: url(http://test) }", parser, parser._parseFontFace.bind(parser)); + assertNode("@font-face { font-style: normal; font-stretch: normal; }", parser, parser._parseFontFace.bind(parser)); assertNode( - `:host >>> .data-table - width: 100%`, + "@font-face { unicode-range: U+0021-007F, u+1f49C, U+4??, U+??????; }", parser, - parser._parseStylesheet.bind(parser), + parser._parseFontFace.bind(parser), ); - assertError( - `:host >> .data-table - width: 100%`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.IndentExpected, - ); - }); - - test("unary_operator", () => { - assertNode("-", parser, parser._parseUnaryOperator.bind(parser)); - assertNode("+", parser, parser._parseUnaryOperator.bind(parser)); - }); - - test("property", () => { - assertNode("asdsa", parser, parser._parseProperty.bind(parser)); - assertNode("asdsa334", parser, parser._parseProperty.bind(parser)); - - assertNode("--color", parser, parser._parseProperty.bind(parser)); - assertNode("--primary-font", parser, parser._parseProperty.bind(parser)); - assertNode("-color", parser, parser._parseProperty.bind(parser)); - assertNode("somevar", parser, parser._parseProperty.bind(parser)); - assertNode("some--let", parser, parser._parseProperty.bind(parser)); - assertNode("somevar--", parser, parser._parseProperty.bind(parser)); - }); - - test("ruleset", () => { - assertNode( - ` -.foo - font: - family: Arial - size: 20px - color: #ff0000`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `selector - property: value - @keyframes foo - from - top: 0 - 100% - top: 100% - @-moz-keyframes foo - from - top: 0 - 100% - top: 100%`, - parser, - parser._parseRuleset.bind(parser), - ); - - assertNode("foo|bar\n\t//", parser, parser._parseRuleset.bind(parser)); - - assertNode( - `name - foo: bar`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` - -name - foo: "asdfasdf"`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` - -name - foo : "asdfasdf" !important`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `* - foo: bar`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `.far - foo: bar`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `boo - foo: bar`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `.far #boo - foo: bar`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `name - foo: bar - baz: bar`, + "@font-face { font-style: normal font-stretch: normal; }", parser, - parser._parseRuleset.bind(parser), + parser._parseFontFace.bind(parser), + ParseError.SemiColonExpected, ); - - assertError( - `name - --minimal:`, - parser, - parser._parseRuleset.bind(parser), - ParseError.PropertyValueExpected, - ); - assertNode( - `name - --minimal: - - other - padding: 1rem`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `name - --normal-text: red yellow green`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `name - --normal-text: red !important`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `name - --nested: - color: green`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `name - --normal-text: this()is()ok()`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `name - --normal-text: this[]is[]ok[]`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `name - --normal-text: ([{{[]()()}[]{}}])()`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `name - --normal-text: , 0 0`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `name - --normal-text: {}`, - parser, - parser._parseRuleset.bind(parser), - ); - - assertNode( - `.selector - prop: erty $const 1px`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `.selector - prop: erty $const 1px m.$foo`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `selector:active - property: value - nested: hover - property: value`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `selector - property: declaration`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `selector - $variable: declaration`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `selector - property: value - property: $value`, - parser, - parser._parseRuleset.bind(parser), - ); - - assertError( - `name - font-size: {}`, - parser, - parser._parseRuleset.bind(parser), - ParseError.PropertyValueExpected, - ); - assertError( - `boo, - `, - parser, - parser._parseRuleset.bind(parser), - ParseError.SelectorExpected, - ); - }); - - test("ruleset /Panic/", () => { - assertError( - ` -foo - bar:`, - parser, - parser._parseRuleset.bind(parser), - ParseError.PropertyValueExpected, - ); - assertError( - ` -foo - bar: - far: 12em`, - parser, - parser._parseRuleset.bind(parser), - ParseError.PropertyValueExpected, - ); - assertError( - ` -foo - bar`, - parser, - parser._parseRuleset.bind(parser), - ParseError.ColonExpected, - ); - assertError( - ` -foo - --too-minimal:`, - parser, - parser._parseRuleset.bind(parser), - ParseError.PropertyValueExpected, - ); - assertError( - ` -foo - --double-important: red !important !important`, - parser, - parser._parseRuleset.bind(parser), - ParseError.NewlineExpected, - ); - assertError( - ` -foo - --unbalanced-parens: not)()(cool`, - parser, - parser._parseRuleset.bind(parser), - ParseError.LeftParenthesisExpected, - ); - assertError( - ` -foo - --unbalanced-parens: not][][cool`, - parser, - parser._parseRuleset.bind(parser), - ParseError.LeftSquareBracketExpected, - ); - }); - - test("nested ruleset", () => { - assertNode( - ` -.foo - color: red - input - color: blue`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -.foo - color: red - :focus - color: blue`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -.foo - color: red - .bar - color: blue`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -.foo - color: red - &:hover - color: blue`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -.foo - color: red - + .bar - color: blue`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -.foo - color: red - foo:hover - color: blue`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -.foo - color: red - @media screen - color: blue`, - parser, - parser._parseRuleset.bind(parser), - ); - - assertNode( - ` -.foo - $const: 1 - .class - $const: 2 - $const: 3 - one: $const`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -.class1 - > .class2 - & > .class4 - rule1: v1`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -foo - @at-root - display: none`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -th, tr - @at-root #{selector-replace(&, "tr")} - border-bottom: 0`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -foo - @supports(display: grid) - .bar - display: none`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -foo - @supports(display: grid) - display: none`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -foo - @supports(position: sticky) - @media (min-width: map-get($grid-breakpoints, medium)) - position: sticky`, - parser, - parser._parseRuleset.bind(parser), - ); - }); - - test("nested ruleset 2", () => { - assertNode( - ` -.foo - .parent & - color: blue`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -.foo - color: red - & > .bar, > .baz - color: blue`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -.foo - & .bar & .baz & .hmm - color: blue`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -.foo - color: red - :not(&) - color: blue - + .bar + & - color: green`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -.foo - color: red - & - color: blue - && - color: green`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -.foo - & :is(.bar, &.baz) - color: red`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -figure - > figcaption - background: hsl(0 0% 0% / 50%) - > p - font-size: .9rem`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ` -@layer base - html - & body - min-block-size: 100%`, - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("selector", () => { - assertNode("asdsa", parser, parser._parseSelector.bind(parser)); - assertNode("asdsa + asdas", parser, parser._parseSelector.bind(parser)); - assertNode("asdsa + asdas + name", parser, parser._parseSelector.bind(parser)); - assertNode("asdsa + asdas + name", parser, parser._parseSelector.bind(parser)); - assertNode("name #id#anotherid", parser, parser._parseSelector.bind(parser)); - assertNode("name.far .boo", parser, parser._parseSelector.bind(parser)); - assertNode("name .name .zweitername", parser, parser._parseSelector.bind(parser)); - assertNode("*", parser, parser._parseSelector.bind(parser)); - assertNode("#id", parser, parser._parseSelector.bind(parser)); - assertNode("far.boo", parser, parser._parseSelector.bind(parser)); - assertNode("::slotted(div)::after", parser, parser._parseSelector.bind(parser)); // 35076 - }); - - test("attrib", () => { - assertNode("[name]", parser, parser._parseAttrib.bind(parser)); - assertNode("[name = name2]", parser, parser._parseAttrib.bind(parser)); - assertNode("[name ~= name3]", parser, parser._parseAttrib.bind(parser)); - assertNode("[name~=name3]", parser, parser._parseAttrib.bind(parser)); - assertNode("[name |= name3]", parser, parser._parseAttrib.bind(parser)); - assertNode('[name |= "this is a striiiing"]', parser, parser._parseAttrib.bind(parser)); - assertNode('[href*="insensitive" i]', parser, parser._parseAttrib.bind(parser)); - assertNode('[href*="sensitive" S]', parser, parser._parseAttrib.bind(parser)); - - // Single namespace - assertNode("[namespace|name]", parser, parser._parseAttrib.bind(parser)); - assertNode("[name-space|name = name2]", parser, parser._parseAttrib.bind(parser)); - assertNode("[name_space|name ~= name3]", parser, parser._parseAttrib.bind(parser)); - assertNode("[name0spae|name~=name3]", parser, parser._parseAttrib.bind(parser)); - assertNode('[NameSpace|name |= "this is a striiiing"]', parser, parser._parseAttrib.bind(parser)); - assertNode("[name\\*space|name |= name3]", parser, parser._parseAttrib.bind(parser)); - assertNode("[*|name]", parser, parser._parseAttrib.bind(parser)); - }); - - test("pseudo", () => { - assertNode(":some", parser, parser._parsePseudo.bind(parser)); - assertNode(":some(thing)", parser, parser._parsePseudo.bind(parser)); - assertNode(":nth-child(12)", parser, parser._parsePseudo.bind(parser)); - assertNode(":nth-child(1n)", parser, parser._parsePseudo.bind(parser)); - assertNode(":nth-child(-n+3)", parser, parser._parsePseudo.bind(parser)); - assertNode(":nth-child(2n+1)", parser, parser._parsePseudo.bind(parser)); - assertNode(":nth-child(2n+1 of .foo)", parser, parser._parsePseudo.bind(parser)); - assertNode(':nth-child(2n+1 of .foo > bar, :not(*) ~ [other="value"])', parser, parser._parsePseudo.bind(parser)); - assertNode(":lang(it)", parser, parser._parsePseudo.bind(parser)); - assertNode(":not(.class)", parser, parser._parsePseudo.bind(parser)); - assertNode(":not(:disabled)", parser, parser._parsePseudo.bind(parser)); - assertNode(":not(#foo)", parser, parser._parsePseudo.bind(parser)); - assertNode("::slotted(*)", parser, parser._parsePseudo.bind(parser)); // #35076 - assertNode("::slotted(div:hover)", parser, parser._parsePseudo.bind(parser)); // #35076 - assertNode(":global(.output ::selection)", parser, parser._parsePseudo.bind(parser)); // #49010 - assertNode(":matches(:hover, :focus)", parser, parser._parsePseudo.bind(parser)); // #49010 - assertNode(":host([foo=bar][bar=foo])", parser, parser._parsePseudo.bind(parser)); // #49589 - assertNode(":has(> .test)", parser, parser._parsePseudo.bind(parser)); // #250 - assertNode(":has(~ .test)", parser, parser._parsePseudo.bind(parser)); // #250 - assertNode(":has(+ .test)", parser, parser._parsePseudo.bind(parser)); // #250 - assertNode(":has(~ div .test)", parser, parser._parsePseudo.bind(parser)); // #250 - assertError("::", parser, parser._parsePseudo.bind(parser), ParseError.IdentifierExpected); - assertError(":: foo", parser, parser._parsePseudo.bind(parser), ParseError.IdentifierExpected); - assertError(":nth-child(1n of)", parser, parser._parsePseudo.bind(parser), ParseError.SelectorExpected); - }); - - test("declaration", () => { - assertNode('name : "this is a string" !important', parser, parser._parseDeclaration.bind(parser)); - assertNode('name : "this is a string"', parser, parser._parseDeclaration.bind(parser)); - assertNode("property:12", parser, parser._parseDeclaration.bind(parser)); - assertNode("-vendor-property: 12", parser, parser._parseDeclaration.bind(parser)); - assertNode("font-size: 12px", parser, parser._parseDeclaration.bind(parser)); - assertNode("color : #888 /4", parser, parser._parseDeclaration.bind(parser)); - assertNode( - "filter : progid:DXImageTransform.Microsoft.Shadow(color=#000000,direction=45)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "filter : progid: DXImageTransform.Microsoft.DropShadow(offx=2, offy=1, color=#000000)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode("font-size: 12px", parser, parser._parseDeclaration.bind(parser)); - assertNode("*background: #f00 /* IE 7 and below */", parser, parser._parseDeclaration.bind(parser)); - assertNode("_background: #f60 /* IE 6 and below */", parser, parser._parseDeclaration.bind(parser)); - assertNode( - "background-image: linear-gradient(to right, silver, white 50px, white calc(100% - 50px), silver)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "grid-template-columns: [first nav-start] 150px [main-start] 1fr [last]", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "grid-template-columns: repeat(4, 10px [col-start] 250px [col-end]) 10px", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "grid-template-columns: [a] auto [b] minmax(min-content, 1fr) [b c d] repeat(2, [e] 40px)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode("grid-template: [foo] 10px / [bar] 10px", parser, parser._parseDeclaration.bind(parser)); - assertNode( - `grid-template: 'left1 footer footer' 1fr [end] / [ini] 1fr [info-start] 2fr 1fr [end]`, - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode(`content: "("counter(foo) ")"`, parser, parser._parseDeclaration.bind(parser)); - assertNode(`content: 'Hello\\0A''world'`, parser, parser._parseDeclaration.bind(parser)); - }); - - test("term", () => { - assertNode('"asdasd"', parser, parser._parseTerm.bind(parser)); - assertNode("name", parser, parser._parseTerm.bind(parser)); - assertNode("#FFFFFF", parser, parser._parseTerm.bind(parser)); - assertNode('url("this is a url")', parser, parser._parseTerm.bind(parser)); - assertNode("+324", parser, parser._parseTerm.bind(parser)); - assertNode("-45", parser, parser._parseTerm.bind(parser)); - assertNode("+45", parser, parser._parseTerm.bind(parser)); - assertNode("-45%", parser, parser._parseTerm.bind(parser)); - assertNode("-45mm", parser, parser._parseTerm.bind(parser)); - assertNode("-45em", parser, parser._parseTerm.bind(parser)); - assertNode('"asdsa"', parser, parser._parseTerm.bind(parser)); - assertNode("faa", parser, parser._parseTerm.bind(parser)); - assertNode('url("this is a striiiiing")', parser, parser._parseTerm.bind(parser)); - assertNode("#FFFFFF", parser, parser._parseTerm.bind(parser)); - assertNode("name(asd)", parser, parser._parseTerm.bind(parser)); - assertNode("calc(50% + 20px)", parser, parser._parseTerm.bind(parser)); - assertNode("calc(50% + (100%/3 - 2*1em - 2*1px))", parser, parser._parseTerm.bind(parser)); - assertNoNode( - "%('repetitions: %S file: %S', 1 + 2, \"directory/file.less\")", - parser, - parser._parseTerm.bind(parser), - ); // less syntax - assertNoNode('~"ms:alwaysHasItsOwnSyntax.For.Stuff()"', parser, parser._parseTerm.bind(parser)); // less syntax - assertNode("U+002?-0199", parser, parser._parseTerm.bind(parser)); - assertNoNode("U+002?-01??", parser, parser._parseTerm.bind(parser)); - assertNoNode("U+00?0;", parser, parser._parseTerm.bind(parser)); - assertNoNode("U+0XFF;", parser, parser._parseTerm.bind(parser)); - }); - - test("function", () => { - assertNode('name( "bla" )', parser, parser._parseFunction.bind(parser)); - assertNode("name( name )", parser, parser._parseFunction.bind(parser)); - assertNode("name( -500mm )", parser, parser._parseFunction.bind(parser)); - assertNode("\u060frf()", parser, parser._parseFunction.bind(parser)); - assertNode("über()", parser, parser._parseFunction.bind(parser)); - - assertNoNode("über ()", parser, parser._parseFunction.bind(parser)); - assertNoNode("%()", parser, parser._parseFunction.bind(parser)); - assertNoNode("% ()", parser, parser._parseFunction.bind(parser)); - - assertFunction("let(--color)", parser, parser._parseFunction.bind(parser)); - assertFunction("let(--color, somevalue)", parser, parser._parseFunction.bind(parser)); - assertFunction("let(--variable1, --variable2)", parser, parser._parseFunction.bind(parser)); - assertFunction("let(--variable1, let(--variable2))", parser, parser._parseFunction.bind(parser)); - assertFunction("fun(value1, value2)", parser, parser._parseFunction.bind(parser)); - assertFunction("fun(value1,)", parser, parser._parseFunction.bind(parser)); - }); - - test("test token prio", () => { - assertNode("!important", parser, parser._parsePrio.bind(parser)); - assertNode("!/*demo*/important", parser, parser._parsePrio.bind(parser)); - assertNode("! /*demo*/ important", parser, parser._parsePrio.bind(parser)); - assertNode("! /*dem o*/ important", parser, parser._parsePrio.bind(parser)); - }); - - test("hexcolor", () => { - assertNode("#FFF", parser, parser._parseHexColor.bind(parser)); - assertNode("#FFFF", parser, parser._parseHexColor.bind(parser)); - assertNode("#FFFFFF", parser, parser._parseHexColor.bind(parser)); - assertNode("#FFFFFFFF", parser, parser._parseHexColor.bind(parser)); - }); - - test("test class", () => { - assertNode(".faa", parser, parser._parseClass.bind(parser)); - assertNode("faa", parser, parser._parseElementName.bind(parser)); - assertNode("*", parser, parser._parseElementName.bind(parser)); - assertNode(".faa42", parser, parser._parseClass.bind(parser)); - }); - - test("prio", () => { - assertNode("!important", parser, parser._parsePrio.bind(parser)); - }); - - test("expr", () => { - assertNode("45,5px", parser, parser._parseExpr.bind(parser)); - assertNode(" 45 , 5px ", parser, parser._parseExpr.bind(parser)); - assertNode("5/6", parser, parser._parseExpr.bind(parser)); - assertNode("36mm, -webkit-calc(100%-10px)", parser, parser._parseExpr.bind(parser)); - }); - - test("url", () => { - assertNode("url(foo())", parser, parser._parseURILiteral.bind(parser)); - assertNode( - "url('data:image/svg+xml;utf8,%3Csvg%20fill%3D%22%23' + $color + 'foo')", - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode("url(//yourdomain/yourpath.png)", parser, parser._parseURILiteral.bind(parser)); - assertNode("url('http://msft.com')", parser, parser._parseURILiteral.bind(parser)); - assertNode('url("http://msft.com")', parser, parser._parseURILiteral.bind(parser)); - assertNode('url( "http://msft.com")', parser, parser._parseURILiteral.bind(parser)); - assertNode('url(\t"http://msft.com")', parser, parser._parseURILiteral.bind(parser)); - assertNode('url("")', parser, parser._parseURILiteral.bind(parser)); - assertNode('uRL("")', parser, parser._parseURILiteral.bind(parser)); - assertNode('URL("")', parser, parser._parseURILiteral.bind(parser)); - assertNode("url(http://msft.com)", parser, parser._parseURILiteral.bind(parser)); - assertNode("url()", parser, parser._parseURILiteral.bind(parser)); - assertError( - 'url("http://msft.com"', - parser, - parser._parseURILiteral.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "url(http://msft.com')", - parser, - parser._parseURILiteral.bind(parser), - ParseError.RightParenthesisExpected, - ); - }); - - test("map", () => { - assertNode("(key1: 1px, key2: solid + px, key3: (2+3))", parser, parser._parseExpr.bind(parser)); - assertNode("($key1 + 3: 1px)", parser, parser._parseExpr.bind(parser)); - }); - - test("parent selector", () => { - assertNode("&:hover", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&.float", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-bar", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-1", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&1", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-foo-1", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&&", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-10-thing", parser, parser._parseSimpleSelector.bind(parser)); - }); - - test("placeholder selector", () => { - assertNode("%hover", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("a%float", parser, parser._parseSimpleSelector.bind(parser)); - }); - - test("selector interpolation", function () { - assertNode(`.#{$name}\n\t//`, parser, parser._parseRuleset.bind(parser)); - assertNode(`.#{$name}-foo\n\t//`, parser, parser._parseRuleset.bind(parser)); - assertNode(`.#{$name}-foo-3\n\t//`, parser, parser._parseRuleset.bind(parser)); - assertNode(`.#{$name}-1\n\t//`, parser, parser._parseRuleset.bind(parser)); - assertNode(`.sc-col#{$postfix}-2-1\n\t//`, parser, parser._parseRuleset.bind(parser)); - assertNode(`p.#{$name}\n\t#{$attr}-color: blue`, parser, parser._parseRuleset.bind(parser)); - assertNode( - `sans-#{serif} - a-#{1 + 2}-color-#{$attr}: blue`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode(`##{f} .#{f} #{f}:#{f}\n\t//`, parser, parser._parseRuleset.bind(parser)); - assertNode(`.foo-#{&} .foo-#{&-sub}\n\t//`, parser, parser._parseRuleset.bind(parser)); - assertNode(`.-#{$variable}\n\t//`, parser, parser._parseRuleset.bind(parser)); - assertNode(`#{&}([foo=bar][bar=foo])\n\t//`, parser, parser._parseRuleset.bind(parser)); - - assertNode(`.#{module.$name}\n\t//`, parser, parser._parseRuleset.bind(parser)); - assertNode(`.#{module.$name}-foo\n\t//`, parser, parser._parseRuleset.bind(parser)); - assertNode(`.#{module.$name}-foo-3\n\t//`, parser, parser._parseRuleset.bind(parser)); - assertNode(`.#{module.$name}-1\n\t//`, parser, parser._parseRuleset.bind(parser)); - assertNode(`.sc-col#{module.$postfix}-2-1\n\t//`, parser, parser._parseRuleset.bind(parser)); - assertNode( - `p.#{module.$name} - #{$attr}-color: blue`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `p.#{$name} - #{module.$attr}-color: blue`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `p.#{module.$name} - #{module.$attr}-color: blue`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - `sans-#{serif} - a-#{1 + 2}-color-#{module.$attr}: blue`, - parser, - parser._parseRuleset.bind(parser), - ); - assertNode(`.-#{module.$variable}\n\t//`, parser, parser._parseRuleset.bind(parser)); - }); - - test("@at-root", () => { - assertNode( - `@mixin unify-parent($child) - @at-root f#{selector.unify(&, $child)} - color: f`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@at-root #main2 .some-class - padding-left: calc( #{$a-variable} + 8px)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@media print - .page - @at-root (without: media) - foo: bar`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@media print - .page - @at-root (with: rule) - foo: bar`, - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@function", () => { - assertNode( - `@function grid-width($n) - @return $n * $grid-width + ($n - 1) * $gutter-width`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@function grid-width($n: 1, $e) - @return 0`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@function foo($total, $a) - @for $i from 0 to $total - // - @return $grid`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@function foo() - @if (unit($a) == "%") and ($i == ($total - 1)) - @return 0 - @return 1`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@function is-even($int) - @if $int%2 == 0 - @return true - @return false`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@function bar ($i) - @if $i > 0 - @return $i * bar($i - 1) - @return 1`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@function foo($a,) - //`, - parser, - parser._parseStylesheet.bind(parser), - ); - - assertError( - `@function foo - //`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.LeftParenthesisExpected, - ); - assertError( - `@function - //`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.IdentifierExpected, - ); - assertError( - `@function foo($a $b) - //`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - `@function foo($a - //`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - `@function foo($a...) - @return`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.ExpressionExpected, - ); - assertError( - `@function foo($a:) - //`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.VariableValueExpected, - ); - }); - - test("@include", () => { - assertNode( - `p - @include double-border(blue)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `.shadows - @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `$values: #ff0000, #00ff00, #0000ff - -.primary - @include colors($values...)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode(`@include colors(this("styles")...)`, parser, parser._parseStylesheet.bind(parser)); - assertNode( - `.test - @include fontsize(16px, 21px !important)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `p - @include apply-to-ie6-only - #logo - background-image: url(/logo.gif)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `p - @include foo($values,)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `p - @include foo($values,)`, - parser, - parser._parseStylesheet.bind(parser), - ); - - assertError( - `p - @include double-border($values blue`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - `p - @include`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.IdentifierExpected, - ); - assertError( - `p - @include foo($values`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - `p - @include foo($values,`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.ExpressionExpected, - ); - - assertNode( - `p - @include lib.double-border(blue)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `.shadows - @include lib.box-shadow(0px 4px 5px #666, 2px 6px 10px #999)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `$values: #ff0000, #00ff00, #0000ff -.primary - @include lib.colors($values...)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `.primary - @include colors(lib.$values...)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `.primary - @include lib.colors(lib.$values...)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode(`@include lib.colors(this("styles")...)`, parser, parser._parseStylesheet.bind(parser)); - assertNode(`@include colors(lib.this("styles")...)`, parser, parser._parseStylesheet.bind(parser)); - assertNode(`@include lib.colors(lib.this("styles")...)`, parser, parser._parseStylesheet.bind(parser)); - assertNode( - `.test - @include lib.fontsize(16px, 21px !important)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `p - @include lib.apply-to-ie6-only - #logo - background-image: url(/logo.gif)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `p - @include lib.foo($values,)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `p - @include foo(lib.$values,)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `p - @include lib.foo(m.$values,)`, - parser, - parser._parseStylesheet.bind(parser), - ); - - assertError( - `p - @include foo.($values)`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.IdentifierExpected, - ); - - assertNode( - `@include rtl("left") using ($dir) - margin-#{$dir}: 10px`, - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@content", () => { - assertNode("@content", parser, parser._parseMixinContent.bind(parser)); - assertNode("@content($type)", parser, parser._parseMixinContent.bind(parser)); - }); - - test("@mixin", () => { - assertNode( - `@mixin large-text - font: - family: Arial - size: 20px - color: #ff0000`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@mixin double-border($color, $width: 1in) - color: black`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@mixin box-shadow($shadows...) - -moz-box-shadow: $shadows`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@mixin apply-to-ie6-only - * html - @content`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode(`@mixin #{foo}($color)\n\t//`, parser, parser._parseStylesheet.bind(parser)); - assertNode( - `@mixin foo ($i:4) - size: $i - @include wee ($i - 1)`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode(`@mixin foo ($i,)\n\t//`, parser, parser._parseStylesheet.bind(parser)); - - assertError(`@mixin $1\n\t//`, parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); - assertError(`@mixin foo() i\n\t//`, parser, parser._parseStylesheet.bind(parser), ParseError.IndentExpected); - assertError( - `@mixin foo(1)\n\t//`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - `@mixin foo($color = 9)\n\t//`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError(`@mixin foo($color)`, parser, parser._parseStylesheet.bind(parser), ParseError.IndentExpected); - }); - - test("@while", () => { - assertNode( - `@while $i < 0 - .item-#{$i} - width: 2em * $i - $i: $i - 2`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertError(`@while\n\t//`, parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); - assertError(`@while $i != 4`, parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.IndentExpected); - }); - - test("@each", () => { - assertNode(`@each $i in 1, 2, 3\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode(`@each $i in 1 2 3\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode( - `@each $animal, $color, $cursor in (puma, black, default), (egret, white, move)\n\t`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertError( - `@each i in 4\n\t`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.VariableNameExpected, - ); - assertError(`@each $i from 4\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser), SassParseError.InExpected); - assertError(`@each $i in\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); - assertError( - `@each $animal, in (1, 1, 1), (2, 2, 2)\n\t`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.VariableNameExpected, - ); - }); - - test("@for", () => { - assertNode( - `@for $i from 1 to 5 - .item-#{$i} - width: 2em * $i`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode(`@for $k from 1 + $x through 5 + $x\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); - assertError( - `@for i from 0 to 4\n\t`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.VariableNameExpected, - ); - assertError(`@for $i to 4\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser), SassParseError.FromExpected); - assertError( - `@for $i from 0 by 4\n\t`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - SassParseError.ThroughOrToExpected, - ); - assertError( - `@for $i from\n\t`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.ExpressionExpected, - ); - assertError( - `@for $i from 0 to\n\t`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.ExpressionExpected, - ); - assertNode( - `@for $i from 1 through 60 - $s: $i + "%"`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - - assertNode(`@for $k from 1 + m.$x through 5 + $x\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode(`@for $k from 1 + $x through 5 + m.$x\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode(`@for $k from 1 + m.$x through 5 + m.$x\n\t`, parser, parser._parseRuleSetDeclaration.bind(parser)); - }); - - test("@if", () => { - assertNode( - `@if 1 + 1 == 2 - border: 1px solid`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - `@if 5 < 3 - border: 2px dotted`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - `@if null - border: 3px double`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - `@if 1 <= $const - border: 3px -@else - border: 4px`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - `@if 1 >= (1 + $foo) - border: 3px -@else if 1 + 1 == 2 - border: 4px`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - `p - @if $i == 1 - x: 3px - @else if $i == 1 - x: 4px - @else - x: 4px`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@if (index($_RESOURCES, "clean") != null) - @error "sdssd"`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@if $i == 1 - p - x: 3px`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertError( - `@if - border: 1px solid`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.ExpressionExpected, - ); - - assertNode( - `@if 1 <= m.$const - border: 3px -@else - border: 4px`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - `@if 1 >= (1 + m.$foo) - border: 3px -@else if 1 + 1 == 2 - border: 4px`, - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - `p - @if m.$i == 1 - x: 3px - @else if $i == 1 - x: 4px - @else - x: 4px`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `p - @if $i == 1 - x: 3px - @else if m.$i == 1 - x: 4px - @else - x: 4px`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `p - @if m.$i == 1 - x: 3px - @else if m.$i == 1 - x: 4px - @else - x: 4px`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@if (list.index($_RESOURCES, "clean") != null) - @error "sdssd"`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@if (index(m.$_RESOURCES, "clean") != null) - @error "sdssd"`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@if (list.index(m.$_RESOURCES, "clean") != null) - @error "sdssd"`, - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@debug", () => { - assertNode(`@debug test`, parser, parser._parseStylesheet.bind(parser)); - assertNode( - `foo - @debug 1 + 4 - nested - @warn 1 4`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@if $foo == 1 - @debug 1 + 4`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@function setStyle($map, $object, $style) - @warn "The key ´#{$object} is not available in the map." - @return null`, - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@extend", () => { - assertNode( - `.themable - @extend %theme`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `foo - @extend .error - border-width: 3px`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `a.important - @extend .notice !optional`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `.hoverlink - @extend a:hover`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `.seriousError - @extend .error - @extend .attention`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `#context a%extreme - color: blue -.notice - @extend %extreme`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@media print - .error - color: red - .seriousError - @extend .error`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@mixin error($a: false) - @extend .#{$a} - @extend ##{$a}`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `.foo - @extend .text-center, .uppercase`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `.foo - @extend .text-center, .uppercase, `, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `.foo - @extend .text-center, .uppercase !optional `, - parser, - parser._parseStylesheet.bind(parser), - ); - assertError( - `.hoverlink - @extend`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.SelectorExpected, - ); - assertError( - `.hoverlink - @extend %extreme !default`, - parser, - parser._parseStylesheet.bind(parser), - ParseError.UnknownKeyword, - ); - }); - - test("@forward", () => { - assertNode('@forward "test"', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" as foo-*', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" hide this', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" hide $that', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" hide this $that', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" hide this, $that', parser, parser._parseForward.bind(parser)); - assertNode('@forward "abstracts/functions" show px-to-rem, theme-color', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" show this', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" show $that', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" show this $that', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" as foo-* show this $that', parser, parser._parseForward.bind(parser)); - - assertError("@forward", parser, parser._parseForward.bind(parser), ParseError.StringLiteralExpected); - assertError('@forward "test" as', parser, parser._parseForward.bind(parser), ParseError.IdentifierExpected); - assertError('@forward "test" as foo-', parser, parser._parseForward.bind(parser), ParseError.WildcardExpected); - assertError('@forward "test" as foo- *', parser, parser._parseForward.bind(parser), ParseError.WildcardExpected); - assertError( - '@forward "test" show', - parser, - parser._parseForward.bind(parser), - ParseError.IdentifierOrVariableExpected, - ); - assertError( - '@forward "test" hide', - parser, - parser._parseForward.bind(parser), - ParseError.IdentifierOrVariableExpected, - ); - - assertNode( - '@forward "test" with ( $black: #222 !default, $border-radius: 0.1rem !default )', - parser, - parser._parseForward.bind(parser), - ); - assertNode( - '@forward "../forms.scss" as components-* with ( $field-border: false )', - parser, - parser._parseForward.bind(parser), - ); // #145108 - - assertNode( - `@use "lib" -@forward "test"`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@forward "test" -@forward "lib"`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `$test: "test" -@forward "test"`, - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@use", () => { - assertNode('@use "test"', parser, parser._parseUse.bind(parser)); - assertNode('@use "test" as foo', parser, parser._parseUse.bind(parser)); - assertNode('@use "test" as *', parser, parser._parseUse.bind(parser)); - assertNode('@use "test" with ($foo: "test", $bar: 1)', parser, parser._parseUse.bind(parser)); - assertNode('@use "test" as foo with ($foo: "test", $bar: 1)', parser, parser._parseUse.bind(parser)); - - assertError("@use", parser, parser._parseUse.bind(parser), ParseError.StringLiteralExpected); - assertError('@use "test" foo', parser, parser._parseUse.bind(parser), ParseError.UnknownKeyword); - assertError('@use "test" as', parser, parser._parseUse.bind(parser), ParseError.IdentifierOrWildcardExpected); - assertError('@use "test" with', parser, parser._parseUse.bind(parser), ParseError.LeftParenthesisExpected); - assertError('@use "test" with ($foo)', parser, parser._parseUse.bind(parser), ParseError.VariableValueExpected); - assertError('@use "test" with ("bar")', parser, parser._parseUse.bind(parser), ParseError.VariableNameExpected); - assertError( - '@use "test" with ($foo: 1, "bar")', - parser, - parser._parseUse.bind(parser), - ParseError.VariableNameExpected, - ); - assertError( - '@use "test" with ($foo: "bar"', - parser, - parser._parseUse.bind(parser), - ParseError.RightParenthesisExpected, - ); - - assertNode( - `@forward "test" -@use "lib"`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `@use "test" -@use "lib"`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `$test: "test" -@use "lib"`, - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@container", () => { - assertNode( - `@container (min-width: #{$minWidth}) - .scss-interpolation - line-height: 10cqh`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `.item-icon - @container (max-height: 100px) - .item-icon - display: none`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `:root - @container (max-height: 100px) - display: none`, - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@layer", () => { - assertNode("@layer #{$layer}\n\t", parser, parser._parseLayer.bind(parser)); - }); - - test("@import", () => { - assertNode('@import "test.css"', parser, parser._parseImport.bind(parser)); - assertNode('@import url("test.css")', parser, parser._parseImport.bind(parser)); - assertNode('@import "test.css", "bar.css"', parser, parser._parseImport.bind(parser)); - assertNode('@import "test.css", "bar.css" screen, projection', parser, parser._parseImport.bind(parser)); - assertNode( - `foo - @import "test.css"`, - parser, - parser._parseStylesheet.bind(parser), - ); - - assertError( - '@import "test.css" "bar.css"', - parser, - parser._parseStylesheet.bind(parser), - ParseError.MediaQueryExpected, - ); - assertError('@import "test.css", screen', parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); - assertError("@import", parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); - assertNode('@import url("override.css") layer', parser, parser._parseStylesheet.bind(parser)); - }); - - test("declaration", () => { - assertNode("border: thin solid 1px", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: $color", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: blue", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: (20 / $const)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: (20 / 20 + $const)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: func($red)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: func($red) !important", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: desaturate($red, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: desaturate(16, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: $base-color + #111", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: 100% / 2 + $ref", parser, parser._parseDeclaration.bind(parser)); - assertNode("border: ($width * 2) solid black", parser, parser._parseDeclaration.bind(parser)); - assertNode("property: $class", parser, parser._parseDeclaration.bind(parser)); - assertNode("prop-erty: fnc($t, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("width: (1em + 2em) * 3", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: #010203 + #040506", parser, parser._parseDeclaration.bind(parser)); - assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser)); - assertNode("margin: 3px + 4px auto", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: hsl(0, 100%, 50%)", parser, parser._parseDeclaration.bind(parser)); - assertNode( - "color: hsl($hue: 0, $saturation: 100%, $lightness: 50%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode("foo: if($value == 'default', flex-gutter(), $value)", parser, parser._parseDeclaration.bind(parser)); - assertNode("foo: if(true, !important, null)", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: selector-replace(&, 1)", parser, parser._parseDeclaration.bind(parser)); - - assertNode("dummy: module.$color", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: (20 / module.$const)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: (20 / 20 + module.$const)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: module.func($red)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: module.func($red) !important", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: module.desaturate($red, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: desaturate(module.$red, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: module.desaturate(module.$red, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: module.desaturate(16, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: module.$base-color + #111", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: 100% / 2 + module.$ref", parser, parser._parseDeclaration.bind(parser)); - assertNode("border: (module.$width * 2) solid black", parser, parser._parseDeclaration.bind(parser)); - assertNode("property: module.$class", parser, parser._parseDeclaration.bind(parser)); - assertNode("prop-erty: module.fnc($t, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("prop-erty: fnc(module.$t, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("prop-erty: module.fnc(module.$t, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("width: (1em + 2em) * 3", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: #010203 + #040506", parser, parser._parseDeclaration.bind(parser)); - assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser)); - assertNode("margin: 3px + 4px auto", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: color.hsl(0, 100%, 50%)", parser, parser._parseDeclaration.bind(parser)); - assertNode( - "color: color.hsl($hue: 0, $saturation: 100%, $lightness: 50%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if(module.$value == 'default', flex-gutter(), $value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if($value == 'default', module.flex-gutter(), $value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if($value == 'default', flex-gutter(), module.$value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if(module.$value == 'default', module.flex-gutter(), $value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if($value == 'default', module.flex-gutter(), module.$value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if(module.$value == 'default', module.flex-gutter(), module.$value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode("color: selector.replace(&, 1)", parser, parser._parseDeclaration.bind(parser)); - - assertError("fo = 8", parser, parser._parseDeclaration.bind(parser), ParseError.ColonExpected); - assertError("fo:", parser, parser._parseDeclaration.bind(parser), ParseError.PropertyValueExpected); - assertError("color: hsl($hue: 0,", parser, parser._parseDeclaration.bind(parser), ParseError.ExpressionExpected); - assertError( - "color: hsl($hue: 0", - parser, - parser._parseDeclaration.bind(parser), - ParseError.RightParenthesisExpected, - ); - }); - - test("interpolation", () => { - assertNode("--#{module.$propname}: some-value", parser, parser._parseDeclaration.bind(parser)); - }); - - test("operators", () => { - assertNode(">=", parser, parser._parseOperator.bind(parser)); - assertNode(">", parser, parser._parseOperator.bind(parser)); - assertNode("<", parser, parser._parseOperator.bind(parser)); - assertNode("<=", parser, parser._parseOperator.bind(parser)); - assertNode("==", parser, parser._parseOperator.bind(parser)); - assertNode("!=", parser, parser._parseOperator.bind(parser)); - assertNode("and", parser, parser._parseOperator.bind(parser)); - assertNode("+", parser, parser._parseOperator.bind(parser)); - assertNode("-", parser, parser._parseOperator.bind(parser)); - assertNode("*", parser, parser._parseOperator.bind(parser)); - assertNode("/", parser, parser._parseOperator.bind(parser)); - assertNode("%", parser, parser._parseOperator.bind(parser)); - assertNode("not", parser, parser._parseUnaryOperator.bind(parser)); - }); - - test("expressions", () => { - assertNode("($const + 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($const - 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($const * 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($const / 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 - $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 * $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 / $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 / 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + $const + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($var1 + $var2)", parser, parser._parseExpr.bind(parser)); - assertNode("(($const + 5) * 2)", parser, parser._parseExpr.bind(parser)); - assertNode("(($const + (5 + 2)) * 2)", parser, parser._parseExpr.bind(parser)); - assertNode("($const + ((5 + 2) * 2))", parser, parser._parseExpr.bind(parser)); - assertNode("$color", parser, parser._parseExpr.bind(parser)); - assertNode("$color, $color", parser, parser._parseExpr.bind(parser)); - assertNode("$color, 42%", parser, parser._parseExpr.bind(parser)); - assertNode("$color, 42%, $color", parser, parser._parseExpr.bind(parser)); - assertNode("$color - ($color + 10%)", parser, parser._parseExpr.bind(parser)); - assertNode("($base + $filler)", parser, parser._parseExpr.bind(parser)); - assertNode("(100% / 2 + $filler)", parser, parser._parseExpr.bind(parser)); - assertNode("100% / 2 + $filler", parser, parser._parseExpr.bind(parser)); - assertNode("not ($v and $b) or $c", parser, parser._parseExpr.bind(parser)); - - assertNode("(module.$const + 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$const - 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$const * 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$const / 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 - module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 * module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 / module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + module.$const + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("($var1 + module.$var2)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$var1 + $var2)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$var1 + module.$var2)", parser, parser._parseExpr.bind(parser)); - assertNode("((module.$const + 5) * 2)", parser, parser._parseExpr.bind(parser)); - assertNode("((module.$const + (5 + 2)) * 2)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$const + ((5 + 2) * 2))", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, $color", parser, parser._parseExpr.bind(parser)); - assertNode("$color, module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, 42%", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, 42%, $color", parser, parser._parseExpr.bind(parser)); - assertNode("$color, 42%, module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, 42%, module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color - ($color + 10%)", parser, parser._parseExpr.bind(parser)); - assertNode("$color - (module.$color + 10%)", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color - (module.$color + 10%)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$base + $filler)", parser, parser._parseExpr.bind(parser)); - assertNode("($base + module.$filler)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$base + module.$filler)", parser, parser._parseExpr.bind(parser)); - assertNode("(100% / 2 + module.$filler)", parser, parser._parseExpr.bind(parser)); - assertNode("100% / 2 + module.$filler", parser, parser._parseExpr.bind(parser)); - assertNode("not (module.$v and $b) or $c", parser, parser._parseExpr.bind(parser)); - assertNode("not ($v and module.$b) or $c", parser, parser._parseExpr.bind(parser)); - assertNode("not ($v and $b) or module.$c", parser, parser._parseExpr.bind(parser)); - assertNode("not (module.$v and module.$b) or $c", parser, parser._parseExpr.bind(parser)); - assertNode("not (module.$v and $b) or module.$c", parser, parser._parseExpr.bind(parser)); - assertNode("not ($v and module.$b) or module.$c", parser, parser._parseExpr.bind(parser)); - assertNode("not (module.$v and module.$b) or module.$c", parser, parser._parseExpr.bind(parser)); - assertNode("not module.$v", parser, parser._parseExpr.bind(parser)); - - assertError("(20 + 20", parser, parser._parseExpr.bind(parser), ParseError.RightParenthesisExpected); - }); - - test("variable declaration", () => { - assertNode("$color: #F5F5F5", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$color: 0", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$color: 255", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$color: 25.5", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$color: 25px", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$color: 25.5px !default", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$text-color: green !global", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode( - '$_RESOURCES: append($_RESOURCES, "clean") !global', - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode("$footer-height: 40px !default !global", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode( - '$primary-font: "wf_SegoeUI","Segoe UI","Segoe","Segoe WP"', - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode("$color: red !important", parser, parser._parseVariableDeclaration.bind(parser)); - - assertError("$color: red !def", parser, parser._parseVariableDeclaration.bind(parser), ParseError.UnknownKeyword); - assertError( - "$color : !default", - parser, - parser._parseVariableDeclaration.bind(parser), - ParseError.VariableValueExpected, - ); - assertError("$color !default", parser, parser._parseVariableDeclaration.bind(parser), ParseError.ColonExpected); - }); - - test("variable", () => { - assertNode("$color", parser, parser._parseVariable.bind(parser)); - assertNode("$co42lor", parser, parser._parseVariable.bind(parser)); - assertNode("$-co42lor", parser, parser._parseVariable.bind(parser)); - }); - - test("module variable", () => { - assertNode("module.$color", parser, parser._parseModuleMember.bind(parser)); - assertNode("module.$co42lor", parser, parser._parseModuleMember.bind(parser)); - assertNode("module.$-co42lor", parser, parser._parseModuleMember.bind(parser)); - assertNode("module.function()", parser, parser._parseModuleMember.bind(parser)); - - assertError("module.", parser, parser._parseModuleMember.bind(parser), ParseError.IdentifierOrVariableExpected); }); }); diff --git a/packages/vscode-css-languageservice/src/test/scss/scssCompletion.test.ts b/packages/vscode-css-languageservice/src/test/sass/scssCompletion.test.ts similarity index 100% rename from packages/vscode-css-languageservice/src/test/scss/scssCompletion.test.ts rename to packages/vscode-css-languageservice/src/test/sass/scssCompletion.test.ts diff --git a/packages/vscode-css-languageservice/src/test/scss/scssNavigation.test.ts b/packages/vscode-css-languageservice/src/test/sass/scssNavigation.test.ts similarity index 99% rename from packages/vscode-css-languageservice/src/test/scss/scssNavigation.test.ts rename to packages/vscode-css-languageservice/src/test/sass/scssNavigation.test.ts index 00312cb4..1e8e9826 100644 --- a/packages/vscode-css-languageservice/src/test/scss/scssNavigation.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/scssNavigation.test.ts @@ -239,7 +239,7 @@ suite("SCSS - Navigation", () => { suite("Links", () => { // For invalid links that have no corresponding file on disk, return no link test("Invalid SCSS partial file links", async () => { - const fixtureRoot = path.resolve(__dirname, "../../../src/test/scss/linkFixture/non-existent"); + const fixtureRoot = path.resolve(__dirname, "../../../src/test/sass/linkFixture/non-existent"); const getDocumentUri = (relativePath: string) => { return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true); }; @@ -254,7 +254,7 @@ suite("SCSS - Navigation", () => { }); test("SCSS partial file dynamic links", async () => { - const fixtureRoot = path.resolve(__dirname, "../../../src/test/scss/linkFixture"); + const fixtureRoot = path.resolve(__dirname, "../../../src/test/sass/linkFixture"); const getDocumentUri = (relativePath: string) => { return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true); }; @@ -314,7 +314,7 @@ suite("SCSS - Navigation", () => { }); test("SCSS aliased links", async function () { - const fixtureRoot = path.resolve(__dirname, "../../../src/test/scss/linkFixture"); + const fixtureRoot = path.resolve(__dirname, "../../../src/test/sass/linkFixture"); const getDocumentUri = (relativePath: string) => { return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true); }; @@ -357,7 +357,7 @@ suite("SCSS - Navigation", () => { }); test("SCSS module file links", async () => { - const fixtureRoot = path.resolve(__dirname, "../../../src/test/scss/linkFixture/module"); + const fixtureRoot = path.resolve(__dirname, "../../../src/test/sass/linkFixture/module"); const getDocumentUri = (relativePath: string) => { return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true); }; diff --git a/packages/vscode-css-languageservice/src/test/scss/selectorPrinting.test.ts b/packages/vscode-css-languageservice/src/test/sass/selectorPrinting.test.ts similarity index 56% rename from packages/vscode-css-languageservice/src/test/scss/selectorPrinting.test.ts rename to packages/vscode-css-languageservice/src/test/sass/selectorPrinting.test.ts index 1b5b9df6..07f496d9 100644 --- a/packages/vscode-css-languageservice/src/test/scss/selectorPrinting.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/selectorPrinting.test.ts @@ -45,3 +45,41 @@ suite("SCSS - Selector Printing", () => { assertSelector(p, "%o1 { e1 { } }", "e1", "{%o1{…{e1}}}"); }); }); + +suite("Sass - Selector Printing", () => { + test("simple selector", function () { + let p = new SassParser({ syntax: "indented" }); + assertSelector(p, "o1\n\t", "o1", "{o1}"); + assertSelector(p, ".div\n\t ", ".div", "{[class=div]}"); + assertSelector(p, "#div\n\t ", "#div", "{[id=div]}"); + assertSelector(p, "o1.div\n\t ", "o1", "{o1[class=div]}"); + assertSelector(p, "o1#div\n\t", "o1", "{o1[id=div]}"); + assertSelector(p, "#div.o1\n\t", "o1", "{[id=div|class=o1]}"); + assertSelector(p, ".o1#div\n\t", "o1", "{[class=o1|id=div]}"); + }); + + test("nested selector", function () { + let p = new SassParser(); + assertSelector(p, "o1 { e1 { } }", "e1", "{o1{…{e1}}}"); + assertSelector(p, "o1 { e1.div { } }", "e1", "{o1{…{e1[class=div]}}}"); + assertSelector(p, "o1 o2 { e1 { } }", "e1", "{o1{…{o2{…{e1}}}}}"); + assertSelector(p, "o1, o2 { e1 { } }", "e1", "{o1{…{e1}}}"); + assertSelector(p, "o1 { @if $a { e1 { } } }", "e1", "{o1{…{e1}}}"); + assertSelector(p, "o1 { @mixin a { e1 { } } }", "e1", "{e1}"); + assertSelector(p, "o1 { @mixin a { e1 { } } }", "e1", "{e1}"); + }); + + test("referencing selector", function () { + let p = new SassParser(); + assertSelector(p, "o1 { &:hover { }}", "&", "{o1[:hover=]}"); + assertSelector(p, "o1 { &:hover & { }}", "&", "{o1[:hover=]{…{o1}}}"); + assertSelector(p, "o1 { &__bar {}}", "&", "{o1__bar}"); + assertSelector(p, ".c1 { &__bar {}}", "&", "{[class=c1__bar]}"); + assertSelector(p, "o.c1 { &__bar {}}", "&", "{o[class=c1__bar]}"); + }); + + test("placeholders", function () { + let p = new SassParser(); + assertSelector(p, "%o1 { e1 { } }", "e1", "{%o1{…{e1}}}"); + }); +}); diff --git a/packages/vscode-css-languageservice/src/test/scss/parser.test.ts b/packages/vscode-css-languageservice/src/test/scss/parser.test.ts deleted file mode 100644 index acb9d46b..00000000 --- a/packages/vscode-css-languageservice/src/test/scss/parser.test.ts +++ /dev/null @@ -1,1157 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -"use strict"; -import { suite, test } from "vitest"; - -import { SassParser } from "../../parser/sassParser"; -import { ParseError } from "../../parser/cssErrors"; -import { SassParseError } from "../../parser/sassErrors"; - -import { assertNode, assertError } from "../css/parser.test"; - -suite("SCSS - Parser", () => { - test("Comments", function () { - const parser = new SassParser(); - assertNode(" a { b: /* comment */ c }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - " a { b: /* comment \n * is several\n * lines long\n */ c }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode(" a { b: // single line comment\n c }", parser, parser._parseStylesheet.bind(parser)); - }); - - test("Variable", function () { - const parser = new SassParser(); - assertNode("$color", parser, parser._parseVariable.bind(parser)); - assertNode("$co42lor", parser, parser._parseVariable.bind(parser)); - assertNode("$-co42lor", parser, parser._parseVariable.bind(parser)); - }); - - test("Module variable", function () { - const parser = new SassParser(); - assertNode("module.$color", parser, parser._parseModuleMember.bind(parser)); - assertNode("module.$co42lor", parser, parser._parseModuleMember.bind(parser)); - assertNode("module.$-co42lor", parser, parser._parseModuleMember.bind(parser)); - assertNode("module.function()", parser, parser._parseModuleMember.bind(parser)); - - assertError("module.", parser, parser._parseModuleMember.bind(parser), ParseError.IdentifierOrVariableExpected); - }); - - test("VariableDeclaration", function () { - const parser = new SassParser(); - assertNode("$color: #F5F5F5", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$color: 0", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$color: 255", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$color: 25.5", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$color: 25px", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$color: 25.5px !default", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode("$text-color: green !global", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode( - '$_RESOURCES: append($_RESOURCES, "clean") !global', - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode("$footer-height: 40px !default !global", parser, parser._parseVariableDeclaration.bind(parser)); - assertNode( - '$primary-font: "wf_SegoeUI","Segoe UI","Segoe","Segoe WP"', - parser, - parser._parseVariableDeclaration.bind(parser), - ); - assertNode("$color: red !important", parser, parser._parseVariableDeclaration.bind(parser)); - - assertError("$color: red !def", parser, parser._parseVariableDeclaration.bind(parser), ParseError.UnknownKeyword); - assertError( - "$color : !default", - parser, - parser._parseVariableDeclaration.bind(parser), - ParseError.VariableValueExpected, - ); - assertError("$color !default", parser, parser._parseVariableDeclaration.bind(parser), ParseError.ColonExpected); - }); - - test("Expr", function () { - const parser = new SassParser(); - assertNode("($const + 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($const - 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($const * 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($const / 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 - $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 * $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 / $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 / 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + $const + 20 + 20 + $const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20)", parser, parser._parseExpr.bind(parser)); - assertNode("($var1 + $var2)", parser, parser._parseExpr.bind(parser)); - assertNode("(($const + 5) * 2)", parser, parser._parseExpr.bind(parser)); - assertNode("(($const + (5 + 2)) * 2)", parser, parser._parseExpr.bind(parser)); - assertNode("($const + ((5 + 2) * 2))", parser, parser._parseExpr.bind(parser)); - assertNode("$color", parser, parser._parseExpr.bind(parser)); - assertNode("$color, $color", parser, parser._parseExpr.bind(parser)); - assertNode("$color, 42%", parser, parser._parseExpr.bind(parser)); - assertNode("$color, 42%, $color", parser, parser._parseExpr.bind(parser)); - assertNode("$color - ($color + 10%)", parser, parser._parseExpr.bind(parser)); - assertNode("($base + $filler)", parser, parser._parseExpr.bind(parser)); - assertNode("(100% / 2 + $filler)", parser, parser._parseExpr.bind(parser)); - assertNode("100% / 2 + $filler", parser, parser._parseExpr.bind(parser)); - assertNode("not ($v and $b) or $c", parser, parser._parseExpr.bind(parser)); - - assertNode("(module.$const + 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$const - 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$const * 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$const / 20)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 - module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 * module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 / module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("(20 + 20 + module.$const + 20 + 20 + module.$const)", parser, parser._parseExpr.bind(parser)); - assertNode("($var1 + module.$var2)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$var1 + $var2)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$var1 + module.$var2)", parser, parser._parseExpr.bind(parser)); - assertNode("((module.$const + 5) * 2)", parser, parser._parseExpr.bind(parser)); - assertNode("((module.$const + (5 + 2)) * 2)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$const + ((5 + 2) * 2))", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, $color", parser, parser._parseExpr.bind(parser)); - assertNode("$color, module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, 42%", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, 42%, $color", parser, parser._parseExpr.bind(parser)); - assertNode("$color, 42%, module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color, 42%, module.$color", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color - ($color + 10%)", parser, parser._parseExpr.bind(parser)); - assertNode("$color - (module.$color + 10%)", parser, parser._parseExpr.bind(parser)); - assertNode("module.$color - (module.$color + 10%)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$base + $filler)", parser, parser._parseExpr.bind(parser)); - assertNode("($base + module.$filler)", parser, parser._parseExpr.bind(parser)); - assertNode("(module.$base + module.$filler)", parser, parser._parseExpr.bind(parser)); - assertNode("(100% / 2 + module.$filler)", parser, parser._parseExpr.bind(parser)); - assertNode("100% / 2 + module.$filler", parser, parser._parseExpr.bind(parser)); - assertNode("not (module.$v and $b) or $c", parser, parser._parseExpr.bind(parser)); - assertNode("not ($v and module.$b) or $c", parser, parser._parseExpr.bind(parser)); - assertNode("not ($v and $b) or module.$c", parser, parser._parseExpr.bind(parser)); - assertNode("not (module.$v and module.$b) or $c", parser, parser._parseExpr.bind(parser)); - assertNode("not (module.$v and $b) or module.$c", parser, parser._parseExpr.bind(parser)); - assertNode("not ($v and module.$b) or module.$c", parser, parser._parseExpr.bind(parser)); - assertNode("not (module.$v and module.$b) or module.$c", parser, parser._parseExpr.bind(parser)); - assertNode("not module.$v", parser, parser._parseExpr.bind(parser)); - - assertError("(20 + 20", parser, parser._parseExpr.bind(parser), ParseError.RightParenthesisExpected); - }); - - test("SCSSOperator", function () { - const parser = new SassParser(); - assertNode(">=", parser, parser._parseOperator.bind(parser)); - assertNode(">", parser, parser._parseOperator.bind(parser)); - assertNode("<", parser, parser._parseOperator.bind(parser)); - assertNode("<=", parser, parser._parseOperator.bind(parser)); - assertNode("==", parser, parser._parseOperator.bind(parser)); - assertNode("!=", parser, parser._parseOperator.bind(parser)); - assertNode("and", parser, parser._parseOperator.bind(parser)); - assertNode("+", parser, parser._parseOperator.bind(parser)); - assertNode("-", parser, parser._parseOperator.bind(parser)); - assertNode("*", parser, parser._parseOperator.bind(parser)); - assertNode("/", parser, parser._parseOperator.bind(parser)); - assertNode("%", parser, parser._parseOperator.bind(parser)); - assertNode("not", parser, parser._parseUnaryOperator.bind(parser)); - }); - - test("Interpolation", function () { - const parser = new SassParser(); - // assertNode('#{red}', parser, parser._parseIdent.bind(parser)); - // assertNode('#{$color}', parser, parser._parseIdent.bind(parser)); - // assertNode('#{3 + 4}', parser, parser._parseIdent.bind(parser)); - // assertNode('#{3 + #{3 + 4}}', parser, parser._parseIdent.bind(parser)); - // assertNode('#{$d}-style: 0', parser, parser._parseDeclaration.bind(parser)); - // assertNode('foo-#{$d}: 1', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{$d}-bar-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('foo-#{$d}-bar: 1', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('&:nth-child(#{$query}+1) { clear: $opposite-direction; }', parser, parser._parseRuleset.bind(parser)); - // assertNode('--#{$propname}: some-value', parser, parser._parseDeclaration.bind(parser)); - // assertNode('some-property: var(--#{$propname})', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{}', parser, parser._parseIdent.bind(parser)); - // assertError('#{1 + 2', parser, parser._parseIdent.bind(parser), ParseError.RightCurlyExpected); - - // assertNode('#{module.$color}', parser, parser._parseIdent.bind(parser)); - // assertNode('#{module.$d}-style: 0', parser, parser._parseDeclaration.bind(parser)); - // assertNode('foo-#{module.$d}: 1', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{module.$d}-bar-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{$d}-bar-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{module.$d}-bar-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('foo-#{module.$d}-bar: 1', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{module.$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{$d}-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('#{module.$d}-#{module.$d}: 2', parser, parser._parseDeclaration.bind(parser)); - // assertNode('&:nth-child(#{module.$query}+1) { clear: $opposite-direction; }', parser, parser._parseRuleset.bind(parser)); - // assertNode('&:nth-child(#{$query}+1) { clear: module.$opposite-direction; }', parser, parser._parseRuleset.bind(parser)); - // assertNode('&:nth-child(#{module.$query}+1) { clear: module.$opposite-direction; }', parser, parser._parseRuleset.bind(parser)); - assertNode("--#{module.$propname}: some-value", parser, parser._parseDeclaration.bind(parser)); - // assertNode('some-property: var(--#{module.$propname})', parser, parser._parseDeclaration.bind(parser)); - // assertNode('@supports #{$val} { }', parser, parser._parseStylesheet.bind(parser)); // #88283 - // assertNode('.mb-#{$i}0np {} .push-up-#{$i}0 {} .mt-#{$i}0vh {}', parser, parser._parseStylesheet.bind(parser)); - }); - - test("Declaration", function () { - const parser = new SassParser(); - assertNode("border: thin solid 1px", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: $color", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: blue", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: (20 / $const)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: (20 / 20 + $const)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: func($red)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: func($red) !important", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: desaturate($red, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: desaturate(16, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: $base-color + #111", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: 100% / 2 + $ref", parser, parser._parseDeclaration.bind(parser)); - assertNode("border: ($width * 2) solid black", parser, parser._parseDeclaration.bind(parser)); - assertNode("property: $class", parser, parser._parseDeclaration.bind(parser)); - assertNode("prop-erty: fnc($t, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("width: (1em + 2em) * 3", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: #010203 + #040506", parser, parser._parseDeclaration.bind(parser)); - assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser)); - assertNode("margin: 3px + 4px auto", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: hsl(0, 100%, 50%)", parser, parser._parseDeclaration.bind(parser)); - assertNode( - "color: hsl($hue: 0, $saturation: 100%, $lightness: 50%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode("foo: if($value == 'default', flex-gutter(), $value)", parser, parser._parseDeclaration.bind(parser)); - assertNode("foo: if(true, !important, null)", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: selector-replace(&, 1)", parser, parser._parseDeclaration.bind(parser)); - - assertNode("dummy: module.$color", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: (20 / module.$const)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: (20 / 20 + module.$const)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: module.func($red)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: module.func($red) !important", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: module.desaturate($red, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: desaturate(module.$red, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: module.desaturate(module.$red, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("dummy: module.desaturate(16, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: module.$base-color + #111", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: 100% / 2 + module.$ref", parser, parser._parseDeclaration.bind(parser)); - assertNode("border: (module.$width * 2) solid black", parser, parser._parseDeclaration.bind(parser)); - assertNode("property: module.$class", parser, parser._parseDeclaration.bind(parser)); - assertNode("prop-erty: module.fnc($t, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("prop-erty: fnc(module.$t, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("prop-erty: module.fnc(module.$t, 10%)", parser, parser._parseDeclaration.bind(parser)); - assertNode("width: (1em + 2em) * 3", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: #010203 + #040506", parser, parser._parseDeclaration.bind(parser)); - assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser)); - assertNode("margin: 3px + 4px auto", parser, parser._parseDeclaration.bind(parser)); - assertNode("color: color.hsl(0, 100%, 50%)", parser, parser._parseDeclaration.bind(parser)); - assertNode( - "color: color.hsl($hue: 0, $saturation: 100%, $lightness: 50%)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if(module.$value == 'default', flex-gutter(), $value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if($value == 'default', module.flex-gutter(), $value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if($value == 'default', flex-gutter(), module.$value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if(module.$value == 'default', module.flex-gutter(), $value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if($value == 'default', module.flex-gutter(), module.$value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode( - "foo: if(module.$value == 'default', module.flex-gutter(), module.$value)", - parser, - parser._parseDeclaration.bind(parser), - ); - assertNode("color: selector.replace(&, 1)", parser, parser._parseDeclaration.bind(parser)); - - assertError("fo = 8", parser, parser._parseDeclaration.bind(parser), ParseError.ColonExpected); - assertError("fo:", parser, parser._parseDeclaration.bind(parser), ParseError.PropertyValueExpected); - assertError("color: hsl($hue: 0,", parser, parser._parseDeclaration.bind(parser), ParseError.ExpressionExpected); - assertError( - "color: hsl($hue: 0", - parser, - parser._parseDeclaration.bind(parser), - ParseError.RightParenthesisExpected, - ); - }); - - test("Stylesheet", function () { - const parser = new SassParser(); - assertNode("$color: #F5F5F5;", parser, parser._parseStylesheet.bind(parser)); - assertNode("$color: #F5F5F5; $color: #F5F5F5;", parser, parser._parseStylesheet.bind(parser)); - assertNode("$color: #F5F5F5; $color: #F5F5F5; $color: #F5F5F5;", parser, parser._parseStylesheet.bind(parser)); - assertNode("$color: #F5F5F5 !important;", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "#main { width: 97%; p, div { a { font-weight: bold; } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("a { &:hover { color: red; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode("fo { font: 2px/3px { family: fantasy; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode(".foo { bar: { yoo: fantasy; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "selector { propsuffix: { nested: 1px; } rule: 1px; nested.selector { foo: 1; } nested:selector { foo: 2 }}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:s(1)}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@mixin keyframe { @keyframes name { @content; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode("@include keyframe { 10% { top: 3px; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode(".class{&--sub-class-with-ampersand{color: red;}}", parser, parser._parseStylesheet.bind(parser)); - assertError( - "fo { font: 2px/3px { family } }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.ColonExpected, - ); - - assertNode( - "legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:m.s(1)}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@include module.keyframe { 10% { top: 3px; } }", parser, parser._parseStylesheet.bind(parser)); - }); - - test("@import", function () { - const parser = new SassParser(); - assertNode('@import "test.css"', parser, parser._parseImport.bind(parser)); - assertNode('@import url("test.css")', parser, parser._parseImport.bind(parser)); - assertNode('@import "test.css", "bar.css"', parser, parser._parseImport.bind(parser)); - assertNode('@import "test.css", "bar.css" screen, projection', parser, parser._parseImport.bind(parser)); - assertNode('foo { @import "test.css"; }', parser, parser._parseStylesheet.bind(parser)); - - assertError( - '@import "test.css" "bar.css"', - parser, - parser._parseStylesheet.bind(parser), - ParseError.MediaQueryExpected, - ); - assertError('@import "test.css", screen', parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); - assertError("@import", parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected); - assertNode('@import url("override.css") layer;', parser, parser._parseStylesheet.bind(parser)); - }); - - test("@layer", function () { - const parser = new SassParser(); - assertNode("@layer #{$layer} { }", parser, parser._parseLayer.bind(parser)); - }); - - test("@container", function () { - const parser = new SassParser(); - assertNode( - `@container (min-width: #{$minWidth}) { .scss-interpolation { line-height: 10cqh; } }`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `.item-icon { @container (max-height: 100px) { .item-icon { display: none; } } }`, - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - `:root { @container (max-height: 100px) { display: none;} }`, - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@use", function () { - const parser = new SassParser(); - assertNode('@use "test"', parser, parser._parseUse.bind(parser)); - assertNode('@use "test" as foo', parser, parser._parseUse.bind(parser)); - assertNode('@use "test" as *', parser, parser._parseUse.bind(parser)); - assertNode('@use "test" with ($foo: "test", $bar: 1)', parser, parser._parseUse.bind(parser)); - assertNode('@use "test" as foo with ($foo: "test", $bar: 1)', parser, parser._parseUse.bind(parser)); - - assertError("@use", parser, parser._parseUse.bind(parser), ParseError.StringLiteralExpected); - assertError('@use "test" foo', parser, parser._parseUse.bind(parser), ParseError.UnknownKeyword); - assertError('@use "test" as', parser, parser._parseUse.bind(parser), ParseError.IdentifierOrWildcardExpected); - assertError('@use "test" with', parser, parser._parseUse.bind(parser), ParseError.LeftParenthesisExpected); - assertError('@use "test" with ($foo)', parser, parser._parseUse.bind(parser), ParseError.VariableValueExpected); - assertError('@use "test" with ("bar")', parser, parser._parseUse.bind(parser), ParseError.VariableNameExpected); - assertError( - '@use "test" with ($foo: 1, "bar")', - parser, - parser._parseUse.bind(parser), - ParseError.VariableNameExpected, - ); - assertError( - '@use "test" with ($foo: "bar"', - parser, - parser._parseUse.bind(parser), - ParseError.RightParenthesisExpected, - ); - - assertNode('@forward "test"; @use "lib"', parser, parser._parseStylesheet.bind(parser)); - assertNode('@use "test"; @use "lib"', parser, parser._parseStylesheet.bind(parser)); - assertNode('$test: "test"; @use "lib"', parser, parser._parseStylesheet.bind(parser)); - }); - - test("@forward", function () { - const parser = new SassParser(); - assertNode('@forward "test"', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" as foo-*', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" hide this', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" hide $that', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" hide this $that', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" hide this, $that', parser, parser._parseForward.bind(parser)); - assertNode('@forward "abstracts/functions" show px-to-rem, theme-color', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" show this', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" show $that', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" show this $that', parser, parser._parseForward.bind(parser)); - assertNode('@forward "test" as foo-* show this $that', parser, parser._parseForward.bind(parser)); - - assertError("@forward", parser, parser._parseForward.bind(parser), ParseError.StringLiteralExpected); - assertError('@forward "test" foo', parser, parser._parseForward.bind(parser), ParseError.SemiColonExpected); - assertError('@forward "test" as', parser, parser._parseForward.bind(parser), ParseError.IdentifierExpected); - assertError('@forward "test" as foo-', parser, parser._parseForward.bind(parser), ParseError.WildcardExpected); - assertError('@forward "test" as foo- *', parser, parser._parseForward.bind(parser), ParseError.WildcardExpected); - assertError( - '@forward "test" show', - parser, - parser._parseForward.bind(parser), - ParseError.IdentifierOrVariableExpected, - ); - assertError( - '@forward "test" hide', - parser, - parser._parseForward.bind(parser), - ParseError.IdentifierOrVariableExpected, - ); - - assertNode( - '@forward "test" with ( $black: #222 !default, $border-radius: 0.1rem !default )', - parser, - parser._parseForward.bind(parser), - ); - assertNode( - '@forward "../forms.scss" as components-* with ( $field-border: false )', - parser, - parser._parseForward.bind(parser), - ); // #145108 - - assertNode('@use "lib"; @forward "test"', parser, parser._parseStylesheet.bind(parser)); - assertNode('@forward "test"; @forward "lib"', parser, parser._parseStylesheet.bind(parser)); - assertNode('$test: "test"; @forward "test"', parser, parser._parseStylesheet.bind(parser)); - }); - - test("@media", function () { - const parser = new SassParser(); - assertNode( - "@media screen { .sidebar { @media (orientation: landscape) { width: 500px; } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@media #{$media} and ($feature: $value) {}", parser, parser._parseStylesheet.bind(parser)); - assertNode("@media only screen and #{$query} {}", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "foo { bar { @media screen and (orientation: landscape) {}} }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@media screen and (nth($query, 1): nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); - assertNode( - ".something { @media (max-width: 760px) { > .test { color: blue; } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".something { @media (max-width: 760px) { ~ div { display: block; } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - ".something { @media (max-width: 760px) { + div { display: block; } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@media (max-width: 760px) { + div { display: block; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode("@media (height <= 600px) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media (height >= 600px) { }", parser, parser._parseMedia.bind(parser)); - - assertNode("@media #{layout.$media} and ($feature: $value) {}", parser, parser._parseStylesheet.bind(parser)); - assertNode("@media #{$media} and (layout.$feature: $value) {}", parser, parser._parseStylesheet.bind(parser)); - assertNode("@media #{$media} and ($feature: layout.$value) {}", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "@media #{layout.$media} and (layout.$feature: $value) {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media #{$media} and (layout.$feature: layout.$value) {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media #{layout.$media} and (layout.$feature: layout.$value) {}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@media screen and (list.nth($query, 1): nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media screen and (nth(list.$query, 1): nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media screen and (nth($query, 1): list.nth($query, 2)) { }", parser, parser._parseMedia.bind(parser)); - assertNode("@media screen and (nth($query, 1): nth(list.$query, 2)) { }", parser, parser._parseMedia.bind(parser)); - assertNode( - "@media screen and (list.nth(list.$query, 1): nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (list.nth($query, 1): list.nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (list.nth($query, 1): nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (nth(list.$query, 1): list.nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (nth(list.$query, 1): nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (nth($query, 1): list.nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (list.nth(list.$query, 1): list.nth($query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (nth(list.$query, 1): list.nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - assertNode( - "@media screen and (list.nth(list.$query, 1): list.nth(list.$query, 2)) { }", - parser, - parser._parseMedia.bind(parser), - ); - }); - - test("@keyframe", function () { - const parser = new SassParser(); - assertNode("@keyframes name { @content; }", parser, parser._parseKeyframe.bind(parser)); - assertNode( - "@keyframes name { @for $i from 0 through $steps { #{$i * (100%/$steps)} { transform: $rotate $translate; } } }", - parser, - parser._parseKeyframe.bind(parser), - ); // issue 42086 - assertNode( - '@keyframes test-keyframe { @for $i from 1 through 60 { $s: ($i * 100) / 60 + "%"; } }', - parser, - parser._parseKeyframe.bind(parser), - ); - - assertNode( - "@keyframes name { @for $i from 0 through m.$steps { #{$i * (100%/$steps)} { transform: $rotate $translate; } } }", - parser, - parser._parseKeyframe.bind(parser), - ); - assertNode("@keyframes name { @function bar() { } }", parser, parser._parseKeyframe.bind(parser)); // #197742 - assertNode("@keyframes name { @include keyframe-mixin(); }", parser, parser._parseKeyframe.bind(parser)); // #197742 - }); - - test("@extend", function () { - const parser = new SassParser(); - assertNode(".themable { @extend %theme; }", parser, parser._parseStylesheet.bind(parser)); - assertNode("foo { @extend .error; border-width: 3px; }", parser, parser._parseStylesheet.bind(parser)); - assertNode("a.important { @extend .notice !optional; }", parser, parser._parseStylesheet.bind(parser)); - assertNode(".hoverlink { @extend a:hover; }", parser, parser._parseStylesheet.bind(parser)); - assertNode(".seriousError { @extend .error; @extend .attention; }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "#context a%extreme { color: blue; } .notice { @extend %extreme }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media print { .error { } .seriousError { @extend .error; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@mixin error($a: false) { @extend .#{$a}; @extend ##{$a}; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode(".foo { @extend .text-center, .uppercase; }", parser, parser._parseStylesheet.bind(parser)); - assertNode(".foo { @extend .text-center, .uppercase, ; }", parser, parser._parseStylesheet.bind(parser)); - assertNode(".foo { @extend .text-center, .uppercase !optional ; }", parser, parser._parseStylesheet.bind(parser)); - assertError(".hoverlink { @extend }", parser, parser._parseStylesheet.bind(parser), ParseError.SelectorExpected); - assertError( - ".hoverlink { @extend %extreme !default }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.UnknownKeyword, - ); - }); - - test("@debug", function () { - const parser = new SassParser(); - assertNode("@debug test;", parser, parser._parseStylesheet.bind(parser)); - assertNode("foo { @debug 1 + 4; nested { @warn 1 4; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode("@if $foo == 1 { @debug 1 + 4 }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - '@function setStyle($map, $object, $style) { @warn "The key ´#{$object} is not available in the map."; @return null; }', - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@if", function () { - const parser = new SassParser(); - assertNode("@if 1 + 1 == 2 { border: 1px solid; }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode("@if 5 < 3 { border: 2px dotted; }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode("@if null { border: 3px double; }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode( - "@if 1 <= $const { border: 3px; } @else { border: 4px; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "@if 1 >= (1 + $foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "p { @if $i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@if (index($_RESOURCES, "clean") != null) { @error "sdssd"; }', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@if $i == 1 { p { x: 3px; } }", parser, parser._parseStylesheet.bind(parser)); - assertError( - "@if { border: 1px solid; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.ExpressionExpected, - ); - assertError("@if 1 }", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.LeftCurlyExpected); - - assertNode( - "@if 1 <= m.$const { border: 3px; } @else { border: 4px; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "@if 1 >= (1 + m.$foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode( - "p { @if m.$i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "p { @if $i == 1 { x: 3px; } @else if m.$i == 1 { x: 4px; } @else { x: 4px; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "p { @if m.$i == 1 { x: 3px; } @else if m.$i == 1 { x: 4px; } @else { x: 4px; } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@if (list.index($_RESOURCES, "clean") != null) { @error "sdssd"; }', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@if (index(m.$_RESOURCES, "clean") != null) { @error "sdssd"; }', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@if (list.index(m.$_RESOURCES, "clean") != null) { @error "sdssd"; }', - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@for", function () { - const parser = new SassParser(); - assertNode( - "@for $i from 1 to 5 { .item-#{$i} { width: 2em * $i; } }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertNode("@for $k from 1 + $x through 5 + $x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertError( - "@for i from 0 to 4 {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.VariableNameExpected, - ); - assertError("@for $i to 4 {}", parser, parser._parseRuleSetDeclaration.bind(parser), SassParseError.FromExpected); - assertError( - "@for $i from 0 by 4 {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - SassParseError.ThroughOrToExpected, - ); - assertError("@for $i from {}", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); - assertError( - "@for $i from 0 to {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.ExpressionExpected, - ); - assertNode('@for $i from 1 through 60 { $s: $i + "%"; }', parser, parser._parseRuleSetDeclaration.bind(parser)); - - assertNode("@for $k from 1 + m.$x through 5 + $x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode("@for $k from 1 + $x through 5 + m.$x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode("@for $k from 1 + m.$x through 5 + m.$x { }", parser, parser._parseRuleSetDeclaration.bind(parser)); - }); - - test("@each", function () { - const parser = new SassParser(); - assertNode("@each $i in 1, 2, 3 { }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode("@each $i in 1 2 3 { }", parser, parser._parseRuleSetDeclaration.bind(parser)); - assertNode( - "@each $animal, $color, $cursor in (puma, black, default), (egret, white, move) {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertError( - "@each i in 4 {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.VariableNameExpected, - ); - assertError("@each $i from 4 {}", parser, parser._parseRuleSetDeclaration.bind(parser), SassParseError.InExpected); - assertError("@each $i in {}", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); - assertError( - "@each $animal, in (1, 1, 1), (2, 2, 2) {}", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.VariableNameExpected, - ); - }); - - test("@while", function () { - const parser = new SassParser(); - assertNode( - "@while $i < 0 { .item-#{$i} { width: 2em * $i; } $i: $i - 2; }", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ); - assertError("@while {}", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected); - assertError("@while $i != 4", parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.LeftCurlyExpected); - assertError( - "@while ($i >= 4) {", - parser, - parser._parseRuleSetDeclaration.bind(parser), - ParseError.RightCurlyExpected, - ); - }); - - test("@mixin", function () { - const parser = new SassParser(); - assertNode( - "@mixin large-text { font: { family: Arial; size: 20px; } color: #ff0000; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@mixin sexy-border($color, $width: 1in) { color: black; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@mixin box-shadow($shadows...) { -moz-box-shadow: $shadows; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@mixin apply-to-ie6-only { * html { @content; } }", parser, parser._parseStylesheet.bind(parser)); - assertNode("@mixin #{foo}($color){}", parser, parser._parseStylesheet.bind(parser)); - assertNode("@mixin foo ($i:4) { size: $i; @include wee ($i - 1); }", parser, parser._parseStylesheet.bind(parser)); - assertNode("@mixin foo ($i,) { }", parser, parser._parseStylesheet.bind(parser)); - - assertError("@mixin $1 {}", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); - assertError("@mixin foo() i {}", parser, parser._parseStylesheet.bind(parser), ParseError.LeftCurlyExpected); - assertError("@mixin foo(1) {}", parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected); - assertError( - "@mixin foo($color = 9) {}", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError("@mixin foo($color)", parser, parser._parseStylesheet.bind(parser), ParseError.LeftCurlyExpected); - assertError("@mixin foo($color){", parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected); - assertError("@mixin foo($color,){", parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected); - }); - - test("@content", function () { - const parser = new SassParser(); - assertNode("@content", parser, parser._parseMixinContent.bind(parser)); - assertNode("@content($type)", parser, parser._parseMixinContent.bind(parser)); - }); - - test("@include", function () { - const parser = new SassParser(); - assertNode("p { @include sexy-border(blue); }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - ".shadows { @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "$values: #ff0000, #00ff00, #0000ff; .primary { @include colors($values...); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode('@include colors(this("styles")...);', parser, parser._parseStylesheet.bind(parser)); - assertNode(".test { @include fontsize(16px, 21px !important); }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "p { @include apply-to-ie6-only { #logo { background-image: url(/logo.gif); } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("p { @include foo($values,) }", parser, parser._parseStylesheet.bind(parser)); - assertNode("p { @include foo($values,); }", parser, parser._parseStylesheet.bind(parser)); - - assertError( - "p { @include sexy-border blue", - parser, - parser._parseStylesheet.bind(parser), - ParseError.SemiColonExpected, - ); - assertError( - "p { @include sexy-border($values blue", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError("p { @include }", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); - assertError( - "p { @include foo($values }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "p { @include foo($values, }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.ExpressionExpected, - ); - - assertNode("p { @include lib.sexy-border(blue); }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - ".shadows { @include lib.box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "$values: #ff0000, #00ff00, #0000ff; .primary { @include lib.colors($values...); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode(".primary { @include colors(lib.$values...); }", parser, parser._parseStylesheet.bind(parser)); - assertNode(".primary { @include lib.colors(lib.$values...); }", parser, parser._parseStylesheet.bind(parser)); - assertNode('@include lib.colors(this("styles")...);', parser, parser._parseStylesheet.bind(parser)); - assertNode('@include colors(lib.this("styles")...);', parser, parser._parseStylesheet.bind(parser)); - assertNode('@include lib.colors(lib.this("styles")...);', parser, parser._parseStylesheet.bind(parser)); - assertNode(".test { @include lib.fontsize(16px, 21px !important); }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "p { @include lib.apply-to-ie6-only { #logo { background-image: url(/logo.gif); } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("p { @include lib.foo($values,) }", parser, parser._parseStylesheet.bind(parser)); - assertNode("p { @include foo(lib.$values,) }", parser, parser._parseStylesheet.bind(parser)); - assertNode("p { @include lib.foo(m.$values,); }", parser, parser._parseStylesheet.bind(parser)); - - assertError( - "p { @include foo.($values) }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.IdentifierExpected, - ); - - assertNode( - '@include rtl("left") using ($dir) { margin-#{$dir}: 10px; }', - parser, - parser._parseStylesheet.bind(parser), - ); - }); - - test("@function", function () { - const parser = new SassParser(); - assertNode( - "@function grid-width($n) { @return $n * $grid-width + ($n - 1) * $gutter-width; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@function grid-width($n: 1, $e) { @return 0; }", parser, parser._parseStylesheet.bind(parser)); - assertNode( - "@function foo($total, $a) { @for $i from 0 to $total { } @return $grid; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - '@function foo() { @if (unit($a) == "%") and ($i == ($total - 1)) { @return 0; } @return 1; }', - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@function is-even($int) { @if $int%2 == 0 { @return true; } @return false }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@function bar ($i) { @if $i > 0 { @return $i * bar($i - 1); } @return 1; }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@function foo($a,) {} ", parser, parser._parseStylesheet.bind(parser)); - - assertError("@function foo {} ", parser, parser._parseStylesheet.bind(parser), ParseError.LeftParenthesisExpected); - assertError("@function {} ", parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected); - assertError( - "@function foo($a $b) {} ", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "@function foo($a {} ", - parser, - parser._parseStylesheet.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "@function foo($a...) { @return; }", - parser, - parser._parseStylesheet.bind(parser), - ParseError.ExpressionExpected, - ); - assertError( - "@function foo($a:) {} ", - parser, - parser._parseStylesheet.bind(parser), - ParseError.VariableValueExpected, - ); - }); - - test("@at-root", function () { - const parser = new SassParser(); - assertNode( - "@mixin unify-parent($child) { @at-root #{selector.unify(&, $child)} { }}", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@at-root #main2 .some-class { padding-left: calc( #{$a-variable} + 8px ); }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode( - "@media print { .page { @at-root (without: media) { } } }", - parser, - parser._parseStylesheet.bind(parser), - ); - assertNode("@media print { .page { @at-root (with: rule) { } } }", parser, parser._parseStylesheet.bind(parser)); - }); - - test("Ruleset", function () { - const parser = new SassParser(); - assertNode(".selector { prop: erty $const 1px; }", parser, parser._parseRuleset.bind(parser)); - assertNode(".selector { prop: erty $const 1px m.$foo; }", parser, parser._parseRuleset.bind(parser)); - assertNode("selector:active { property:value; nested:hover {}}", parser, parser._parseRuleset.bind(parser)); - assertNode("selector {}", parser, parser._parseRuleset.bind(parser)); - assertNode("selector { property: declaration }", parser, parser._parseRuleset.bind(parser)); - assertNode("selector { $variable: declaration }", parser, parser._parseRuleset.bind(parser)); - assertNode("selector { nested {}}", parser, parser._parseRuleset.bind(parser)); - assertNode("selector { nested, a, b {}}", parser, parser._parseRuleset.bind(parser)); - assertNode("selector { property: value; property: $value; }", parser, parser._parseRuleset.bind(parser)); - assertNode( - "selector { property: value; @keyframes foo {} @-moz-keyframes foo {}}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode("foo|bar { }", parser, parser._parseRuleset.bind(parser)); - }); - - test("Nested Ruleset", function () { - const parser = new SassParser(); - assertNode( - ".class1 { $const: 1; .class { $const: 2; three: $const; const: 3; } one: $const; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".class1 { $const: 1; .class { $const: m.$foo; } one: $const; }", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode(".class1 { > .class2 { & > .class4 { rule1: v1; } } }", parser, parser._parseRuleset.bind(parser)); - assertNode("foo { @at-root { display: none; } }", parser, parser._parseRuleset.bind(parser)); - assertNode( - 'th, tr { @at-root #{selector-replace(&, "tr")} { border-bottom: 0; } }', - parser, - parser._parseRuleset.bind(parser), - ); - assertNode( - ".foo { @supports(display: grid) { .bar { display: none; }}}", - parser, - parser._parseRuleset.bind(parser), - ); - assertNode(".foo { @supports(display: grid) { display: none; }}", parser, parser._parseRuleset.bind(parser)); - assertNode( - ".foo { @supports (position: sticky) { @media (min-width: map-get($grid-breakpoints, md)) { position: sticky; } }}", - parser, - parser._parseRuleset.bind(parser), - ); // issue #152 - }); - - test("Selector Interpolation", function () { - const parser = new SassParser(); - assertNode(".#{$name} { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".#{$name}-foo { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".#{$name}-foo-3 { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".#{$name}-1 { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".sc-col#{$postfix}-2-1 { }", parser, parser._parseRuleset.bind(parser)); - assertNode("p.#{$name} { #{$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); - assertNode("sans-#{serif} { a-#{1 + 2}-color-#{$attr}: blue; }", parser, parser._parseRuleset.bind(parser)); - assertNode("##{f} .#{f} #{f}:#{f} { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".foo-#{&} .foo-#{&-sub} { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".-#{$variable} { }", parser, parser._parseRuleset.bind(parser)); - assertNode("#{&}([foo=bar][bar=foo]) { }", parser, parser._parseRuleset.bind(parser)); // #49589 - - assertNode(".#{module.$name} { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".#{module.$name}-foo { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".#{module.$name}-foo-3 { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".#{module.$name}-1 { }", parser, parser._parseRuleset.bind(parser)); - assertNode(".sc-col#{module.$postfix}-2-1 { }", parser, parser._parseRuleset.bind(parser)); - assertNode("p.#{module.$name} { #{$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); - assertNode("p.#{$name} { #{module.$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); - assertNode("p.#{module.$name} { #{module.$attr}-color: blue; }", parser, parser._parseRuleset.bind(parser)); - assertNode("sans-#{serif} { a-#{1 + 2}-color-#{module.$attr}: blue; }", parser, parser._parseRuleset.bind(parser)); - assertNode(".-#{module.$variable} { }", parser, parser._parseRuleset.bind(parser)); - }); - - test("Parent Selector", function () { - const parser = new SassParser(); - assertNode("&:hover", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&.float", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-bar", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-1", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&1", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-foo-1", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&&", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("&-10-thing", parser, parser._parseSimpleSelector.bind(parser)); - }); - - test("Selector Placeholder", function () { - const parser = new SassParser(); - assertNode("%hover", parser, parser._parseSimpleSelector.bind(parser)); - assertNode("a%float", parser, parser._parseSimpleSelector.bind(parser)); - }); - - test("Map", function () { - const parser = new SassParser(); - assertNode("(key1: 1px, key2: solid + px, key3: (2+3))", parser, parser._parseExpr.bind(parser)); - assertNode("($key1 + 3: 1px)", parser, parser._parseExpr.bind(parser)); - }); - - test("Url", function () { - const parser = new SassParser(); - assertNode("url(foo())", parser, parser._parseURILiteral.bind(parser)); - assertNode( - "url('data:image/svg+xml;utf8,%3Csvg%20fill%3D%22%23' + $color + 'foo')", - parser, - parser._parseURILiteral.bind(parser), - ); - assertNode("url(//yourdomain/yourpath.png)", parser, parser._parseURILiteral.bind(parser)); - assertNode("url('http://msft.com')", parser, parser._parseURILiteral.bind(parser)); - assertNode('url("http://msft.com")', parser, parser._parseURILiteral.bind(parser)); - assertNode('url( "http://msft.com")', parser, parser._parseURILiteral.bind(parser)); - assertNode('url(\t"http://msft.com")', parser, parser._parseURILiteral.bind(parser)); - assertNode('url(\n"http://msft.com")', parser, parser._parseURILiteral.bind(parser)); - assertNode('url("http://msft.com"\n)', parser, parser._parseURILiteral.bind(parser)); - assertNode('url("")', parser, parser._parseURILiteral.bind(parser)); - assertNode('uRL("")', parser, parser._parseURILiteral.bind(parser)); - assertNode('URL("")', parser, parser._parseURILiteral.bind(parser)); - assertNode("url(http://msft.com)", parser, parser._parseURILiteral.bind(parser)); - assertNode("url()", parser, parser._parseURILiteral.bind(parser)); - assertNode("url('http://msft.com\n)", parser, parser._parseURILiteral.bind(parser)); - assertError( - 'url("http://msft.com"', - parser, - parser._parseURILiteral.bind(parser), - ParseError.RightParenthesisExpected, - ); - assertError( - "url(http://msft.com')", - parser, - parser._parseURILiteral.bind(parser), - ParseError.RightParenthesisExpected, - ); - }); - - test("@font-face", function () { - const parser = new SassParser(); - assertNode("@font-face {}", parser, parser._parseFontFace.bind(parser)); - assertNode("@font-face { src: url(http://test) }", parser, parser._parseFontFace.bind(parser)); - assertNode("@font-face { font-style: normal; font-stretch: normal; }", parser, parser._parseFontFace.bind(parser)); - assertNode( - "@font-face { unicode-range: U+0021-007F, u+1f49C, U+4??, U+??????; }", - parser, - parser._parseFontFace.bind(parser), - ); - assertError( - "@font-face { font-style: normal font-stretch: normal; }", - parser, - parser._parseFontFace.bind(parser), - ParseError.SemiColonExpected, - ); - }); -}); From 27a15a4819049e0b0aa7a36126c782476e39389d Mon Sep 17 00:00:00 2001 From: William Killerud Date: Fri, 24 May 2024 22:33:34 +0200 Subject: [PATCH 031/138] test: indented selector printing --- .../src/test/sass/selectorPrinting.test.ts | 126 +++++++++++++++--- 1 file changed, 110 insertions(+), 16 deletions(-) diff --git a/packages/vscode-css-languageservice/src/test/sass/selectorPrinting.test.ts b/packages/vscode-css-languageservice/src/test/sass/selectorPrinting.test.ts index 07f496d9..5faefa85 100644 --- a/packages/vscode-css-languageservice/src/test/sass/selectorPrinting.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/selectorPrinting.test.ts @@ -59,27 +59,121 @@ suite("Sass - Selector Printing", () => { }); test("nested selector", function () { - let p = new SassParser(); - assertSelector(p, "o1 { e1 { } }", "e1", "{o1{…{e1}}}"); - assertSelector(p, "o1 { e1.div { } }", "e1", "{o1{…{e1[class=div]}}}"); - assertSelector(p, "o1 o2 { e1 { } }", "e1", "{o1{…{o2{…{e1}}}}}"); - assertSelector(p, "o1, o2 { e1 { } }", "e1", "{o1{…{e1}}}"); - assertSelector(p, "o1 { @if $a { e1 { } } }", "e1", "{o1{…{e1}}}"); - assertSelector(p, "o1 { @mixin a { e1 { } } }", "e1", "{e1}"); - assertSelector(p, "o1 { @mixin a { e1 { } } }", "e1", "{e1}"); + let p = new SassParser({ syntax: "indented" }); + assertSelector( + p, + `o1 + e1 + //`, + "e1", + "{o1{…{e1}}}", + ); + assertSelector( + p, + `o1 + e1.div + //`, + "e1", + "{o1{…{e1[class=div]}}}", + ); + assertSelector( + p, + `o1 o2 + e1 + //`, + "e1", + "{o1{…{o2{…{e1}}}}}", + ); + assertSelector( + p, + `o1, o2 + e1 + //`, + "e1", + "{o1{…{e1}}}", + ); + assertSelector( + p, + `o1 + @if $a + e1 + //`, + "e1", + "{o1{…{e1}}}", + ); + assertSelector( + p, + `o1 + @mixin a + e1 + //`, + "e1", + "{e1}", + ); + assertSelector( + p, + `o1 + @mixin a + e1 + //`, + "e1", + "{e1}", + ); }); test("referencing selector", function () { - let p = new SassParser(); - assertSelector(p, "o1 { &:hover { }}", "&", "{o1[:hover=]}"); - assertSelector(p, "o1 { &:hover & { }}", "&", "{o1[:hover=]{…{o1}}}"); - assertSelector(p, "o1 { &__bar {}}", "&", "{o1__bar}"); - assertSelector(p, ".c1 { &__bar {}}", "&", "{[class=c1__bar]}"); - assertSelector(p, "o.c1 { &__bar {}}", "&", "{o[class=c1__bar]}"); + let p = new SassParser({ syntax: "indented" }); + assertSelector( + p, + `o1 + &:hover + //`, + "&", + "{o1[:hover=]}", + ); + assertSelector( + p, + `o1 + &:hover & + //`, + "&", + "{o1[:hover=]{…{o1}}}", + ); + assertSelector( + p, + `o1 + &__bar + //`, + "&", + "{o1__bar}", + ); + assertSelector( + p, + `.c1 + &__bar + //`, + "&", + "{[class=c1__bar]}", + ); + assertSelector( + p, + `o.c1 + &__bar + //`, + "&", + "{o[class=c1__bar]}", + ); }); test("placeholders", function () { - let p = new SassParser(); - assertSelector(p, "%o1 { e1 { } }", "e1", "{%o1{…{e1}}}"); + let p = new SassParser({ syntax: "indented" }); + assertSelector( + p, + `%o1 + e1 + //`, + "e1", + "{%o1{…{e1}}}", + ); }); }); From 571fde84dc7a688819c1d38e9101037f04828fd6 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sat, 25 May 2024 13:45:03 +0200 Subject: [PATCH 032/138] test: indented fixtures --- .../src/test/sass/example.sass | 345 ++++++++++++++++++ .../src/test/sass/linkFixture/both/_bar.sass | 0 .../src/test/sass/linkFixture/both/bar.sass | 0 .../indented-index/bar/_index.sass | 0 .../linkFixture/indented-index/foo/index.sass | 0 .../src/test/sass/linkFixture/module/bar.sass | 0 .../sass/linkFixture/noUnderscore/bar.sass | 0 .../sass/linkFixture/underscore/_bar.sass | 0 .../node_modules/sass/package.json | 13 + .../node_modules/sass/styles/button.sass | 0 .../node_modules/sass/styles/colors.sass | 0 .../node_modules/sass/styles/index.sass | 0 12 files changed, 358 insertions(+) create mode 100644 packages/vscode-css-languageservice/src/test/sass/example.sass create mode 100644 packages/vscode-css-languageservice/src/test/sass/linkFixture/both/_bar.sass create mode 100644 packages/vscode-css-languageservice/src/test/sass/linkFixture/both/bar.sass create mode 100644 packages/vscode-css-languageservice/src/test/sass/linkFixture/indented-index/bar/_index.sass create mode 100644 packages/vscode-css-languageservice/src/test/sass/linkFixture/indented-index/foo/index.sass create mode 100644 packages/vscode-css-languageservice/src/test/sass/linkFixture/module/bar.sass create mode 100644 packages/vscode-css-languageservice/src/test/sass/linkFixture/noUnderscore/bar.sass create mode 100644 packages/vscode-css-languageservice/src/test/sass/linkFixture/underscore/_bar.sass create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/sass/package.json create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/sass/styles/button.sass create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/sass/styles/colors.sass create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/sass/styles/index.sass diff --git a/packages/vscode-css-languageservice/src/test/sass/example.sass b/packages/vscode-css-languageservice/src/test/sass/example.sass new file mode 100644 index 00000000..bd3d97f9 --- /dev/null +++ b/packages/vscode-css-languageservice/src/test/sass/example.sass @@ -0,0 +1,345 @@ +// snippets from the scss documentation at http://sass-lang.com/ + +/* css stuff */ +/* charset */ +@charset "UTF-8" + +/* nested rules */ +#main + width: 97% + p, div + font-size: 2em + a + font-weight: bold + + pre + font-size: 3em + + +/* parent selector (&) */ +#main + color: black + a + font-weight: bold + &:hover + color: red + +/* nested properties */ +.funky + font: 2px/3px + family: fantasy + size: 30em + weight: bold + + color: black + +/* nesting conflicts */ +tr.default + foo: + // properties + foo: 1 + + foo: 1px // rule + foo.bar + // selector + foo: 1 + + foo:bar + // selector + foo: 1 + + foo: 1px // rule + + +// These comments are only one line long each. +// They won't appear in the CSS output, +// since they use the single-line comment syntax. +a + color: green + + +/* variables */ +$width: 5em +$width: "Second width?" !default +#main + $localvar: 6em + width: $width + + $font-size: 12px + $line-height: 30px + font: #{$font-size}/#{$line-height} + +$name: foo +$attr: border +p.#{$name} + #{$attr}-color: blue + +/* variable declaration with whitespaces */ +// Set the color of your columns +$grid-background-column-color: rgba(100, 100, 225, 0.25) !default + +/* operations*/ +p + width: (1em + 2em) * 3 + font-family: sans- + "serif" + margin: 3px + 4px auto + content: "I ate #{5 + 10} pies!" + color: hsl(0, 100%, 50%) + color: hsl($hue: 0, $saturation: 100%, $lightness: 50%) + +/* functions*/ +$grid-width: 40px +$gutter-width: 10px +@function grid-width($n) + @return $n * $grid-width + ($n - 1) * $gutter-width + +#sidebar + width: grid-width(5) + + +/* @import */ +@import "foo.scss" +$family: unquote("Droid+Sans") +@import "rounded-corners", url("http://fonts.googleapis.com/css?family=#{$family}") +#main + @import "example" + + +/* @media */ +.sidebar + width: 300px + @media screen and (orientation: landscape) + width: 500px + + +/* @extend */ +.error + border: 1px #f00 + background-color: #fdd + +.seriousError + @extend .error + border-width: 3px + +#context a%extreme + color: blue + font-weight: bold + font-size: 2em + +.notice + @extend %extreme !optional + +/* @debug and @warn */ +@debug 10em + 12em +@mixin adjust-location($x, $y) + @if unitless($x) + @warn "Assuming #{$x} to be in pixels" + $x: 1px * $x + + @if unitless($y) + @warn "Assuming #{$y} to be in pixels" + $y: 1px * $y + + position: relative + left: $x + top: $y + +/* control directives */ + +/* if statement */ +p + @if 1 + 1 == 2 + border: 1px solid + + @if 5 < 3 + border: 2px dotted + + @if null + border: 3px double + + +/* if else statement */ +$type: monster +p + @if $type == ocean + color: blue + @else + color: black + +/* for statement */ +@for $i from 1 through 3 + .item-#{$i} + width: 2em * $i + + +/* each statement */ +@each $animal in puma, sea-slug, egret, salamander + .#{$animal}-icon + background-image: url("/images/#{$animal}.png") + + +/* while statement */ +$i: 6 +@while $i > 0 + .item-#{$i} + width: 2em * $i + + $i: $i - 2 + + +/* function with controlstatements */ +@function foo($total, $a) + @for $i from 0 to $total + @if (unit($a) == "%") and ($i == ($total - 1)) + $z: 100% + @return "1" + + @return $grid + + +/* @mixin simple*/ +@mixin large-text + font: + family: Arial + size: 20px + weight: bold + color: #ff0000 + +.page-title + @include large-text + padding: 4px + +/* mixin with parameters */ +@mixin double-border($color, $width: 1in) + border: + color: $color + width: $width + style: dashed + +p + @include double-border(blue) + + +/* mixin with varargs */ +@mixin box-shadow($shadows...) + -moz-box-shadow: $shadows + -webkit-box-shadow: $shadows + box-shadow: $shadows + +.shadows + @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999) + +/* include with varargs */ +@mixin colors($text, $background, $border) + color: $text + background-color: $background + border-color: $border + +$values: #ff0000, #00ff00, #0000ff +.primary + @include colors($values...) + +/* include with body */ +@mixin apply-to-ie6-only + * html + @content + + +@include apply-to-ie6-only + #logo + background-image: url(/logo.gif) + + +/* attributes */ +[rel="external"]::after + content: "s" + +/*page */ +@page :left + margin-left: 4cm + margin-right: 3cm + +tr.default + foo.bar + $foo: 1px + foo: + foo: white + foo.bar1 + @extend tr, .default + foo.bar2 + @import "compass" + bar: black + +/* rules without whitespace */ +legend + foo + a: s + margin-top: 0 + margin-bottom: #123 + margin-top: s(1) + +/* extend with interpolation variable */ +@mixin error($a: false) + @extend .#{$a} + @extend ##{$a} + +#bar + a: 1px + +.bar + b: 1px + +foo + @include error("bar") + + +/* css3: @font face */ +@font-face + font-family: Delicious + src: url("Delicious-Roman.otf") + + +/* rule names with variables */ +.orbit-#{$d}-prev + #{$d}-style: 0 + foo-#{$d}: 1 + #{$d}-bar-#{$d}: 2 + foo-#{$d}-bar: 1 + + +/* keyframes */ +@-webkit-keyframes NAME-YOUR-ANIMATION + 0% + opacity: 0 + 100% + opacity: 1 + + +@-moz-keyframes NAME-YOUR-ANIMATION + 0% + opacity: 0 + 100% + opacity: 1 + + +@-o-keyframes NAME-YOUR-ANIMATION + 0% + opacity: 0 + 100% + opacity: 1 + +@keyframes NAME-YOUR-ANIMATION + 0% + opacity: 0 + 100% + opacity: 1 + + +/* string escaping */ +[data-icon="test-1"]:before + content: "\\" + +/* a comment */ +$var1: "'" +$var2: '"' +/* another comment */ diff --git a/packages/vscode-css-languageservice/src/test/sass/linkFixture/both/_bar.sass b/packages/vscode-css-languageservice/src/test/sass/linkFixture/both/_bar.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/src/test/sass/linkFixture/both/bar.sass b/packages/vscode-css-languageservice/src/test/sass/linkFixture/both/bar.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/src/test/sass/linkFixture/indented-index/bar/_index.sass b/packages/vscode-css-languageservice/src/test/sass/linkFixture/indented-index/bar/_index.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/src/test/sass/linkFixture/indented-index/foo/index.sass b/packages/vscode-css-languageservice/src/test/sass/linkFixture/indented-index/foo/index.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/src/test/sass/linkFixture/module/bar.sass b/packages/vscode-css-languageservice/src/test/sass/linkFixture/module/bar.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/src/test/sass/linkFixture/noUnderscore/bar.sass b/packages/vscode-css-languageservice/src/test/sass/linkFixture/noUnderscore/bar.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/src/test/sass/linkFixture/underscore/_bar.sass b/packages/vscode-css-languageservice/src/test/sass/linkFixture/underscore/_bar.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/sass/package.json b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/sass/package.json new file mode 100644 index 00000000..82c8f4cc --- /dev/null +++ b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/sass/package.json @@ -0,0 +1,13 @@ +{ + "exports": { + ".": { + "sass": "./styles/index.sass" + }, + "./colors.sass": { + "sass": "./styles/colors.sass" + }, + "./button": { + "sass": "./styles/button.sass" + } + } +} diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/sass/styles/button.sass b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/sass/styles/button.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/sass/styles/colors.sass b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/sass/styles/colors.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/sass/styles/index.sass b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/sass/styles/index.sass new file mode 100644 index 00000000..e69de29b From 2d463a49e092181bf65c987cb198d1dc63f55cb1 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sat, 25 May 2024 14:02:33 +0200 Subject: [PATCH 033/138] refactor: configure syntax based on textdocument Since the two syntaxes can link interchangably --- .../src/cssLanguageService.ts | 10 +++---- .../src/parser/cssParser.ts | 14 +++++++-- .../src/parser/cssScanner.ts | 6 +++- .../{scssCompletion.ts => sassCompletion.ts} | 20 ++++++------- .../{scssNavigation.ts => sassNavigation.ts} | 2 +- .../src/test/css/completion.test.ts | 4 +-- .../src/test/css/hover.test.ts | 4 +-- .../src/test/css/selectorPrinting.test.ts | 2 +- .../src/test/sass/scssNavigation.test.ts | 8 ++--- .../src/test/sass/selectorPrinting.test.ts | 30 +++++++++++++++++-- 10 files changed, 68 insertions(+), 32 deletions(-) rename packages/vscode-css-languageservice/src/services/{scssCompletion.ts => sassCompletion.ts} (97%) rename packages/vscode-css-languageservice/src/services/{scssNavigation.ts => sassNavigation.ts} (98%) diff --git a/packages/vscode-css-languageservice/src/cssLanguageService.ts b/packages/vscode-css-languageservice/src/cssLanguageService.ts index 253e4a01..ffadcfd6 100644 --- a/packages/vscode-css-languageservice/src/cssLanguageService.ts +++ b/packages/vscode-css-languageservice/src/cssLanguageService.ts @@ -12,7 +12,7 @@ import { CSSCodeActions } from "./services/cssCodeActions"; import { CSSValidation } from "./services/cssValidation"; import { SassParser } from "./parser/sassParser"; -import { SCSSCompletion } from "./services/scssCompletion"; +import { SassCompletion } from "./services/sassCompletion"; import { getFoldingRanges } from "./services/cssFolding"; import { @@ -49,7 +49,7 @@ import { import { CSSDataManager } from "./languageFacts/dataManager"; import { CSSDataProvider } from "./languageFacts/dataProvider"; import { getSelectionRanges } from "./services/cssSelectionRange"; -import { SCSSNavigation } from "./services/scssNavigation"; +import { SassNavigation } from "./services/sassNavigation"; import { cssData } from "./data/webCustomData"; export type Stylesheet = {}; @@ -183,15 +183,15 @@ export function getCSSLanguageService( ); } -export function getSCSSLanguageService( +export function getSassLanguageService( options: LanguageServiceOptions = defaultLanguageServiceOptions, ): LanguageService { const cssDataManager = new CSSDataManager(options); return createFacade( new SassParser(), - new SCSSCompletion(options, cssDataManager), + new SassCompletion(options, cssDataManager), new CSSHover(options && options.clientCapabilities, cssDataManager), - new SCSSNavigation(options && options.fileSystemProvider), + new SassNavigation(options && options.fileSystemProvider), new CSSCodeActions(cssDataManager), new CSSValidation(cssDataManager), cssDataManager, diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 32a804fb..1fcfdc99 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -35,13 +35,18 @@ export class Parser { syntax; - constructor({ scanner, syntax: dialect }: ParserOptions = {}) { - this.syntax = dialect; - this.scanner = scanner || new Scanner({ syntax: dialect }); + constructor({ scanner, syntax }: ParserOptions = {}) { + this.syntax = syntax; + this.scanner = scanner || new Scanner({ syntax }); this.token = { type: TokenType.EOF, offset: -1, len: 0, text: "" }; this.prevToken = undefined!; } + public configure({ syntax }: Pick): void { + this.syntax = syntax; + this.scanner.configure({ syntax }); + } + public peekIdent(text: string): boolean { return ( TokenType.Ident === this.token.type && @@ -257,6 +262,9 @@ export class Parser { public parseStylesheet(textDocument: TextDocument): nodes.Stylesheet { const versionId = textDocument.version; + const syntax = textDocument.languageId === "sass" ? "indented" : "scss"; + this.configure({ syntax }); + const text = textDocument.getText(); const textProvider = (offset: number, length: number) => { if (textDocument.version !== versionId) { diff --git a/packages/vscode-css-languageservice/src/parser/cssScanner.ts b/packages/vscode-css-languageservice/src/parser/cssScanner.ts index a6dabe23..218c0b43 100644 --- a/packages/vscode-css-languageservice/src/parser/cssScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/cssScanner.ts @@ -234,7 +234,7 @@ staticUnitTable["cqmin"] = TokenType.ContainerQueryLength; staticUnitTable["cqmax"] = TokenType.ContainerQueryLength; export type ScannerOptions = { - syntax?: "indented"; + syntax?: "indented" | "scss"; }; export class Scanner { @@ -249,6 +249,10 @@ export class Scanner { this.syntax = syntax; } + public configure({ syntax }: ScannerOptions): void { + this.syntax = syntax; + } + public setSource(input: string): void { this.stream = new MultiLineStream(input); } diff --git a/packages/vscode-css-languageservice/src/services/scssCompletion.ts b/packages/vscode-css-languageservice/src/services/sassCompletion.ts similarity index 97% rename from packages/vscode-css-languageservice/src/services/scssCompletion.ts rename to packages/vscode-css-languageservice/src/services/sassCompletion.ts index b76231ef..c4f038fc 100644 --- a/packages/vscode-css-languageservice/src/services/scssCompletion.ts +++ b/packages/vscode-css-languageservice/src/services/sassCompletion.ts @@ -28,7 +28,7 @@ interface IFunctionInfo { const sassDocumentationName = l10n.t("Sass documentation"); -export class SCSSCompletion extends CSSCompletion { +export class SassCompletion extends CSSCompletion { private static variableDefaults: { [key: string]: string } = { $red: "1", $green: "2", @@ -329,8 +329,8 @@ export class SCSSCompletion extends CSSCompletion { constructor(lsServiceOptions: LanguageServiceOptions, cssDataManager: CSSDataManager) { super("$", lsServiceOptions, cssDataManager); - addReferencesToDocumentation(SCSSCompletion.scssModuleLoaders); - addReferencesToDocumentation(SCSSCompletion.scssModuleBuiltIns); + addReferencesToDocumentation(SassCompletion.scssModuleLoaders); + addReferencesToDocumentation(SassCompletion.scssModuleBuiltIns); } protected isImportPathParent(type: nodes.NodeType): boolean { @@ -341,7 +341,7 @@ export class SCSSCompletion extends CSSCompletion { const parentType = importPathNode.getParent()!.type; if (parentType === nodes.NodeType.Forward || parentType === nodes.NodeType.Use) { - for (let p of SCSSCompletion.scssModuleBuiltIns) { + for (let p of SassCompletion.scssModuleBuiltIns) { const item: CompletionItem = { label: p.label, documentation: p.documentation, @@ -358,7 +358,7 @@ export class SCSSCompletion extends CSSCompletion { private createReplaceFunction() { let tabStopCounter = 1; return (_match: string, p1: string) => { - return "\\" + p1 + ": ${" + tabStopCounter++ + ":" + (SCSSCompletion.variableDefaults[p1] || "") + "}"; + return "\\" + p1 + ": ${" + tabStopCounter++ + ":" + (SassCompletion.variableDefaults[p1] || "") + "}"; }; } @@ -392,7 +392,7 @@ export class SCSSCompletion extends CSSCompletion { isNested: boolean, result: CompletionList, ): CompletionList { - this.createFunctionProposals(SCSSCompletion.selectorFuncs, null, true, result); + this.createFunctionProposals(SassCompletion.selectorFuncs, null, true, result); return super.getCompletionsForSelector(ruleSet, isNested, result); } @@ -401,7 +401,7 @@ export class SCSSCompletion extends CSSCompletion { existingNode: nodes.Node, result: CompletionList, ): CompletionList { - let functions = SCSSCompletion.builtInFuncs; + let functions = SassCompletion.builtInFuncs; if (entry) { functions = functions.filter((f) => !f.type || !entry.restrictions || entry.restrictions.indexOf(f.type) !== -1); } @@ -410,7 +410,7 @@ export class SCSSCompletion extends CSSCompletion { } protected getColorProposals(entry: IPropertyData, existingNode: nodes.Node, result: CompletionList): CompletionList { - this.createFunctionProposals(SCSSCompletion.colorProposals, existingNode, false, result); + this.createFunctionProposals(SassCompletion.colorProposals, existingNode, false, result); return super.getColorProposals(entry, existingNode, result); } @@ -438,7 +438,7 @@ export class SCSSCompletion extends CSSCompletion { } public getCompletionForAtDirectives(result: CompletionList): CompletionList { - result.items.push(...SCSSCompletion.scssAtDirectives); + result.items.push(...SassCompletion.scssAtDirectives); return result; } @@ -450,7 +450,7 @@ export class SCSSCompletion extends CSSCompletion { } public getCompletionForModuleLoaders(result: CompletionList): CompletionList { - result.items.push(...SCSSCompletion.scssModuleLoaders); + result.items.push(...SassCompletion.scssModuleLoaders); return result; } } diff --git a/packages/vscode-css-languageservice/src/services/scssNavigation.ts b/packages/vscode-css-languageservice/src/services/sassNavigation.ts similarity index 98% rename from packages/vscode-css-languageservice/src/services/scssNavigation.ts rename to packages/vscode-css-languageservice/src/services/sassNavigation.ts index 005b340b..d2ffa608 100644 --- a/packages/vscode-css-languageservice/src/services/scssNavigation.ts +++ b/packages/vscode-css-languageservice/src/services/sassNavigation.ts @@ -10,7 +10,7 @@ import * as nodes from "../parser/cssNodes"; import { URI, Utils } from "vscode-uri"; import { startsWith } from "../utils/strings"; -export class SCSSNavigation extends CSSNavigation { +export class SassNavigation extends CSSNavigation { constructor(fileSystemProvider: FileSystemProvider | undefined) { super(fileSystemProvider, true); } diff --git a/packages/vscode-css-languageservice/src/test/css/completion.test.ts b/packages/vscode-css-languageservice/src/test/css/completion.test.ts index e2f26cef..abbcfcdd 100644 --- a/packages/vscode-css-languageservice/src/test/css/completion.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/completion.test.ts @@ -22,7 +22,7 @@ import { Command, MarkupContent, MixinReferenceCompletionContext, - getSCSSLanguageService, + getSassLanguageService, ICSSDataProvider, newCSSDataProvider, } from "../../cssLanguageService"; @@ -135,7 +135,7 @@ export async function testCompletionFor( const lsOptions = { fileSystemProvider: getFsProvider() }; let ls; if (lang === "scss") { - ls = getSCSSLanguageService(lsOptions); + ls = getSassLanguageService(lsOptions); } else { ls = getCSSLanguageService(lsOptions); } diff --git a/packages/vscode-css-languageservice/src/test/css/hover.test.ts b/packages/vscode-css-languageservice/src/test/css/hover.test.ts index 64bb67d3..34651e8b 100644 --- a/packages/vscode-css-languageservice/src/test/css/hover.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/hover.test.ts @@ -6,13 +6,13 @@ "use strict"; import { suite, test, assert } from "vitest"; -import { Hover, TextDocument, getCSSLanguageService, getSCSSLanguageService } from "../../cssLanguageService"; +import { Hover, TextDocument, getCSSLanguageService, getSassLanguageService } from "../../cssLanguageService"; import { HoverSettings } from "../../cssLanguageTypes"; function assertHover(value: string, expected: Hover, languageId = "css", hoverSettings?: HoverSettings): void { let offset = value.indexOf("|"); value = value.substr(0, offset) + value.substr(offset + 1); - const ls = languageId === "css" ? getCSSLanguageService() : getSCSSLanguageService(); + const ls = languageId === "css" ? getCSSLanguageService() : getSassLanguageService(); const document = TextDocument.create(`test://foo/bar.${languageId}`, languageId, 1, value); const hoverResult = ls.doHover(document, document.positionAt(offset), ls.parseStylesheet(document), hoverSettings); diff --git a/packages/vscode-css-languageservice/src/test/css/selectorPrinting.test.ts b/packages/vscode-css-languageservice/src/test/css/selectorPrinting.test.ts index e4c9771a..80e9ae23 100644 --- a/packages/vscode-css-languageservice/src/test/css/selectorPrinting.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/selectorPrinting.test.ts @@ -12,7 +12,7 @@ import { CSSDataManager } from "../../languageFacts/dataManager"; const cssDataManager = new CSSDataManager({ useDefaultDataProvider: true }); -function elementToString(element: selectorPrinting.Element): string { +export function elementToString(element: selectorPrinting.Element): string { let label = element.findAttribute("name") || ""; let attributes = element.attributes && element.attributes.filter((a) => a.name !== "name"); if (attributes && attributes.length > 0) { diff --git a/packages/vscode-css-languageservice/src/test/sass/scssNavigation.test.ts b/packages/vscode-css-languageservice/src/test/sass/scssNavigation.test.ts index 1e8e9826..72df13c9 100644 --- a/packages/vscode-css-languageservice/src/test/sass/scssNavigation.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/scssNavigation.test.ts @@ -18,7 +18,7 @@ import { assertDocumentSymbols, } from "../css/navigation.test"; import { - getSCSSLanguageService, + getSassLanguageService, TextDocument, SymbolKind, LanguageSettings, @@ -30,7 +30,7 @@ import { getFsProvider } from "../testUtil/fsProvider"; import { getDocumentContext } from "../testUtil/documentContext"; function getSCSSLS() { - return getSCSSLanguageService({ fileSystemProvider: getFsProvider() }); + return getSassLanguageService({ fileSystemProvider: getFsProvider() }); } function aliasSettings(): LanguageSettings { @@ -44,7 +44,7 @@ function aliasSettings(): LanguageSettings { }; } -async function assertDynamicLinks( +export async function assertDynamicLinks( docUri: string, input: string, expected: StylesheetDocumentLink[], @@ -62,7 +62,7 @@ async function assertDynamicLinks( assert.deepEqual(links, expected); } -async function assertNoDynamicLinks(docUri: string, input: string, extecedTarget: string | undefined) { +export async function assertNoDynamicLinks(docUri: string, input: string, extecedTarget: string | undefined) { const ls = getSCSSLS(); const document = TextDocument.create(docUri, "scss", 0, input); diff --git a/packages/vscode-css-languageservice/src/test/sass/selectorPrinting.test.ts b/packages/vscode-css-languageservice/src/test/sass/selectorPrinting.test.ts index 5faefa85..b04fc7ad 100644 --- a/packages/vscode-css-languageservice/src/test/sass/selectorPrinting.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/selectorPrinting.test.ts @@ -3,10 +3,34 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { suite, test } from "vitest"; - +import { suite, test, assert } from "vitest"; +import { TextDocument } from "../../cssLanguageTypes"; +import * as nodes from "../../parser/cssNodes"; import { SassParser } from "../../parser/sassParser"; -import { assertSelector } from "../css/selectorPrinting.test"; +import * as selectorPrinting from "../../services/selectorPrinting"; +import { elementToString } from "../css/selectorPrinting.test"; + +function doParse(p: SassParser, input: string, selectorName: string): nodes.Selector | null { + let ext = p.syntax === "indented" ? "sass" : "scss"; + let document = TextDocument.create(`test://test/test.${ext}`, ext, 0, input); + let styleSheet = p.parseStylesheet(document); + + let node = nodes.getNodeAtOffset(styleSheet, input.indexOf(selectorName)); + if (!node) { + return null; + } + return node.findParent(nodes.NodeType.Selector); +} + +export function assertSelector(p: SassParser, input: string, selectorName: string, expected: string): void { + let selector = doParse(p, input, selectorName); + assert.ok(selector); + + let element = selectorPrinting.selectorToElement(selector!); + assert.ok(element); + + assert.equal(elementToString(element!), expected); +} suite("SCSS - Selector Printing", () => { test("simple selector", function () { From 2998e29b828062a6d4f69a2d09473052276f31fa Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sat, 25 May 2024 14:45:47 +0200 Subject: [PATCH 034/138] refactor: find document links sass indented --- .../src/services/cssNavigation.ts | 33 +- .../src/services/sassNavigation.ts | 15 +- ...pletion.test.ts => sassCompletion.test.ts} | 0 ...igation.test.ts => sassNavigation.test.ts} | 545 +++++++++++++++++- .../node_modules/@foo/foo/package.json | 13 + .../node_modules/bar-indented/package.json | 13 + .../bar-indented/styles/button.sass | 0 .../bar-indented/styles/colors.sass | 0 .../bar-indented/styles/index.sass | 0 .../bar-pattern-indented/package.json | 13 + .../bar-pattern-indented/styles/button.sass | 0 .../bar-pattern-indented/styles/colors.sass | 0 .../bar-pattern-indented/styles/index.sass | 0 .../styles/theme/button.sass | 0 .../styles/theme/colors.sass | 0 .../styles/theme/index.sass | 0 .../node_modules/root-indented/package.json | 3 + .../root-indented/styles/index.sass | 0 .../root-style-indented/package.json | 3 + .../root-style-indented/styles/index.sass | 0 20 files changed, 601 insertions(+), 37 deletions(-) rename packages/vscode-css-languageservice/src/test/sass/{scssCompletion.test.ts => sassCompletion.test.ts} (100%) rename packages/vscode-css-languageservice/src/test/sass/{scssNavigation.test.ts => sassNavigation.test.ts} (59%) create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/@foo/foo/package.json create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-indented/package.json create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-indented/styles/button.sass create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-indented/styles/colors.sass create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-indented/styles/index.sass create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/package.json create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/button.sass create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/colors.sass create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/index.sass create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/theme/button.sass create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/theme/colors.sass create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/theme/index.sass create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-indented/package.json create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-indented/styles/index.sass create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-style-indented/package.json create mode 100644 packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-style-indented/styles/index.sass diff --git a/packages/vscode-css-languageservice/src/services/cssNavigation.ts b/packages/vscode-css-languageservice/src/services/cssNavigation.ts index 4c0760d7..850c7f16 100644 --- a/packages/vscode-css-languageservice/src/services/cssNavigation.ts +++ b/packages/vscode-css-languageservice/src/services/cssNavigation.ts @@ -45,6 +45,7 @@ type DocumentSymbolCollector = ( const startsWithSchemeRegex = /^\w+:\/\//; const startsWithData = /^data:/; const startsWithSass = /^sass:/; +const sassExt = /\.s[ac]ss$/; export class CSSNavigation { protected defaultSettings?: AliasSettings; @@ -646,30 +647,34 @@ export class CSSNavigation { if (packageJson.exports) { if (!subpath) { // look for the default/index export - const entry = + const entry: string = // @ts-expect-error If ['.'] is a string this just produces undefined packageJson.exports["."]["sass"] || // @ts-expect-error If ['.'] is a string this just produces undefined packageJson.exports["."]["style"] || // @ts-expect-error If ['.'] is a string this just produces undefined packageJson.exports["."]["default"]; - // the 'default' entry can be whatever, typically .js – confirm it looks like `scss` - if (entry && entry.endsWith(".scss")) { + // the 'default' entry can be whatever, typically .js – confirm it looks like `scss` or `sass` + if (entry && entry.match(sassExt)) { const entryPath = joinPath(modulePath, entry); return entryPath; } } else { - // The import string may be with or without .scss. + // The import string may be with or without a file extension. // Likewise the exports entry. Look up both paths. // However, they need to be relative (start with ./). - const lookupSubpath = subpath.endsWith(".scss") ? `./${subpath.replace(".scss", "")}` : `./${subpath}`; - const lookupSubpathScss = subpath.endsWith(".scss") ? `./${subpath}` : `./${subpath}.scss`; - const subpathObject = packageJson.exports[lookupSubpathScss] || packageJson.exports[lookupSubpath]; + const lookupSubpath = subpath.match(sassExt) ? `./${subpath.replace(sassExt, "")}` : `./${subpath}`; + const lookupSubpathScss = subpath.match(sassExt) ? `./${subpath}` : `./${subpath}.scss`; + const lookupSubpathSass = subpath.match(sassExt) ? `./${subpath}` : `./${subpath}.sass`; + const subpathObject = + packageJson.exports[lookupSubpathScss] || + packageJson.exports[lookupSubpathSass] || + packageJson.exports[lookupSubpath]; if (subpathObject) { // @ts-expect-error If subpathObject is a string this just produces undefined const entry = subpathObject["sass"] || subpathObject["styles"] || subpathObject["default"]; - // the 'default' entry can be whatever, typically .js – confirm it looks like `scss` - if (entry && entry.endsWith(".scss")) { + // the 'default' entry can be whatever, typically .js – confirm it looks like `scss` or `sass` + if (entry && entry.match(sassExt)) { const entryPath = joinPath(modulePath, entry); return entryPath; } @@ -680,14 +685,14 @@ export class CSSNavigation { if (!maybePattern.includes("*")) { continue; } - // Patterns may also be without `.scss` on the left side, so compare without on both sides - const re = new RegExp(maybePattern.replace("./", "\\./").replace(".scss", "").replace("*", "(.+)")); + // Patterns may also be without a file extension on the left side, so compare without on both sides + const re = new RegExp(maybePattern.replace("./", "\\./").replace(sassExt, "").replace("*", "(.+)")); const match = re.exec(lookupSubpath); if (match) { // @ts-expect-error If subpathObject is a string this just produces undefined - const entry = subpathObject["sass"] || subpathObject["styles"] || subpathObject["default"]; - // the 'default' entry can be whatever, typically .js – confirm it looks like `scss` - if (entry && entry.endsWith(".scss")) { + const entry: string = subpathObject["sass"] || subpathObject["styles"] || subpathObject["default"]; + // the 'default' entry can be whatever, typically .js – confirm it looks like `scss` or `sass` + if (entry && entry.match(sassExt)) { // The right-hand side of a subpath pattern is also a pattern. // Replace the pattern with the match from our regexp capture group above. const expandedPattern = entry.replace("*", match[1]); diff --git a/packages/vscode-css-languageservice/src/services/sassNavigation.ts b/packages/vscode-css-languageservice/src/services/sassNavigation.ts index d2ffa608..02e9075c 100644 --- a/packages/vscode-css-languageservice/src/services/sassNavigation.ts +++ b/packages/vscode-css-languageservice/src/services/sassNavigation.ts @@ -54,17 +54,20 @@ function toPathVariations(target: string): DocumentUri[] { return [target]; } - // If a link is like a/, try resolving a/index.scss and a/_index.scss + // If a link is like a/, try resolving a/index.scss and a/_index.scss, and likewise for .sass if (target.endsWith("/")) { - return [target + "index.scss", target + "_index.scss"]; + return [target + "index.scss", target + "_index.scss", target + "index.sass", target + "_index.sass"]; } - const targetUri = URI.parse(target.replace(/\.scss$/, "")); + const targetUri = URI.parse(target.replace(/\.s[ac]ss$/, "")); const basename = Utils.basename(targetUri); const dirname = Utils.dirname(targetUri); if (basename.startsWith("_")) { // No variation for links such as _a - return [Utils.joinPath(dirname, basename + ".scss").toString(true)]; + return [ + Utils.joinPath(dirname, basename + ".scss").toString(true), + Utils.joinPath(dirname, basename + ".sass").toString(true), + ]; } return [ @@ -72,6 +75,10 @@ function toPathVariations(target: string): DocumentUri[] { Utils.joinPath(dirname, "_" + basename + ".scss").toString(true), target + "/index.scss", target + "/_index.scss", + Utils.joinPath(dirname, basename + ".sass").toString(true), + Utils.joinPath(dirname, "_" + basename + ".sass").toString(true), + target + "/index.sass", + target + "/_index.sass", Utils.joinPath(dirname, basename + ".css").toString(true), ]; } diff --git a/packages/vscode-css-languageservice/src/test/sass/scssCompletion.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassCompletion.test.ts similarity index 100% rename from packages/vscode-css-languageservice/src/test/sass/scssCompletion.test.ts rename to packages/vscode-css-languageservice/src/test/sass/sassCompletion.test.ts diff --git a/packages/vscode-css-languageservice/src/test/sass/scssNavigation.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts similarity index 59% rename from packages/vscode-css-languageservice/src/test/sass/scssNavigation.test.ts rename to packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts index 72df13c9..610d68ae 100644 --- a/packages/vscode-css-languageservice/src/test/sass/scssNavigation.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts @@ -23,6 +23,8 @@ import { SymbolKind, LanguageSettings, StylesheetDocumentLink, + Range, + Position, } from "../../cssLanguageService"; import * as path from "path"; import { URI } from "vscode-uri"; @@ -54,7 +56,7 @@ export async function assertDynamicLinks( if (settings) { ls.configure(settings); } - const document = TextDocument.create(docUri, "scss", 0, input); + const document = TextDocument.create(docUri, docUri.endsWith(".sass") ? "sass" : "scss", 0, input); const stylesheet = ls.parseStylesheet(document); @@ -64,7 +66,7 @@ export async function assertDynamicLinks( export async function assertNoDynamicLinks(docUri: string, input: string, extecedTarget: string | undefined) { const ls = getSCSSLS(); - const document = TextDocument.create(docUri, "scss", 0, input); + const document = TextDocument.create(docUri, docUri.endsWith(".sass") ? "sass" : "scss", 0, input); const stylesheet = ls.parseStylesheet(document); @@ -245,14 +247,23 @@ suite("SCSS - Navigation", () => { }; await assertNoDynamicLinks(getDocumentUri("./index.scss"), `@import 'foo'`, getDocumentUri("foo")); - await assertNoDynamicLinks(getDocumentUri("./index.scss"), `@import './foo'`, getDocumentUri("foo")); - await assertNoDynamicLinks(getDocumentUri("./index.scss"), `@import './_foo'`, getDocumentUri("_foo")); - await assertNoDynamicLinks(getDocumentUri("./index.scss"), `@import './foo-baz'`, getDocumentUri("foo-baz")); }); + test("Invalid Sass partial file links", async () => { + const fixtureRoot = path.resolve(__dirname, "../../../src/test/sass/linkFixture/non-existent"); + const getDocumentUri = (relativePath: string) => { + return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true); + }; + + await assertNoDynamicLinks(getDocumentUri("./index.sass"), `@import 'foo'`, getDocumentUri("foo")); + await assertNoDynamicLinks(getDocumentUri("./index.sass"), `@import './foo'`, getDocumentUri("foo")); + await assertNoDynamicLinks(getDocumentUri("./index.sass"), `@import './_foo'`, getDocumentUri("_foo")); + await assertNoDynamicLinks(getDocumentUri("./index.sass"), `@import './foo-baz'`, getDocumentUri("foo-baz")); + }); + test("SCSS partial file dynamic links", async () => { const fixtureRoot = path.resolve(__dirname, "../../../src/test/sass/linkFixture"); const getDocumentUri = (relativePath: string) => { @@ -262,32 +273,60 @@ suite("SCSS - Navigation", () => { await assertDynamicLinks(getDocumentUri("./noUnderscore/index.scss"), `@import 'foo'`, [ { range: newRange(8, 13), target: getDocumentUri("./noUnderscore/foo.scss"), type: nodes.NodeType.Import }, ]); - await assertDynamicLinks(getDocumentUri("./underscore/index.scss"), `@import 'foo'`, [ { range: newRange(8, 13), target: getDocumentUri("./underscore/_foo.scss"), type: nodes.NodeType.Import }, ]); - await assertDynamicLinks(getDocumentUri("./underscore/index.scss"), `@import 'foo.scss'`, [ { range: newRange(8, 18), target: getDocumentUri("./underscore/_foo.scss"), type: nodes.NodeType.Import }, ]); - await assertDynamicLinks(getDocumentUri("./both/index.scss"), `@import 'foo'`, [ { range: newRange(8, 13), target: getDocumentUri("./both/foo.scss"), type: nodes.NodeType.Import }, ]); - await assertDynamicLinks(getDocumentUri("./both/index.scss"), `@import '_foo'`, [ { range: newRange(8, 14), target: getDocumentUri("./both/_foo.scss"), type: nodes.NodeType.Import }, ]); - await assertDynamicLinks(getDocumentUri("./index/index.scss"), `@import 'foo'`, [ { range: newRange(8, 13), target: getDocumentUri("./index/foo/index.scss"), type: nodes.NodeType.Import }, ]); - await assertDynamicLinks(getDocumentUri("./index/index.scss"), `@import 'bar'`, [ { range: newRange(8, 13), target: getDocumentUri("./index/bar/_index.scss"), type: nodes.NodeType.Import }, ]); }); + test("Sass partial file dynamic links", async () => { + const fixtureRoot = path.resolve(__dirname, "../../../src/test/sass/linkFixture"); + const getDocumentUri = (relativePath: string) => { + return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true); + }; + + await assertDynamicLinks(getDocumentUri("./underscore/index.sass"), `@import 'bar'`, [ + { range: newRange(8, 13), target: getDocumentUri("./underscore/_bar.sass"), type: nodes.NodeType.Import }, + ]); + await assertDynamicLinks(getDocumentUri("./underscore/index.sass"), `@import 'bar.sass'`, [ + { range: newRange(8, 18), target: getDocumentUri("./underscore/_bar.sass"), type: nodes.NodeType.Import }, + ]); + await assertDynamicLinks(getDocumentUri("./both/index.sass"), `@import 'bar'`, [ + { range: newRange(8, 13), target: getDocumentUri("./both/bar.sass"), type: nodes.NodeType.Import }, + ]); + await assertDynamicLinks(getDocumentUri("./both/index.sass"), `@import '_bar'`, [ + { range: newRange(8, 14), target: getDocumentUri("./both/_bar.sass"), type: nodes.NodeType.Import }, + ]); + await assertDynamicLinks(getDocumentUri("./indented-index/index.sass"), `@import "foo"`, [ + { + range: newRange(8, 13), + target: getDocumentUri("./indented-index/foo/index.sass"), + type: nodes.NodeType.Import, + }, + ]); + await assertDynamicLinks(getDocumentUri("./indented-index/index.sass"), `@import "bar"`, [ + { + range: newRange(8, 13), + target: getDocumentUri("./indented-index/bar/_index.sass"), + type: nodes.NodeType.Import, + }, + ]); + }); + test("SCSS straight links", async () => { const ls = getSCSSLS(); @@ -297,7 +336,6 @@ suite("SCSS - Navigation", () => { [{ range: newRange(8, 17), target: "test://test/foo.css", type: nodes.NodeType.Import }], "scss", ); - await assertLinks(ls, `@import 'foo.scss' print;`, [ { range: newRange(8, 18), target: "test://test/foo.scss", type: nodes.NodeType.Import }, ]); @@ -307,12 +345,37 @@ suite("SCSS - Navigation", () => { [{ range: newRange(8, 32), target: "http://foo.com/foo.css", type: nodes.NodeType.Import }], "scss", ); - await assertLinks(ls, `@import url("foo.css") print;`, [ { range: newRange(12, 21), target: "test://test/foo.css" }, ]); }); + test("Sass straight links", async () => { + const ls = getSCSSLS(); + + await assertLinks( + ls, + `@import 'foo.css'`, + [{ range: newRange(8, 17), target: "test://test/foo.css", type: nodes.NodeType.Import }], + "sass", + ); + await assertLinks(ls, `@import 'foo.sass' print`, [ + { range: newRange(8, 18), target: "test://test/foo.sass", type: nodes.NodeType.Import }, + ]); + await assertLinks( + ls, + `@import 'http://foo.com/foo.css'`, + [{ range: newRange(8, 32), target: "http://foo.com/foo.css", type: nodes.NodeType.Import }], + "sass", + ); + await assertLinks( + ls, + `@import url("foo.css") print`, + [{ range: newRange(12, 21), target: "test://test/foo.css" }], + "sass", + ); + }); + test("SCSS aliased links", async function () { const fixtureRoot = path.resolve(__dirname, "../../../src/test/sass/linkFixture"); const getDocumentUri = (relativePath: string) => { @@ -356,6 +419,52 @@ suite("SCSS - Navigation", () => { ); }); + test("Sass aliased links", async function () { + const fixtureRoot = path.resolve(__dirname, "../../../src/test/sass/linkFixture"); + const getDocumentUri = (relativePath: string) => { + return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true); + }; + + const settings: LanguageSettings = { + importAliases: { + "@SassStylesheet": "/src/assets/styles.sass", + "@NoUnderscoreDir/": "/noUnderscore/", + "@UnderscoreDir/": "/underscore/", + "@BothDir/": "/both/", + }, + }; + const ls = getSCSSLS(); + ls.configure(settings); + + await assertLinks(ls, '@import "@SassStylesheet"', [ + { range: newRange(8, 25), target: "test://test/src/assets/styles.sass", type: nodes.NodeType.Import }, + ]); + await assertDynamicLinks( + getDocumentUri("./"), + `@import '@NoUnderscoreDir/bar'`, + [{ range: newRange(8, 30), target: getDocumentUri("./noUnderscore/bar.sass"), type: nodes.NodeType.Import }], + settings, + ); + await assertDynamicLinks( + getDocumentUri("./"), + `@import '@UnderscoreDir/bar'`, + [{ range: newRange(8, 28), target: getDocumentUri("./underscore/_bar.sass"), type: nodes.NodeType.Import }], + settings, + ); + await assertDynamicLinks( + getDocumentUri("./"), + `@import '@BothDir/bar'`, + [{ range: newRange(8, 22), target: getDocumentUri("./both/bar.sass"), type: nodes.NodeType.Import }], + settings, + ); + await assertDynamicLinks( + getDocumentUri("./"), + `@import '@BothDir/_bar'`, + [{ range: newRange(8, 23), target: getDocumentUri("./both/_bar.sass"), type: nodes.NodeType.Import }], + settings, + ); + }); + test("SCSS module file links", async () => { const fixtureRoot = path.resolve(__dirname, "../../../src/test/sass/linkFixture/module"); const getDocumentUri = (relativePath: string) => { @@ -371,11 +480,9 @@ suite("SCSS - Navigation", () => { namespace: "f", }, ]); - await assertDynamicLinks(getDocumentUri("./index.scss"), `@use 'sass:math' as *;`, [ { range: newRange(5, 16), type: nodes.NodeType.Use, as: "*", target: "sass:math" }, ]); - await assertDynamicLinks(getDocumentUri("./index.scss"), `@forward './foo' hide $private;`, [ { range: newRange(9, 16), @@ -384,7 +491,6 @@ suite("SCSS - Navigation", () => { hide: ["$private"], }, ]); - await assertDynamicLinks(getDocumentUri("./index.scss"), `@forward './foo' show $public;`, [ { range: newRange(9, 16), @@ -393,7 +499,6 @@ suite("SCSS - Navigation", () => { show: ["$public"], }, ]); - await assertDynamicLinks(getDocumentUri("./index.scss"), `@forward './foo' as foo-*;`, [ { range: newRange(9, 16), @@ -402,7 +507,6 @@ suite("SCSS - Navigation", () => { as: "foo-", }, ]); - await assertDynamicLinks(getDocumentUri("./index.scss"), `@forward './foo' as foo-* hide $private;`, [ { range: newRange(9, 16), @@ -412,7 +516,6 @@ suite("SCSS - Navigation", () => { hide: ["$private"], }, ]); - await assertDynamicLinks(getDocumentUri("./index.scss"), `@use 'sass:math';`, [ { range: newRange(5, 16), type: nodes.NodeType.Use, namespace: "math", target: "sass:math" }, ]); @@ -423,6 +526,67 @@ suite("SCSS - Navigation", () => { ); }); + test("Sass module file links", async () => { + const fixtureRoot = path.resolve(__dirname, "../../../src/test/sass/linkFixture/module"); + const getDocumentUri = (relativePath: string) => { + return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true); + }; + + await assertDynamicLinks(getDocumentUri("./index.sass"), `@use './bar' as f`, [ + { + range: newRange(5, 12), + target: getDocumentUri("./bar.sass"), + type: nodes.NodeType.Use, + as: "f", + namespace: "f", + }, + ]); + await assertDynamicLinks(getDocumentUri("./index.sass"), `@use 'sass:math' as *`, [ + { range: newRange(5, 16), type: nodes.NodeType.Use, as: "*", target: "sass:math" }, + ]); + await assertDynamicLinks(getDocumentUri("./index.sass"), `@forward './bar' hide $private`, [ + { + range: newRange(9, 16), + target: getDocumentUri("./bar.sass"), + type: nodes.NodeType.Forward, + hide: ["$private"], + }, + ]); + await assertDynamicLinks(getDocumentUri("./index.sass"), `@forward './bar' show $public`, [ + { + range: newRange(9, 16), + target: getDocumentUri("./bar.sass"), + type: nodes.NodeType.Forward, + show: ["$public"], + }, + ]); + await assertDynamicLinks(getDocumentUri("./index.sass"), `@forward './bar' as bar-*`, [ + { + range: newRange(9, 16), + target: getDocumentUri("./bar.sass"), + type: nodes.NodeType.Forward, + as: "bar-", + }, + ]); + await assertDynamicLinks(getDocumentUri("./index.sass"), `@forward './bar' as bar-* hide $private`, [ + { + range: newRange(9, 16), + target: getDocumentUri("./bar.sass"), + type: nodes.NodeType.Forward, + as: "bar-", + hide: ["$private"], + }, + ]); + await assertDynamicLinks(getDocumentUri("./index.sass"), `@use 'sass:math'`, [ + { range: newRange(5, 16), type: nodes.NodeType.Use, namespace: "math", target: "sass:math" }, + ]); + await assertNoDynamicLinks( + getDocumentUri("./index.sass"), + `@use './non-existent'`, + getDocumentUri("non-existent"), + ); + }); + test("SCSS empty path", async () => { const ls = getSCSSLS(); @@ -438,6 +602,27 @@ suite("SCSS - Navigation", () => { ); }); + test("Sass empty path", async () => { + const ls = getSCSSLS(); + + /** + * https://github.com/microsoft/vscode/issues/79215 + * No valid path — gradient-verlay.png is authority and path is '' + */ + await assertLinks( + ls, + `#navigation + background: #3d3d3d url(gantry-media://gradient-overlay.png)`, + [ + { + range: Range.create(Position.create(1, 25), Position.create(1, 60)), + target: "gantry-media://gradient-overlay.png", + }, + ], + "sass", + ); + }); + test("SCSS node module resolving", async function () { let ls = getSCSSLS(); let testUri = getTestResource("about.scss"); @@ -521,6 +706,101 @@ suite("SCSS - Navigation", () => { ); }); + test("Sass node module resolving", async function () { + let ls = getSCSSLS(); + let testUri = getTestResource("about.sass"); + let workspaceFolder = getTestResource(""); + + await assertLinks( + ls, + `html + background-image: url("~foo/hello.html")`, + [ + { + range: Range.create(Position.create(1, 23), Position.create(1, 40)), + target: getTestResource("node_modules/foo/hello.html"), + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `html + background-image: url("foo/hello.html")`, + [ + { + range: Range.create(Position.create(1, 23), Position.create(1, 39)), + target: getTestResource("node_modules/foo/hello.html"), + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use '@foo/bar/baz'`, + [ + { + range: newRange(5, 19), + target: getTestResource("node_modules/@foo/bar/_baz.scss"), + type: nodes.NodeType.Use, + namespace: "baz", + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use '@foo/bar'`, + [ + { + range: newRange(5, 15), + target: getTestResource("node_modules/@foo/bar/_index.scss"), + type: nodes.NodeType.Use, + namespace: "bar", + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + '@import "green/d"', + [{ range: newRange(8, 17), target: getTestResource("green/d.scss"), type: nodes.NodeType.Import }], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + '@import "./green/d"', + [{ range: newRange(8, 19), target: getTestResource("green/d.scss"), type: nodes.NodeType.Import }], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + '@import "green/e"', + [ + { + range: newRange(8, 17), + target: getTestResource("node_modules/green/_e.scss"), + type: nodes.NodeType.Import, + }, + ], + "sass", + testUri, + workspaceFolder, + ); + }); + test("SCSS node package resolving", async () => { let ls = getSCSSLS(); let testUri = getTestResource("about.scss"); @@ -721,6 +1001,233 @@ suite("SCSS - Navigation", () => { workspaceFolder, ); }); + + test("Sass node package resolving", async () => { + let ls = getSCSSLS(); + let testUri = getTestResource("about.sass"); + let workspaceFolder = getTestResource(""); + await assertLinks( + ls, + `@use "pkg:bar-indented"`, + [ + { + namespace: "bar-indented", + range: newRange(5, 23), + target: getTestResource("node_modules/bar-indented/styles/index.sass"), + type: nodes.NodeType.Use, + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:bar-indented/colors"`, + [ + { + namespace: "colors", + range: newRange(5, 30), + target: getTestResource("node_modules/bar-indented/styles/colors.sass"), + type: nodes.NodeType.Use, + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:bar-indented/colors.sass"`, + [ + { + namespace: "colors", + range: newRange(5, 35), + target: getTestResource("node_modules/bar-indented/styles/colors.sass"), + type: nodes.NodeType.Use, + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:@foo/foo"`, + [ + { + namespace: "foo", + range: newRange(5, 19), + target: getTestResource("node_modules/@foo/foo/styles/index.sass"), + type: nodes.NodeType.Use, + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:@foo/foo/colors"`, + [ + { + namespace: "colors", + range: newRange(5, 26), + target: getTestResource("node_modules/@foo/foo/styles/colors.sass"), + type: nodes.NodeType.Use, + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:@foo/foo/colors.sass"`, + [ + { + namespace: "colors", + range: newRange(5, 31), + target: getTestResource("node_modules/@foo/foo/styles/colors.sass"), + type: nodes.NodeType.Use, + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:@foo/foo/button"`, + [ + { + namespace: "button", + range: newRange(5, 26), + target: getTestResource("node_modules/@foo/foo/styles/button.sass"), + type: nodes.NodeType.Use, + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:@foo/foo/button.sass"`, + [ + { + namespace: "button", + range: newRange(5, 31), + target: getTestResource("node_modules/@foo/foo/styles/button.sass"), + type: nodes.NodeType.Use, + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:root-indented"`, + [ + { + namespace: "root-indented", + range: newRange(5, 24), + target: getTestResource("node_modules/root-indented/styles/index.sass"), + type: nodes.NodeType.Use, + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:root-style-indented"`, + [ + { + namespace: "root-style-indented", + range: newRange(5, 30), + target: getTestResource("node_modules/root-style-indented/styles/index.sass"), + type: nodes.NodeType.Use, + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:bar-pattern-indented/anything"`, + [ + { + namespace: "anything", + range: newRange(5, 40), + target: getTestResource("node_modules/bar-pattern-indented/styles/anything.sass"), + type: nodes.NodeType.Use, + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:bar-pattern-indented/anything.sass"`, + [ + { + namespace: "anything", + range: newRange(5, 45), + target: getTestResource("node_modules/bar-pattern-indented/styles/anything.sass"), + type: nodes.NodeType.Use, + }, + ], + "sass", + testUri, + workspaceFolder, + ); + await assertLinks( + ls, + `@use "pkg:bar-pattern-indented/theme/dark.sass"`, + [ + { + namespace: "dark", + range: newRange(5, 47), + target: getTestResource("node_modules/bar-pattern-indented/styles/theme/dark.sass"), + type: nodes.NodeType.Use, + }, + ], + "sass", + testUri, + workspaceFolder, + ); + }); + + test("links between Sass and SCSS", async () => { + const fixtureRoot = path.resolve(__dirname, "../../../src/test/sass/linkFixture/module"); + const getDocumentUri = (relativePath: string) => { + return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true); + }; + + // scss using sass + await assertDynamicLinks(getDocumentUri("./index.scss"), `@use './bar'`, [ + { + range: newRange(5, 12), + target: getDocumentUri("./bar.sass"), + type: nodes.NodeType.Use, + namespace: "bar", + }, + ]); + // sass using scss + await assertDynamicLinks(getDocumentUri("./index.sass"), `@use './foo'`, [ + { + range: newRange(5, 12), + target: getDocumentUri("./foo.scss"), + type: nodes.NodeType.Use, + namespace: "foo", + }, + ]); + }); }); suite("Symbols", () => { diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/@foo/foo/package.json b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/@foo/foo/package.json new file mode 100644 index 00000000..82c8f4cc --- /dev/null +++ b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/@foo/foo/package.json @@ -0,0 +1,13 @@ +{ + "exports": { + ".": { + "sass": "./styles/index.sass" + }, + "./colors.sass": { + "sass": "./styles/colors.sass" + }, + "./button": { + "sass": "./styles/button.sass" + } + } +} diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-indented/package.json b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-indented/package.json new file mode 100644 index 00000000..82c8f4cc --- /dev/null +++ b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-indented/package.json @@ -0,0 +1,13 @@ +{ + "exports": { + ".": { + "sass": "./styles/index.sass" + }, + "./colors.sass": { + "sass": "./styles/colors.sass" + }, + "./button": { + "sass": "./styles/button.sass" + } + } +} diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-indented/styles/button.sass b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-indented/styles/button.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-indented/styles/colors.sass b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-indented/styles/colors.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-indented/styles/index.sass b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-indented/styles/index.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/package.json b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/package.json new file mode 100644 index 00000000..2be12a86 --- /dev/null +++ b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/package.json @@ -0,0 +1,13 @@ +{ + "exports": { + ".": { + "sass": "./styles/index.sass" + }, + "./*.sass": { + "sass": "./styles/*.sass" + }, + "./theme/*": { + "sass": "./styles/theme/*.sass" + } + } +} diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/button.sass b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/button.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/colors.sass b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/colors.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/index.sass b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/index.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/theme/button.sass b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/theme/button.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/theme/colors.sass b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/theme/colors.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/theme/index.sass b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/bar-pattern-indented/styles/theme/index.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-indented/package.json b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-indented/package.json new file mode 100644 index 00000000..c88b999d --- /dev/null +++ b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-indented/package.json @@ -0,0 +1,3 @@ +{ + "sass": "./styles/index.sass" +} diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-indented/styles/index.sass b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-indented/styles/index.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-style-indented/package.json b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-style-indented/package.json new file mode 100644 index 00000000..52441887 --- /dev/null +++ b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-style-indented/package.json @@ -0,0 +1,3 @@ +{ + "style": "./styles/index.sass" +} diff --git a/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-style-indented/styles/index.sass b/packages/vscode-css-languageservice/test/linksTestFixtures/node_modules/root-style-indented/styles/index.sass new file mode 100644 index 00000000..e69de29b From 9a4cbc17f45e884474e1bd326eb12e2c6378325a Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sat, 25 May 2024 14:58:56 +0200 Subject: [PATCH 035/138] test: scope navigation test --- .../src/test/css/navigation.test.ts | 8 +- .../src/test/sass/sassNavigation.test.ts | 224 ++++++++++++++++-- 2 files changed, 212 insertions(+), 20 deletions(-) diff --git a/packages/vscode-css-languageservice/src/test/css/navigation.test.ts b/packages/vscode-css-languageservice/src/test/css/navigation.test.ts index d04a6527..7b7048d3 100644 --- a/packages/vscode-css-languageservice/src/test/css/navigation.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/navigation.test.ts @@ -33,8 +33,8 @@ import { URI } from "vscode-uri"; import { getFsProvider } from "../testUtil/fsProvider"; import { getDocumentContext } from "../testUtil/documentContext"; -export function assertScopesAndSymbols(ls: LanguageService, input: string, expected: string): void { - const global = createScope(ls, input); +export function assertScopesAndSymbols(ls: LanguageService, input: string, expected: string, lang = "css"): void { + const global = createScope(ls, input, lang); assert.equal(scopeToString(global), expected); } @@ -217,8 +217,8 @@ function assertNoErrors(stylesheet: Stylesheet): void { } } -function createScope(ls: LanguageService, input: string): Scope { - const document = TextDocument.create("test://test/test.css", "css", 0, input); +export function createScope(ls: LanguageService, input: string, lang = "css"): Scope { + const document = TextDocument.create(`test://test/test.${lang}`, `${lang}`, 0, input); const styleSheet = ls.parseStylesheet(document), global = new GlobalScope(), diff --git a/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts index 610d68ae..273adbae 100644 --- a/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts @@ -8,7 +8,6 @@ import { suite, test, assert } from "vitest"; import * as nodes from "../../parser/cssNodes"; import { - assertSymbolsInScope, assertScopesAndSymbols, assertHighlights, assertColorSymbols, @@ -16,6 +15,7 @@ import { newRange, getTestResource, assertDocumentSymbols, + createScope, } from "../css/navigation.test"; import { getSassLanguageService, @@ -79,60 +79,252 @@ export async function assertNoDynamicLinks(docUri: string, input: string, extece } } +export function assertSymbolsInScope( + lang: "scss" | "sass", + input: string, + offset: number, + ...selections: { name: string; type: nodes.ReferenceType }[] +): void { + const ls = getSCSSLS(); + const global = createScope(ls, input, lang); + + const scope = global.findScope(offset)!; + + const getErrorMessage = function (name: string) { + let all = "symbol " + name + " not found. In scope: "; + scope.getSymbols().forEach((sym) => { + all += sym.name + " "; + }); + return all; + }; + + for (let i = 0; i < selections.length; i++) { + const selection = selections[i]; + const sym = scope.getSymbol(selection.name, selection.type) || global.getSymbol(selection.name, selection.type); + assert.ok(!!sym, getErrorMessage(selection.name)); + } +} + suite("SCSS - Navigation", () => { suite("Scopes and Symbols", () => { test("symbols in scopes", () => { const ls = getSCSSLS(); - assertSymbolsInScope(ls, "$var: iable;", 0, { name: "$var", type: nodes.ReferenceType.Variable }); - assertSymbolsInScope(ls, "$var: iable;", 11, { name: "$var", type: nodes.ReferenceType.Variable }); + assertSymbolsInScope("scss", "$var: iable;", 0, { name: "$var", type: nodes.ReferenceType.Variable }); + assertSymbolsInScope("scss", "$var: iable;", 11, { name: "$var", type: nodes.ReferenceType.Variable }); assertSymbolsInScope( - ls, + "scss", "$var: iable; .class { $color: blue; }", 11, { name: "$var", type: nodes.ReferenceType.Variable }, { name: ".class", type: nodes.ReferenceType.Rule }, ); - assertSymbolsInScope(ls, "$var: iable; .class { $color: blue; }", 22, { + assertSymbolsInScope("scss", "$var: iable; .class { $color: blue; }", 22, { name: "$color", type: nodes.ReferenceType.Variable, }); - assertSymbolsInScope(ls, "$var: iable; .class { $color: blue; }", 36, { + assertSymbolsInScope("scss", "$var: iable; .class { $color: blue; }", 36, { name: "$color", type: nodes.ReferenceType.Variable, }); - assertSymbolsInScope(ls, '@namespace "x"; @mixin mix() {}', 0, { name: "mix", type: nodes.ReferenceType.Mixin }); - assertSymbolsInScope(ls, "@mixin mix { @mixin nested() {} }", 12, { + assertSymbolsInScope("scss", '@namespace "x"; @mixin mix() {}', 0, { + name: "mix", + type: nodes.ReferenceType.Mixin, + }); + assertSymbolsInScope("scss", "@mixin mix { @mixin nested() {} }", 12, { name: "nested", type: nodes.ReferenceType.Mixin, }); - assertSymbolsInScope(ls, "@mixin mix () { @mixin nested() {} }", 13); + assertSymbolsInScope("scss", "@mixin mix () { @mixin nested() {} }", 13); + }); + + test("Sass symbols in scopes", () => { + assertSymbolsInScope("sass", "$var: iable", 0, { name: "$var", type: nodes.ReferenceType.Variable }); + assertSymbolsInScope("sass", "$var: iable", 11, { name: "$var", type: nodes.ReferenceType.Variable }); + assertSymbolsInScope( + "sass", + `$var: iable +.class + $color: blue`, + 11, + { name: "$var", type: nodes.ReferenceType.Variable }, + { name: ".class", type: nodes.ReferenceType.Rule }, + ); + assertSymbolsInScope( + "sass", + `$var: iable +.class + $color: blue`, + 22, + { + name: "$color", + type: nodes.ReferenceType.Variable, + }, + ); + assertSymbolsInScope( + "sass", + `$var: iable +.class + $color: blue`, + 32, + { + name: "$color", + type: nodes.ReferenceType.Variable, + }, + ); + + assertSymbolsInScope( + "sass", + `@namespace "x" +@mixin mix() + content: "hello"`, + 0, + { + name: "mix", + type: nodes.ReferenceType.Mixin, + }, + ); + assertSymbolsInScope( + "sass", + `@mixin mix + @mixin nested() + content: "hello"`, + 12, + { + name: "nested", + type: nodes.ReferenceType.Mixin, + }, + ); + assertSymbolsInScope( + "sass", + `@mixin mix() + @mixin nested() + content: "hello"`, + 13, + ); }); test("scopes and symbols", () => { const ls = getSCSSLS(); - assertScopesAndSymbols(ls, "$var1: 1; $var2: 2; .foo { $var3: 3; }", "$var1,$var2,.foo,[$var3]"); + assertScopesAndSymbols(ls, "$var1: 1; $var2: 2; .foo { $var3: 3; }", "$var1,$var2,.foo,[$var3]", "scss"); assertScopesAndSymbols( ls, "@mixin mixin1 { $var0: 1} @mixin mixin2($var1) { $var3: 3 }", "mixin1,mixin2,[$var0],[$var1,$var3]", + "scss", ); - assertScopesAndSymbols(ls, "a b { $var0: 1; c { d { } } }", "[$var0,c,[d,[]]]"); - assertScopesAndSymbols(ls, "@function a($p1: 1, $p2: 2) { $v1: 3; @return $v1; }", "a,[$p1,$p2,$v1]"); + assertScopesAndSymbols(ls, "a b { $var0: 1; c { d { } } }", "[$var0,c,[d,[]]]", "scss"); + assertScopesAndSymbols(ls, "@function a($p1: 1, $p2: 2) { $v1: 3; @return $v1; }", "a,[$p1,$p2,$v1]", "scss"); assertScopesAndSymbols( ls, "$var1: 3; @if $var1 == 2 { $var2: 1; } @else { $var2: 2; $var3: 2;} ", "$var1,[$var2],[$var2,$var3]", + "scss", ); assertScopesAndSymbols( ls, "@if $var1 == 2 { $var2: 1; } @else if $var1 == 2 { $var3: 2; } @else { $var3: 2; } ", "[$var2],[$var3],[$var3]", + "scss", + ); + assertScopesAndSymbols(ls, "$var1: 3; @while $var1 < 2 { #rule { a: b; } }", "$var1,[#rule,[]]", "scss"); + assertScopesAndSymbols(ls, "$i:0; @each $name in f1, f2, f3 { $i:$i+1; }", "$i,[$name,$i]", "scss"); + assertScopesAndSymbols(ls, "$i:0; @for $x from $i to 5 { }", "$i,[$x]", "scss"); + assertScopesAndSymbols(ls, "@each $i, $j, $k in f1, f2, f3 { }", "[$i,$j,$k]", "scss"); + }); + + test("Sass scopes and symbols", () => { + const ls = getSCSSLS(); + assertScopesAndSymbols( + ls, + `$var1: 1 +$var2: 2 +.foo + $var3: 3`, + "$var1,$var2,.foo,[$var3]", + "sass", + ); + assertScopesAndSymbols( + ls, + `@mixin mixin1 + $var0: 1 +@mixin mixin2($var1) + $var3: 3`, + "mixin1,mixin2,[$var0],[$var1,$var3]", + "sass", + ); + assertScopesAndSymbols( + ls, + `a b + $var0: 1 + c + d + //`, + "[$var0,c,[d,[]]]", + "sass", + ); + assertScopesAndSymbols( + ls, + `@function a($p1: 1, $p2: 2) + $v1: 3 + @return $v1`, + "a,[$p1,$p2,$v1]", + "sass", + ); + assertScopesAndSymbols( + ls, + `$var1: 3 +@if $var1 == 2 + $var2: 1 +@else + $var2: 2 + $var3: 2`, + "$var1,[$var2],[$var2,$var3]", + "sass", + ); + assertScopesAndSymbols( + ls, + `@if $var1 == 2 + $var2: 1 +@else if $var1 == 2 + $var3: 2 +@else + $var3: 2`, + "[$var2],[$var3],[$var3]", + "sass", + ); + assertScopesAndSymbols( + ls, + `$var1: 3 +@while $var1 < 2 + #rule + a: b`, + "$var1,[#rule,[]]", + "sass", + ); + assertScopesAndSymbols( + ls, + `$i:0 +@each $name in f1, f2, f3 + $i:$i+1`, + "$i,[$name,$i]", + "sass", + ); + assertScopesAndSymbols( + ls, + `$i:0 +@for $x from $i to 5 + //`, + "$i,[$x]", + "sass", + ); + assertScopesAndSymbols( + ls, + `@each $i, $j, $k in f1, f2, f3 + //`, + "[$i,$j,$k]", + "sass", ); - assertScopesAndSymbols(ls, "$var1: 3; @while $var1 < 2 { #rule { a: b; } }", "$var1,[#rule,[]]"); - assertScopesAndSymbols(ls, "$i:0; @each $name in f1, f2, f3 { $i:$i+1; }", "$i,[$name,$i]"); - assertScopesAndSymbols(ls, "$i:0; @for $x from $i to 5 { }", "$i,[$x]"); - assertScopesAndSymbols(ls, "@each $i, $j, $k in f1, f2, f3 { }", "[$i,$j,$k]"); }); }); From 13fc0a806cb726f9e748183608a94050f1c5b513 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sat, 25 May 2024 15:26:03 +0200 Subject: [PATCH 036/138] test: sass navigation tests --- .../src/test/css/navigation.test.ts | 10 +- .../src/test/sass/sassNavigation.test.ts | 316 ++++++++++++++++++ 2 files changed, 324 insertions(+), 2 deletions(-) diff --git a/packages/vscode-css-languageservice/src/test/css/navigation.test.ts b/packages/vscode-css-languageservice/src/test/css/navigation.test.ts index 7b7048d3..2329d418 100644 --- a/packages/vscode-css-languageservice/src/test/css/navigation.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/navigation.test.ts @@ -33,7 +33,12 @@ import { URI } from "vscode-uri"; import { getFsProvider } from "../testUtil/fsProvider"; import { getDocumentContext } from "../testUtil/documentContext"; -export function assertScopesAndSymbols(ls: LanguageService, input: string, expected: string, lang = "css"): void { +export function assertScopesAndSymbols( + ls: LanguageService, + input: string, + expected: string, + lang: "css" | "sass" | "scss" = "css", +): void { const global = createScope(ls, input, lang); assert.equal(scopeToString(global), expected); } @@ -45,8 +50,9 @@ export function assertHighlights( expectedMatches: number, expectedWrites: number, elementName?: string, + lang: "css" | "sass" | "scss" = "css", ) { - const document = TextDocument.create("test://test/test.css", "css", 0, input); + const document = TextDocument.create(`test://test/test.${lang}`, lang, 0, input); const stylesheet = ls.parseStylesheet(document); assertNoErrors(stylesheet); diff --git a/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts index 273adbae..899d81f8 100644 --- a/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts @@ -428,6 +428,283 @@ $var2: 2 "$multiplier", ); }); + + test("Sass mark highlights", () => { + const ls = getSCSSLS(); + + assertHighlights(ls, "$var1: 1\n$var2: /**/$var1", "$var1", 2, 1, undefined, "sass"); + assertHighlights( + ls, + `$var1: 1 +ls + $var2: /**/$var1`, + "/**/", + 2, + 1, + "$var1", + "sass", + ); + assertHighlights( + ls, + `r1 + $var1: 1 + p1: $var1 +r2,r3 + $var1: 1 + p1: /**/$var1 + $var1`, + "/**/", + 3, + 1, + "$var1", + "sass", + ); + assertHighlights( + ls, + `.r1 + r1: 1em +r2 + r1: 2em + @extend /**/.r1`, + "/**/", + 2, + 1, + ".r1", + "sass", + ); + assertHighlights( + ls, + `/**/%r1 + r1: 1em +r2 + r1: 2em + @extend %r1`, + "/**/", + 2, + 1, + "%r1", + "sass", + ); + assertHighlights( + ls, + `@mixin r1 + r1: $p1 +r2 + r2: 2em + @include /**/r1`, + "/**/", + 2, + 1, + "r1", + "sass", + ); + assertHighlights( + ls, + `@mixin r1($p1) + r1: $p1 +r2 + r2: 2em + @include /**/r1(2px)`, + "/**/", + 2, + 1, + "r1", + "sass", + ); + assertHighlights( + ls, + `$p1: 1 +@mixin r1($p1: $p1) + r1: $p1 +r2 + r2: 2em + @include /**/r1`, + "/**/", + 2, + 1, + "r1", + "sass", + ); + assertHighlights( + ls, + `/**/$p1: 1 +@mixin r1($p1: $p1) + r1: $p1`, + "/**/", + 2, + 1, + "$p1", + "sass", + ); + assertHighlights( + ls, + `$p1 : 1 +@mixin r1($p1) + r1: /**/$p1`, + "/**/", + 2, + 1, + "$p1", + "sass", + ); + assertHighlights( + ls, + `/**/$p1 : 1 +@mixin r1($p1) + r1: $p1 +`, + "/**/", + 1, + 1, + "$p1", + "sass", + ); + assertHighlights( + ls, + `$p1 : 1 +@mixin r1(/**/$p1) + r1: $p1`, + "/**/", + 2, + 1, + "$p1", + "sass", + ); + assertHighlights( + ls, + `$p1 : 1 +@function r1($p1, $p2: /**/$p1) + @return $p1 + $p1 + $p2`, + "/**/", + 2, + 1, + "$p1", + "sass", + ); + assertHighlights( + ls, + `$p1 : 1 +@function r1($p1, /**/$p2: $p1) + @return $p1 + $p2 + $p2`, + "/**/", + 3, + 1, + "$p2", + "sass", + ); + assertHighlights( + ls, + `@function r1($p1, $p2) + @return $p1 + $p2 +@function r2() + @return /**/r1(1, 2)`, + "/**/", + 2, + 1, + "r1", + "sass", + ); + assertHighlights( + ls, + `@function /**/r1($p1, $p2) + @return $p1 + $p2 +@function r2() + @return r1(1, 2) +ls + x: r2()`, + "/**/", + 2, + 1, + "r1", + "sass", + ); + assertHighlights( + ls, + `@function r1($p1, $p2) + @return $p1 + $p2 +@function r2() + @return r1(/**/$p1 : 1, $p2 : 2) +ls + x: r2()`, + "/**/", + 3, + 1, + "$p1", + "sass", + ); + + assertHighlights( + ls, + `@mixin /*here*/foo + display: inline +foo + @include foo`, + "/*here*/", + 2, + 1, + "foo", + "sass", + ); + assertHighlights( + ls, + `@mixin foo + display: inline +foo + @include /*here*/foo`, + "/*here*/", + 2, + 1, + "foo", + "sass", + ); + assertHighlights( + ls, + `@mixin foo + display: inline +/*here*/foo + @include foo`, + "/*here*/", + 1, + 1, + "foo", + "sass", + ); + assertHighlights( + ls, + `@function /*here*/foo($i) + @return $i*$i +#foo + width: foo(2)`, + "/*here*/", + 2, + 1, + "foo", + "sass", + ); + assertHighlights( + ls, + `@function foo($i) + @return $i*$i +#foo + width: /*here*/foo(2)`, + "/*here*/", + 2, + 1, + "foo", + "sass", + ); + + assertHighlights( + ls, + `.text + @include mixins.responsive using ($multiplier) + font-size: /*here*/$multiplier * 10px`, + "/*here*/$", + 2, + 1, + "$multiplier", + "sass", + ); + }); }); suite("Links", () => { @@ -1434,6 +1711,31 @@ $var2: 2 { name: "", kind: SymbolKind.Method, range: newRange(0, 9), selectionRange: newRange(0, 0) }, ]); }); + + test("sass document symbols", () => { + const ls = getSCSSLS(); + + // Incomplete Mixin + assertDocumentSymbols( + ls, + "@mixin foo\n\t", + [ + { + name: "foo", + kind: SymbolKind.Method, + range: Range.create(Position.create(0, 0), Position.create(1, 1)), + selectionRange: newRange(7, 10), + }, + ], + "sass", + ); + assertDocumentSymbols( + ls, + "@mixin \n\t", + [{ name: "", kind: SymbolKind.Method, range: newRange(0, 6), selectionRange: newRange(0, 0) }], + "sass", + ); + }); }); suite("Color", () => { @@ -1441,5 +1743,19 @@ $var2: 2 const ls = getSCSSLS(); assertColorSymbols(ls, "$colors: (blue: $blue,indigo: $indigo)"); // issue #47209 }); + + test("Sass color symbols", () => { + // map names are not colors + const ls = getSCSSLS(); + const document = TextDocument.create( + "test://test/test.sass", + "sass", + 0, + "$colors: (blue: $blue,indigo: $indigo)", + ); + const stylesheet = ls.parseStylesheet(document); + const result = ls.findDocumentColors(document, stylesheet); + assert.deepEqual(result, []); + }); }); }); From cc67b3820468ab5d20bb580cd06e0edf820a0aa7 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Thu, 30 May 2024 08:36:48 +0200 Subject: [PATCH 037/138] refactor: indented completions --- package-lock.json | 361 +++++++++++++----- package.json | 1 + .../src/parser/cssParser.ts | 38 +- .../src/parser/sassParser.ts | 2 +- .../src/services/pathCompletion.ts | 4 +- .../src/test/css/completion.test.ts | 6 +- .../src/test/sass/parser-indented.test.ts | 29 +- .../src/test/sass/parser.test.ts | 9 + .../src/test/sass/sassCompletion.test.ts | 349 ++++++++++++++++- .../pathCompletionFixtures/sass/_foo.sass | 0 .../pathCompletionFixtures/sass/main.sass | 0 11 files changed, 652 insertions(+), 147 deletions(-) create mode 100644 packages/vscode-css-languageservice/test/pathCompletionFixtures/sass/_foo.sass create mode 100644 packages/vscode-css-languageservice/test/pathCompletionFixtures/sass/main.sass diff --git a/package-lock.json b/package-lock.json index d61d2453..d12de6ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42,6 +42,7 @@ "shx": "0.3.4", "ts-loader": "9.5.1", "typescript": "5.4.5", + "vitest": "1.6.0", "webpack": "5.91.0", "webpack-cli": "5.1.4" } @@ -5158,13 +5159,13 @@ } }, "node_modules/@vitest/expect": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.3.tgz", - "integrity": "sha512-y+waPz31pOFr3rD7vWTbwiLe5+MgsMm40jTZbQE8p8/qXyBX3CQsIXRx9XK12IbY7q/t5a5aM/ckt33b4PxK2g==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz", + "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==", "dev": true, "dependencies": { - "@vitest/spy": "1.5.3", - "@vitest/utils": "1.5.3", + "@vitest/spy": "1.6.0", + "@vitest/utils": "1.6.0", "chai": "^4.3.10" }, "funding": { @@ -5172,12 +5173,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.3.tgz", - "integrity": "sha512-7PlfuReN8692IKQIdCxwir1AOaP5THfNkp0Uc4BKr2na+9lALNit7ub9l3/R7MP8aV61+mHKRGiqEKRIwu6iiQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz", + "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==", "dev": true, "dependencies": { - "@vitest/utils": "1.5.3", + "@vitest/utils": "1.6.0", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -5201,9 +5202,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.3.tgz", - "integrity": "sha512-K3mvIsjyKYBhNIDujMD2gfQEzddLe51nNOAf45yKRt/QFJcUIeTQd2trRvv6M6oCBHNVnZwFWbQ4yj96ibiDsA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", + "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -5215,9 +5216,9 @@ } }, "node_modules/@vitest/spy": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.3.tgz", - "integrity": "sha512-Llj7Jgs6lbnL55WoshJUUacdJfjU2honvGcAJBxhra5TPEzTJH8ZuhI3p/JwqqfnTr4PmP7nDmOXP53MS7GJlg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz", + "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -5227,9 +5228,9 @@ } }, "node_modules/@vitest/utils": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.3.tgz", - "integrity": "sha512-rE9DTN1BRhzkzqNQO+kw8ZgfeEBCLXiHJwetk668shmNBpSagQxneT5eSqEBLP+cqSiAeecvQmbpFfdMyLcIQA==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz", + "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -19022,9 +19023,9 @@ } }, "node_modules/vite-node": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.3.tgz", - "integrity": "sha512-axFo00qiCpU/JLd8N1gu9iEYL3xTbMbMrbe5nDp9GL0nb6gurIdZLkkFogZXWnE8Oyy5kfSLwNVIcVsnhE7lgQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz", + "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -19044,16 +19045,16 @@ } }, "node_modules/vitest": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.3.tgz", - "integrity": "sha512-2oM7nLXylw3mQlW6GXnRriw+7YvZFk/YNV8AxIC3Z3MfFbuziLGWP9GPxxu/7nRlXhqyxBikpamr+lEEj1sUEw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz", + "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==", "dev": true, "dependencies": { - "@vitest/expect": "1.5.3", - "@vitest/runner": "1.5.3", - "@vitest/snapshot": "1.5.3", - "@vitest/spy": "1.5.3", - "@vitest/utils": "1.5.3", + "@vitest/expect": "1.6.0", + "@vitest/runner": "1.6.0", + "@vitest/snapshot": "1.6.0", + "@vitest/spy": "1.6.0", + "@vitest/utils": "1.6.0", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", @@ -19067,7 +19068,7 @@ "tinybench": "^2.5.1", "tinypool": "^0.8.3", "vite": "^5.0.0", - "vite-node": "1.5.3", + "vite-node": "1.6.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -19082,8 +19083,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.5.3", - "@vitest/ui": "1.5.3", + "@vitest/browser": "1.6.0", + "@vitest/ui": "1.6.0", "happy-dom": "*", "jsdom": "*" }, @@ -20124,6 +20125,177 @@ "node": ">=20" } }, + "packages/language-server/node_modules/@vitest/expect": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.3.tgz", + "integrity": "sha512-y+waPz31pOFr3rD7vWTbwiLe5+MgsMm40jTZbQE8p8/qXyBX3CQsIXRx9XK12IbY7q/t5a5aM/ckt33b4PxK2g==", + "dev": true, + "dependencies": { + "@vitest/spy": "1.5.3", + "@vitest/utils": "1.5.3", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/language-server/node_modules/@vitest/runner": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.3.tgz", + "integrity": "sha512-7PlfuReN8692IKQIdCxwir1AOaP5THfNkp0Uc4BKr2na+9lALNit7ub9l3/R7MP8aV61+mHKRGiqEKRIwu6iiQ==", + "dev": true, + "dependencies": { + "@vitest/utils": "1.5.3", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/language-server/node_modules/@vitest/snapshot": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.3.tgz", + "integrity": "sha512-K3mvIsjyKYBhNIDujMD2gfQEzddLe51nNOAf45yKRt/QFJcUIeTQd2trRvv6M6oCBHNVnZwFWbQ4yj96ibiDsA==", + "dev": true, + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/language-server/node_modules/@vitest/spy": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.3.tgz", + "integrity": "sha512-Llj7Jgs6lbnL55WoshJUUacdJfjU2honvGcAJBxhra5TPEzTJH8ZuhI3p/JwqqfnTr4PmP7nDmOXP53MS7GJlg==", + "dev": true, + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/language-server/node_modules/@vitest/utils": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.3.tgz", + "integrity": "sha512-rE9DTN1BRhzkzqNQO+kw8ZgfeEBCLXiHJwetk668shmNBpSagQxneT5eSqEBLP+cqSiAeecvQmbpFfdMyLcIQA==", + "dev": true, + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/language-server/node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/language-server/node_modules/vite-node": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.3.tgz", + "integrity": "sha512-axFo00qiCpU/JLd8N1gu9iEYL3xTbMbMrbe5nDp9GL0nb6gurIdZLkkFogZXWnE8Oyy5kfSLwNVIcVsnhE7lgQ==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/language-server/node_modules/vitest": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.3.tgz", + "integrity": "sha512-2oM7nLXylw3mQlW6GXnRriw+7YvZFk/YNV8AxIC3Z3MfFbuziLGWP9GPxxu/7nRlXhqyxBikpamr+lEEj1sUEw==", + "dev": true, + "dependencies": { + "@vitest/expect": "1.5.3", + "@vitest/runner": "1.5.3", + "@vitest/snapshot": "1.5.3", + "@vitest/spy": "1.5.3", + "@vitest/utils": "1.5.3", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.5.3", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.5.3", + "@vitest/ui": "1.5.3", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "packages/language-services": { "name": "@somesass/language-services", "version": "1.0.2", @@ -20143,50 +20315,27 @@ "node": ">=20" } }, - "packages/vscode-css-languageservice": { - "name": "@somesass/vscode-css-languageservice", - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "@vscode/l10n": "0.0.18", - "vscode-languageserver-textdocument": "1.0.11", - "vscode-languageserver-types": "3.17.5", - "vscode-uri": "3.0.8" - }, - "devDependencies": { - "@types/mocha": "10.0.6", - "@types/node": "20.12.8", - "@typescript-eslint/eslint-plugin": "7.8.0", - "@typescript-eslint/parser": "7.8.0", - "@vscode/web-custom-data": "0.4.9", - "eslint": "8.57.0", - "rimraf": "5.0.5", - "source-map-support": "0.5.21", - "typescript": "5.4.5", - "vitest": "1.6.0" - } - }, - "packages/vscode-css-languageservice/node_modules/@vitest/expect": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz", - "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==", + "packages/language-services/node_modules/@vitest/expect": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.5.3.tgz", + "integrity": "sha512-y+waPz31pOFr3rD7vWTbwiLe5+MgsMm40jTZbQE8p8/qXyBX3CQsIXRx9XK12IbY7q/t5a5aM/ckt33b4PxK2g==", "dev": true, "dependencies": { - "@vitest/spy": "1.6.0", - "@vitest/utils": "1.6.0", + "@vitest/spy": "1.5.3", + "@vitest/utils": "1.5.3", "chai": "^4.3.10" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "packages/vscode-css-languageservice/node_modules/@vitest/runner": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz", - "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==", + "packages/language-services/node_modules/@vitest/runner": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.5.3.tgz", + "integrity": "sha512-7PlfuReN8692IKQIdCxwir1AOaP5THfNkp0Uc4BKr2na+9lALNit7ub9l3/R7MP8aV61+mHKRGiqEKRIwu6iiQ==", "dev": true, "dependencies": { - "@vitest/utils": "1.6.0", + "@vitest/utils": "1.5.3", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -20194,10 +20343,10 @@ "url": "https://opencollective.com/vitest" } }, - "packages/vscode-css-languageservice/node_modules/@vitest/snapshot": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", - "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", + "packages/language-services/node_modules/@vitest/snapshot": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.5.3.tgz", + "integrity": "sha512-K3mvIsjyKYBhNIDujMD2gfQEzddLe51nNOAf45yKRt/QFJcUIeTQd2trRvv6M6oCBHNVnZwFWbQ4yj96ibiDsA==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -20208,10 +20357,10 @@ "url": "https://opencollective.com/vitest" } }, - "packages/vscode-css-languageservice/node_modules/@vitest/spy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz", - "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==", + "packages/language-services/node_modules/@vitest/spy": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.5.3.tgz", + "integrity": "sha512-Llj7Jgs6lbnL55WoshJUUacdJfjU2honvGcAJBxhra5TPEzTJH8ZuhI3p/JwqqfnTr4PmP7nDmOXP53MS7GJlg==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -20220,10 +20369,10 @@ "url": "https://opencollective.com/vitest" } }, - "packages/vscode-css-languageservice/node_modules/@vitest/utils": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz", - "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==", + "packages/language-services/node_modules/@vitest/utils": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.5.3.tgz", + "integrity": "sha512-rE9DTN1BRhzkzqNQO+kw8ZgfeEBCLXiHJwetk668shmNBpSagQxneT5eSqEBLP+cqSiAeecvQmbpFfdMyLcIQA==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -20235,7 +20384,7 @@ "url": "https://opencollective.com/vitest" } }, - "packages/vscode-css-languageservice/node_modules/p-limit": { + "packages/language-services/node_modules/p-limit": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", @@ -20250,10 +20399,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/vscode-css-languageservice/node_modules/vite-node": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz", - "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==", + "packages/language-services/node_modules/vite-node": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.5.3.tgz", + "integrity": "sha512-axFo00qiCpU/JLd8N1gu9iEYL3xTbMbMrbe5nDp9GL0nb6gurIdZLkkFogZXWnE8Oyy5kfSLwNVIcVsnhE7lgQ==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -20272,17 +20421,17 @@ "url": "https://opencollective.com/vitest" } }, - "packages/vscode-css-languageservice/node_modules/vitest": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz", - "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==", + "packages/language-services/node_modules/vitest": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.5.3.tgz", + "integrity": "sha512-2oM7nLXylw3mQlW6GXnRriw+7YvZFk/YNV8AxIC3Z3MfFbuziLGWP9GPxxu/7nRlXhqyxBikpamr+lEEj1sUEw==", "dev": true, "dependencies": { - "@vitest/expect": "1.6.0", - "@vitest/runner": "1.6.0", - "@vitest/snapshot": "1.6.0", - "@vitest/spy": "1.6.0", - "@vitest/utils": "1.6.0", + "@vitest/expect": "1.5.3", + "@vitest/runner": "1.5.3", + "@vitest/snapshot": "1.5.3", + "@vitest/spy": "1.5.3", + "@vitest/utils": "1.5.3", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", @@ -20296,7 +20445,7 @@ "tinybench": "^2.5.1", "tinypool": "^0.8.3", "vite": "^5.0.0", - "vite-node": "1.6.0", + "vite-node": "1.5.3", "why-is-node-running": "^2.2.2" }, "bin": { @@ -20311,8 +20460,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.0", - "@vitest/ui": "1.6.0", + "@vitest/browser": "1.5.3", + "@vitest/ui": "1.5.3", "happy-dom": "*", "jsdom": "*" }, @@ -20337,6 +20486,28 @@ } } }, + "packages/vscode-css-languageservice": { + "name": "@somesass/vscode-css-languageservice", + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "@vscode/l10n": "0.0.18", + "vscode-languageserver-textdocument": "1.0.11", + "vscode-languageserver-types": "3.17.5", + "vscode-uri": "3.0.8" + }, + "devDependencies": { + "@types/node": "20.12.8", + "@typescript-eslint/eslint-plugin": "7.8.0", + "@typescript-eslint/parser": "7.8.0", + "@vscode/web-custom-data": "0.4.9", + "eslint": "8.57.0", + "rimraf": "5.0.5", + "source-map-support": "0.5.21", + "typescript": "5.4.5", + "vitest": "1.6.0" + } + }, "vscode-extension": { "name": "some-sass", "version": "3.1.4", diff --git a/package.json b/package.json index c9e805e2..add826b6 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "shx": "0.3.4", "ts-loader": "9.5.1", "typescript": "5.4.5", + "vitest": "1.6.0", "webpack": "5.91.0", "webpack-cli": "5.1.4" } diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 1fcfdc99..89fdcb1f 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -324,12 +324,7 @@ export class Parser { node.addChild(statement); hasMatch = true; inRecovery = false; - if ( - this.syntax !== "indented" && - !this.peek(TokenType.EOF) && - this._needsSemicolonAfter(statement) && - !this.accept(TokenType.SemiColon) - ) { + if (!this.peek(TokenType.EOF) && this._needsSemicolonAfter(statement) && !this.accept(TokenType.SemiColon)) { this.markError(node, ParseError.SemiColonExpected); } } @@ -459,6 +454,9 @@ export class Parser { } public _needsSemicolonAfter(node: nodes.Node): boolean { + if (this.syntax === "indented") { + return false; + } switch (node.type) { case nodes.NodeType.Keyframe: case nodes.NodeType.ViewPort: @@ -514,15 +512,6 @@ export class Parser { if (this.peek(TokenType.Dedent)) { break; } - if (this._needsSemicolonAfter(decl) && !this.accept(TokenType.Newline)) { - if (!this.accept(TokenType.EOF)) { - return this.finish(node, ParseError.NewlineExpected, [TokenType.Newline, TokenType.Dedent]); - } - } - // We accepted newline token. Link it to declaration. - if (decl && this.prevToken && this.prevToken.type === TokenType.Newline) { - (decl as nodes.Declaration).semicolonPosition = this.prevToken.offset; - } while (this.accept(TokenType.Newline)) { // accept empty statements } @@ -530,8 +519,8 @@ export class Parser { if (this.peek(TokenType.CurlyR)) { break; } - if (this.syntax !== "indented" && this._needsSemicolonAfter(decl) && !this.accept(TokenType.SemiColon)) { - return this.finish(node, ParseError.SemiColonExpected, [TokenType.SemiColon, TokenType.CurlyR]); + if (this._needsSemicolonAfter(decl) && !this.accept(TokenType.SemiColon)) { + return this.finish(node, ParseError.SemiColonExpected, [TokenType.SemiColon, TokenType.CurlyR]); // It's this resync! It consumes stuff up until the syncToken. Need something similar for indented. } // We accepted semicolon token. Link it to declaration. if (decl && this.prevToken && this.prevToken.type === TokenType.SemiColon) { @@ -546,15 +535,14 @@ export class Parser { } if (this.syntax === "indented") { - while (this.accept(TokenType.Newline)) { - // accept empty statements - } if (this.accept(TokenType.EOF)) { return this.finish(node); } + if (this.peek(TokenType.AtKeyword)) { + return this.finish(node); + } if (!this.accept(TokenType.Dedent)) { - // TODO: figure out if/when we should raise this - // return this.finish(node, ParseError.DedentExpected, [TokenType.Newline, TokenType.Indent]); + return this.finish(node, ParseError.DedentExpected, [TokenType.Newline, TokenType.Indent, TokenType.EOF]); } } else { if (!this.accept(TokenType.CurlyR)) { @@ -745,7 +733,7 @@ export class Parser { break done; case TokenType.EOF: if (this.syntax === "indented") { - break done; + return this.finish(node); } // We shouldn't have reached the end of input, something is // unterminated. @@ -1551,6 +1539,7 @@ export class Parser { done: while (true) { switch (this.token.type) { case TokenType.SemiColon: + case TokenType.Newline: if (isTopLevel()) { break done; } @@ -1583,9 +1572,6 @@ export class Parser { this.consumeToken(); if (bracketsDepth > 0) { - if (this.syntax === "indented" && !this.accept(TokenType.EOF)) { - return this.finish(node, ParseError.DedentExpected); - } return this.finish(node, ParseError.RightSquareBracketExpected); } else if (parensDepth > 0) { return this.finish(node, ParseError.RightParenthesisExpected); diff --git a/packages/vscode-css-languageservice/src/parser/sassParser.ts b/packages/vscode-css-languageservice/src/parser/sassParser.ts index 4b0c0ef7..7841e7cc 100644 --- a/packages/vscode-css-languageservice/src/parser/sassParser.ts +++ b/packages/vscode-css-languageservice/src/parser/sassParser.ts @@ -607,7 +607,7 @@ export class SassParser extends cssParser.Parser { this.consumeToken(); if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Mixin]))) { - return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); + return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR, TokenType.Dedent]); } if (this.accept(TokenType.ParenthesisL)) { diff --git a/packages/vscode-css-languageservice/src/services/pathCompletion.ts b/packages/vscode-css-languageservice/src/services/pathCompletion.ts index 5255fcfd..8fe7b9aa 100644 --- a/packages/vscode-css-languageservice/src/services/pathCompletion.ts +++ b/packages/vscode-css-languageservice/src/services/pathCompletion.ts @@ -70,9 +70,9 @@ export class PathCompletionParticipant implements ICompletionParticipant { documentContext, ); - if (document.languageId === "scss") { + if (document.languageId === "scss" || document.languageId === "sass") { suggestions.forEach((s) => { - if (startsWith(s.label, "_") && endsWith(s.label, ".scss")) { + if (startsWith(s.label, "_") && (endsWith(s.label, ".scss") || endsWith(s.label, ".sass"))) { if (s.textEdit) { s.textEdit.newText = s.label.slice(1, -5); } else { diff --git a/packages/vscode-css-languageservice/src/test/css/completion.test.ts b/packages/vscode-css-languageservice/src/test/css/completion.test.ts index abbcfcdd..04ccced8 100644 --- a/packages/vscode-css-languageservice/src/test/css/completion.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/completion.test.ts @@ -134,7 +134,7 @@ export async function testCompletionFor( const lang = path.extname(testUri).substr(1); const lsOptions = { fileSystemProvider: getFsProvider() }; let ls; - if (lang === "scss") { + if (lang === "scss" || lang === "sass") { ls = getSassLanguageService(lsOptions); } else { ls = getCSSLanguageService(lsOptions); @@ -155,7 +155,7 @@ export async function testCompletionFor( } const document = TextDocument.create(testUri, lang, 0, value); - const position = Position.create(0, offset); + const position = document.positionAt(offset); const jsonDoc = ls.parseStylesheet(document); const context = getDocumentContext(workspaceFolderUri); @@ -1159,7 +1159,7 @@ suite("CSS - Completion", () => { await testCompletionFor( 'html { background-image: url("../|")', { - count: 4, + count: 5, }, undefined, testUri, diff --git a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts index 78873af9..302777f3 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts @@ -1320,16 +1320,6 @@ $color: #F5F5F5`, }); test("ruleset", () => { - assertNode( - ` -.foo - font: - family: Arial - size: 20px - color: #ff0000`, - parser, - parser._parseRuleset.bind(parser), - ); assertNode( `selector property: value @@ -1347,6 +1337,17 @@ $color: #F5F5F5`, parser._parseRuleset.bind(parser), ); + assertNode( + ` +.foo + font: + family: Arial + size: 20px + color: #ff0000`, + parser, + parser._parseRuleset.bind(parser), + ); + assertNode("foo|bar\n\t//", parser, parser._parseRuleset.bind(parser)); assertNode( @@ -1561,14 +1562,6 @@ foo ); assertError( ` -foo - --double-important: red !important !important`, - parser, - parser._parseRuleset.bind(parser), - ParseError.NewlineExpected, - ); - assertError( - ` foo --unbalanced-parens: not)()(cool`, parser, diff --git a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts index acb9d46b..21e11471 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser.test.ts @@ -1001,6 +1001,15 @@ suite("SCSS - Parser", () => { assertNode("@media print { .page { @at-root (with: rule) { } } }", parser, parser._parseStylesheet.bind(parser)); }); + test("debug", () => { + const parser = new SassParser(); + assertNode( + "selector { property: value; @keyframes foo {} @-moz-keyframes foo {}}", + parser, + parser._parseRuleset.bind(parser), + ); + }); + test("Ruleset", function () { const parser = new SassParser(); assertNode(".selector { prop: erty $const 1px; }", parser, parser._parseRuleset.bind(parser)); diff --git a/packages/vscode-css-languageservice/src/test/sass/sassCompletion.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassCompletion.test.ts index 2e3260eb..6e8e89a2 100644 --- a/packages/vscode-css-languageservice/src/test/sass/sassCompletion.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassCompletion.test.ts @@ -22,7 +22,7 @@ function testCompletionFor( return testCSSCompletionFor(value, expected, settings, testUri, workspaceFolderUri); } -suite("SCSS - Completions", () => { +suite("Sass - Completions", () => { test("stylesheet", async () => { await testCompletionFor("$i: 0; body { width: |", { items: [{ label: "$i", documentation: "0" }], @@ -115,6 +115,243 @@ suite("SCSS - Completions", () => { }); }); + test("Sass stylesheet", async () => { + await testCompletionFor( + `$i: 0 +body + width: |`, + { + items: [{ label: "$i", documentation: "0" }], + }, + undefined, + "test://test/test.sass", + ); + await testCompletionFor( + `@for $i from 1 through 3 + .item-#{|} + width: 2em * $i`, + { + items: [{ label: "$i" }], + }, + undefined, + "test://test/test.sass", + ); + await testCompletionFor( + `.foo + background-color: d|`, + { + items: [ + { + label: "darken", + resultText: `.foo + background-color: darken(\\$color: \${1:#000000}, \\$amount: \${2:0})`, + }, + { label: "desaturate" }, + ], + }, + undefined, + "test://test/test.sass", + ); + await testCompletionFor( + `@function foo($x, $y) + @return $x + $y +.foo + background-color: f|`, + { + items: [ + { + label: "foo", + resultText: `@function foo($x, $y) + @return $x + $y +.foo + background-color: foo(\${1:$x}, \${2:$y})`, + }, + ], + }, + undefined, + "test://test/test.sass", + ); + await testCompletionFor( + `@mixin mixin($a: 1, $b) + content: $|`, + { + items: [ + { label: "$a", documentation: "1", detail: "argument from 'mixin'" }, + { label: "$b", documentation: null, detail: "argument from 'mixin'" }, + ], + }, + undefined, + "test://test/test.sass", + ); + await testCompletionFor( + `@mixin mixin($a: 1, $b) + content: $a + $b +@include m|`, + { + items: [ + { + label: "mixin", + resultText: `@mixin mixin($a: 1, $b) + content: $a + $b +@include mixin(\${1:$a}, \${2:$b})`, + }, + ], + }, + undefined, + "test://test/test.sass", + ); + await testCompletionFor( + "di| span", + { + items: [{ label: "div" }, { label: "display", notAvailable: true }], + }, + undefined, + "test://test/test.sass", + ); + await testCompletionFor( + `span + di|`, + { + items: [{ notAvailable: true, label: "div" }, { label: "display" }], + }, + undefined, + "test://test/test.sass", + ); + await testCompletionFor( + `.foo + .|`, + { + items: [{ label: ".foo" }], + }, + undefined, + "test://test/test.sass", + ); + // issue microsoft/vscode#17726 + await testCompletionFor( + `.foo + &:|`, + { + items: [ + { + label: ":last-of-type", + resultText: `.foo + &:last-of-type`, + }, + ], + }, + undefined, + "test://test/test.sass", + ); + await testCompletionFor( + `.foo + &:l|`, + { + items: [ + { + label: ":last-of-type", + resultText: `.foo + &:last-of-type`, + }, + ], + }, + undefined, + "test://test/test.sass", + ); + // issue microsoft/vscode#109185 + await testCompletionFor( + `.test + &::|`, + { + items: [ + { + label: ":hover", + resultText: `.test + &:hover`, + }, + { + label: "::after", + resultText: `.test + &::after`, + }, + ], + }, + undefined, + "test://test/test.sass", + ); + // issue microsoft/vscode#33911 + await testCompletionFor( + `@include media('ddd') + dis| &:not(:first-child)`, + { + items: [{ label: "display" }], + }, + undefined, + "test://test/test.sass", + ); + // issue 43876 + await testCompletionFor( + `.foo + +@mixin bar + @extend |`, + { + items: [{ label: ".foo" }], + }, + undefined, + "test://test/test.sass", + ); + await testCompletionFor( + `.foo + +@mixin bar + @extend fo|`, + { + items: [{ label: ".foo" }], + }, + undefined, + "test://test/test.sass", + ); + // issue 76572 + await testCompletionFor( + `.foo + mask: no|`, + { + items: [{ label: "round" }], + }, + undefined, + "test://test/test.sass", + ); + // issue 76507 + await testCompletionFor( + `.foo + .foobar + .foobar2 + outline-color: blue + cool + | + .fokzlb + + .baaaa + counter - reset: unset`, + { + items: [{ label: "display" }], + }, + undefined, + "test://test/test.sass", + ); + await testCompletionFor( + `div + &:hover + + |`, + { + items: [{ label: "display" }], + }, + undefined, + "test://test/test.sass", + ); + }); + test("suggestParticipants", async () => { await testCompletionFor(`html { @include | }`, { participant: { @@ -171,14 +408,59 @@ suite("SCSS - Completions", () => { }); await testCompletionFor(".foo { | }", allAtProposals); + await testCompletionFor( + `.foo + |`, + allAtProposals, + undefined, + "test://test/test.sass", + ); await testCompletionFor(`@for $i from 1 through 3 { .item-#{$i} { width: 2em * $i; } } @|`, allAtProposals); + await testCompletionFor( + `@for $i from 1 through 3 + .item-#{$i} + width: 2em * $i +@|`, + allAtProposals, + undefined, + "test://test/test.sass", + ); await testCompletionFor(".foo { @if $a = 5 { } @| }", allAtProposals); + await testCompletionFor( + `.foo + @if $a = 5 + + @|`, + allAtProposals, + undefined, + "test://test/test.sass", + ); + await testCompletionFor(".foo { @debug 10em + 22em; @| }", allAtProposals); + await testCompletionFor( + `.foo + @debug 10em + 22em + @|`, + allAtProposals, + undefined, + "test://test/test.sass", + ); + await testCompletionFor(".foo { @if $a = 5 { } @f| }", { items: [{ label: "@for" }], }); + await testCompletionFor( + `.foo + @if $a = 5 + @f|`, + { + items: [{ label: "@for" }], + }, + undefined, + "test://test/test.sass", + ); }); suite("Modules", async () => { @@ -203,6 +485,18 @@ suite("SCSS - Completions", () => { { label: "@forward", notAvailable: true }, ], }); + await testCompletionFor( + `.foo + @|`, + { + items: [ + { label: "@use", notAvailable: true }, + { label: "@forward", notAvailable: true }, + ], + }, + undefined, + "test://test/test.sass", + ); const builtIns = { items: [ @@ -282,12 +576,27 @@ suite("SCSS - Completions", () => { { label: "inherit", sortText: undefined }, ], }); + await testCompletionFor( + `.foo + text-decoration: |`, + { + items: [ + // Enum come before everything + { label: "dashed", sortText: " " }, + // Others come later + { label: "aqua", sortText: undefined }, + { label: "inherit", sortText: undefined }, + ], + }, + undefined, + "test://test/test.sass", + ); }); const testFixturesPath = path.join(__dirname, "../../../test"); /** - * For SCSS, `@import 'foo';` can be used for importing partial file `_foo.scss` + * For Sass, `@import 'foo';` can be used for importing partial file `_foo.scss` */ test("SCSS @import Path completion", async function () { const testCSSUri = URI.file(path.resolve(testFixturesPath, "pathCompletionFixtures/about/about.css")).toString( @@ -324,4 +633,40 @@ suite("SCSS - Completions", () => { workspaceFolderUri, ); }); + + test("Sass @import Path completion", async function () { + const testCSSUri = URI.file(path.resolve(testFixturesPath, "pathCompletionFixtures/about/about.css")).toString( + true, + ); + const workspaceFolderUri = URI.file(path.resolve(testFixturesPath)).toString(true); + + /** + * We are in a CSS file, so no special treatment for SCSS partial files + */ + await testCSSCompletionFor( + `@import '../sass/|'`, + { + items: [ + { label: "main.sass", resultText: `@import '../sass/main.sass'` }, + { label: "_foo.sass", resultText: `@import '../sass/_foo.sass'` }, + ], + }, + undefined, + testCSSUri, + workspaceFolderUri, + ); + + const testSassUri = URI.file(path.resolve(testFixturesPath, "pathCompletionFixtures/sass/main.sass")).toString( + true, + ); + await testCompletionFor( + `@import './|'`, + { + items: [{ label: "_foo.sass", resultText: `@import './foo'` }], + }, + undefined, + testSassUri, + workspaceFolderUri, + ); + }); }); diff --git a/packages/vscode-css-languageservice/test/pathCompletionFixtures/sass/_foo.sass b/packages/vscode-css-languageservice/test/pathCompletionFixtures/sass/_foo.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/test/pathCompletionFixtures/sass/main.sass b/packages/vscode-css-languageservice/test/pathCompletionFixtures/sass/main.sass new file mode 100644 index 00000000..e69de29b From 40ad48a4b94f37deb2d922c4f8a4eca987bab610 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 16 Jun 2024 15:13:01 +0200 Subject: [PATCH 038/138] test: sass indented linting --- .../src/test/sass/sassLint.test.ts | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 packages/vscode-css-languageservice/src/test/sass/sassLint.test.ts diff --git a/packages/vscode-css-languageservice/src/test/sass/sassLint.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassLint.test.ts new file mode 100644 index 00000000..3d044af8 --- /dev/null +++ b/packages/vscode-css-languageservice/src/test/sass/sassLint.test.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { suite, test, assert } from "vitest"; +import { TextDocument } from "vscode-languageserver-textdocument"; +import { LintConfigurationSettings, Rule, Rules } from "../../services/lintRules"; +import { SassParser } from "../../parser/sassParser"; +import { assertEntries } from "../css/lint.test"; + +const parser = new SassParser(); + +function assertRuleSet(input: string, ...rules: Rule[]): void { + assertRuleSet2(input, rules); +} + +function assertRuleSet2(input: string, rules: Rule[], messages?: string[], settings?: LintConfigurationSettings): void { + const document = TextDocument.create("test://test/test.sass", "sass", 0, input); + const node = parser.parseStylesheet(document); + assertEntries(node, document, rules, messages, settings); +} + +suite("Sass - Lint", () => { + test("universal selector", () => { + assertRuleSet(`*\n\tcolor: perty`, Rules.UniversalSelector); + assertRuleSet(`*, div\n\tcolor: perty`, Rules.UniversalSelector); + assertRuleSet(`div, *\n\tcolor: perty`, Rules.UniversalSelector); + assertRuleSet(`div > *\n\tcolor: perty`, Rules.UniversalSelector); + assertRuleSet(`div + *\n\tcolor: perty`, Rules.UniversalSelector); + }); + + test("empty ruleset", () => { + assertRuleSet("selector\n\t", Rules.EmptyRuleSet); + // this should probably be flagged if the Indent is missing as well + }); + + test("property ignored due to inline", () => { + assertRuleSet( + `selector + display: inline + float: right`, + Rules.AvoidFloat, + ); + }); + + test("avoid !important", () => { + assertRuleSet(`selector\n\tdisplay: inline !important`, Rules.AvoidImportant); + }); + + test("avoid float", () => { + assertRuleSet(`selector\n\tfloat: right`, Rules.AvoidFloat); + }); + + test("avoid id selectors", () => { + assertRuleSet(`#selector\n\tdisplay: inline`, Rules.AvoidIdSelector); + }); + + test("zero with unit", () => { + assertRuleSet(`selector\n\twidth: 0px`, Rules.ZeroWithUnit); + }); + + test("duplicate declarations", () => { + assertRuleSet( + `selector + color: perty + color: perty`, + Rules.DuplicateDeclarations, + Rules.DuplicateDeclarations, + ); + }); + + test("unknown property", () => { + assertRuleSet(`selector\n\t-ms-property: "rest is missing"`, Rules.UnknownVendorSpecificProperty); + assertRuleSet( + `selector\n\t-moz-box-shadow: "rest is missing"`, + Rules.UnknownVendorSpecificProperty, + Rules.IncludeStandardPropertyWhenUsingVendorPrefix, + ); + assertRuleSet(`selector\n\tbox-property: "rest is missing"`, Rules.UnknownProperty); + }); +}); From f28b91516599ac9f1980278ce1e36daaa92a27f1 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 16 Jun 2024 15:24:26 +0200 Subject: [PATCH 039/138] test: language facts indented --- .../src/test/css/languageFacts.test.ts | 11 ++++ .../src/test/sass/languageFacts.test.ts | 60 +++++++++++++++---- 2 files changed, 58 insertions(+), 13 deletions(-) diff --git a/packages/vscode-css-languageservice/src/test/css/languageFacts.test.ts b/packages/vscode-css-languageservice/src/test/css/languageFacts.test.ts index 639ed261..1287a207 100644 --- a/packages/vscode-css-languageservice/src/test/css/languageFacts.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/languageFacts.test.ts @@ -32,6 +32,17 @@ export function assertColor( isColor = expected !== null, ): void { let document = TextDocument.create("test://test/test.css", "css", 0, text); + assertColor2(parser, document, selection, expected, isColor); +} + +export function assertColor2( + parser: Parser, + document: TextDocument, + selection: string, + expected: Color | null, + isColor = expected !== null, +) { + const text = document.getText(); let stylesheet = parser.parseStylesheet(document); assert.equal(nodes.ParseErrorCollector.entries(stylesheet).length, 0, "compile errors"); diff --git a/packages/vscode-css-languageservice/src/test/sass/languageFacts.test.ts b/packages/vscode-css-languageservice/src/test/sass/languageFacts.test.ts index 27b1e891..08f8c7b6 100644 --- a/packages/vscode-css-languageservice/src/test/sass/languageFacts.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/languageFacts.test.ts @@ -6,21 +6,55 @@ import { suite, test } from "vitest"; import { SassParser } from "../../parser/sassParser"; -import { assertColor } from "../css/languageFacts.test"; +import { assertColor2 } from "../css/languageFacts.test"; import { colorFrom256RGB as newColor } from "../../languageFacts/facts"; +import { Color } from "vscode"; +import { TextDocument } from "vscode-languageserver-textdocument"; + +function assertColor(text: string, selection: string, expected: Color | null, isColor = expected !== null) { + let parser = new SassParser(); + let document = TextDocument.create(`test://test/test.scss`, "scss", 1, text); + assertColor2(parser, document, selection, expected, isColor); +} + +function assertColorSass(text: string, selection: string, expected: Color | null, isColor = expected !== null) { + let parser = new SassParser(); + let document = TextDocument.create(`test://test/test.sass`, "sass", 1, text); + assertColor2(parser, document, selection, expected, isColor); +} + +suite("SCSS - Language facts", () => { + test("is color", () => { + assertColor("#main { color: foo(red) }", "red", newColor(0xff, 0, 0)); + assertColor("#main { color: red() }", "red", null); + assertColor("#main { red { nested: 1px } }", "red", null); + assertColor("#main { @include red; }", "red", null); + assertColor("#main { @include foo($f: red); }", "red", newColor(0xff, 0, 0)); + assertColor("@function red($p) { @return 1px; }", "red", null); + assertColor("@function foo($p) { @return red; }", "red", newColor(0xff, 0, 0)); + assertColor("@function foo($r: red) { @return $r; }", "red", newColor(0xff, 0, 0)); + assertColor("#main { color: rgba($input-border, 0.7) }", "rgba", null, true); + assertColor("#main { color: rgba($input-border, 1, 1, 0.7) }", "rgba", null, true); + }); +}); suite("Sass - Language facts", () => { - test("is color", function () { - let parser = new SassParser(); - assertColor(parser, "#main { color: foo(red) }", "red", newColor(0xff, 0, 0)); - assertColor(parser, "#main { color: red() }", "red", null); - assertColor(parser, "#main { red { nested: 1px } }", "red", null); - assertColor(parser, "#main { @include red; }", "red", null); - assertColor(parser, "#main { @include foo($f: red); }", "red", newColor(0xff, 0, 0)); - assertColor(parser, "@function red($p) { @return 1px; }", "red", null); - assertColor(parser, "@function foo($p) { @return red; }", "red", newColor(0xff, 0, 0)); - assertColor(parser, "@function foo($r: red) { @return $r; }", "red", newColor(0xff, 0, 0)); - assertColor(parser, "#main { color: rgba($input-border, 0.7) }", "rgba", null, true); - assertColor(parser, "#main { color: rgba($input-border, 1, 1, 0.7) }", "rgba", null, true); + test("is color", () => { + assertColorSass( + `#main + red + nested: 1px`, + "red", + null, + ); + assertColorSass("#main\n\tcolor: foo(red)", "red", newColor(0xff, 0, 0)); + assertColorSass("#main\n\tcolor: red()", "red", null); + assertColorSass("#main\n\t@include red", "red", null); + assertColorSass("#main\n\t@include foo($f: red)", "red", newColor(0xff, 0, 0)); + assertColorSass("@function red($p)\n\t@return 1px", "red", null); + assertColorSass("@function foo($p)\n\t@return red", "red", newColor(0xff, 0, 0)); + assertColorSass("@function foo($r: red)\n\t@return $r", "red", newColor(0xff, 0, 0)); + assertColorSass("#main\n\tcolor: rgba($input-border, 0.7)", "rgba", null, true); + assertColorSass("#main\n\tcolor: rgba($input-border, 1, 1, 0.7)", "rgba", null, true); }); }); From c5211b9f2af8bc36174d34778cd821970ee1ceea Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 16 Jun 2024 15:38:40 +0200 Subject: [PATCH 040/138] refactor: selection range indented --- .../src/services/cssSelectionRange.ts | 7 ++- .../src/test/sass/sassSelectionRange.test.ts | 52 +++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts diff --git a/packages/vscode-css-languageservice/src/services/cssSelectionRange.ts b/packages/vscode-css-languageservice/src/services/cssSelectionRange.ts index 6ce0d496..78a6bc0b 100644 --- a/packages/vscode-css-languageservice/src/services/cssSelectionRange.ts +++ b/packages/vscode-css-languageservice/src/services/cssSelectionRange.ts @@ -47,8 +47,11 @@ export function getSelectionRanges( // The `{ }` part of `.a { }` if (currNode.type === NodeType.Declarations) { if (offset > currNode.offset && offset < currNode.end) { - // Return `{ }` and the range inside `{` and `}` - result.push([currNode.offset + 1, currNode.end - 1]); + // not sure there's a useful equivalent for indented that we can provide consistently (offset + depth could work, but Node doesn't have its depth) + if (document.languageId !== "sass") { + // Return `{ }` and the range inside `{` and `}` + result.push([currNode.offset + 1, currNode.end - 1]); + } } } diff --git a/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts new file mode 100644 index 00000000..d5697237 --- /dev/null +++ b/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { suite, test, assert } from "vitest"; +import { getSassLanguageService, TextDocument, SelectionRange } from "../../cssLanguageService"; + +function assertRanges(content: string, expected: (number | string)[][]): void { + let message = `${content} gives selection range:\n`; + + const offset = content.indexOf("|"); + content = content.substr(0, offset) + content.substr(offset + 1); + + const ls = getSassLanguageService(); + + const document = TextDocument.create("test://foo/bar.sass", "sass", 1, content); + const actualRanges = ls.getSelectionRanges(document, [document.positionAt(offset)], ls.parseStylesheet(document)); + assert.equal(actualRanges.length, 1); + const offsetPairs: [number, string][] = []; + let curr: SelectionRange | undefined = actualRanges[0]; + while (curr) { + offsetPairs.push([document.offsetAt(curr.range.start), document.getText(curr.range)]); + curr = curr.parent; + } + + message += `${JSON.stringify(offsetPairs)}\n but should give:\n${JSON.stringify(expected)}\n`; + assert.deepEqual(offsetPairs, expected, message); +} + +suite("Sass SelectionRange", () => { + test("basic", () => { + assertRanges( + `.foo + |color: blue`, + [ + [6, "color"], + [6, "color: blue"], + [ + 4, + ` + color: blue`, + ], + [ + 0, + `.foo + color: blue`, + ], + ], + ); + }); +}); From 4510d478e38f691fa3f11a371d9536d5fc44a307 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 16 Jun 2024 15:49:43 +0200 Subject: [PATCH 041/138] test: selection range indented --- .../src/test/sass/sassSelectionRange.test.ts | 103 +++++++++++++++--- 1 file changed, 90 insertions(+), 13 deletions(-) diff --git a/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts index d5697237..aee94caa 100644 --- a/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts @@ -30,22 +30,99 @@ function assertRanges(content: string, expected: (number | string)[][]): void { suite("Sass SelectionRange", () => { test("basic", () => { + assertRanges(`.foo\n\t|color: blue`, [ + [6, "color"], + [6, "color: blue"], + [4, `\n\tcolor: blue`], + [0, `.foo\n\tcolor: blue`], + ]); + assertRanges(`.foo\n\tc|olor: blue`, [ + [6, "color"], + [6, "color: blue"], + [4, `\n\tcolor: blue`], + [0, `.foo\n\tcolor: blue`], + ]); + assertRanges(`.foo\n\tcolor|: blue`, [ + [6, "color"], + [6, "color: blue"], + [4, `\n\tcolor: blue`], + [0, `.foo\n\tcolor: blue`], + ]); + assertRanges(`.foo\n\tcolor: |blue`, [ + [13, "blue"], + [6, "color: blue"], + [4, `\n\tcolor: blue`], + [0, `.foo\n\tcolor: blue`], + ]); + assertRanges(`.foo\n\tcolor: b|lue`, [ + [13, "blue"], + [6, "color: blue"], + [4, `\n\tcolor: blue`], + [0, `.foo\n\tcolor: blue`], + ]); + assertRanges(`.foo\n\tcolor: blue|`, [ + [13, "blue"], + [6, "color: blue"], + [4, `\n\tcolor: blue`], + [0, `.foo\n\tcolor: blue`], + ]); + + assertRanges(`.|foo\n\tcolor: blue`, [ + [1, `foo`], + [0, `.foo`], + [0, `.foo\n\tcolor: blue`], + ]); + assertRanges(`.fo|o\n\tcolor: blue`, [ + [1, `foo`], + [0, `.foo`], + [0, `.foo\n\tcolor: blue`], + ]); + + assertRanges(`.foo|\n\tcolor: blue`, [ + [4, `\n\tcolor: blue`], + [0, `.foo\n\tcolor: blue`], + ]); + }); + + test("multiple values", () => { + assertRanges( + `.foo + font-family: '|Courier New', Courier, monospace`, + [ + [19, `'Courier New'`], + [19, `'Courier New', Courier, monospace`], + [6, `font-family: 'Courier New', Courier, monospace`], + [4, `\n\tfont-family: 'Courier New', Courier, monospace`], + [0, `.foo\n\tfont-family: 'Courier New', Courier, monospace`], + ], + ); + }); + + test("edge behavior for declaration", () => { + assertRanges( + `.foo| + `, + [ + [4, "\n\t"], + [0, ".foo\n\t"], + ], + ); + assertRanges( + `.foo + color: red +|`, + [ + [4, "\n\tcolor: red\n"], + [0, ".foo\n\tcolor: red\n"], + ], + ); assertRanges( `.foo - |color: blue`, + | +`, [ - [6, "color"], - [6, "color: blue"], - [ - 4, - ` - color: blue`, - ], - [ - 0, - `.foo - color: blue`, - ], + [4, "\n\t\n"], + [0, ".foo\n\t\n"], ], ); }); From aabf877eb2bf0b1ed8612e0046a6f68dd5e6bbe2 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 16 Jun 2024 16:21:02 +0200 Subject: [PATCH 042/138] refactor: hover indented --- .../src/services/cssHover.ts | 2 +- .../src/test/css/hover.test.ts | 2 +- .../src/test/sass/sassHover.test.ts | 86 +++++++++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 packages/vscode-css-languageservice/src/test/sass/sassHover.test.ts diff --git a/packages/vscode-css-languageservice/src/services/cssHover.ts b/packages/vscode-css-languageservice/src/services/cssHover.ts index 4b494514..98577564 100644 --- a/packages/vscode-css-languageservice/src/services/cssHover.ts +++ b/packages/vscode-css-languageservice/src/services/cssHover.ts @@ -61,7 +61,7 @@ export class CSSHover { const node = nodepath[i]; if (node instanceof nodes.Media) { - const regex = /@media[^\{]+/g; + const regex = document.languageId === "sass" ? /@media.+/ : /@media[^\{]+/g; const matches = node.getText().match(regex); flagOpts = { isMedia: true, diff --git a/packages/vscode-css-languageservice/src/test/css/hover.test.ts b/packages/vscode-css-languageservice/src/test/css/hover.test.ts index 34651e8b..2f34d1a0 100644 --- a/packages/vscode-css-languageservice/src/test/css/hover.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/hover.test.ts @@ -9,7 +9,7 @@ import { suite, test, assert } from "vitest"; import { Hover, TextDocument, getCSSLanguageService, getSassLanguageService } from "../../cssLanguageService"; import { HoverSettings } from "../../cssLanguageTypes"; -function assertHover(value: string, expected: Hover, languageId = "css", hoverSettings?: HoverSettings): void { +export function assertHover(value: string, expected: Hover, languageId = "css", hoverSettings?: HoverSettings): void { let offset = value.indexOf("|"); value = value.substr(0, offset) + value.substr(offset + 1); const ls = languageId === "css" ? getCSSLanguageService() : getSassLanguageService(); diff --git a/packages/vscode-css-languageservice/src/test/sass/sassHover.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassHover.test.ts new file mode 100644 index 00000000..5faaacd1 --- /dev/null +++ b/packages/vscode-css-languageservice/src/test/sass/sassHover.test.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { suite, test, assert } from "vitest"; +import { Hover, TextDocument, getSassLanguageService } from "../../cssLanguageService"; +import { HoverSettings } from "../../cssLanguageTypes"; +import { assertHover as assertCssHover } from "../css/hover.test"; + +function assertHover(value: string, expected: Hover, hoverSettings?: HoverSettings): void { + assertCssHover(value, expected, "sass", hoverSettings); +} + +suite("Sass hover", () => { + test("basic", () => { + assertHover(`.test\n\t|color: blue`, { + contents: { + kind: "markdown", + value: + "Sets the color of an element's text\n\n(Edge 12, Firefox 1, Safari 1, Chrome 1, IE 3, Opera 3)\n\nSyntax: <color>\n\n[MDN Reference](https://developer.mozilla.org/docs/Web/CSS/color)", + }, + }); + assertHover( + `.test\n\t|color: blue`, + { + contents: { + kind: "markdown", + value: "[MDN Reference](https://developer.mozilla.org/docs/Web/CSS/color)", + }, + }, + { documentation: false }, + ); + assertHover( + `.test\n\t|color: blue`, + { + contents: { + kind: "markdown", + value: + "Sets the color of an element's text\n\n(Edge 12, Firefox 1, Safari 1, Chrome 1, IE 3, Opera 3)\n\nSyntax: <color>", + }, + }, + { references: false }, + ); + }); + + test("specificity", () => { + assertHover(`.|foo\n\t`, { + contents: [ + { language: "html", value: '' }, + "[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (0, 1, 0)", + ], + }); + }); + + test("nested", () => { + assertHover( + `.foo + .bar + @media only screen + .|bar`, + { + contents: [ + { + language: "html", + value: + '@media only screen\n … \n …\n \n …\n ', + }, + "[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (0, 1, 0)", + ], + }, + ); + assertHover(`div\n\td|iv\n\t\t`, { + contents: [ + { language: "html", value: "
\n …\n
" }, + "[Selector Specificity](https://developer.mozilla.org/docs/Web/CSS/Specificity): (0, 0, 1)", + ], + }); + }); + + test("at-root", () => { + assertHover(".test\n\t@|at-root", { + contents: [], + }); + }); +}); From 5dd862cc4e11ed0149f5954629291be4524bbee6 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 16 Jun 2024 18:13:21 +0200 Subject: [PATCH 043/138] refactor: folding indented --- .../src/services/cssFolding.ts | 26 ++++- .../src/test/css/folding.test.ts | 2 +- .../src/test/sass/sassFolding.test.ts | 95 +++++++++++++++++++ 3 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 packages/vscode-css-languageservice/src/test/sass/sassFolding.test.ts diff --git a/packages/vscode-css-languageservice/src/services/cssFolding.ts b/packages/vscode-css-languageservice/src/services/cssFolding.ts index 1769e03e..50cb03e2 100644 --- a/packages/vscode-css-languageservice/src/services/cssFolding.ts +++ b/packages/vscode-css-languageservice/src/services/cssFolding.ts @@ -27,6 +27,8 @@ function computeFoldingRanges(document: TextDocument): FoldingRange[] { switch (document.languageId) { case "scss": return new SassScanner(); + case "sass": + return new SassScanner({ syntax: "indented" }); default: return new Scanner(); } @@ -56,7 +58,7 @@ function computeFoldingRanges(document: TextDocument): FoldingRange[] { let token = scanner.scan(); let prevToken: IToken | null = null; - while (token.type !== TokenType.EOF) { + done: while (true) { switch (token.type) { case TokenType.CurlyL: case TokenType.Indent: @@ -64,10 +66,20 @@ function computeFoldingRanges(document: TextDocument): FoldingRange[] { delimiterStack.push({ line: getStartLine(token), type: "brace", isStart: true }); break; } + case TokenType.EOF: + case TokenType.Dedent: case TokenType.CurlyR: { + // for indented EOF can fill the same role as CurlyR, but we don't want that to be the case for (S)CSS + if (token.type === TokenType.EOF && scanner.syntax !== "indented") { + break done; + } + if (delimiterStack.length !== 0) { const prevDelimiter = popPrevStartDelimiterOfType(delimiterStack, "brace"); if (!prevDelimiter) { + if (token.type === TokenType.EOF) { + break done; + } break; } @@ -92,6 +104,12 @@ function computeFoldingRanges(document: TextDocument): FoldingRange[] { }); } } + } else { + // There might be several Indents we should create ranges from, + // so only break out here if the stack is empty. + if (token.type === TokenType.EOF) { + break done; + } } break; } @@ -112,7 +130,11 @@ function computeFoldingRanges(document: TextDocument): FoldingRange[] { const matches = token.text.match(/^\s*\/\*\s*(#region|#endregion)\b\s*(.*?)\s*\*\//); if (matches) { return commentRegionMarkerToDelimiter(matches[1]); - } else if (document.languageId === "scss" || document.languageId === "less") { + } else if ( + document.languageId === "scss" || + document.languageId === "sass" || + document.languageId === "less" + ) { const matches = token.text.match(/^\s*\/\/\s*(#region|#endregion)\b\s*(.*?)\s*/); if (matches) { return commentRegionMarkerToDelimiter(matches[1]); diff --git a/packages/vscode-css-languageservice/src/test/css/folding.test.ts b/packages/vscode-css-languageservice/src/test/css/folding.test.ts index 2b92f996..fcb93ed1 100644 --- a/packages/vscode-css-languageservice/src/test/css/folding.test.ts +++ b/packages/vscode-css-languageservice/src/test/css/folding.test.ts @@ -8,7 +8,7 @@ import { suite, test, assert } from "vitest"; import { TextDocument, FoldingRange, FoldingRangeKind, getCSSLanguageService } from "../../cssLanguageService"; -function assertRanges( +export function assertRanges( lines: string[], expected: FoldingRange[], languageId = "css", diff --git a/packages/vscode-css-languageservice/src/test/sass/sassFolding.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassFolding.test.ts new file mode 100644 index 00000000..328a1575 --- /dev/null +++ b/packages/vscode-css-languageservice/src/test/sass/sassFolding.test.ts @@ -0,0 +1,95 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { suite, test, assert } from "vitest"; +import { FoldingRange, FoldingRangeKind } from "vscode-languageserver-types"; +import { assertRanges as assertCssRanges } from "../css/folding.test"; + +function assertRanges(lines: string[], expected: FoldingRange[], rangeLimit: number | null = null) { + assertCssRanges(lines, expected, "sass", rangeLimit); +} + +function r(startLine: number, endLine: number, kind?: FoldingRangeKind | string): FoldingRange { + return { startLine, endLine, kind }; +} + +suite("Sass folding", () => { + suite("basic", () => { + test("fold single rule", () => { + let input = [".foo", " color: red"]; + assertRanges(input, [r(0, 1)]); + }); + + test("fold multiple rules", () => { + let input = [".foo", " color: red", " opacity: 1"]; + assertRanges(input, [r(0, 2)]); + }); + }); + + suite("nested", () => { + test("selectors", () => { + let input = [".foo", " & .bar", " color: red"]; + assertRanges(input, [r(0, 2), r(1, 2)]); + }); + + test("media query", () => { + let input = ["@media screen", " .foo", " color: red"]; + assertRanges(input, [r(0, 2), r(1, 2)]); + }); + }); + + suite("regions", () => { + test("region", () => { + let input = ["/* #region */", ".bar", " color: red", "/* #endregion */"]; + assertRanges(input, [r(0, 3, "region"), r(1, 2)]); + }); + + test("region with description", () => { + let input = ["/* #region This is a description */", ".bar", " color: red", "/* #endregion */"]; + assertRanges(input, [r(0, 3, "region"), r(1, 2)]); + }); + + test("region with Sass comment", () => { + let input = ["// #region", ".bar", " color: red", "// #endregion"]; + assertRanges(input, [r(0, 3, "region"), r(1, 2)]); + }); + + test("region with Sass comment and description", () => { + let input = ["// #region This is a description", ".bar", " color: red", "// #endregion"]; + assertRanges(input, [r(0, 3, "region"), r(1, 2)]); + }); + }); + + suite("max ranges option", () => { + test("max range one omits anything but the outermost range", () => { + let input = ["/* #region This is a description */", ".bar", " color: red", "/* #endregion */"]; + assertRanges(input, [r(0, 3, "region")], 1); + }); + }); + + suite("Sass features", () => { + test("mixin declaration", () => { + let input = [ + "@mixin clearfix($width)", + " @if !$width", + " // if width is not passed", + " @else", + " display: inline-block", + " width: $width", + ]; + assertRanges(input, [r(0, 5), r(1, 2), r(3, 5)]); + }); + + test("interpolation", () => { + let input = [".orbit-#{$d}-prev", " foo-#{$d}-bar: 1", " #{$d}-bar-#{$d}: 2"]; + assertRanges(input, [r(0, 2)]); + }); + + test("while", () => { + let input = ["@while $i > 0", " .item-#{$i}", " width: 2em * $i", " $i: $i - 2"]; + assertRanges(input, [r(0, 3), r(1, 2)]); + }); + }); +}); From bd6bb3b4bb9b58d6cfcd18b0b4587ab9fd6db0d8 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 16 Jun 2024 19:25:16 +0200 Subject: [PATCH 044/138] refactor: completions indented --- .../src/services/cssCompletion.ts | 14 ++- .../src/test/sass/sassCompletion.test.ts | 109 ++++++++++++++++++ 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/packages/vscode-css-languageservice/src/services/cssCompletion.ts b/packages/vscode-css-languageservice/src/services/cssCompletion.ts index c4fb815b..76544db9 100644 --- a/packages/vscode-css-languageservice/src/services/cssCompletion.ts +++ b/packages/vscode-css-languageservice/src/services/cssCompletion.ts @@ -275,13 +275,23 @@ export class CSSCompletion { } // Empty .selector { | } case if (!declaration && completePropertyWithSemicolon) { - insertText += "$0;"; + if (this.textDocument.languageId === "sass") { + // Semicolons are syntax errors in indented + insertText += "$0"; + } else { + insertText += "$0;"; + } } // Cases such as .selector { p; } or .selector { p:; } if (declaration && !declaration.semicolonPosition) { if (completePropertyWithSemicolon && this.offset >= this.textDocument.offsetAt(range.end)) { - insertText += "$0;"; + if (this.textDocument.languageId === "sass") { + // Semicolons are syntax errors in indented + insertText += "$0"; + } else { + insertText += "$0;"; + } } } diff --git a/packages/vscode-css-languageservice/src/test/sass/sassCompletion.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassCompletion.test.ts index 6e8e89a2..d4b0c873 100644 --- a/packages/vscode-css-languageservice/src/test/sass/sassCompletion.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassCompletion.test.ts @@ -669,4 +669,113 @@ body workspaceFolderUri, ); }); + + suite("CSS completions", () => { + // test a subset of CSS completions in indented syntax to confirm things are working + test("stylesheet", async () => { + await testCompletionFor( + `| `, + { + items: [ + { label: "@import", resultText: "@import " }, + { label: "@keyframes", resultText: "@keyframes " }, + { label: "div", resultText: "div " }, + ], + }, + undefined, + "test://test/test.sass", + ); + await testCompletionFor( + `| body `, + { + items: [ + { label: "@import", resultText: "@import body " }, + { label: "@keyframes", resultText: "@keyframes body " }, + { label: "html", resultText: "html body " }, + ], + }, + undefined, + "test://test/test.sass", + ); + }); + + test("selectors", async () => { + await testCompletionFor( + "a:h| ", + { + items: [ + { label: ":hover", resultText: "a:hover " }, + { label: "::after", resultText: "a::after " }, + ], + }, + undefined, + "test://test/test.sass", + ); + }); + + test("properties", async () => { + await testCompletionFor( + `body + |`, + { + items: [ + { + label: "display", + resultText: `body + display: `, + }, + { + label: "background", + resultText: `body + background: `, + }, + ], + }, + undefined, + "test://test/test.sass", + ); + }); + + test("variables", async () => { + await testCompletionFor( + `:root + --myvar: red + +body + color: |`, + { + items: [ + { + label: "--myvar", + resultText: `:root + --myvar: red + +body + color: var(--myvar)`, + }, + ], + }, + undefined, + "test://test/test.sass", + ); + }); + }); + + test("no semicolon, even if configured to for (S)CSS", async () => { + await testCompletionFor( + `.foo + p|`, + { + items: [ + { + label: "position", + resultText: `.foo + position: $0`, + }, + ], + }, + { completion: { triggerPropertyValueCompletion: true, completePropertyWithSemicolon: true } }, + "test://test/test.sass", + ); + }); }); From 547a35d5b0f4ee8445f11c6a88d9036dcf0be1a7 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 16 Jun 2024 19:52:49 +0200 Subject: [PATCH 045/138] refactor: find symbols indented --- .../find-symbols-document-indented.test.ts | 60 +++++++++++++++++++ .../language-services/src/language-feature.ts | 6 +- .../src/language-model-cache.ts | 8 +-- .../src/language-services.ts | 45 +++++++++----- .../vscode-css-languageservice/package.json | 2 +- .../src/test/sass/sassNavigation.test.ts | 41 +++++++++++++ .../src/tsconfig.esm.json | 9 ++- .../src/tsconfig.json | 7 +-- 8 files changed, 146 insertions(+), 32 deletions(-) create mode 100644 packages/language-services/src/features/__tests__/find-symbols-document-indented.test.ts diff --git a/packages/language-services/src/features/__tests__/find-symbols-document-indented.test.ts b/packages/language-services/src/features/__tests__/find-symbols-document-indented.test.ts new file mode 100644 index 00000000..3c999fed --- /dev/null +++ b/packages/language-services/src/features/__tests__/find-symbols-document-indented.test.ts @@ -0,0 +1,60 @@ +import { test, assert, beforeEach } from "vitest"; +import { getLanguageService } from "../../language-services"; +import { SymbolKind, Range, Position } from "../../language-services-types"; +import { getOptions } from "../../utils/test-helpers"; + +const { fileSystemProvider, ...rest } = getOptions(); +const ls = getLanguageService({ fileSystemProvider, ...rest }); + +beforeEach(() => { + ls.clearCache(); +}); + +test("should return symbols", async () => { + const document = fileSystemProvider.createDocument( + `$name: "value" +@mixin mixin($a: 1, $b) + line-height: $a + color: $b +@function function($a: 1, $b) + @return $a * $b +%placeholder + color: blue`, + { + languageId: "sass", + }, + ); + + const symbols = ls.findDocumentSymbols(document); + const [variable, mixin, func, placeholder] = symbols; + + assert.deepStrictEqual(variable, { + kind: SymbolKind.Variable, + name: "$name", + range: Range.create(Position.create(0, 0), Position.create(0, 14)), + selectionRange: Range.create(Position.create(0, 0), Position.create(0, 5)), + }); + assert.deepStrictEqual(mixin, { + kind: SymbolKind.Method, + name: "mixin", + detail: "($a: 1, $b)", + range: Range.create(Position.create(1, 0), Position.create(4, 0)), + selectionRange: Range.create(Position.create(1, 7), Position.create(1, 12)), + }); + assert.deepStrictEqual(func, { + kind: SymbolKind.Function, + name: "function", + detail: "($a: 1, $b)", + range: Range.create(Position.create(4, 0), Position.create(6, 0)), + selectionRange: Range.create( + Position.create(4, 10), + Position.create(4, 18), + ), + }); + assert.deepStrictEqual(placeholder, { + kind: SymbolKind.Class, + name: "%placeholder", + range: Range.create(Position.create(6, 0), Position.create(7, 12)), + selectionRange: Range.create(Position.create(6, 0), Position.create(6, 12)), + }); +}); diff --git a/packages/language-services/src/language-feature.ts b/packages/language-services/src/language-feature.ts index a4ad5709..74fb219d 100644 --- a/packages/language-services/src/language-feature.ts +++ b/packages/language-services/src/language-feature.ts @@ -25,7 +25,7 @@ import { asDollarlessVariable } from "./utils/sass"; export type LanguageFeatureInternal = { cache: LanguageModelCache; - scssLs: VSCodeLanguageService; + sassLs: VSCodeLanguageService; }; type FindOptions = { @@ -84,11 +84,11 @@ export abstract class LanguageFeature { false, }, }; - this._internal.scssLs.configure(configuration); + this._internal.sassLs.configure(configuration); } protected getUpstreamLanguageServer(): VSCodeLanguageService { - return this._internal.scssLs; + return this._internal.sassLs; } protected getDocumentContext() { diff --git a/packages/language-services/src/language-model-cache.ts b/packages/language-services/src/language-model-cache.ts index 587ed201..a477a365 100644 --- a/packages/language-services/src/language-model-cache.ts +++ b/packages/language-services/src/language-model-cache.ts @@ -32,11 +32,11 @@ const defaultCacheEvictInterval = 0; // default off to not leave an interval run export class LanguageModelCache { #languageModels: LanguageModels = {}; #nModels = 0; - #options: LanguageModelCacheOptions & { scssLs: VSCodeLanguageService }; + #options: LanguageModelCacheOptions & { sassLs: VSCodeLanguageService }; #cleanupInterval: NodeJS.Timeout | undefined = undefined; constructor( - options: LanguageModelCacheOptions & { scssLs: VSCodeLanguageService }, + options: LanguageModelCacheOptions & { sassLs: VSCodeLanguageService }, ) { this.#options = { maxEntries: 10_000, @@ -75,7 +75,7 @@ export class LanguageModelCache { languageModelInfo.cTime = Date.now(); return languageModelInfo.languageModel; } - const languageModel = this.#options.scssLs.parseStylesheet( + const languageModel = this.#options.sassLs.parseStylesheet( document, ) as Node; let sassdoc: ParseResult[] = []; @@ -167,7 +167,7 @@ export class LanguageModelCache { onDocumentChanged(document: TextDocument) { const version = document.version; const languageId = document.languageId; - const languageModel = this.#options.scssLs.parseStylesheet( + const languageModel = this.#options.sassLs.parseStylesheet( document, ) as Node; let sassdoc: ParseResult[] = []; diff --git a/packages/language-services/src/language-services.ts b/packages/language-services/src/language-services.ts index 8ed28261..87e6655e 100644 --- a/packages/language-services/src/language-services.ts +++ b/packages/language-services/src/language-services.ts @@ -1,4 +1,4 @@ -import { getSCSSLanguageService } from "@somesass/vscode-css-languageservice"; +import { getSassLanguageService } from "@somesass/vscode-css-languageservice"; import { CodeActions } from "./features/code-actions"; import { DoComplete } from "./features/do-complete"; import { DoDiagnostics } from "./features/do-diagnostics"; @@ -53,37 +53,52 @@ class LanguageServiceImpl implements LanguageService { #findSymbols: FindSymbols; constructor(options: LanguageServiceOptions) { - const scssLs = getSCSSLanguageService({ + const sassLs = getSassLanguageService({ clientCapabilities: options.clientCapabilities, fileSystemProvider: mapFsProviders(options.fileSystemProvider), }); const cache = new LanguageServerCache({ - scssLs, + sassLs, ...options.languageModelCache, }); this.#cache = cache; - this.#codeActions = new CodeActions(this, options, { scssLs, cache }); - this.#doComplete = new DoComplete(this, options, { scssLs, cache }); - this.#doDiagnostics = new DoDiagnostics(this, options, { scssLs, cache }); - this.#doHover = new DoHover(this, options, { scssLs, cache }); - this.#doRename = new DoRename(this, options, { scssLs, cache }); + this.#codeActions = new CodeActions(this, options, { + sassLs, + cache, + }); + this.#doComplete = new DoComplete(this, options, { sassLs, cache }); + this.#doDiagnostics = new DoDiagnostics(this, options, { + sassLs, + cache, + }); + this.#doHover = new DoHover(this, options, { sassLs, cache }); + this.#doRename = new DoRename(this, options, { sassLs, cache }); this.#doSignatureHelp = new DoSignatureHelp(this, options, { - scssLs, + sassLs, + cache, + }); + this.#findColors = new FindColors(this, options, { sassLs, cache }); + this.#findDefinition = new FindDefinition(this, options, { + sassLs, cache, }); - this.#findColors = new FindColors(this, options, { scssLs, cache }); - this.#findDefinition = new FindDefinition(this, options, { scssLs, cache }); this.#findDocumentHighlights = new FindDocumentHighlights(this, options, { - scssLs, + sassLs, cache, }); this.#findDocumentLinks = new FindDocumentLinks(this, options, { - scssLs, + sassLs, + cache, + }); + this.#findReferences = new FindReferences(this, options, { + sassLs, + cache, + }); + this.#findSymbols = new FindSymbols(this, options, { + sassLs, cache, }); - this.#findReferences = new FindReferences(this, options, { scssLs, cache }); - this.#findSymbols = new FindSymbols(this, options, { scssLs, cache }); } configure(configuration: LanguageServiceConfiguration): void { diff --git a/packages/vscode-css-languageservice/package.json b/packages/vscode-css-languageservice/package.json index 46d56cf2..52416d58 100644 --- a/packages/vscode-css-languageservice/package.json +++ b/packages/vscode-css-languageservice/package.json @@ -34,7 +34,7 @@ }, "scripts": { "build": "npm run compile && npm run compile-esm", - "compile": "tsc -p ./src && npm run lint", + "compile": "tsc -p ./src/tsconfig.json && npm run lint", "compile-esm": "tsc -p ./src/tsconfig.esm.json", "clean": "rimraf lib", "watch": "tsc -w -p ./src", diff --git a/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts index 899d81f8..37bf1f10 100644 --- a/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts @@ -1735,6 +1735,47 @@ foo [{ name: "", kind: SymbolKind.Method, range: newRange(0, 6), selectionRange: newRange(0, 0) }], "sass", ); + + assertDocumentSymbols( + ls, + `$name: "value" +@mixin mixin($a: 1, $b) + line-height: $a + color: $b +@function function($a: 1, $b) + @return $a * $b +%placeholder + color: blue`, + [ + { + kind: SymbolKind.Variable, + name: "$name", + range: Range.create(Position.create(0, 0), Position.create(0, 14)), + selectionRange: Range.create(Position.create(0, 0), Position.create(0, 5)), + }, + { + detail: "($a: 1, $b)", + kind: SymbolKind.Method, + name: "mixin", + range: Range.create(Position.create(1, 0), Position.create(4, 0)), + selectionRange: Range.create(Position.create(1, 7), Position.create(1, 12)), + }, + { + detail: "($a: 1, $b)", + kind: SymbolKind.Function, + name: "function", + range: Range.create(Position.create(4, 0), Position.create(6, 0)), + selectionRange: Range.create(Position.create(4, 10), Position.create(4, 18)), + }, + { + kind: SymbolKind.Class, + name: "%placeholder", + range: Range.create(Position.create(6, 0), Position.create(7, 12)), + selectionRange: Range.create(Position.create(6, 0), Position.create(6, 12)), + }, + ], + "sass", + ); }); }); diff --git a/packages/vscode-css-languageservice/src/tsconfig.esm.json b/packages/vscode-css-languageservice/src/tsconfig.esm.json index 63606e2e..bc6eaa7a 100644 --- a/packages/vscode-css-languageservice/src/tsconfig.esm.json +++ b/packages/vscode-css-languageservice/src/tsconfig.esm.json @@ -8,8 +8,7 @@ "strict": true, "stripInternal": true, "outDir": "../lib/esm", - "lib": [ - "es2020" - ] - } -} \ No newline at end of file + "lib": ["es2020"] + }, + "exclude": ["./test/"] +} diff --git a/packages/vscode-css-languageservice/src/tsconfig.json b/packages/vscode-css-languageservice/src/tsconfig.json index 31425e6f..fce46f01 100644 --- a/packages/vscode-css-languageservice/src/tsconfig.json +++ b/packages/vscode-css-languageservice/src/tsconfig.json @@ -8,8 +8,7 @@ "strict": true, "stripInternal": true, "outDir": "../lib/umd", - "lib": [ - "es2020" - ] - } + "lib": ["es2020"] + }, + "exclude": ["./test/"] } From 08d6a1b81eafa43b1e5633681702fda9a1b37faf Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 16 Jun 2024 20:43:30 +0200 Subject: [PATCH 046/138] refactor: hover indented minus SassDoc --- .../src/features/__tests__/do-hover.test.ts | 49 +++++++++++++++++++ .../src/features/do-hover.ts | 14 ++++-- .../language-services/src/language-feature.ts | 4 +- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/packages/language-services/src/features/__tests__/do-hover.test.ts b/packages/language-services/src/features/__tests__/do-hover.test.ts index 632be64f..1f8a9df3 100644 --- a/packages/language-services/src/features/__tests__/do-hover.test.ts +++ b/packages/language-services/src/features/__tests__/do-hover.test.ts @@ -21,6 +21,23 @@ test("should show hover information for symbol in the same document", async () = assert.match(JSON.stringify(result), /\$primary/); }); +test("symbol declared with indented syntax is not previewed as SCSS", async () => { + const document = fileSystemProvider.createDocument( + `$primary: limegreen +.a + color: $primary +`, + { languageId: "sass" }, + ); + + const result = await ls.doHover(document, Position.create(2, 10)); + assert.isNotNull(result, "Expected to find a hover result for $primary"); + const json = JSON.stringify(result); + assert.match(json, /\$primary/); + assert.match(json, /```sass/); + assert.notInclude(json, ";"); +}); + test("should show hover information for symbol in a different document via @import", async () => { const one = fileSystemProvider.createDocument("$primary: limegreen;", { uri: "one.scss", @@ -169,6 +186,38 @@ test("should show hover information for Sassdoc annotation", async () => { assert.match(JSON.stringify(result), /@type/); }); +test("Sass indented should show hover information for Sassdoc annotation", async () => { + const document = fileSystemProvider.createDocument( + [ + "$a: 1", + "/// Some wise words", + "/// @type String", + '$documented-variable: "value"', + ], + { languageId: "sass" }, + ); + + const result = await ls.doHover(document, Position.create(2, 8)); + assert.isNotNull(result, "Expected to find a hover result for @type"); + assert.match(JSON.stringify(result), /@type/); +}); + +test.skip("SassDoc hover info works for indented", async () => { + const document = fileSystemProvider.createDocument( + `/// Foo bar +/// @type Color +$_decoration: underline dotted red +a + text-decoration: $_decoration + `, + { languageId: "sass" }, + ); + + const result = await ls.doHover(document, Position.create(4, 24)); + assert.isNotNull(result, "Expected to find a hover result for $_decoration"); + assert.match(JSON.stringify(result), /Foo bar/); +}); + test("should show hover information for Sassdoc annotation at the start of the document", async () => { const document = fileSystemProvider.createDocument([ "/// Some wise words", diff --git a/packages/language-services/src/features/do-hover.ts b/packages/language-services/src/features/do-hover.ts index 854056d8..7562868e 100644 --- a/packages/language-services/src/features/do-hover.ts +++ b/packages/language-services/src/features/do-hover.ts @@ -277,7 +277,7 @@ export class DoHover extends LanguageFeature { const result = { kind: MarkupKind.Markdown, value: [ - "```scss", + document.languageId === "sass" ? "```sass" : "```scss", `@function ${maybePrefixedName}${symbol.detail || "()"}`, "```", ].join("\n"), @@ -305,7 +305,7 @@ export class DoHover extends LanguageFeature { const result = { kind: MarkupKind.Markdown, value: [ - "```scss", + document.languageId === "sass" ? "```sass" : "```scss", `@mixin ${maybePrefixedName}${symbol.detail || "()"}`, "```", ].join("\n"), @@ -331,7 +331,11 @@ export class DoHover extends LanguageFeature { ): Hover { const result = { kind: MarkupKind.Markdown, - value: ["```scss", symbol.name, "```"].join("\n"), + value: [ + document.languageId === "sass" ? "```sass" : "```scss", + symbol.name, + "```", + ].join("\n"), }; const sassdoc = applySassDoc(symbol); @@ -358,8 +362,8 @@ export class DoHover extends LanguageFeature { const result = { kind: MarkupKind.Markdown, value: [ - "```scss", - `${maybePrefixedName}: ${value};${ + document.languageId === "sass" ? "```sass" : "```scss", + `${maybePrefixedName}: ${value}${document.languageId === "sass" ? "" : ";"}${ value !== rawValue ? ` // via ${rawValue}` : "" }`, "```", diff --git a/packages/language-services/src/language-feature.ts b/packages/language-services/src/language-feature.ts index 74fb219d..f8e9df4c 100644 --- a/packages/language-services/src/language-feature.ts +++ b/packages/language-services/src/language-feature.ts @@ -116,7 +116,9 @@ export abstract class LanguageFeature { * @param range Optional range passed to {@link TextDocument.getText} */ protected getScanner(document: TextDocument, range?: Range): Scanner { - const scanner = new SCSSScanner(); + const scanner = new SCSSScanner({ + syntax: document.languageId === "sass" ? "indented" : "scss", + }); scanner.ignoreComment = false; scanner.setSource(document.getText(range)); return scanner; From b1e4ec5a359311ef0f0ce5db3d8898f40d807428 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 16 Jun 2024 20:48:03 +0200 Subject: [PATCH 047/138] refactor: prep for indented syntax in sassdoc examples --- packages/language-services/src/utils/sassdoc.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/language-services/src/utils/sassdoc.ts b/packages/language-services/src/utils/sassdoc.ts index cae98f0b..9ef14ad5 100644 --- a/packages/language-services/src/utils/sassdoc.ts +++ b/packages/language-services/src/utils/sassdoc.ts @@ -157,7 +157,12 @@ export function applySassDoc(symbol: SassDocumentSymbol): string { description += ` ${example.description}`; } - description += ["\n", "```scss", example.code, "```"].join("\n"); + description += [ + "\n", + `\`\`\`${example.type || "scss"}`, + example.code, + "```", + ].join("\n"); } } From c3b8b221a28a4e419aa9f65fe6a55cf8ffecb47d Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 23 Jun 2024 18:50:14 +0200 Subject: [PATCH 048/138] refactor: add support for the sass file extension --- packages/language-server/package.json | 4 +- packages/language-server/src/server.ts | 28 ++--- .../src/utils/__tests__/embedded.test.ts | 101 +++++++++--------- .../language-server/src/utils/embedded.ts | 41 ++++--- .../language-server/src/workspace-scanner.ts | 12 ++- vscode-extension/package.json | 4 +- vscode-extension/src/browser-client.ts | 1 + vscode-extension/src/client.ts | 6 +- vscode-extension/src/node-client.ts | 1 + 9 files changed, 113 insertions(+), 85 deletions(-) diff --git a/packages/language-server/package.json b/packages/language-server/package.json index bd5a733a..b730cfbb 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -1,9 +1,11 @@ { "name": "some-sass-language-server", "version": "1.2.3", - "description": "LSP server for SCSS. Full support for @use and @forward, including aliases, prefixes and hiding. Rich documentation through SassDoc. Workspace-wide code navigation and refactoring.", + "description": "LSP server for Sass. Full support for @use and @forward, including aliases, prefixes and hiding. Rich documentation through SassDoc. Workspace-wide code navigation and refactoring.", "keywords": [ + "sass", "scss", + "sass-indented", "lsp", "language-server-protocol" ], diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index 4a21ed0a..a53e39a5 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -22,7 +22,7 @@ import type { FileSystemProvider } from "./file-system"; import { getFileSystemProvider } from "./file-system-provider"; import { RuntimeEnvironment } from "./runtime"; import { defaultSettings, IEditorSettings, ISettings } from "./settings"; -import { getSCSSRegionsDocument } from "./utils/embedded"; +import { getSassRegionsDocument } from "./utils/embedded"; import WorkspaceScanner from "./workspace-scanner"; export class SomeSassServer { @@ -167,7 +167,7 @@ export class SomeSassServer { ); const files = await fileSystemProvider.findFiles( - "**/*.{scss,svelte,astro,vue}", + "**/*.{scss,sass,svelte,astro,vue}", settings.scannerExclude, ); @@ -264,7 +264,7 @@ export class SomeSassServer { this.connection.onCompletion(async (params) => { if (!ls) return null; - const document = getSCSSRegionsDocument( + const document = getSassRegionsDocument( documents.get(params.textDocument.uri), params.position, ); @@ -277,7 +277,7 @@ export class SomeSassServer { this.connection.onHover((params) => { if (!ls) return null; - const document = getSCSSRegionsDocument( + const document = getSassRegionsDocument( documents.get(params.textDocument.uri), params.position, ); @@ -290,7 +290,7 @@ export class SomeSassServer { this.connection.onSignatureHelp(async (params) => { if (!ls) return null; - const document = getSCSSRegionsDocument( + const document = getSassRegionsDocument( documents.get(params.textDocument.uri), params.position, ); @@ -303,7 +303,7 @@ export class SomeSassServer { this.connection.onDefinition((params) => { if (!ls) return null; - const document = getSCSSRegionsDocument( + const document = getSassRegionsDocument( documents.get(params.textDocument.uri), params.position, ); @@ -316,7 +316,7 @@ export class SomeSassServer { this.connection.onDocumentHighlight((params) => { if (!ls) return null; - const document = getSCSSRegionsDocument( + const document = getSassRegionsDocument( documents.get(params.textDocument.uri), params.position, ); @@ -333,7 +333,7 @@ export class SomeSassServer { this.connection.onDocumentLinks((params) => { if (!ls) return null; - const document = getSCSSRegionsDocument( + const document = getSassRegionsDocument( documents.get(params.textDocument.uri), ); if (!document) return null; @@ -349,7 +349,7 @@ export class SomeSassServer { this.connection.onReferences(async (params) => { if (!ls) return null; - const document = getSCSSRegionsDocument( + const document = getSassRegionsDocument( documents.get(params.textDocument.uri), params.position, ); @@ -373,7 +373,7 @@ export class SomeSassServer { this.connection.onCodeAction(async (params) => { if (!ls) return null; - const document = getSCSSRegionsDocument( + const document = getSassRegionsDocument( documents.get(params.textDocument.uri), ); if (!document) return null; @@ -415,7 +415,7 @@ export class SomeSassServer { this.connection.onPrepareRename(async (params) => { if (!ls) return null; - const document = getSCSSRegionsDocument( + const document = getSassRegionsDocument( documents.get(params.textDocument.uri), params.position, ); @@ -428,7 +428,7 @@ export class SomeSassServer { this.connection.onRenameRequest(async (params) => { if (!ls) return null; - const document = getSCSSRegionsDocument( + const document = getSassRegionsDocument( documents.get(params.textDocument.uri), params.position, ); @@ -445,7 +445,7 @@ export class SomeSassServer { this.connection.onDocumentColor(async (params) => { if (!ls) return null; - const document = getSCSSRegionsDocument( + const document = getSassRegionsDocument( documents.get(params.textDocument.uri), ); if (!document) return null; @@ -461,7 +461,7 @@ export class SomeSassServer { this.connection.onColorPresentation((params) => { if (!ls) return null; - const document = getSCSSRegionsDocument( + const document = getSassRegionsDocument( documents.get(params.textDocument.uri), ); if (!document) return null; diff --git a/packages/language-server/src/utils/__tests__/embedded.test.ts b/packages/language-server/src/utils/__tests__/embedded.test.ts index 523792af..2d4b9a4c 100644 --- a/packages/language-server/src/utils/__tests__/embedded.test.ts +++ b/packages/language-server/src/utils/__tests__/embedded.test.ts @@ -3,9 +3,9 @@ import { Position } from "vscode-languageserver"; import { TextDocument } from "vscode-languageserver-textdocument"; import { isFileWhereScssCanBeEmbedded, - getSCSSRegions, - getSCSSContent, - getSCSSRegionsDocument, + getSassRegions, + getSassContent, + getSassRegionsDocument, } from "../embedded"; describe("Utils/VueSvelte", () => { @@ -64,97 +64,102 @@ describe("Utils/VueSvelte", () => { it("getSCSSRegions", () => { assert.deepStrictEqual( - getSCSSRegions(''), - [[34, 34]], + getSassRegions(''), + [{ type: "scss", range: [34, 34] }], ); assert.deepStrictEqual( - getSCSSRegions(''), - [[26, 26]], + getSassRegions(''), + [{ type: "scss", range: [26, 26] }], ); assert.deepStrictEqual( - getSCSSRegions(''), - [[26, 26]], + getSassRegions(''), + [{ type: "scss", range: [26, 26] }], ); - assert.deepStrictEqual(getSCSSRegions(''), [ - [19, 19], + assert.deepStrictEqual(getSassRegions(''), [ + { type: "scss", range: [19, 19] }, ]); - assert.deepStrictEqual(getSCSSRegions(""), [ - [19, 19], + assert.deepStrictEqual(getSassRegions(""), [ + { type: "scss", range: [19, 19] }, ]); - assert.deepStrictEqual(getSCSSRegions(''), [ - [24, 24], + assert.deepStrictEqual(getSassRegions(''), [ + { type: "scss", range: [24, 24] }, ]); assert.deepStrictEqual( - getSCSSRegions( + getSassRegions( "", ), - [[90, 90]], + [{ type: "scss", range: [90, 90] }], ); assert.deepStrictEqual( - getSCSSRegions( + getSassRegions( "\n", ), - [[91, 91]], + [{ type: "scss", range: [91, 91] }], ); assert.deepStrictEqual( - getSCSSRegions( + getSassRegions( "\n", ), - [[91, 92]], + [{ type: "scss", range: [91, 92] }], ); assert.deepStrictEqual( - getSCSSRegions( + getSassRegions( "\n", ), - [[91, 110]], + [{ type: "scss", range: [91, 110] }], ); assert.deepStrictEqual( - getSCSSRegions( + getSassRegions( "\n", ), [ - [90, 109], - [143, 162], + { type: "scss", range: [90, 109] }, + { type: "scss", range: [143, 162] }, ], ); assert.deepStrictEqual( - getSCSSRegions( + getSassRegions( "\n\n", ), [ - [90, 109], - [143, 162], - [202, 221], + { type: "scss", range: [90, 109] }, + { type: "scss", range: [143, 162] }, + { type: "scss", range: [202, 221] }, ], ); - assert.deepStrictEqual(getSCSSRegions(''), []); - assert.deepStrictEqual(getSCSSRegions(''), []); + assert.deepStrictEqual(getSassRegions(''), [ + { type: "sass", range: [19, 19] }, + ]); + assert.deepStrictEqual(getSassRegions(''), [ + { type: "sass", range: [19, 19] }, + ]); + assert.deepStrictEqual(getSassRegions(''), []); assert.deepStrictEqual( - getSCSSRegions(''), - [], + getSassRegions(''), + [{ type: "sass", range: [26, 26] }], ); - assert.deepStrictEqual(getSCSSRegions(""), []); - assert.deepStrictEqual(getSCSSRegions(''), []); + assert.deepStrictEqual(getSassRegions(""), []); + assert.deepStrictEqual(getSassRegions(''), []); }); it("getSCSSContent", () => { assert.strictEqual( - getSCSSContent("sadja|sio|fuioaf", [[5, 10]]), + getSassContent("sadja|sio|fuioaf", [{ type: "scss", range: [5, 10] }]), " |sio| ", ); assert.strictEqual( - getSCSSContent("sadja|sio|fuio^af^", [ - [5, 10], - [14, 18], + getSassContent("sadja|sio|fuio^af^", [ + { type: "scss", range: [5, 10] }, + { type: "scss", range: [14, 18] }, ]), " |sio| ^af^", ); assert.strictEqual( - getSCSSContent( + getSassContent( "", ), `${" ".repeat(90)} a\n { color: white; }${" ".repeat(8)}`, @@ -169,7 +174,7 @@ describe("Utils/VueSvelte", () => { "", ); assert.strictEqual( - getSCSSRegionsDocument(exSCSSDocument, Position.create(0, 0)), + getSassRegionsDocument(exSCSSDocument, Position.create(0, 0)), exSCSSDocument, ); @@ -187,29 +192,29 @@ describe("Utils/VueSvelte", () => { `, ); assert.notDeepEqual( - getSCSSRegionsDocument(exVueDocument, Position.create(2, 15)), + getSassRegionsDocument(exVueDocument, Position.create(2, 15)), exVueDocument, ); assert.deepStrictEqual( - getSCSSRegionsDocument(exVueDocument, Position.create(2, 15)), + getSassRegionsDocument(exVueDocument, Position.create(2, 15)), null, ); assert.notDeepEqual( - getSCSSRegionsDocument(exVueDocument, Position.create(5, 15)), + getSassRegionsDocument(exVueDocument, Position.create(5, 15)), exVueDocument, ); assert.notDeepEqual( - getSCSSRegionsDocument(exVueDocument, Position.create(5, 15)), + getSassRegionsDocument(exVueDocument, Position.create(5, 15)), null, ); assert.notDeepEqual( - getSCSSRegionsDocument(exVueDocument, Position.create(6, 9)), + getSassRegionsDocument(exVueDocument, Position.create(6, 9)), exVueDocument, ); assert.deepStrictEqual( - getSCSSRegionsDocument(exVueDocument, Position.create(6, 9)), + getSassRegionsDocument(exVueDocument, Position.create(6, 9)), null, ); }); diff --git a/packages/language-server/src/utils/embedded.ts b/packages/language-server/src/utils/embedded.ts index f9610bc2..bd0bfc88 100644 --- a/packages/language-server/src/utils/embedded.ts +++ b/packages/language-server/src/utils/embedded.ts @@ -1,19 +1,22 @@ import { TextDocument } from "vscode-languageserver-textdocument"; import type { Position } from "vscode-languageserver-textdocument"; -type Region = [number, number]; +type Region = { + type: "scss" | "sass"; + range: [number, number]; +}; export function isFileWhereScssCanBeEmbedded(path: string) { - if (path.endsWith(".scss")) { + if (path.endsWith(".scss") || path.endsWith(".sass")) { return false; } return true; } -export function getSCSSRegions(content: string) { +export function getSassRegions(content: string) { const regions: Region[] = []; const startRe = - //g; + /s(a|c)ss)["'][\w\t\n "'=]*>/g; const endRe = /<\/style>/g; let start: RegExpExecArray | null; let end: RegExpExecArray | null; @@ -22,16 +25,19 @@ export function getSCSSRegions(content: string) { (end = endRe.exec(content)) !== null ) { if (start[0] !== undefined) { - regions.push([start.index + start[0].length, end.index]); + regions.push({ + type: (start.groups?.type || "scss") as "sass" | "scss", + range: [start.index + start[0].length, end.index], + }); } } return regions; } -export function getSCSSContent( +export function getSassContent( content: string, - regions = getSCSSRegions(content), + regions = getSassRegions(content), ) { const oldContent = content; @@ -40,25 +46,25 @@ export function getSCSSContent( .map((line) => " ".repeat(line.length)) .join("\n"); - for (const r of regions) { + for (const { range } of regions) { newContent = - newContent.slice(0, r[0]) + - oldContent.slice(r[0], r[1]) + - newContent.slice(r[1]); + newContent.slice(0, range[0]) + + oldContent.slice(range[0], range[1]) + + newContent.slice(range[1]); } return newContent; } /** - * Function that extracts only the SCSS region of a template + * Function that extracts only the Sass region of a template * language such as Vue, Svelte or Astro. This is not the correct * approach for embedded languages, compared to say the HTML language * server. * * @todo Look into how to do this properly with a goal to unship this custom handling. */ -export function getSCSSRegionsDocument( +export function getSassRegionsDocument( document: TextDocument | null | undefined = null, position?: Position, ): TextDocument | null { @@ -71,20 +77,21 @@ export function getSCSSRegionsDocument( } const text = document.getText(); - const scssRegions = getSCSSRegions(text); + const regions = getSassRegions(text); if ( typeof position === "undefined" || - scssRegions.some((region) => region[0] <= offset && region[1] >= offset) + (regions.some(({ range }) => range[0] <= offset && range[1] >= offset) && + regions.every(({ type }) => type === regions[0].type)) ) { const uri = document.uri; const version = document.version; return TextDocument.create( uri, - "scss", + regions[0].type, version, - getSCSSContent(text, scssRegions), + getSassContent(text, regions), ); } diff --git a/packages/language-server/src/workspace-scanner.ts b/packages/language-server/src/workspace-scanner.ts index 573f9e7c..9584beeb 100644 --- a/packages/language-server/src/workspace-scanner.ts +++ b/packages/language-server/src/workspace-scanner.ts @@ -4,7 +4,7 @@ import { } from "@somesass/language-services"; import { TextDocument } from "vscode-languageserver-textdocument"; import { URI } from "vscode-uri"; -import { getSCSSRegionsDocument } from "./utils/embedded"; +import { getSassRegionsDocument } from "./utils/embedded"; export default class WorkspaceScanner { #ls: LanguageService; @@ -63,9 +63,15 @@ export default class WorkspaceScanner { try { const content = await this.#fs.readFile(uri); + const uriString = uri.toString(); - const document = getSCSSRegionsDocument( - TextDocument.create(uri.toString(), "scss", 1, content), + const document = getSassRegionsDocument( + TextDocument.create( + uriString, + uriString.endsWith(".sass") ? "sass" : "scss", + 1, + content, + ), ); if (!document) return; diff --git a/vscode-extension/package.json b/vscode-extension/package.json index f506d89e..2302eb37 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -1,6 +1,6 @@ { "name": "some-sass", - "displayName": "Some Sass: extended support for SCSS and SassDoc", + "displayName": "Some Sass: extended support for Sass and SassDoc", "description": "Full support for @use and @forward, including aliases, prefixes and hiding. Rich documentation through SassDoc. Workspace-wide code navigation and refactoring.", "version": "3.1.4", "private": true, @@ -17,6 +17,7 @@ }, "keywords": [ "scss", + "sass", "sassdoc", "autocompletion", "intellisense", @@ -27,6 +28,7 @@ ], "activationEvents": [ "onLanguage:scss", + "onLanguage:sass", "onLanguage:vue", "onLanguage:svelte", "onLanguage:astro", diff --git a/vscode-extension/src/browser-client.ts b/vscode-extension/src/browser-client.ts index 7812ce3d..141c57d0 100644 --- a/vscode-extension/src/browser-client.ts +++ b/vscode-extension/src/browser-client.ts @@ -73,6 +73,7 @@ export async function activate(context: ExtensionContext): Promise { document.uri.scheme !== "vscode-vfs" && document.uri.scheme !== "vscode-test-web" && document.languageId !== "scss" && + document.languageId !== "sass" && document.languageId !== "vue" && document.languageId !== "svelte" && document.languageId !== "astro" diff --git a/vscode-extension/src/client.ts b/vscode-extension/src/client.ts index 852787f1..f1add4ec 100644 --- a/vscode-extension/src/client.ts +++ b/vscode-extension/src/client.ts @@ -38,6 +38,7 @@ export function createLanguageClientOptions( ): LanguageClientOptions { let documentSelector: DocumentSelector = [ { scheme: "untitled", language: "scss" }, + { scheme: "untitled", language: "sass" }, { scheme: "untitled", language: "vue" }, { scheme: "untitled", language: "svelte" }, { scheme: "untitled", language: "astro" }, @@ -53,14 +54,17 @@ export function createLanguageClientOptions( documentSelector = [ { scheme: "file", language: "scss", pattern }, + { scheme: "file", language: "sass", pattern }, { scheme: "file", language: "vue", pattern }, { scheme: "file", language: "svelte", pattern }, { scheme: "file", language: "astro", pattern }, { scheme: "vscode-vfs", language: "scss", pattern }, + { scheme: "vscode-vfs", language: "sass", pattern }, { scheme: "vscode-vfs", language: "vue", pattern }, { scheme: "vscode-vfs", language: "svelte", pattern }, { scheme: "vscode-vfs", language: "astro", pattern }, { scheme: "vscode-test-web", language: "scss", pattern: webPattern }, + { scheme: "vscode-test-web", language: "sass", pattern: webPattern }, { scheme: "vscode-test-web", language: "vue", pattern: webPattern }, { scheme: "vscode-test-web", language: "svelte", pattern: webPattern }, { scheme: "vscode-test-web", language: "astro", pattern: webPattern }, @@ -75,7 +79,7 @@ export function createLanguageClientOptions( ? workspace.createFileSystemWatcher({ baseUri: currentWorkspace.uri, base: currentWorkspace.uri.fsPath, - pattern: "**/*.{scss,vue,svelte,astro}", + pattern: "**/*.{scss,sass,vue,svelte,astro}", }) : undefined, }, diff --git a/vscode-extension/src/node-client.ts b/vscode-extension/src/node-client.ts index 673c814b..f5e02a08 100644 --- a/vscode-extension/src/node-client.ts +++ b/vscode-extension/src/node-client.ts @@ -75,6 +75,7 @@ export async function activate(context: ExtensionContext): Promise { document.uri.scheme !== "untitled" && document.uri.scheme !== "vscode-vfs" && document.languageId !== "scss" && + document.languageId !== "sass" && document.languageId !== "vue" && document.languageId !== "svelte" && document.languageId !== "astro" From 39d63a24f20421ea4f13711e3b36fc3f81cb6715 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 23 Jun 2024 20:14:47 +0200 Subject: [PATCH 049/138] feat: syntax for sass indented --- vscode-extension/LICENSE | 10 + vscode-extension/package.json | 33 + vscode-extension/sass.configuration.json | 28 + .../syntaxes/sass.hover.highlighting.json | 43 ++ .../syntaxes/sass.tmLanguage.json | 617 ++++++++++++++++++ .../test/fixtures/pkg-import/src/styles.scss | 1 + 6 files changed, 732 insertions(+) create mode 100644 vscode-extension/sass.configuration.json create mode 100644 vscode-extension/syntaxes/sass.hover.highlighting.json create mode 100644 vscode-extension/syntaxes/sass.tmLanguage.json diff --git a/vscode-extension/LICENSE b/vscode-extension/LICENSE index 6a9fbf49..f4416be8 100644 --- a/vscode-extension/LICENSE +++ b/vscode-extension/LICENSE @@ -19,3 +19,13 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Sass grammar definitions https://github.com/atom/language-sass +The MIT License (MIT) +Copyright (c) 2011-2019 GitHub Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vscode-extension/package.json b/vscode-extension/package.json index 2302eb37..00dd0645 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -40,6 +40,39 @@ "browser": "./dist/browser-client.js", "main": "./dist/node-client.js", "contributes": { + "languages": [ + { + "id": "sass", + "aliases": [ + "Sass", + "sass-indented" + ], + "extensions": [ + ".sass", + ".sass.erb" + ], + "configuration": "./sass.configuration.json" + }, + { + "id": "sass.hover", + "extensions": [ + ".sass.hover" + ], + "configuration": "./sass.configuration.json" + } + ], + "grammars": [ + { + "language": "sass", + "scopeName": "source.sass", + "path": "./syntaxes/sass.tmLanguage.json" + }, + { + "language": "sass.hover", + "scopeName": "source.sass.hover", + "path": "./syntaxes/sass.hover.highlighting.json" + } + ], "configuration": { "properties": { "somesass.scannerDepth": { diff --git a/vscode-extension/sass.configuration.json b/vscode-extension/sass.configuration.json new file mode 100644 index 00000000..7a28b590 --- /dev/null +++ b/vscode-extension/sass.configuration.json @@ -0,0 +1,28 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": ["/*", "*/"] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ], + "folding": { + "offSide": true + } +} diff --git a/vscode-extension/syntaxes/sass.hover.highlighting.json b/vscode-extension/syntaxes/sass.hover.highlighting.json new file mode 100644 index 00000000..71b2783d --- /dev/null +++ b/vscode-extension/syntaxes/sass.hover.highlighting.json @@ -0,0 +1,43 @@ +{ + "fileTypes": ["sass.hover"], + "name": "Sass Hover", + "patterns": [ + { + "match": "(Path)", + "name": "entity.name.function" + }, + { + "match": "([A-z\\/-]+): (.*)", + "name": "meta.path", + "captures": { + "1": { + "name": "variable" + }, + "2": { + "name": "entity.name.type" + } + } + }, + { + "match": "([A-z\\/-]+)", + "name": "meta.path", + "captures": { + "1": { + "name": "keyword.control" + } + } + }, + { + "begin": "\\.([A-z-]+)", + "end": "\\n|[\\t ]", + "name": "meta.extension", + "captures": { + "1": { + "name": "keyword.other" + } + } + } + ], + + "scopeName": "source.sass.hover" +} diff --git a/vscode-extension/syntaxes/sass.tmLanguage.json b/vscode-extension/syntaxes/sass.tmLanguage.json new file mode 100644 index 00000000..390397d8 --- /dev/null +++ b/vscode-extension/syntaxes/sass.tmLanguage.json @@ -0,0 +1,617 @@ +{ + "fileTypes": [ + "sass" + ], + "foldingStartMarker": "/\\*|^#|^\\*|^\\b|*#?region|^\\.", + "foldingStopMarker": "\\*/|*#?endregion|^\\s*$", + "name": "Sass", + "patterns": [ + { + "begin": "^(\\s*)(/\\*)", + "end": "(\\*/)|^(?!\\s\\1)", + "name": "comment.block.sass", + "patterns": [ + { + "include": "#comment-tag" + }, + { + "include": "#comment-param" + } + ] + }, + { + "match": "^[\\t ]*/?//[\\t ]*[SRI][\\t ]*$", + "name": "keyword.other.sass.formatter.action" + }, + { + "begin": "^[\\t ]*//[\\t ]*(import)[\\t ]*(css-variables)[\\t ]*(from)", + "end": "$\\n?", + "name": "comment.import.css.variables", + "captures": { + "1": { + "name": "keyword.control" + }, + "2": { + "name": "variable" + }, + "3": { + "name": "keyword.control" + } + }, + "patterns": [ + { + "include": "#import-quotes" + } + ] + }, + { + "include": "#double-slash" + }, + { + "include": "#double-quoted" + }, + { + "include": "#single-quoted" + }, + { + "include": "#interpolation" + }, + { + "include": "#curly-brackets" + }, + { + "include": "#placeholder-selector" + }, + { + "begin": "\\$[a-zA-Z0-9_-]+(?=:)", + "end": "$\\n?|(?=\\)\\s\\)|\\)\\n)", + "name": "sass.script.maps", + "captures": { + "0": { + "name": "variable.other.name" + } + }, + "patterns": [ + { + "include": "#double-slash" + }, + { + "include": "#double-quoted" + }, + { + "include": "#single-quoted" + }, + { + "include": "#interpolation" + }, + { + "include": "#variable" + }, + { + "include": "#rgb-value" + }, + { + "include": "#numeric" + }, + { + "include": "#unit" + }, + { + "include": "#flag" + }, + { + "include": "#comma" + }, + { + "include": "#function" + }, + { + "include": "#function-content" + }, + { + "include": "#operator" + }, + { + "include": "#reserved-words" + }, + { + "include": "#parent-selector" + }, + { + "include": "#property-value" + }, + { + "include": "#semicolon" + }, + { + "include": "#dotdotdot" + } + ] + }, + { + "include": "#variable-root" + }, + { + "include": "#numeric" + }, + { + "include": "#unit" + }, + { + "include": "#flag" + }, + { + "include": "#comma" + }, + { + "include": "#semicolon" + }, + { + "include": "#dotdotdot" + }, + { + "begin": "@include|\\+(?!\\W|\\d)", + "end": "(?=\\n|\\()", + "name": "support.function.name.sass.library", + "captures": { + "0": { + "name": "keyword.control.at-rule.css.sass" + } + } + }, + { + "begin": "^(@use)", + "end": "(?=\\n)", + "name": "sass.use", + "captures": { + "0": { + "name": "keyword.control.at-rule.css.sass.use" + } + }, + "patterns": [ + { + "match": "as|with", + "name": "support.type.css.sass" + }, + { + "include": "#numeric" + }, + { + "include": "#unit" + }, + { + "include": "#variable-root" + }, + { + "include": "#rgb-value" + }, + { + "include": "#comma" + }, + { + "include": "#parenthesis-open" + }, + { + "include": "#parenthesis-close" + }, + { + "include": "#colon" + }, + { + "include": "#import-quotes" + } + ] + }, + { + "begin": "^@import(.*?)( as.*)?$", + "end": "(?=\\n)", + "name": "keyword.control.at-rule.use", + "captures": { + "1": { + "name": "constant.character.css.sass" + }, + "2": { + "name": "invalid" + } + } + }, + { + "begin": "@mixin|^[\\t ]*=|@function", + "end": "$\\n?|(?=\\()", + "name": "support.function.name.sass", + "captures": { + "0": { + "name": "keyword.control.at-rule.css.sass" + } + }, + "patterns": [ + { + "match": "[\\w-]+", + "name": "entity.name.function" + } + ] + }, + { + "begin": "@", + "end": "$\\n?|\\s(?!(all|braille|embossed|handheld|print|projection|screen|speech|tty|tv|if|only|not)(\\s|,))", + "name": "keyword.control.at-rule.css.sass" + }, + { + "begin": "(?|-|_)", + "name": "entity.name.tag.css.sass.symbol", + "patterns": [ + { + "include": "#interpolation" + }, + { + "include": "#pseudo-class" + } + ] + }, + { + "begin": "#", + "end": "$\\n?|(?=\\s|,|\\(|\\)|\\.|\\[|>)", + "name": "entity.other.attribute-name.id.css.sass", + "patterns": [ + { + "include": "#interpolation" + }, + { + "include": "#pseudo-class" + } + ] + }, + { + "begin": "\\.|(?<=&)(-|_)", + "end": "$\\n?|(?=\\s|,|\\(|\\)|\\[|>)", + "name": "entity.other.attribute-name.class.css.sass", + "patterns": [ + { + "include": "#interpolation" + }, + { + "include": "#pseudo-class" + } + ] + }, + { + "begin": "\\[", + "end": "\\]", + "name": "entity.other.attribute-selector.sass", + "patterns": [ + { + "include": "#double-quoted" + }, + { + "include": "#single-quoted" + }, + { + "match": "\\^|\\$|\\*|~", + "name": "keyword.other.regex.sass" + } + ] + }, + { + "match": "^((?<=\\]|\\)|not\\(|\\*|>|>\\s)|\n*):[a-z:-]+|(::|:-)[a-z:-]+", + "name": "entity.other.attribute-name.pseudo-class.css.sass" + }, + { + "include": "#module" + }, + { + "match": "[\\w-]*\\(", + "name": "entity.name.function" + }, + { + "match": "\\)", + "name": "entity.name.function.close" + }, + { + "begin": ":", + "end": "$\\n?|(?=\\s\\(|and\\(|\\),)", + "name": "meta.property-list.css.sass.prop", + "patterns": [ + { + "match": "(?<=:)[a-z-]+\\s", + "name": "support.type.property-name.css.sass.prop.name" + }, + { + "include": "#double-slash" + }, + { + "include": "#double-quoted" + }, + { + "include": "#single-quoted" + }, + { + "include": "#interpolation" + }, + { + "include": "#curly-brackets" + }, + { + "include": "#variable" + }, + { + "include": "#rgb-value" + }, + { + "include": "#numeric" + }, + { + "include": "#unit" + }, + { + "include": "#module" + }, + { + "match": "--.+?(?=\\))", + "name": "variable.css" + }, + { + "match": "[\\w-]*\\(", + "name": "entity.name.function" + }, + { + "match": "\\)", + "name": "entity.name.function.close" + }, + { + "include": "#flag" + }, + { + "include": "#comma" + }, + { + "include": "#semicolon" + }, + { + "include": "#function" + }, + { + "include": "#function-content" + }, + { + "include": "#operator" + }, + { + "include": "#parent-selector" + }, + { + "include": "#property-value" + } + ] + }, + { + "include": "#rgb-value" + }, + { + "include": "#function" + }, + { + "include": "#function-content" + }, + { + "begin": "(?<=})(?!\\n|\\(|\\)|[a-zA-Z0-9_-]+:)", + "end": "\\s|(?=,|\\.|\\[|\\)|\\n)", + "name": "entity.name.tag.css.sass", + "patterns": [ + { + "include": "#interpolation" + }, + { + "include": "#pseudo-class" + } + ] + }, + { + "include": "#operator" + }, + { + "match": "[a-z-]+((?=:|#{))", + "name": "support.type.property-name.css.sass.prop.name" + }, + { + "include": "#reserved-words" + }, + { + "include": "#property-value" + } + ], + "repository": { + "module": { + "match": "([\\w-]+?)(\\.)", + "name": "constant.character.module", + "captures": { + "1": { + "name": "constant.character.module.name" + }, + "2": { + "name": "constant.numeric.module.dot" + } + } + }, + "comma": { + "match": "\\band\\b|\\bor\\b|,", + "name": "comment.punctuation.comma.sass" + }, + "comment-tag": { + "begin": "(?<={{)", + "end": "(?=}})", + "name": "comment.tag.sass" + }, + "comment-param": { + "match": "\\@(\\w+)", + "name": "storage.type.class.jsdoc" + }, + "curly-brackets": { + "match": "{|}", + "name": "invalid" + }, + "dotdotdot": { + "match": "\\.\\.\\.", + "name": "variable.other" + }, + "double-slash": { + "begin": "//", + "end": "$\\n?", + "name": "comment.line.sass", + "patterns": [ + { + "include": "#comment-tag" + } + ] + }, + "double-quoted": { + "begin": "\"", + "end": "\"", + "name": "string.quoted.double.css.sass", + "patterns": [ + { + "include": "#quoted-interpolation" + } + ] + }, + "flag": { + "match": "!(important|default|optional|global)", + "name": "keyword.other.important.css.sass" + }, + "function": { + "match": "(?<=[\\s|\\(|,|:])(?!url|format|attr)[a-zA-Z0-9_-][\\w-]*(?=\\()", + "name": "support.function.name.sass" + }, + "function-content": { + "begin": "(?<=url\\(|format\\(|attr\\()", + "end": ".(?=\\))", + "name": "string.quoted.double.css.sass" + }, + "parenthesis-open": { + "match": "\\(", + "name": "entity.name.function.parenthesis.open" + }, + "parenthesis-close": { + "match": "\\)", + "name": "entity.name.function.parenthesis.close" + }, + "colon": { + "match": ":", + "name": "meta.property-list.css.sass.colon" + }, + "interpolation": { + "begin": "#{", + "end": "}", + "name": "support.function.interpolation.sass", + "patterns": [ + { + "include": "#variable" + }, + { + "include": "#numeric" + }, + { + "include": "#operator" + }, + { + "include": "#unit" + }, + { + "include": "#comma" + }, + { + "include": "#double-quoted" + }, + { + "include": "#single-quoted" + } + ] + }, + "numeric": { + "match": "(-|\\.)?[0-9]+(\\.[0-9]+)?", + "name": "constant.numeric.css.sass" + }, + "operator": { + "match": "\\+|\\s-\\s|\\s-(?=\\$)|(?<=\\()-(?=\\$)|\\s-(?=\\()|\\*|/|%|=|!|<|>|~", + "name": "keyword.operator.sass" + }, + "parent-selector": { + "match": "&", + "name": "entity.name.tag.css.sass" + }, + "placeholder-selector": { + "begin": "(? Date: Sun, 23 Jun 2024 21:19:16 +0200 Subject: [PATCH 050/138] fix: add support for unquoted import strings in indented --- .../src/parser/cssParser.ts | 9 +++++---- .../src/parser/sassParser.ts | 16 ++++++++++++++-- .../src/services/cssNavigation.ts | 3 +++ .../test/sass/linkFixture/both/nested/bar.sass | 0 .../src/test/sass/sassNavigation.test.ts | 14 ++++++++++++++ 5 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 packages/vscode-css-languageservice/src/test/sass/linkFixture/both/nested/bar.sass diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 89fdcb1f..0beabda3 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -184,18 +184,19 @@ export class Parser { return this.finish(node); } - protected acceptUnquotedString(): boolean { + protected acceptUnquotedString(): nodes.Node | null { const pos = this.scanner.pos(); const depth = this.scanner.stream.depth; this.scanner.goBackTo(this.token.offset, depth); const unquoted = this.scanner.scanUnquotedString(); if (unquoted) { + let node = this.createNode(nodes.NodeType.StringLiteral); this.token = unquoted; this.consumeToken(); - return true; + return this.finish(node); } this.scanner.goBackTo(pos, depth); - return false; + return null; } public resync(resyncTokens: TokenType[] | undefined, resyncStopTokens: TokenType[] | undefined): boolean { @@ -2028,7 +2029,7 @@ export class Parser { } public _parseStringLiteral(): nodes.Node | null { - if (!this.peek(TokenType.String) && !this.peek(TokenType.BadString)) { + if (!this.peek(TokenType.String) && !this.peek(TokenType.BadString) && !this.peek(TokenType.UnquotedString)) { return null; } const node = this.createNode(nodes.NodeType.StringLiteral); diff --git a/packages/vscode-css-languageservice/src/parser/sassParser.ts b/packages/vscode-css-languageservice/src/parser/sassParser.ts index 7841e7cc..056c14b5 100644 --- a/packages/vscode-css-languageservice/src/parser/sassParser.ts +++ b/packages/vscode-css-languageservice/src/parser/sassParser.ts @@ -45,9 +45,21 @@ export class SassParser extends cssParser.Parser { const node = this.create(nodes.Import); this.consumeToken(); - if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) { - return this.finish(node, ParseError.URIOrStringExpected); + if (this.syntax === "indented") { + // For indented syntax quotes are optional for @import + if ( + !node.addChild(this._parseURILiteral()) && + !node.addChild(this._parseStringLiteral()) && + !node.addChild(this.acceptUnquotedString()) + ) { + return this.finish(node, ParseError.URIOrStringExpected); + } + } else { + if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) { + return this.finish(node, ParseError.URIOrStringExpected); + } } + while (this.accept(TokenType.Comma)) { if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) { return this.finish(node, ParseError.URIOrStringExpected); diff --git a/packages/vscode-css-languageservice/src/services/cssNavigation.ts b/packages/vscode-css-languageservice/src/services/cssNavigation.ts index 850c7f16..5460d7af 100644 --- a/packages/vscode-css-languageservice/src/services/cssNavigation.ts +++ b/packages/vscode-css-languageservice/src/services/cssNavigation.ts @@ -290,6 +290,9 @@ export class CSSNavigation { const rawText = candidate.getText(); if (startsWith(rawText, `'`) || startsWith(rawText, `"`)) { collect(candidate); + } else if (document.languageId === "sass" && candidate.type === nodes.NodeType.StringLiteral) { + // In the Sass indented syntax the string quotes are optional for @import + collect(candidate); } return false; } diff --git a/packages/vscode-css-languageservice/src/test/sass/linkFixture/both/nested/bar.sass b/packages/vscode-css-languageservice/src/test/sass/linkFixture/both/nested/bar.sass new file mode 100644 index 00000000..e69de29b diff --git a/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts index 37bf1f10..00b8fd76 100644 --- a/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts @@ -762,6 +762,20 @@ foo ]); }); + test("Sass @import without string quotes", async () => { + const fixtureRoot = path.resolve(__dirname, "../../../src/test/sass/linkFixture"); + const getDocumentUri = (relativePath: string) => { + return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true); + }; + + await assertDynamicLinks(getDocumentUri("./both/index.sass"), `@import nested/bar`, [ + { range: newRange(8, 18), target: getDocumentUri("./both/nested/bar.sass"), type: nodes.NodeType.Import }, + ]); + await assertDynamicLinks(getDocumentUri("./both/index.sass"), `@import bar`, [ + { range: newRange(8, 11), target: getDocumentUri("./both/bar.sass"), type: nodes.NodeType.Import }, + ]); + }); + test("Sass partial file dynamic links", async () => { const fixtureRoot = path.resolve(__dirname, "../../../src/test/sass/linkFixture"); const getDocumentUri = (relativePath: string) => { From f125ee8c9ab097f6a3f930be281c285466f0460d Mon Sep 17 00:00:00 2001 From: William Killerud Date: Wed, 3 Jul 2024 20:45:37 +0200 Subject: [PATCH 051/138] fix: hover info for CSS --- .../src/features/do-hover.ts | 9 +++++++ .../src/test/sass/parser-indented.test.ts | 24 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/packages/language-services/src/features/do-hover.ts b/packages/language-services/src/features/do-hover.ts index 7562868e..4d9cb370 100644 --- a/packages/language-services/src/features/do-hover.ts +++ b/packages/language-services/src/features/do-hover.ts @@ -67,6 +67,15 @@ export class DoHover extends LanguageFeature { type = SymbolKind.Method; } if (type === null) { + if (document.languageId === "sass") { + // We are probably hovering over a CSS identifier + // and want to defer this to vscode-css-languageservice's hover handler + return this.getUpstreamLanguageServer().doHover( + document, + position, + stylesheet, + ); + } return null; } if (node) { diff --git a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts index 302777f3..f2d0a0a2 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts @@ -2367,6 +2367,30 @@ figure ); }); + // https://sass-lang.com/documentation/at-rules/mixin/#indented-mixin-syntax + // This is discouraged in the documentation. Consider not supporting if + // implementation is complex (i. e. at-rule parsing can't be patched easily + // to consider these valid) + suite.skip("shorthand mixin syntax", () => { + test("@include shorthand (+)", () => { + assertNode( + `p + +double-border(blue)`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("@mixin shorthand (=)", () => { + assertNode( + `=double-border($color) + color: $color`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + }); + test("@content", () => { assertNode("@content", parser, parser._parseMixinContent.bind(parser)); assertNode("@content($type)", parser, parser._parseMixinContent.bind(parser)); From 331424e742a72cf8a4aa0cd96b9e6bab04d3b95c Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 7 Jul 2024 11:25:23 +0200 Subject: [PATCH 052/138] chore: reset nx as part of clean script Clean the build cache --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index add826b6..64623420 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "prepare": "husky", "postinstall": "patch-package", "build": "nx run-many -t build", + "preclean": "nx reset", "clean": "nx run-many -t clean", "coverage": "nx run-many -t coverage", "dev": "echo Watching workspace for changes... && nx watch --all -- nx run \\$NX_PROJECT_NAME:build", From c87ab57c9672f021d4d633a535dd0ca65f8ce892 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 7 Jul 2024 11:25:38 +0200 Subject: [PATCH 053/138] test: add test for diagnostics in sass-indented --- .../do-diagnostics-deprecation.test.ts | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) diff --git a/packages/language-services/src/features/__tests__/do-diagnostics-deprecation.test.ts b/packages/language-services/src/features/__tests__/do-diagnostics-deprecation.test.ts index d0a60680..abecc05b 100644 --- a/packages/language-services/src/features/__tests__/do-diagnostics-deprecation.test.ts +++ b/packages/language-services/src/features/__tests__/do-diagnostics-deprecation.test.ts @@ -42,6 +42,34 @@ test("reports a deprecated variable declared in the same document", async () => ]); }); +test("indented: reports a deprecated variable declared in the same document", async () => { + const document = fileSystemProvider.createDocument( + ["/// @deprecated", "$a: 1", ".a", " content: $a"], + { languageId: "sass" }, + ); + + const result = await ls.doDiagnostics(document); + + assert.deepStrictEqual(result, [ + { + message: "$a is deprecated", + range: { + start: { + line: 3, + character: 11, + }, + end: { + line: 3, + character: 13, + }, + }, + severity: DiagnosticSeverity.Hint, + source: "Some Sass", + tags: [DiagnosticTag.Deprecated], + }, + ]); +}); + test("includes the deprecation message if one is given", async () => { const document = fileSystemProvider.createDocument([ "/// @deprecated Use something else", @@ -102,6 +130,40 @@ test("reports a deprecated function declared in the same document", async () => ]); }); +test("indented: reports a deprecated function declared in the same document", async () => { + const document = fileSystemProvider.createDocument( + [ + "/// @deprecated", + "@function old-function()", + " @return 1", + ".a", + " content: old-function()", + ], + { languageId: "sass" }, + ); + + const result = await ls.doDiagnostics(document); + + assert.deepStrictEqual(result, [ + { + message: "old-function is deprecated", + range: { + start: { + line: 4, + character: 11, + }, + end: { + line: 4, + character: 23, + }, + }, + severity: DiagnosticSeverity.Hint, + source: "Some Sass", + tags: [DiagnosticTag.Deprecated], + }, + ]); +}); + test("reports a deprecated mixin declared in the same document", async () => { const document = fileSystemProvider.createDocument([ "/// @deprecated", @@ -133,6 +195,40 @@ test("reports a deprecated mixin declared in the same document", async () => { ]); }); +test("indented: reports a deprecated mixin declared in the same document", async () => { + const document = fileSystemProvider.createDocument( + [ + "/// @deprecated", + "@mixin old-mixin", + " content: 'mixin'", + ".a", + " @include old-mixin()", + ], + { languageId: "sass" }, + ); + + const result = await ls.doDiagnostics(document); + + assert.deepStrictEqual(result, [ + { + message: "old-mixin is deprecated", + range: { + start: { + line: 4, + character: 11, + }, + end: { + line: 4, + character: 20, + }, + }, + severity: DiagnosticSeverity.Hint, + source: "Some Sass", + tags: [DiagnosticTag.Deprecated], + }, + ]); +}); + test("reports a deprecated variable with prefix", async () => { const variables = fileSystemProvider.createDocument( ["/// @deprecated", "$old-a: 1;"], @@ -174,6 +270,45 @@ test("reports a deprecated variable with prefix", async () => { ]); }); +test("indented: reports a deprecated variable with prefix", async () => { + const variables = fileSystemProvider.createDocument( + ["/// @deprecated", "$old-a: 1;"], + { uri: "variables.scss" }, + ); + const forward = fileSystemProvider.createDocument( + "@forward './variables' as var-*;", + { uri: "namespace.scss" }, + ); + const document = fileSystemProvider.createDocument( + ["@use 'namespace' as ns", ".foo", " color: ns.$var-old-a"], + { languageId: "sass" }, + ); + + ls.parseStylesheet(variables); + ls.parseStylesheet(forward); + + const result = await ls.doDiagnostics(document); + + assert.deepStrictEqual(result, [ + { + message: "$old-a is deprecated", + range: { + start: { + line: 2, + character: 12, + }, + end: { + line: 2, + character: 22, + }, + }, + severity: DiagnosticSeverity.Hint, + source: "Some Sass", + tags: [DiagnosticTag.Deprecated], + }, + ]); +}); + test("reports a deprecated function with prefix", async () => { const functions = fileSystemProvider.createDocument( ["/// @deprecated", "@function old-function() { @return 1; }"], @@ -215,6 +350,45 @@ test("reports a deprecated function with prefix", async () => { ]); }); +test("indented: reports a deprecated function with prefix", async () => { + const functions = fileSystemProvider.createDocument( + ["/// @deprecated", "@function old-function() { @return 1; }"], + { uri: "functions.scss" }, + ); + const forward = fileSystemProvider.createDocument( + "@forward './functions' as fun-*;", + { uri: "namespace.scss" }, + ); + const document = fileSystemProvider.createDocument( + ["@use 'namespace' as ns", ".foo", " line-height: ns.fun-old-function()"], + { languageId: "sass" }, + ); + + ls.parseStylesheet(functions); + ls.parseStylesheet(forward); + + const result = await ls.doDiagnostics(document); + + assert.deepStrictEqual(result, [ + { + message: "old-function is deprecated", + range: { + start: { + line: 2, + character: 18, + }, + end: { + line: 2, + character: 34, + }, + }, + severity: DiagnosticSeverity.Hint, + source: "Some Sass", + tags: [DiagnosticTag.Deprecated], + }, + ]); +}); + test("reports a deprecated mixin with prefix", async () => { const mixins = fileSystemProvider.createDocument( ["/// @deprecated", "@mixin old-mixin { content: 'mixin'; }"], @@ -256,6 +430,45 @@ test("reports a deprecated mixin with prefix", async () => { ]); }); +test("indented: reports a deprecated mixin with prefix", async () => { + const mixins = fileSystemProvider.createDocument( + ["/// @deprecated", "@mixin old-mixin { content: 'mixin'; }"], + { uri: "mixins.scss" }, + ); + const forward = fileSystemProvider.createDocument( + "@forward './mixins' as mix-*;", + { uri: "namespace.scss" }, + ); + const document = fileSystemProvider.createDocument( + ["@use 'namespace' as ns", ".foo", " @include ns.mix-old-mixin"], + { languageId: "sass" }, + ); + + ls.parseStylesheet(mixins); + ls.parseStylesheet(forward); + + const result = await ls.doDiagnostics(document); + + assert.deepStrictEqual(result, [ + { + message: "old-mixin is deprecated", + range: { + start: { + line: 2, + character: 14, + }, + end: { + line: 2, + character: 27, + }, + }, + severity: DiagnosticSeverity.Hint, + source: "Some Sass", + tags: [DiagnosticTag.Deprecated], + }, + ]); +}); + test("reports a deprecated placeholder", async () => { const document = fileSystemProvider.createDocument([ "/// @deprecated Use something else", From 59ecfa6240e01c3c4df953a946063f1142ede8de Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 7 Jul 2024 11:31:05 +0200 Subject: [PATCH 054/138] chore: skip source maps since we ship a development build Save on asset size --- packages/language-server/webpack.config.js | 2 +- vscode-extension/webpack.config.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/language-server/webpack.config.js b/packages/language-server/webpack.config.js index ac4f71b0..d883d4e1 100644 --- a/packages/language-server/webpack.config.js +++ b/packages/language-server/webpack.config.js @@ -92,7 +92,7 @@ function defineConfig(config) { }, ], }, - devtool: "hidden-source-map", + devtool: false, }; /** @type WebpackConfig */ diff --git a/vscode-extension/webpack.config.js b/vscode-extension/webpack.config.js index 3a94ef2e..84074805 100644 --- a/vscode-extension/webpack.config.js +++ b/vscode-extension/webpack.config.js @@ -94,6 +94,7 @@ function defineConfig(config) { vscode: "commonjs vscode", }, plugins: [], + devtool: false, }; /** @type WebpackConfig */ From 01b7d93e311e932472d69bb637b04dfc53742215 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 7 Jul 2024 12:00:39 +0200 Subject: [PATCH 055/138] refactor: make onInitialized awaitable for other handlers Fix a timing issue that was particularly visible for color decorators. --- packages/language-server/src/server.ts | 147 +++++++++++------- .../language-server/src/workspace-scanner.ts | 6 +- 2 files changed, 90 insertions(+), 63 deletions(-) diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index a53e39a5..fd68eb98 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -40,6 +40,7 @@ export class SomeSassServer { let workspaceScanner: WorkspaceScanner | undefined = undefined; let fileSystemProvider: FileSystemProvider | undefined = undefined; let clientCapabilities: ClientCapabilities | undefined = undefined; + let initialScan: Promise | null = null; // Create a simple text document manager. The text document manager // _supports full document sync only @@ -119,67 +120,84 @@ export class SomeSassServer { }); this.connection.onInitialized(async () => { + this.connection.console.debug( + `[Server${process.pid ? `(${process.pid})` : ""} ${workspaceRoot}] received`, + ); try { - this.connection.console.debug( - `[Server${process.pid ? `(${process.pid})` : ""} ${workspaceRoot}] received`, - ); - - const somesassConfiguration: Partial = - await this.connection.workspace.getConfiguration("somesass"); - const editorConfiguration: Partial = - await this.connection.workspace.getConfiguration("editor"); - - const settings: ISettings = { - ...defaultSettings, - ...somesassConfiguration, - }; - - const editorSettings: IEditorSettings = { - insertSpaces: false, - indentSize: undefined, - tabSize: 2, - ...editorConfiguration, - }; - - if ( - !ls || - !clientCapabilities || - !workspaceRoot || - !fileSystemProvider - ) { - throw new Error( - "Got onInitialized without onInitialize readying up all required globals", + initialScan = new Promise((resolve, reject) => { + Promise.all([ + this.connection.workspace.getConfiguration("somesass"), + this.connection.workspace.getConfiguration("editor"), + ]).then( + ([somesassConfiguration, editorConfiguration]: [ + Partial, + Partial, + ]) => { + const settings: ISettings = { + ...defaultSettings, + ...somesassConfiguration, + }; + + const editorSettings: IEditorSettings = { + insertSpaces: false, + indentSize: undefined, + tabSize: 2, + ...editorConfiguration, + }; + + if ( + !ls || + !clientCapabilities || + !workspaceRoot || + !fileSystemProvider + ) { + return reject( + new Error( + "Got onInitialized without onInitialize readying up all required globals", + ), + ); + } + + ls.configure({ + editorSettings, + workspaceRoot, + }); + + this.connection.console.debug( + `[Server${process.pid ? `(${process.pid})` : ""} ${workspaceRoot}] scanning workspace for files`, + ); + + return fileSystemProvider + .findFiles( + "**/*.{scss,sass,svelte,astro,vue}", + settings.scannerExclude, + ) + .then((files) => { + this.connection.console.debug( + `[Server${process.pid ? `(${process.pid})` : ""} ${workspaceRoot}] found ${files.length} files, starting parse`, + ); + + workspaceScanner = new WorkspaceScanner( + ls!, + fileSystemProvider!, + { + scannerDepth: settings.scannerDepth, + scanImportedFiles: settings.scanImportedFiles, + }, + ); + + return workspaceScanner.scan(files); + }) + .then((promises) => { + this.connection.console.debug( + `[Server${process.pid ? `(${process.pid})` : ""} ${workspaceRoot}] parsed ${promises.length} files`, + ); + resolve(); + }); + }, ); - } - - ls.configure({ - editorSettings, - workspaceRoot, }); - - workspaceScanner = new WorkspaceScanner(ls, fileSystemProvider, { - scannerDepth: settings.scannerDepth, - scanImportedFiles: settings.scanImportedFiles, - }); - - this.connection.console.debug( - `[Server${process.pid ? `(${process.pid})` : ""} ${workspaceRoot}] scanning workspace for files`, - ); - - const files = await fileSystemProvider.findFiles( - "**/*.{scss,sass,svelte,astro,vue}", - settings.scannerExclude, - ); - - this.connection.console.debug( - `[Server${process.pid ? `(${process.pid})` : ""} ${workspaceRoot}] found ${files.length} files, starting parse`, - ); - - await workspaceScanner.scan(files); - - this.connection.console.debug( - `[Server${process.pid ? `(${process.pid})` : ""} ${workspaceRoot}] parsed ${files.length} files`, - ); + await initialScan; } catch (error) { this.connection.console.log(String(error)); } @@ -313,7 +331,7 @@ export class SomeSassServer { return result; }); - this.connection.onDocumentHighlight((params) => { + this.connection.onDocumentHighlight(async (params) => { if (!ls) return null; const document = getSassRegionsDocument( @@ -323,6 +341,9 @@ export class SomeSassServer { if (!document) return null; try { + if (initialScan) { + await initialScan; + } const result = ls.findDocumentHighlights(document, params.position); return result; } catch { @@ -330,7 +351,7 @@ export class SomeSassServer { } }); - this.connection.onDocumentLinks((params) => { + this.connection.onDocumentLinks(async (params) => { if (!ls) return null; const document = getSassRegionsDocument( @@ -339,6 +360,9 @@ export class SomeSassServer { if (!document) return null; try { + if (initialScan) { + await initialScan; + } const result = ls.findDocumentLinks(document); return result; } catch { @@ -451,6 +475,9 @@ export class SomeSassServer { if (!document) return null; try { + if (initialScan) { + await initialScan; + } const information = await ls.findColors(document); return information; } catch { diff --git a/packages/language-server/src/workspace-scanner.ts b/packages/language-server/src/workspace-scanner.ts index 9584beeb..27a22b8a 100644 --- a/packages/language-server/src/workspace-scanner.ts +++ b/packages/language-server/src/workspace-scanner.ts @@ -21,9 +21,9 @@ export default class WorkspaceScanner { this.#settings = settings; } - public async scan(files: URI[]): Promise { + public async scan(files: URI[]): Promise { // Populate the cache for the new language services - await Promise.all( + return Promise.all( files.map((uri) => { if ( this.#settings.scanImportedFiles && @@ -32,7 +32,7 @@ export default class WorkspaceScanner { // If we scan imported files (which we do by default), don't include partials in the initial scan. // This way we can be reasonably sure that we scan whatever index files there are _before_ we scan // partials which may or may not have been forwarded with a prefix. - return; + return Promise.resolve(); } return this.parse(uri); }), From a849bb375ce0bc6c2e5c0883f4957ef8a37d33d0 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 7 Jul 2024 12:05:59 +0200 Subject: [PATCH 056/138] refactor: add indented variant of code actions --- .../__tests__/code-actions-extract.test.ts | 113 ++++++++++++++++++ .../src/features/code-actions.ts | 111 +++++++++++------ 2 files changed, 186 insertions(+), 38 deletions(-) diff --git a/packages/language-services/src/features/__tests__/code-actions-extract.test.ts b/packages/language-services/src/features/__tests__/code-actions-extract.test.ts index 6734d17d..18a0cc91 100644 --- a/packages/language-services/src/features/__tests__/code-actions-extract.test.ts +++ b/packages/language-services/src/features/__tests__/code-actions-extract.test.ts @@ -59,6 +59,34 @@ test("extraction for variable", async () => { ]); }); +test("indented: extraction for variable", async () => { + const document = fileSystemProvider.createDocument( + ["--var: black", ".a", " color: var(--var)"], + { languageId: "sass" }, + ); + + const result = await ls.getCodeActions( + document, + Range.create(Position.create(0, 7), Position.create(0, 12)), + ); + + assert.deepStrictEqual(getEdit(result[0]), [ + { + newText: `$_variable: black${EOL}--var: $_variable`, + range: { + end: { + character: 12, + line: 0, + }, + start: { + character: 0, + line: 0, + }, + }, + }, + ]); +}); + test("extraction for multiline variable", async () => { const document = fileSystemProvider.createDocument([ `box-shadow: inset 0 0 0 jkl.rem(1px) var(--jkl-calendar-border-color),`, @@ -182,6 +210,54 @@ a.cta { ]); }); +test("indented: extraction for mixin", async () => { + ls.configure({ + editorSettings: { + insertSpaces: true, + indentSize: 2, + }, + }); + + const document = fileSystemProvider.createDocument( + ` +a.cta + color: var(--cta-text) + text-decoration: none + + &:visited + color: var(--cta-text) +`, + { languageId: "sass" }, + ); + + const result = await ls.getCodeActions( + document, + Range.create(Position.create(2, 2), Position.create(6, 26)), + ); + + assert.deepStrictEqual(getEdit(result[1]), [ + { + newText: `@mixin _mixin + color: var(--cta-text) + text-decoration: none + + &:visited + color: var(--cta-text) + @include _mixin`, + range: { + end: { + character: 26, + line: 6, + }, + start: { + character: 2, + line: 2, + }, + }, + }, + ]); +}); + test("extraction for function with tab indents", async () => { ls.configure({ editorSettings: { @@ -258,3 +334,40 @@ box-shadow: _function();`, }, ]); }); + +test("indented: extraction for function", async () => { + ls.configure({ + editorSettings: { + insertSpaces: true, + indentSize: 2, + }, + }); + + const document = fileSystemProvider.createDocument( + ["--var: black", ".a", " color: var(--var)"], + { languageId: "sass" }, + ); + + const result = await ls.getCodeActions( + document, + Range.create(Position.create(0, 7), Position.create(0, 12)), + ); + + assert.deepStrictEqual(getEdit(result[2]), [ + { + newText: `@function _function() + @return black +--var: _function()`, + range: { + end: { + character: 12, + line: 0, + }, + start: { + character: 0, + line: 0, + }, + }, + }, + ]); +}); diff --git a/packages/language-services/src/features/code-actions.ts b/packages/language-services/src/features/code-actions.ts index f29d6286..4895f3af 100644 --- a/packages/language-services/src/features/code-actions.ts +++ b/packages/language-services/src/features/code-actions.ts @@ -49,29 +49,44 @@ export class CodeActions extends LanguageFeature { ), ); - const onlyNonWhitespace = preceedingOnLine.trimStart(); - const lastIndent = preceedingOnLine.length - onlyNonWhitespace.length; + const preceeding = preceedingOnLine.trimStart(); + const lastIndent = preceedingOnLine.length - preceeding.length; const indent = preceedingOnLine.substring(0, lastIndent); const lines = getLinesFromText(selectedText); const eol = lines.length > 1 ? getEOL(selectedText) : getEOL(document.getText()); - const newLines = [ - `${indent}@function _function() {`, - `${indent}${indentText( - `@return ${lines - .map((line, index) => - index === 0 ? line : indentText(line, this.configuration), - ) - .join(eol)}`, - this.configuration, - )}${selectedText.endsWith(";") ? "" : ";"}`, - `${indent}}`, - `${indent}${onlyNonWhitespace}_function()${ - selectedText.endsWith(";") ? ";" : "" - }`, - ].join(eol); + let newLines: string; + if (document.languageId === "sass") { + newLines = [ + `${indent}@function _function()`, + `${indent}${indentText( + `@return ${lines + .map((line, index) => + index === 0 ? line : indentText(line, this.configuration), + ) + .join(eol)}`, + this.configuration, + )}`, + `${indent}${preceeding}_function()`, + ].join(eol); + } else { + const semi = selectedText.endsWith(";"); + newLines = [ + `${indent}@function _function() {`, + `${indent}${indentText( + `@return ${lines + .map((line, index) => + index === 0 ? line : indentText(line, this.configuration), + ) + .join(eol)}`, + this.configuration, + )}${semi ? "" : ";"}`, + `${indent}}`, + `${indent}${preceeding}_function()${semi ? ";" : ""}`, + ].join(eol); + } const workspaceEdit: WorkspaceEdit = { documentChanges: [ @@ -122,19 +137,35 @@ export class CodeActions extends LanguageFeature { const eol = lines.length > 1 ? getEOL(selectedText) : getEOL(document.getText()); - const newLines = [ - "@mixin _mixin {", - ...lines.map((line, index) => - line - ? indentText( - index === 0 ? `${indent}${line}` : line, - this.configuration, - ) - : line, - ), - `${indent}}`, - `${indent}@include _mixin;`, - ].join(eol); + let newLines: string; + if (document.languageId === "sass") { + newLines = [ + "@mixin _mixin", + ...lines.map((line, index) => + line + ? indentText( + index === 0 ? `${indent}${line}` : line, + this.configuration, + ) + : line, + ), + `${indent}@include _mixin`, + ].join(eol); + } else { + newLines = [ + "@mixin _mixin {", + ...lines.map((line, index) => + line + ? indentText( + index === 0 ? `${indent}${line}` : line, + this.configuration, + ) + : line, + ), + `${indent}}`, + `${indent}@include _mixin;`, + ].join(eol); + } const workspaceEdit: WorkspaceEdit = { documentChanges: [ @@ -161,7 +192,7 @@ export class CodeActions extends LanguageFeature { document: TextDocument, range: Range, ): CodeAction { - const selectedText = document.getText(range); + const selected = document.getText(range); const preceedingOnLine = document.getText( Range.create( Position.create(range.start.line, 0), @@ -169,17 +200,21 @@ export class CodeActions extends LanguageFeature { ), ); - const onlyNonWhitespace = preceedingOnLine.trimStart(); - const lastIndent = preceedingOnLine.length - onlyNonWhitespace.length; + const preceeding = preceedingOnLine.trimStart(); + const lastIndent = preceedingOnLine.length - preceeding.length; const indent = preceedingOnLine.substring(0, lastIndent); const eol = getEOL(document.getText()); - const newLines = `${indent}$_variable: ${ - selectedText.endsWith(";") ? selectedText : `${selectedText};` - }${eol}${indent}${onlyNonWhitespace}${ - selectedText.endsWith(";") ? "$_variable;" : "$_variable" - }`; + let newLines: string; + if (document.languageId === "sass") { + newLines = `${indent}$_variable: ${selected}${eol}${indent}${preceeding}$_variable`; + } else { + const semi = selected.endsWith(";"); + newLines = `${indent}$_variable: ${selected}${semi ? "" : ";"}${eol}${indent}${preceeding}$_variable${ + semi ? ";" : "" + }`; + } const workspaceEdit: WorkspaceEdit = { documentChanges: [ From be6a4b37ed47cc28b99807bc362e3da397bc7b4b Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 7 Jul 2024 13:19:03 +0200 Subject: [PATCH 057/138] refactor: selection and folding provider --- packages/language-server/README.md | 20 +++--- packages/language-server/src/server.ts | 26 +++++++ .../src/features/folding-ranges.ts | 19 +++++ .../src/features/selection-ranges.ts | 21 ++++++ .../src/language-services-types.ts | 15 ++++ .../src/language-services.ts | 71 +++++++++---------- 6 files changed, 126 insertions(+), 46 deletions(-) create mode 100644 packages/language-services/src/features/folding-ranges.ts create mode 100644 packages/language-services/src/features/selection-ranges.ts diff --git a/packages/language-server/README.md b/packages/language-server/README.md index 2b34de46..30067688 100644 --- a/packages/language-server/README.md +++ b/packages/language-server/README.md @@ -83,7 +83,7 @@ This language server is designed to run alongside the [VS Code CSS language serv Color picker for CSS colors ✅ - + ✅ @@ -93,7 +93,7 @@ This language server is designed to run alongside the [VS Code CSS language serv CSS completions ✅ - + ✅ (for indented) SCSS same-document completions @@ -133,7 +133,7 @@ This language server is designed to run alongside the [VS Code CSS language serv CSS colors ✅ - + ✅ SCSS variable colors @@ -148,7 +148,7 @@ This language server is designed to run alongside the [VS Code CSS language serv Highlight references in document ✅ - + ✅ @@ -158,7 +158,7 @@ This language server is designed to run alongside the [VS Code CSS language serv Navigate to linked document ✅ - + ✅ @@ -168,7 +168,7 @@ This language server is designed to run alongside the [VS Code CSS language serv Go to symbol in document ✅ - + ✅ (for indented) @@ -178,7 +178,7 @@ This language server is designed to run alongside the [VS Code CSS language serv Code block folding ✅ - + ✅ @@ -198,7 +198,7 @@ This language server is designed to run alongside the [VS Code CSS language serv CSS hover info ✅ - + ✅ (for indented) SCSS hover info @@ -228,7 +228,7 @@ This language server is designed to run alongside the [VS Code CSS language serv CSS references ✅ - + ✅ (for indented) SCSS references @@ -258,7 +258,7 @@ This language server is designed to run alongside the [VS Code CSS language serv Ranges for expand/shrink selection ✅ - + ✅ diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index fd68eb98..63ac4359 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -115,6 +115,8 @@ export class SomeSassServer { }, renameProvider: { prepareProvider: true }, colorProvider: {}, + foldingRangeProvider: true, + selectionRangeProvider: true, }, }; }); @@ -501,6 +503,30 @@ export class SomeSassServer { return result; }); + this.connection.onFoldingRanges(async (params) => { + if (!ls) return null; + + const document = getSassRegionsDocument( + documents.get(params.textDocument.uri), + ); + if (!document) return null; + + const result = await ls.getFoldingRanges(document); + return result; + }); + + this.connection.onSelectionRanges(async (params) => { + if (!ls) return null; + + const document = getSassRegionsDocument( + documents.get(params.textDocument.uri), + ); + if (!document) return null; + + const result = await ls.getSelectionRanges(document, params.positions); + return result; + }); + this.connection.onShutdown(() => { if (!ls) return; diff --git a/packages/language-services/src/features/folding-ranges.ts b/packages/language-services/src/features/folding-ranges.ts new file mode 100644 index 00000000..c89b8456 --- /dev/null +++ b/packages/language-services/src/features/folding-ranges.ts @@ -0,0 +1,19 @@ +import { LanguageFeature } from "../language-feature"; +import { TextDocument, FoldingRange } from "../language-services-types"; + +export type FoldingRangeContext = { + rangeLimit?: number; +}; + +export class FoldingRanges extends LanguageFeature { + async getFoldingRanges( + document: TextDocument, + context?: FoldingRangeContext, + ): Promise { + const result = this.getUpstreamLanguageServer().getFoldingRanges( + document, + context, + ); + return result; + } +} diff --git a/packages/language-services/src/features/selection-ranges.ts b/packages/language-services/src/features/selection-ranges.ts new file mode 100644 index 00000000..42597807 --- /dev/null +++ b/packages/language-services/src/features/selection-ranges.ts @@ -0,0 +1,21 @@ +import { LanguageFeature } from "../language-feature"; +import { + TextDocument, + SelectionRange, + Position, +} from "../language-services-types"; + +export class SelectionRanges extends LanguageFeature { + async getSelectionRanges( + document: TextDocument, + positions: Position[], + ): Promise { + const stylesheet = this.ls.parseStylesheet(document); + const result = this.getUpstreamLanguageServer().getSelectionRanges( + document, + positions, + stylesheet, + ); + return result; + } +} diff --git a/packages/language-services/src/language-services-types.ts b/packages/language-services/src/language-services-types.ts index 7558ccdc..6ae76362 100644 --- a/packages/language-services/src/language-services-types.ts +++ b/packages/language-services/src/language-services-types.ts @@ -23,6 +23,9 @@ import { Marker, CompletionSettings as VSCodeCompletionSettings, StylesheetDocumentLink, + FoldingRange, + FoldingRangeKind, + SelectionRange, } from "@somesass/vscode-css-languageservice"; import type { ParseResult } from "scss-sassdoc-parser"; import { TextDocument } from "vscode-languageserver-textdocument"; @@ -60,6 +63,7 @@ import { VersionedTextDocumentIdentifier, } from "vscode-languageserver-types"; import { URI, Utils } from "vscode-uri"; +import { FoldingRangeContext } from "./features/folding-ranges"; /** * The root of the abstract syntax tree. @@ -133,6 +137,14 @@ export interface LanguageService { range: Range, context?: CodeActionContext, ): Promise; + getFoldingRanges( + document: TextDocument, + context?: FoldingRangeContext, + ): Promise; + getSelectionRanges( + document: TextDocument, + positions: Position[], + ): Promise; hasCached(uri: URI): boolean; /** * Utility function to reparse an updated document. @@ -393,4 +405,7 @@ export { IToken, Module, Marker, + FoldingRange, + FoldingRangeKind, + SelectionRange, }; diff --git a/packages/language-services/src/language-services.ts b/packages/language-services/src/language-services.ts index 87e6655e..421e16bc 100644 --- a/packages/language-services/src/language-services.ts +++ b/packages/language-services/src/language-services.ts @@ -11,6 +11,8 @@ import { FindDocumentHighlights } from "./features/find-document-highlights"; import { FindDocumentLinks } from "./features/find-document-links"; import { FindReferences } from "./features/find-references"; import { FindSymbols } from "./features/find-symbols"; +import { FoldingRangeContext, FoldingRanges } from "./features/folding-ranges"; +import { SelectionRanges } from "./features/selection-ranges"; import { LanguageModelCache as LanguageServerCache } from "./language-model-cache"; import { CodeActionContext, @@ -51,54 +53,41 @@ class LanguageServiceImpl implements LanguageService { #findDocumentLinks: FindDocumentLinks; #findReferences: FindReferences; #findSymbols: FindSymbols; + #foldingRanges: FoldingRanges; + #selectionRanges: SelectionRanges; constructor(options: LanguageServiceOptions) { const sassLs = getSassLanguageService({ clientCapabilities: options.clientCapabilities, fileSystemProvider: mapFsProviders(options.fileSystemProvider), }); - const cache = new LanguageServerCache({ sassLs, ...options.languageModelCache, }); - this.#cache = cache; - this.#codeActions = new CodeActions(this, options, { - sassLs, - cache, - }); - this.#doComplete = new DoComplete(this, options, { sassLs, cache }); - this.#doDiagnostics = new DoDiagnostics(this, options, { - sassLs, - cache, - }); - this.#doHover = new DoHover(this, options, { sassLs, cache }); - this.#doRename = new DoRename(this, options, { sassLs, cache }); - this.#doSignatureHelp = new DoSignatureHelp(this, options, { - sassLs, - cache, - }); - this.#findColors = new FindColors(this, options, { sassLs, cache }); - this.#findDefinition = new FindDefinition(this, options, { - sassLs, - cache, - }); - this.#findDocumentHighlights = new FindDocumentHighlights(this, options, { - sassLs, - cache, - }); - this.#findDocumentLinks = new FindDocumentLinks(this, options, { - sassLs, - cache, - }); - this.#findReferences = new FindReferences(this, options, { - sassLs, - cache, - }); - this.#findSymbols = new FindSymbols(this, options, { + const internal = { sassLs, cache, - }); + }; + this.#cache = cache; + this.#codeActions = new CodeActions(this, options, internal); + this.#doComplete = new DoComplete(this, options, internal); + this.#doDiagnostics = new DoDiagnostics(this, options, internal); + this.#doHover = new DoHover(this, options, internal); + this.#doRename = new DoRename(this, options, internal); + this.#doSignatureHelp = new DoSignatureHelp(this, options, internal); + this.#findColors = new FindColors(this, options, internal); + this.#findDefinition = new FindDefinition(this, options, internal); + this.#findDocumentHighlights = new FindDocumentHighlights( + this, + options, + internal, + ); + this.#findDocumentLinks = new FindDocumentLinks(this, options, internal); + this.#findReferences = new FindReferences(this, options, internal); + this.#findSymbols = new FindSymbols(this, options, internal); + this.#foldingRanges = new FoldingRanges(this, options, internal); + this.#selectionRanges = new SelectionRanges(this, options, internal); } configure(configuration: LanguageServiceConfiguration): void { @@ -114,6 +103,8 @@ class LanguageServiceImpl implements LanguageService { this.#findDocumentLinks.configure(configuration); this.#findReferences.configure(configuration); this.#findSymbols.configure(configuration); + this.#foldingRanges.configure(configuration); + this.#selectionRanges.configure(configuration); } parseStylesheet(document: TextDocument) { @@ -191,6 +182,14 @@ class LanguageServiceImpl implements LanguageService { return this.#codeActions.getCodeActions(document, range, context); } + getFoldingRanges(document: TextDocument, context?: FoldingRangeContext) { + return this.#foldingRanges.getFoldingRanges(document, context); + } + + getSelectionRanges(document: TextDocument, positions: Position[]) { + return this.#selectionRanges.getSelectionRanges(document, positions); + } + onDocumentChanged(document: TextDocument) { return this.#cache.onDocumentChanged(document); } From eb9aa8eb285601786742de102405ea13b6c03d0b Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 7 Jul 2024 14:40:41 +0200 Subject: [PATCH 058/138] refactor: terminate ruleset if depth is zero In the case of nesting, we don't get the same amount of dedent tokens as we would get right curly for SCSS. We need to break out of the loop so we don't end up considering a ruleset as a child of another ruleset when it's really a separate ruleset. --- .../src/parser/cssParser.ts | 9 ++++++ .../src/parser/cssScanner.ts | 14 +++++----- .../src/test/sass/sassSelectionRange.test.ts | 28 +++++++++++++++++-- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 0beabda3..b65c4324 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -532,6 +532,15 @@ export class Parser { } } + if (this.syntax === "indented" && this.scanner.stream.depth === 0) { + // For the indented syntax we might not get the same number of + // dedents as we get indents. If the depth is zero at this point + // we should drop out so we don't end up adding a ruleset as a + // child of another ruleset when in reality it's a direct child + // of Stylesheet. + return this.finish(node); + } + decl = parseDeclaration(); } diff --git a/packages/vscode-css-languageservice/src/parser/cssScanner.ts b/packages/vscode-css-languageservice/src/parser/cssScanner.ts index 218c0b43..3c2336e0 100644 --- a/packages/vscode-css-languageservice/src/parser/cssScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/cssScanner.ts @@ -432,19 +432,19 @@ export class Scanner { // indents and dedents for the indented syntax if (this.syntax === "indented") { - let n = this.stream.advanceWhileChar((ch) => { + let newlines = this.stream.advanceWhileChar((ch) => { return ch === _NWL || ch === _LFD || ch === _CAR; }); - if (n > 0) { - n = this.stream.advanceWhileChar((ch) => { + if (newlines > 0) { + let depth = this.stream.advanceWhileChar((ch) => { return ch === _TAB || ch === _WSP; }); - if (n > this.stream.depth) { - this.stream.depth = n; + if (depth > this.stream.depth) { + this.stream.depth = depth; return this.finishToken(offset, TokenType.Indent); - } else if (n < this.stream.depth) { - this.stream.depth = n; + } else if (depth < this.stream.depth) { + this.stream.depth = depth; return this.finishToken(offset, TokenType.Dedent); } else { return this.finishToken(offset, TokenType.Newline); diff --git a/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts index aee94caa..8e5b165e 100644 --- a/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts @@ -6,7 +6,7 @@ import { suite, test, assert } from "vitest"; import { getSassLanguageService, TextDocument, SelectionRange } from "../../cssLanguageService"; -function assertRanges(content: string, expected: (number | string)[][]): void { +function assertRanges(content: string, expected: (number | string)[][], stylesheetchildren?: number): void { let message = `${content} gives selection range:\n`; const offset = content.indexOf("|"); @@ -15,7 +15,11 @@ function assertRanges(content: string, expected: (number | string)[][]): void { const ls = getSassLanguageService(); const document = TextDocument.create("test://foo/bar.sass", "sass", 1, content); - const actualRanges = ls.getSelectionRanges(document, [document.positionAt(offset)], ls.parseStylesheet(document)); + const stylesheet = ls.parseStylesheet(document); + if (typeof stylesheetchildren === "number") { + assert.equal(stylesheet.children.length, stylesheetchildren); + } + const actualRanges = ls.getSelectionRanges(document, [document.positionAt(offset)], stylesheet); assert.equal(actualRanges.length, 1); const offsetPairs: [number, string][] = []; let curr: SelectionRange | undefined = actualRanges[0]; @@ -126,4 +130,24 @@ suite("Sass SelectionRange", () => { ], ); }); + + test("stops in time for next declaration when there's nesting", () => { + assertRanges( + `.foo + color: red| + &.bar + color: blue + +.baz + color: green`, + [ + [13, "red"], + [6, "color: red"], + [4, "\n\tcolor: red\n\t&.bar\n\t\tcolor: blue\n\n"], + [0, ".foo\n\tcolor: red\n\t&.bar\n\t\tcolor: blue\n\n"], + [0, ".foo\n\tcolor: red\n\t&.bar\n\t\tcolor: blue\n\n.baz\n\tcolor: green"], + ], + 2, + ); + }); }); From 80c8d7d5d6acb2ff25bcf4918c2f38a934ccdbc8 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 7 Jul 2024 16:11:44 +0200 Subject: [PATCH 059/138] test: actually not only at the root where this bug is --- .../src/test/sass/sassSelectionRange.test.ts | 94 ++++++++++++++++++- 1 file changed, 89 insertions(+), 5 deletions(-) diff --git a/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts index 8e5b165e..fe38706a 100644 --- a/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts @@ -6,7 +6,7 @@ import { suite, test, assert } from "vitest"; import { getSassLanguageService, TextDocument, SelectionRange } from "../../cssLanguageService"; -function assertRanges(content: string, expected: (number | string)[][], stylesheetchildren?: number): void { +function assertRanges(content: string, expected: (number | string)[][]): void { let message = `${content} gives selection range:\n`; const offset = content.indexOf("|"); @@ -16,9 +16,6 @@ function assertRanges(content: string, expected: (number | string)[][], styleshe const document = TextDocument.create("test://foo/bar.sass", "sass", 1, content); const stylesheet = ls.parseStylesheet(document); - if (typeof stylesheetchildren === "number") { - assert.equal(stylesheet.children.length, stylesheetchildren); - } const actualRanges = ls.getSelectionRanges(document, [document.positionAt(offset)], stylesheet); assert.equal(actualRanges.length, 1); const offsetPairs: [number, string][] = []; @@ -147,7 +144,94 @@ suite("Sass SelectionRange", () => { [0, ".foo\n\tcolor: red\n\t&.bar\n\t\tcolor: blue\n\n"], [0, ".foo\n\tcolor: red\n\t&.bar\n\t\tcolor: blue\n\n.baz\n\tcolor: green"], ], - 2, + ); + + assertRanges( + `.foo + color: red + &.bar + color: blue| + + &.lol + color: yellow + + &.rofl + color: purple + +.baz + color: green +`, + [ + [33, `blue`], + [26, `color: blue`], + [ + 23, + ` + color: blue + + &.lol + color: yellow + +`, + ], + [ + 18, + `&.bar + color: blue + + &.lol + color: yellow + +`, + ], + [ + 4, + ` + color: red + &.bar + color: blue + + &.lol + color: yellow + + &.rofl + color: purple + +`, + ], + [ + 0, + `.foo + color: red + &.bar + color: blue + + &.lol + color: yellow + + &.rofl + color: purple + +`, + ], + [ + 0, + `.foo + color: red + &.bar + color: blue + + &.lol + color: yellow + + &.rofl + color: purple + +.baz + color: green +`, + ], + ], ); }); }); From cd087c460d837e5cd604b3bc1b925031f5b0a3c2 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 7 Jul 2024 16:13:27 +0200 Subject: [PATCH 060/138] refactor: fix the dedent case also for non-root level --- packages/vscode-css-languageservice/src/parser/cssParser.ts | 4 +++- .../src/test/sass/sassSelectionRange.test.ts | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index b65c4324..2a72837d 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -507,6 +507,8 @@ export class Parser { } } + let initialDepth = this.scanner.stream.depth; + let decl = parseDeclaration(); while (node.addChild(decl)) { if (this.syntax === "indented") { @@ -532,7 +534,7 @@ export class Parser { } } - if (this.syntax === "indented" && this.scanner.stream.depth === 0) { + if (this.syntax === "indented" && this.scanner.stream.depth < initialDepth) { // For the indented syntax we might not get the same number of // dedents as we get indents. If the depth is zero at this point // we should drop out so we don't end up adding a ruleset as a diff --git a/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts index fe38706a..b1253012 100644 --- a/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts @@ -172,7 +172,7 @@ suite("Sass SelectionRange", () => { &.lol color: yellow -`, + `, ], [ 18, @@ -182,7 +182,7 @@ suite("Sass SelectionRange", () => { &.lol color: yellow -`, + `, ], [ 4, From 2bdae99613735401efed43625b2816dd8f131651 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 7 Jul 2024 18:55:50 +0200 Subject: [PATCH 061/138] chore: update scss-sassdoc-parser Now with support for indented (with minor limitations, not affecting the language server). --- package-lock.json | 165 ++++++++++++++++++------ packages/language-services/package.json | 2 +- 2 files changed, 130 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index d12de6ec..93512226 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6802,12 +6802,14 @@ ] }, "node_modules/cdocparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/cdocparser/-/cdocparser-0.13.0.tgz", - "integrity": "sha512-bMi4t0qjeT0xQ8ECBmWcilMYcUNYsERQoatXveMIbItgqliZDCNyv2xfkBoKrs5H08ApeRMoysJLwgPiHtv7HQ==", + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/cdocparser/-/cdocparser-0.15.0.tgz", + "integrity": "sha512-UvDuONjyQTyhYqnO1Glv6TLNi7GYQUq0uMyEYDMTc5QA/wlLmEUl00921pXNW+w0HjHXhoYKiJwwKknIzAcjxg==", + "license": "MIT", "dependencies": { "escape-string-regexp": "^1.0.2", "lodash.assign": "^2.4.1", + "lodash.union": "^3.1.0", "strip-indent": "^1.0.0" } }, @@ -6815,24 +6817,11 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", "engines": { "node": ">=0.8.0" } }, - "node_modules/cdocparser/node_modules/strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==", - "dependencies": { - "get-stdin": "^4.0.1" - }, - "bin": { - "strip-indent": "cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/chai": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", @@ -10283,6 +10272,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -12787,6 +12777,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash._basebind/-/lodash._basebind-2.4.1.tgz", "integrity": "sha512-VGHm6DH+1UiuafQdE/DNMqxOcSyhRu0xO9+jPDq7xITRn5YOorGrHVQmavMVXCYmTm80YRTZZCn/jTW7MokwLg==", + "license": "MIT", "dependencies": { "lodash._basecreate": "~2.4.1", "lodash._setbinddata": "~2.4.1", @@ -12798,6 +12789,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-2.4.1.tgz", "integrity": "sha512-8JJ3FnMPm54t3BwPLk8q8mPyQKQXm/rt9df+awr4NGtyJrtcCXM3Of1I86S6jVy1b4yAyFBb8wbKPEauuqzRmQ==", + "license": "MIT", "dependencies": { "lodash._isnative": "~2.4.1", "lodash.isobject": "~2.4.1", @@ -12808,6 +12800,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash._basecreatecallback/-/lodash._basecreatecallback-2.4.1.tgz", "integrity": "sha512-SLczhg860fGW7AKlYcuOFstDtJuQhaANlJ4Y/jrOoRxhmVtK41vbJDH3OefVRSRkSCQo4HI82QVkAVsoGa5gSw==", + "license": "MIT", "dependencies": { "lodash._setbinddata": "~2.4.1", "lodash.bind": "~2.4.1", @@ -12819,6 +12812,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash._basecreatewrapper/-/lodash._basecreatewrapper-2.4.1.tgz", "integrity": "sha512-x2ja1fa/qmzbizuXgVM4QAP9svtMbdxjG8Anl9bCeDAwLOVQ1vLrA0hLb/NkpbGi9evjtkl0aWLTEoOlUdBPQA==", + "license": "MIT", "dependencies": { "lodash._basecreate": "~2.4.1", "lodash._setbinddata": "~2.4.1", @@ -12826,10 +12820,53 @@ "lodash.isobject": "~2.4.1" } }, + "node_modules/lodash._baseflatten": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lodash._baseflatten/-/lodash._baseflatten-3.1.4.tgz", + "integrity": "sha512-fESngZd+X4k+GbTxdMutf8ohQa0s3sJEHIcwtu4/LsIQ2JTDzdRxDCMQjW+ezzwRitLmHnacVVmosCbxifefbw==", + "license": "MIT", + "dependencies": { + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "node_modules/lodash._baseindexof": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz", + "integrity": "sha512-bSYo8Pc/f0qAkr8fPJydpJjtrHiSynYfYBjtANIgXv5xEf1WlTC63dIDlgu0s9dmTvzRu1+JJTxcIAHe+sH0FQ==", + "license": "MIT" + }, + "node_modules/lodash._baseuniq": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._baseuniq/-/lodash._baseuniq-3.0.3.tgz", + "integrity": "sha512-80ifiewpXTvE5gJ4+dnck+3ys4ix3+ch3N0/1ZvujIfbwIu0SnNIlJE4VsOS2bVjAcxm1JE8LLskpnQBgOR0bQ==", + "license": "MIT", + "dependencies": { + "lodash._baseindexof": "^3.0.0", + "lodash._cacheindexof": "^3.0.0", + "lodash._createcache": "^3.0.0" + } + }, + "node_modules/lodash._cacheindexof": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz", + "integrity": "sha512-S8dUjWr7SUT/X6TBIQ/OYoCHo1Stu1ZRy6uMUSKqzFnZp5G5RyQizSm6kvxD2Ewyy6AVfMg4AToeZzKfF99T5w==", + "license": "MIT" + }, + "node_modules/lodash._createcache": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz", + "integrity": "sha512-ev5SP+iFpZOugyab/DEUQxUeZP5qyciVTlgQ1f4Vlw7VUcCD8fVnyIqVUEIaoFH9zjAqdgi69KiofzvVmda/ZQ==", + "license": "MIT", + "dependencies": { + "lodash._getnative": "^3.0.0" + } + }, "node_modules/lodash._createwrapper": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash._createwrapper/-/lodash._createwrapper-2.4.1.tgz", "integrity": "sha512-5TCfLt1haQpsa7bgLYRKNNE4yqhO4ZxIayN1btQmazMchO6Q8JYFRMqbJ3W+uNmMm4R0Jw7KGkZX5YfDDnywuw==", + "license": "MIT", "dependencies": { "lodash._basebind": "~2.4.1", "lodash._basecreatewrapper": "~2.4.1", @@ -12837,20 +12874,29 @@ "lodash.isfunction": "~2.4.1" } }, + "node_modules/lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA==", + "license": "MIT" + }, "node_modules/lodash._isnative": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", - "integrity": "sha512-BOlKGKNHhCHswGOWtmVb5zBygyxN7EmTuzVOSQI6QSoGhG+kvv71gICFS1TBpnqvT1n53txK8CDK3u5D2/GZxQ==" + "integrity": "sha512-BOlKGKNHhCHswGOWtmVb5zBygyxN7EmTuzVOSQI6QSoGhG+kvv71gICFS1TBpnqvT1n53txK8CDK3u5D2/GZxQ==", + "license": "MIT" }, "node_modules/lodash._objecttypes": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", - "integrity": "sha512-XpqGh1e7hhkOzftBfWE7zt+Yn9mVHFkDhicVttvKLsoCMLVVL+xTQjfjB4X4vtznauxv0QZ5ZAeqjvat0dh62Q==" + "integrity": "sha512-XpqGh1e7hhkOzftBfWE7zt+Yn9mVHFkDhicVttvKLsoCMLVVL+xTQjfjB4X4vtznauxv0QZ5ZAeqjvat0dh62Q==", + "license": "MIT" }, "node_modules/lodash._setbinddata": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash._setbinddata/-/lodash._setbinddata-2.4.1.tgz", "integrity": "sha512-Vx0XKzpg2DFbQw4wrp1xSWd2sfl3W/BG6bucSRZmftS1AzbWRemCmBQDxyQTNhlLNec428PXkuuja+VNBZgu2A==", + "license": "MIT", "dependencies": { "lodash._isnative": "~2.4.1", "lodash.noop": "~2.4.1" @@ -12860,6 +12906,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", "integrity": "sha512-lBrglYxLD/6KAJ8IEa5Lg+YHgNAL7FyKqXg4XOUI+Du/vtniLs1ZqS+yHNKPkK54waAgkdUnDOYaWf+rv4B+AA==", + "license": "MIT", "dependencies": { "lodash._objecttypes": "~2.4.1" } @@ -12867,12 +12914,14 @@ "node_modules/lodash._slice": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash._slice/-/lodash._slice-2.4.1.tgz", - "integrity": "sha512-+odPJa4PE2UgYnQgJgkLs0UD03QU78R2ivhrFnG9GdtYOZdE6ObxOj7KiUEUlqOOgatFT+ZqSypFjDSduTigKg==" + "integrity": "sha512-+odPJa4PE2UgYnQgJgkLs0UD03QU78R2ivhrFnG9GdtYOZdE6ObxOj7KiUEUlqOOgatFT+ZqSypFjDSduTigKg==", + "license": "MIT" }, "node_modules/lodash.assign": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-2.4.1.tgz", "integrity": "sha512-AqQ4AJz5buSx9ELXWt5dONwJyVPd4NTADMKhoVYWCugjoVf172/LpvVhwmSJn4g8/Dc0S8hxTe8rt5Dob3X9KQ==", + "license": "MIT", "dependencies": { "lodash._basecreatecallback": "~2.4.1", "lodash._objecttypes": "~2.4.1", @@ -12883,6 +12932,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-2.4.1.tgz", "integrity": "sha512-hn2VWYZ+N9aYncRad4jORvlGgpFrn+axnPIWRvFxjk6CWcZH5b5alI8EymYsHITI23Z9wrW/+ORq+azrVFpOfw==", + "license": "MIT", "dependencies": { "lodash._createwrapper": "~2.4.1", "lodash._slice": "~2.4.1" @@ -12903,7 +12953,8 @@ "node_modules/lodash.identity": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.identity/-/lodash.identity-2.4.1.tgz", - "integrity": "sha512-VRYX+8XipeLjorag5bz3YBBRJ+5kj8hVBzfnaHgXPZAVTYowBdY5l0M5ZnOmlAMCOXBFabQtm7f5VqjMKEji0w==" + "integrity": "sha512-VRYX+8XipeLjorag5bz3YBBRJ+5kj8hVBzfnaHgXPZAVTYowBdY5l0M5ZnOmlAMCOXBFabQtm7f5VqjMKEji0w==", + "license": "MIT" }, "node_modules/lodash.includes": { "version": "4.3.0", @@ -12911,6 +12962,18 @@ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "dev": true }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "license": "MIT" + }, + "node_modules/lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha512-JwObCrNJuT0Nnbuecmqr5DgtuBppuCvGD9lxjFpAzwnVtdGoDQ1zig+5W8k5/6Gcn0gZ3936HDAlGd28i7sOGQ==", + "license": "MIT" + }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -12920,7 +12983,8 @@ "node_modules/lodash.isfunction": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-2.4.1.tgz", - "integrity": "sha512-6XcAB3izeQxPOQQNAJbbdjXbvWEt2Pn9ezPrjr4CwoLwmqsLVbsiEXD19cmmt4mbzOCOCdHzOQiUivUOJLra7w==" + "integrity": "sha512-6XcAB3izeQxPOQQNAJbbdjXbvWEt2Pn9ezPrjr4CwoLwmqsLVbsiEXD19cmmt4mbzOCOCdHzOQiUivUOJLra7w==", + "license": "MIT" }, "node_modules/lodash.isinteger": { "version": "4.0.4", @@ -12938,6 +13002,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", "integrity": "sha512-sTebg2a1PoicYEZXD5PBdQcTlIJ6hUslrlWr7iV0O7n+i4596s2NQ9I5CaZ5FbXSfya/9WQsrYLANUJv9paYVA==", + "license": "MIT", "dependencies": { "lodash._objecttypes": "~2.4.1" } @@ -12964,6 +13029,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", "integrity": "sha512-ZpJhwvUXHSNL5wYd1RM6CUa2ZuqorG9ngoJ9Ix5Cce+uX7I5O/E06FCJdhSZ33b5dVyeQDnIlWH7B2s5uByZ7g==", + "license": "MIT", "dependencies": { "lodash._isnative": "~2.4.1", "lodash._shimkeys": "~2.4.1", @@ -12991,7 +13057,8 @@ "node_modules/lodash.noop": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.noop/-/lodash.noop-2.4.1.tgz", - "integrity": "sha512-uNcV98/blRhInPUGQEnj9ekXXfG+q+rfoNSFZgl/eBfog9yBDW9gfUv2AHX/rAF7zZRlzWhbslGhbGQFZlCkZA==" + "integrity": "sha512-uNcV98/blRhInPUGQEnj9ekXXfG+q+rfoNSFZgl/eBfog9yBDW9gfUv2AHX/rAF7zZRlzWhbslGhbGQFZlCkZA==", + "license": "MIT" }, "node_modules/lodash.once": { "version": "4.1.1", @@ -12999,6 +13066,12 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "dev": true }, + "node_modules/lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha512-L4/arjjuq4noiUJpt3yS6KIKDtJwNe2fIYgMqyYYKoeIfV1iEqvPwhCx23o+R9dzouGihDAPN1dTIRWa7zk8tw==", + "license": "MIT" + }, "node_modules/lodash.snakecase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", @@ -13015,10 +13088,22 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.support/-/lodash.support-2.4.1.tgz", "integrity": "sha512-6SwqWwGFHhTXEiqB/yQgu8FYd//tm786d49y7kizHVCJH7zdzs191UQn3ES3tkkDbUddNRfkCRYqJFHtbLnbCw==", + "license": "MIT", "dependencies": { "lodash._isnative": "~2.4.1" } }, + "node_modules/lodash.union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-3.1.0.tgz", + "integrity": "sha512-5W10Z523AFWxHAg+KuCynyv7U3L8qQdJL1yyKUXgpmDJYAoZIHl1KJBFqA5eHPcL9ObxHHUmALo8CUhptZQaKw==", + "license": "MIT", + "dependencies": { + "lodash._baseflatten": "^3.0.0", + "lodash._baseuniq": "^3.0.0", + "lodash.restparam": "^3.0.0" + } + }, "node_modules/lodash.uniq": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", @@ -16817,20 +16902,13 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/scss-comment-parser": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/scss-comment-parser/-/scss-comment-parser-0.8.4.tgz", - "integrity": "sha512-ERw4BODvM22n8Ke8hJxuH3fKXLm0Q4chfUNHwDSOAExCths2ZXq8PT32vms4R9Om6dffRSXzzGZS1p38UU4EAg==", - "dependencies": { - "cdocparser": "^0.13.0" - } - }, "node_modules/scss-sassdoc-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/scss-sassdoc-parser/-/scss-sassdoc-parser-3.1.0.tgz", - "integrity": "sha512-SFPs6wCOQoa/TvRJBsWXbQw8Zo4towFrzF0XPocB4UYBfQp045j4eRvvsb646+p+JjFG9MbjOiu0UCWr8zFqKA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/scss-sassdoc-parser/-/scss-sassdoc-parser-3.2.0.tgz", + "integrity": "sha512-zXNVGB6rwBn1AB0PPIFsy4YRNBfQVU08YJpTXvkNxtdl8CY80PPcHUNJm0OEYAwz/SZRmKe6kVtWA0+tnRdfyw==", + "license": "MIT", "dependencies": { - "scss-comment-parser": "^0.8.4" + "cdocparser": "0.15.0" }, "engines": { "node": ">=20" @@ -17686,6 +17764,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==", + "license": "MIT", + "dependencies": { + "get-stdin": "^4.0.1" + }, + "bin": { + "strip-indent": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -20303,7 +20396,7 @@ "dependencies": { "@somesass/vscode-css-languageservice": "1.0.1", "colorjs.io": "0.5.0", - "scss-sassdoc-parser": "3.1.0" + "scss-sassdoc-parser": "3.2.0" }, "devDependencies": { "@vitest/coverage-v8": "1.5.3", diff --git a/packages/language-services/package.json b/packages/language-services/package.json index 3b7c768b..ca700259 100644 --- a/packages/language-services/package.json +++ b/packages/language-services/package.json @@ -50,7 +50,7 @@ "dependencies": { "@somesass/vscode-css-languageservice": "1.0.1", "colorjs.io": "0.5.0", - "scss-sassdoc-parser": "3.1.0" + "scss-sassdoc-parser": "3.2.0" }, "devDependencies": { "@vitest/coverage-v8": "1.5.3", From 86d5b70e597c827258af9fb7673a577b801776f9 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 7 Jul 2024 19:10:44 +0200 Subject: [PATCH 062/138] chore: replace with new package name --- package-lock.json | 26 +++++++++---------- packages/language-services/package.json | 2 +- .../src/features/do-complete.ts | 2 +- .../src/features/find-symbols.ts | 2 +- .../src/language-model-cache.ts | 2 +- .../src/language-services-types.ts | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 93512226..139e4494 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16877,6 +16877,18 @@ } } }, + "node_modules/sassdoc-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sassdoc-parser/-/sassdoc-parser-3.3.0.tgz", + "integrity": "sha512-wuRMNC8eLxCjyYwHy6bye74Y3hhGpsmlzhu2pMfsyfZ0v66uZyWlWfgASoptKoUXyKhwyD41mwp84sgEROmDxA==", + "license": "MIT", + "dependencies": { + "cdocparser": "0.15.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/sax": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", @@ -16902,18 +16914,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/scss-sassdoc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/scss-sassdoc-parser/-/scss-sassdoc-parser-3.2.0.tgz", - "integrity": "sha512-zXNVGB6rwBn1AB0PPIFsy4YRNBfQVU08YJpTXvkNxtdl8CY80PPcHUNJm0OEYAwz/SZRmKe6kVtWA0+tnRdfyw==", - "license": "MIT", - "dependencies": { - "cdocparser": "0.15.0" - }, - "engines": { - "node": ">=20" - } - }, "node_modules/secure-compare": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", @@ -20396,7 +20396,7 @@ "dependencies": { "@somesass/vscode-css-languageservice": "1.0.1", "colorjs.io": "0.5.0", - "scss-sassdoc-parser": "3.2.0" + "sassdoc-parser": "3.3.0" }, "devDependencies": { "@vitest/coverage-v8": "1.5.3", diff --git a/packages/language-services/package.json b/packages/language-services/package.json index ca700259..b849f44e 100644 --- a/packages/language-services/package.json +++ b/packages/language-services/package.json @@ -50,7 +50,7 @@ "dependencies": { "@somesass/vscode-css-languageservice": "1.0.1", "colorjs.io": "0.5.0", - "scss-sassdoc-parser": "3.2.0" + "sassdoc-parser": "3.3.0" }, "devDependencies": { "@vitest/coverage-v8": "1.5.3", diff --git a/packages/language-services/src/features/do-complete.ts b/packages/language-services/src/features/do-complete.ts index 43c4a3f3..2306fe1c 100644 --- a/packages/language-services/src/features/do-complete.ts +++ b/packages/language-services/src/features/do-complete.ts @@ -1,6 +1,6 @@ import { getNodeAtOffset } from "@somesass/vscode-css-languageservice"; import ColorDotJS from "colorjs.io"; -import { ParseResult } from "scss-sassdoc-parser"; +import { ParseResult } from "sassdoc-parser"; import { SassBuiltInModule, sassBuiltInModules } from "../facts/sass"; import { sassDocAnnotations } from "../facts/sassdoc"; import { LanguageFeature } from "../language-feature"; diff --git a/packages/language-services/src/features/find-symbols.ts b/packages/language-services/src/features/find-symbols.ts index ee71edd4..d5d715ca 100644 --- a/packages/language-services/src/features/find-symbols.ts +++ b/packages/language-services/src/features/find-symbols.ts @@ -1,4 +1,4 @@ -import { ParseResult } from "scss-sassdoc-parser"; +import { ParseResult } from "sassdoc-parser"; import { LanguageFeature } from "../language-feature"; import { TextDocument, diff --git a/packages/language-services/src/language-model-cache.ts b/packages/language-services/src/language-model-cache.ts index a477a365..1c139762 100644 --- a/packages/language-services/src/language-model-cache.ts +++ b/packages/language-services/src/language-model-cache.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { LanguageService as VSCodeLanguageService } from "@somesass/vscode-css-languageservice"; -import { ParseResult, parseSync } from "scss-sassdoc-parser"; +import { ParseResult, parseSync } from "sassdoc-parser"; import { TextDocument, Stylesheet, diff --git a/packages/language-services/src/language-services-types.ts b/packages/language-services/src/language-services-types.ts index 6ae76362..45afd9fd 100644 --- a/packages/language-services/src/language-services-types.ts +++ b/packages/language-services/src/language-services-types.ts @@ -27,7 +27,7 @@ import { FoldingRangeKind, SelectionRange, } from "@somesass/vscode-css-languageservice"; -import type { ParseResult } from "scss-sassdoc-parser"; +import type { ParseResult } from "sassdoc-parser"; import { TextDocument } from "vscode-languageserver-textdocument"; import { Range, From 6c141cee113cd57a00c933dd83fc06db2ab6cfaf Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 14 Jul 2024 11:09:39 +0200 Subject: [PATCH 063/138] refactor: trigger css property completions correctly --- .../src/features/do-complete.ts | 35 ++++++++------- .../src/services/cssCompletion.ts | 3 ++ .../test/sass/sassCompletionIndented.test.ts | 44 +++++++++++++++++++ 3 files changed, 67 insertions(+), 15 deletions(-) create mode 100644 packages/vscode-css-languageservice/src/test/sass/sassCompletionIndented.test.ts diff --git a/packages/language-services/src/features/do-complete.ts b/packages/language-services/src/features/do-complete.ts index 2306fe1c..a8a34bf8 100644 --- a/packages/language-services/src/features/do-complete.ts +++ b/packages/language-services/src/features/do-complete.ts @@ -315,22 +315,27 @@ export class DoComplete extends LanguageFeature { } } - return result; + if (result.items.length === 0 && document.languageId === "sass") { + // If we don't have any suggestions, maybe upstream does. + // Only do this for indented. We make the assumption that + // VS Code CSS language server is running and provides + // suggestions in this case. + const upstreamResult = await upstreamLs.doComplete2( + document, + position, + stylesheet, + this.getDocumentContext(), + { + ...this.configuration.completionSettings, + triggerPropertyValueCompletion: + this.configuration.completionSettings + ?.triggerPropertyValueCompletion || false, + }, + ); + return upstreamResult; + } - // // If we don't have any suggestions, maybe upstream does - // const upstreamResult = await upstreamLs.doComplete2( - // document, - // position, - // stylesheet, - // this.getDocumentContext(), - // { - // ...this.configuration.completionSettings, - // triggerPropertyValueCompletion: - // this.configuration.completionSettings - // ?.triggerPropertyValueCompletion || false, - // }, - // ); - // return upstreamResult; + return result; } const upstreamResult = await upstreamLs.doComplete2( diff --git a/packages/vscode-css-languageservice/src/services/cssCompletion.ts b/packages/vscode-css-languageservice/src/services/cssCompletion.ts index 76544db9..4e923025 100644 --- a/packages/vscode-css-languageservice/src/services/cssCompletion.ts +++ b/packages/vscode-css-languageservice/src/services/cssCompletion.ts @@ -957,6 +957,9 @@ export class CSSCompletion { return result; // don't show new properties right after semicolon (see Bug 15421:[intellisense] [css] Be less aggressive when manually typing CSS) } + // complete next property + return this.getCompletionsForDeclarationProperty(null, result); + } else if (this.textDocument.languageId === "sass" && this.offset > declaration.offset + declaration.length) { // complete next property return this.getCompletionsForDeclarationProperty(null, result); } diff --git a/packages/vscode-css-languageservice/src/test/sass/sassCompletionIndented.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassCompletionIndented.test.ts new file mode 100644 index 00000000..9812f1c6 --- /dev/null +++ b/packages/vscode-css-languageservice/src/test/sass/sassCompletionIndented.test.ts @@ -0,0 +1,44 @@ +import * as path from "path"; +import { suite, test } from "vitest"; + +import { Position, InsertTextFormat, CompletionItemKind, LanguageSettings } from "../../cssLanguageService"; +import { testCompletionFor as testCSSCompletionFor, ExpectedCompetions } from "../css/completion.test"; +import { newRange } from "../css/navigation.test"; +import { URI } from "vscode-uri"; + +function testCompletionFor( + value: string, + expected: ExpectedCompetions, + settings: LanguageSettings | undefined = undefined, + testUri: string = "test://test/test.sass", + workspaceFolderUri: string = "test://test", +) { + return testCSSCompletionFor(value, expected, settings, testUri, workspaceFolderUri); +} + +suite("Sass indented - Completions", () => { + test("declarations suggest properties as expected", async () => { + await testCompletionFor( + `.foo + color: red + |`, + + { + items: [ + { + label: "position", + resultText: `.foo + color: red + position: `, + }, + { + label: "display", + resultText: `.foo + color: red + display: `, + }, + ], + }, + ); + }); +}); From ff45acdd2b74a31c2a740c7aa00ccab0f933e841 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 14 Jul 2024 11:52:06 +0200 Subject: [PATCH 064/138] refactor: fix parser error with custom properties in sass --- .../src/parser/cssParser.ts | 6 ++++ .../src/services/cssCompletion.ts | 10 ++++-- .../src/test/sass/parser-indented.test.ts | 3 +- .../test/sass/sassCompletionIndented.test.ts | 5 +-- .../src/test/sass/sassSelectionRange.test.ts | 32 +++++++++++++++++++ 5 files changed, 48 insertions(+), 8 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 2a72837d..51347961 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -694,6 +694,12 @@ export class Parser { let bracketsDepth = 0; done: while (true) { switch (this.token.type) { + case TokenType.Dedent: + case TokenType.Newline: { + if (this.syntax === "indented") { + break done; + } + } case TokenType.SemiColon: // A semicolon only ends things if we're not inside a delimitor. if (isTopLevel()) { diff --git a/packages/vscode-css-languageservice/src/services/cssCompletion.ts b/packages/vscode-css-languageservice/src/services/cssCompletion.ts index 4e923025..dd19f8b1 100644 --- a/packages/vscode-css-languageservice/src/services/cssCompletion.ts +++ b/packages/vscode-css-languageservice/src/services/cssCompletion.ts @@ -959,9 +959,13 @@ export class CSSCompletion { // complete next property return this.getCompletionsForDeclarationProperty(null, result); - } else if (this.textDocument.languageId === "sass" && this.offset > declaration.offset + declaration.length) { - // complete next property - return this.getCompletionsForDeclarationProperty(null, result); + } else if (this.textDocument.languageId === "sass") { + let declBeforeOffsetHasValue = Boolean(declaration.getChild(1)); + let isNextDeclaration = this.offset > declaration.offset + declaration.length && declBeforeOffsetHasValue; + if (isNextDeclaration) { + // complete next property + return this.getCompletionsForDeclarationProperty(null, result); + } } if (declaration instanceof nodes.Declaration) { diff --git a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts index f2d0a0a2..8c55ff3d 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts @@ -1411,7 +1411,7 @@ name parser._parseRuleset.bind(parser), ParseError.PropertyValueExpected, ); - assertNode( + assertError( `name --minimal: @@ -1419,6 +1419,7 @@ name padding: 1rem`, parser, parser._parseRuleset.bind(parser), + ParseError.PropertyValueExpected, ); assertNode( `name diff --git a/packages/vscode-css-languageservice/src/test/sass/sassCompletionIndented.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassCompletionIndented.test.ts index 9812f1c6..471d4ed6 100644 --- a/packages/vscode-css-languageservice/src/test/sass/sassCompletionIndented.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassCompletionIndented.test.ts @@ -1,10 +1,7 @@ -import * as path from "path"; import { suite, test } from "vitest"; -import { Position, InsertTextFormat, CompletionItemKind, LanguageSettings } from "../../cssLanguageService"; +import { LanguageSettings } from "../../cssLanguageService"; import { testCompletionFor as testCSSCompletionFor, ExpectedCompetions } from "../css/completion.test"; -import { newRange } from "../css/navigation.test"; -import { URI } from "vscode-uri"; function testCompletionFor( value: string, diff --git a/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts index b1253012..5325d48e 100644 --- a/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts @@ -234,4 +234,36 @@ suite("Sass SelectionRange", () => { ], ); }); + + test("handles :root and css variables", () => { + assertRanges( + `:root + --myvar: red + +body + color: --myvar|`, + [ + [34, `--myvar`], + [27, `color: --myvar`], + [ + 25, + ` + color: --myvar`, + ], + [ + 21, + `body + color: --myvar`, + ], + [ + 0, + `:root + --myvar: red + +body + color: --myvar`, + ], + ], + ); + }); }); From 085233b3242ba62153f70d4194aaef844b91bf1d Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 14 Jul 2024 12:11:35 +0200 Subject: [PATCH 065/138] refactor: wip completions --- packages/language-server/src/server.ts | 11 ++++ .../src/features/do-complete.ts | 50 ++++++------------- 2 files changed, 27 insertions(+), 34 deletions(-) diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index 63ac4359..46af09be 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -96,6 +96,9 @@ export class SomeSassServer { // For placeholder completion "%", + + // For namespaced completions + ".", ], }, signatureHelpProvider: { @@ -163,6 +166,14 @@ export class SomeSassServer { ls.configure({ editorSettings, workspaceRoot, + completionSettings: { + suggestAllFromOpenDocument: + settings.suggestAllFromOpenDocument, + suggestFromUseOnly: settings.suggestFromUseOnly, + suggestionStyle: settings.suggestionStyle, + suggestFunctionsInStringContextAfterSymbols: + settings.suggestFunctionsInStringContextAfterSymbols, + }, }); this.connection.console.debug( diff --git a/packages/language-services/src/features/do-complete.ts b/packages/language-services/src/features/do-complete.ts index a8a34bf8..85cbdf16 100644 --- a/packages/language-services/src/features/do-complete.ts +++ b/packages/language-services/src/features/do-complete.ts @@ -315,43 +315,25 @@ export class DoComplete extends LanguageFeature { } } - if (result.items.length === 0 && document.languageId === "sass") { - // If we don't have any suggestions, maybe upstream does. - // Only do this for indented. We make the assumption that - // VS Code CSS language server is running and provides - // suggestions in this case. - const upstreamResult = await upstreamLs.doComplete2( - document, - position, - stylesheet, - this.getDocumentContext(), - { - ...this.configuration.completionSettings, - triggerPropertyValueCompletion: - this.configuration.completionSettings - ?.triggerPropertyValueCompletion || false, - }, - ); - return upstreamResult; - } - return result; } - const upstreamResult = await upstreamLs.doComplete2( - document, - position, - stylesheet, - this.getDocumentContext(), - { - ...this.configuration.completionSettings, - triggerPropertyValueCompletion: - this.configuration.completionSettings - ?.triggerPropertyValueCompletion || false, - }, - ); - if (upstreamResult.items.length > 0) { - result.items.push(...upstreamResult.items); + if (document.languageId === "sass" && result.items.length === 0) { + const upstreamResult = await upstreamLs.doComplete2( + document, + position, + stylesheet, + this.getDocumentContext(), + { + ...this.configuration.completionSettings, + triggerPropertyValueCompletion: + this.configuration.completionSettings + ?.triggerPropertyValueCompletion || false, + }, + ); + if (upstreamResult.items.length > 0) { + result.items.push(...upstreamResult.items); + } } return result; } From 269f023356d2b23fd332bc652deda1599d21c882 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 14 Jul 2024 13:51:29 +0200 Subject: [PATCH 066/138] refactor: fix parser error with nested stuff --- packages/vscode-css-languageservice/src/parser/cssParser.ts | 4 ++++ .../vscode-css-languageservice/src/services/sassCompletion.ts | 2 +- .../src/test/sass/sassCompletion.test.ts | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 51347961..2e85a4f4 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -553,6 +553,10 @@ export class Parser { if (this.peek(TokenType.AtKeyword)) { return this.finish(node); } + // nesting + if (this.peekDelim("&")) { + return this.finish(node); + } if (!this.accept(TokenType.Dedent)) { return this.finish(node, ParseError.DedentExpected, [TokenType.Newline, TokenType.Indent, TokenType.EOF]); } diff --git a/packages/vscode-css-languageservice/src/services/sassCompletion.ts b/packages/vscode-css-languageservice/src/services/sassCompletion.ts index c4f038fc..ece19dc9 100644 --- a/packages/vscode-css-languageservice/src/services/sassCompletion.ts +++ b/packages/vscode-css-languageservice/src/services/sassCompletion.ts @@ -180,7 +180,7 @@ export class SassCompletion extends CSSCompletion { { func: "call($name, $args…)", desc: l10n.t("Dynamically calls a Sass function.") }, ]; - private static scssAtDirectives = [ + private static scssAtDirectives: CompletionItem[] = [ { label: "@extend", documentation: l10n.t("Inherits the styles of another selector."), diff --git a/packages/vscode-css-languageservice/src/test/sass/sassCompletion.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassCompletion.test.ts index d4b0c873..b90a6905 100644 --- a/packages/vscode-css-languageservice/src/test/sass/sassCompletion.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassCompletion.test.ts @@ -343,7 +343,7 @@ body `div &:hover - |`, + |`, { items: [{ label: "display" }], }, From 91b8f9a8f9fa3a58a2a6b4c2532f5e7b66b94036 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 14 Jul 2024 14:27:14 +0200 Subject: [PATCH 067/138] refactor: fix parser bug indented custom property expression --- .../src/features/do-complete.ts | 20 +++++++++++-------- .../src/parser/cssParser.ts | 7 +++++-- .../src/test/sass/parser-indented.test.ts | 16 +++++++++++++++ 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/packages/language-services/src/features/do-complete.ts b/packages/language-services/src/features/do-complete.ts index 85cbdf16..c758eaeb 100644 --- a/packages/language-services/src/features/do-complete.ts +++ b/packages/language-services/src/features/do-complete.ts @@ -40,6 +40,7 @@ import { applySassDoc } from "../utils/sassdoc"; const reNewSassdocBlock = /\/\/\/\s?$/; const reSassdocLine = /\/\/\/\s/; const reSassDotExt = /\.s(a|c)ss$/; +const reScssDotExt = /\.scss$/; const rePrivate = /^\$?[_].*$/; const reReturn = /^.*@return/; @@ -756,14 +757,14 @@ export class DoComplete extends LanguageFeature { const dotExt = initialDocument.uri.slice( Math.max(0, initialDocument.uri.lastIndexOf(".")), ); - const isEmbedded = !dotExt.match(reSassDotExt); + const skipDot = !dotExt.match(reScssDotExt); let insertText: string | undefined; let filterText: string | undefined; if (namespace && namespace !== "*") { insertText = currentWord.endsWith(".") - ? `${isEmbedded ? "" : "."}${label}` - : isEmbedded + ? `${skipDot ? "" : "."}${label}` + : skipDot ? asDollarlessVariable(label) : label; @@ -820,9 +821,10 @@ export class DoComplete extends LanguageFeature { : symbol.name; const isEmbedded = this.isEmbedded(initialDocument); - + const includeDot = + namespace !== "*" && !isEmbedded && initialDocument.languageId !== "sass"; const insertText = namespace - ? namespace !== "*" && !isEmbedded + ? includeDot ? `.${prefix}${symbol.name}` : `${prefix}${symbol.name}` : symbol.name; @@ -940,8 +942,10 @@ export class DoComplete extends LanguageFeature { : symbol.name; const isEmbedded = this.isEmbedded(initialDocument); + const includeDot = + namespace !== "*" && !isEmbedded && initialDocument.languageId !== "sass"; const insertText = namespace - ? namespace !== "*" && !isEmbedded + ? includeDot ? `.${prefix}${symbol.name}` : `${prefix}${symbol.name}` : symbol.name; @@ -1020,9 +1024,9 @@ export class DoComplete extends LanguageFeature { // be replaced (except when we're embedded in Vue, Svelte or Astro). // Example result: .floor(${1:number}) const isEmbedded = this.isEmbedded(document); - + const includeDot = isEmbedded && document.languageId !== "sass"; const insertText = context.currentWord.includes(".") - ? `${isEmbedded ? "" : "."}${name}${ + ? `${includeDot ? "" : "."}${name}${ signature ? `(${parameterSnippet})` : "" }` : name; diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 2e85a4f4..f3e3d577 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -661,7 +661,10 @@ export class Parser { const expression = this._parseExpr(); if (expression && !expression.isErroneous(true)) { this._parsePrio(); - if (this.peekOne(...(stopTokens || []), TokenType.SemiColon, TokenType.EOF)) { + if ( + this.peekOne(...(stopTokens || []), TokenType.SemiColon, TokenType.EOF) || + (this.syntax === "indented" && this.peekOne(...(stopTokens || []), TokenType.Newline, TokenType.Dedent)) + ) { node.setValue(expression); if (this.peek(TokenType.SemiColon)) { node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist @@ -755,7 +758,7 @@ export class Parser { break done; case TokenType.EOF: if (this.syntax === "indented") { - return this.finish(node); + break done; } // We shouldn't have reached the end of input, something is // unterminated. diff --git a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts index 8c55ff3d..883d89f5 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts @@ -3185,4 +3185,20 @@ figure assertError("module.", parser, parser._parseModuleMember.bind(parser), ParseError.IdentifierOrVariableExpected); }); + + test("custom property stops in time", () => { + assertNode( + `@use "variables" +@use "mixins" + +:root + --myvar: variables.$altFontFamily + @include mixins.clearfix + +body + color: red`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); }); From 7c2f214c73976ca94d12edb9bfaf327cd2f9c152 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 14 Jul 2024 14:46:35 +0200 Subject: [PATCH 068/138] refactor: fix a completions bug where use and import duplicated --- .../__tests__/do-complete-sassdoc.test.ts | 27 ------------------- .../src/features/do-complete.ts | 12 ++++----- 2 files changed, 6 insertions(+), 33 deletions(-) diff --git a/packages/language-services/src/features/__tests__/do-complete-sassdoc.test.ts b/packages/language-services/src/features/__tests__/do-complete-sassdoc.test.ts index cbe2964c..ec246239 100644 --- a/packages/language-services/src/features/__tests__/do-complete-sassdoc.test.ts +++ b/packages/language-services/src/features/__tests__/do-complete-sassdoc.test.ts @@ -213,33 +213,6 @@ Function declared in timing.scss`, sortText: undefined, tags: [], }, - { - documentation: { - kind: "markdown", - value: `\`\`\`scss -@function timing($mode) -\`\`\` -____ -Get a timing value for use in animations. - - -@param "sonic" | "link" | "homer" | "snorlax"\`mode\` - The timing you want - -@return String - the timing value in ms -____ -Function declared in timing.scss`, - }, - filterText: "timing", - insertText: 'timing(${1|"sonic","link","homer","snorlax"|})', - insertTextFormat: 2, - kind: 3, - label: "timing", - labelDetails: { - detail: "($mode)", - }, - sortText: undefined, - tags: [], - }, ], }); }); diff --git a/packages/language-services/src/features/do-complete.ts b/packages/language-services/src/features/do-complete.ts index c758eaeb..0bbc40d3 100644 --- a/packages/language-services/src/features/do-complete.ts +++ b/packages/language-services/src/features/do-complete.ts @@ -40,7 +40,6 @@ import { applySassDoc } from "../utils/sassdoc"; const reNewSassdocBlock = /\/\/\/\s?$/; const reSassdocLine = /\/\/\/\s/; const reSassDotExt = /\.s(a|c)ss$/; -const reScssDotExt = /\.scss$/; const rePrivate = /^\$?[_].*$/; const reReturn = /^.*@return/; @@ -226,6 +225,7 @@ export class DoComplete extends LanguageFeature { const items = await this.doNamespaceCompletion(document, context); if (items.length > 0) { result.items.push(...items); + return result; } } @@ -757,20 +757,20 @@ export class DoComplete extends LanguageFeature { const dotExt = initialDocument.uri.slice( Math.max(0, initialDocument.uri.lastIndexOf(".")), ); - const skipDot = !dotExt.match(reScssDotExt); + const isEmbedded = !dotExt.match(reSassDotExt); let insertText: string | undefined; let filterText: string | undefined; if (namespace && namespace !== "*") { insertText = currentWord.endsWith(".") - ? `${skipDot ? "" : "."}${label}` - : skipDot + ? `${isEmbedded || dotExt === ".sass" ? "" : "."}${label}` + : isEmbedded ? asDollarlessVariable(label) : label; filterText = currentWord.endsWith(".") ? `${namespace}.${label}` : label; - } else if (dotExt === ".vue" || dotExt === ".astro") { - // In Vue and Astro files, the $ does not get replaced by the suggestion, + } else if (dotExt === ".vue" || dotExt === ".astro" || dotExt === ".sass") { + // In these languages the $ does not get replaced by the suggestion, // so exclude it from the insertText. insertText = asDollarlessVariable(label); } From 81f88df75e9fe2ee08e30cfd9daf2dd3c09f4727 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 14 Jul 2024 15:27:56 +0200 Subject: [PATCH 069/138] refactor: add completions for sassdoc blocks to indented --- .../__tests__/do-complete-sassdoc.test.ts | 82 +++++++++++++++++++ .../src/features/do-complete.ts | 5 +- 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/packages/language-services/src/features/__tests__/do-complete-sassdoc.test.ts b/packages/language-services/src/features/__tests__/do-complete-sassdoc.test.ts index ec246239..bd94474e 100644 --- a/packages/language-services/src/features/__tests__/do-complete-sassdoc.test.ts +++ b/packages/language-services/src/features/__tests__/do-complete-sassdoc.test.ts @@ -28,6 +28,22 @@ test("sassdoc comment block for mixin", async () => { }); }); +test("sassdoc comment block for mixin in indented syntax", async () => { + const document = fileSystemProvider.createDocument( + ["$a: 1", "", "///", "@mixin interactive()", " color: blue"], + { languageId: "sass" }, + ); + + const { items } = await ls.doComplete(document, Position.create(2, 3)); + assert.equal(items.length, 1, "Expected to get a completion result"); + assert.deepStrictEqual(items[0], { + insertText: " ${0}\n/// @output ${1}", + insertTextFormat: 2, + label: "SassDoc Block", + sortText: "-", + }); +}); + test("sassdoc comment block for mixin with parameters", async () => { const document = fileSystemProvider.createDocument([ "$a: 1;", @@ -47,6 +63,23 @@ test("sassdoc comment block for mixin with parameters", async () => { }); }); +test("sassdoc comment block for mixin with parameters in indented syntax", async () => { + const document = fileSystemProvider.createDocument( + ["$a: 1", "", "///", "@mixin interactive($color: blue)", " color: $color"], + { languageId: "sass" }, + ); + + const { items } = await ls.doComplete(document, Position.create(2, 3)); + assert.equal(items.length, 1, "Expected to get a completion result"); + assert.deepStrictEqual(items[0], { + insertText: + " ${0}\n/// @param {${1:type}} \\$color [blue] ${2:-}\n/// @output ${3}", + insertTextFormat: 2, + label: "SassDoc Block", + sortText: "-", + }); +}); + test("sassdoc comment block for function with parameters", async () => { const document = fileSystemProvider.createDocument([ "$a: 1;", @@ -66,6 +99,29 @@ test("sassdoc comment block for function with parameters", async () => { }); }); +test("sassdoc comment block for function with parameters in indented syntax", async () => { + const document = fileSystemProvider.createDocument( + [ + "$a: 1", + "", + "///", + "@function interactive($color: blue)", + " @return $color", + ], + { languageId: "sass" }, + ); + + const { items } = await ls.doComplete(document, Position.create(2, 3)); + assert.equal(items.length, 1, "Expected to get a completion result"); + assert.deepStrictEqual(items[0], { + insertText: + " ${0}\n/// @param {${1:type}} \\$color [blue] ${2:-}\n/// @return {${3:type}} ${4:-}", + insertTextFormat: 2, + label: "SassDoc Block", + sortText: "-", + }); +}); + test("sassdoc comment block for mixin with @content", async () => { const document = fileSystemProvider.createDocument([ "$a: 1;", @@ -113,6 +169,32 @@ test("sassdoc comment block for mixin with parameters and @content", async () => }); }); +test("sassdoc comment block for mixin with parameters and @content in indented syntax", async () => { + const document = fileSystemProvider.createDocument( + [ + "$a: 1", + "", + "///", + "@mixin apply-to-ie6-only($color: #fff, $visibility: hidden)", + " * html", + " color: $color", + " visibility: $visibility", + " @content", + ], + { languageId: "sass" }, + ); + + const { items } = await ls.doComplete(document, Position.create(2, 3)); + assert.equal(items.length, 1, "Expected to get a completion result"); + assert.deepStrictEqual(items[0], { + insertText: + " ${0}\n/// @param {${1:Color}} \\$color [#fff] ${2:-}\n/// @param {${3:type}} \\$visibility [hidden] ${4:-}\n/// @content ${5}\n/// @output ${6}", + insertTextFormat: 2, + label: "SassDoc Block", + sortText: "-", + }); +}); + test("sassdoc annotation values for @example", async () => { const document = fileSystemProvider.createDocument("/// @example "); const { items } = await ls.doComplete(document, Position.create(0, 13)); diff --git a/packages/language-services/src/features/do-complete.ts b/packages/language-services/src/features/do-complete.ts index 0bbc40d3..deff140c 100644 --- a/packages/language-services/src/features/do-complete.ts +++ b/packages/language-services/src/features/do-complete.ts @@ -142,7 +142,10 @@ export class DoComplete extends LanguageFeature { result.items.push(...items); } - prevToken = token; + if (token.type !== TokenType.Newline) { + // ignore newlines in the logic + prevToken = token; + } token = scanner.scan(); } From b77272986007c3a570a5510e3ed7ad24062f7028 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 21 Jul 2024 10:29:54 +0200 Subject: [PATCH 070/138] refactor: shorthand include --- .../src/parser/cssParser.ts | 2 +- .../src/parser/cssScanner.ts | 2 ++ .../src/parser/sassParser.ts | 21 ++++++++++++++++--- .../src/parser/sassScanner.ts | 20 ++++++++++++++++++ .../src/test/sass/parser-indented.test.ts | 11 +++++++++- 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index f3e3d577..6202048c 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -550,7 +550,7 @@ export class Parser { if (this.accept(TokenType.EOF)) { return this.finish(node); } - if (this.peek(TokenType.AtKeyword)) { + if (this.peek(TokenType.AtKeyword) || this.peek(TokenType.AtIncludeShort) || this.peek(TokenType.AtMixinShort)) { return this.finish(node); } // nesting diff --git a/packages/vscode-css-languageservice/src/parser/cssScanner.ts b/packages/vscode-css-languageservice/src/parser/cssScanner.ts index 3c2336e0..803225dc 100644 --- a/packages/vscode-css-languageservice/src/parser/cssScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/cssScanner.ts @@ -7,6 +7,8 @@ export enum TokenType { Ident, AtKeyword, + AtIncludeShort, + AtMixinShort, String, BadString, UnquotedString, diff --git a/packages/vscode-css-languageservice/src/parser/sassParser.ts b/packages/vscode-css-languageservice/src/parser/sassParser.ts index 056c14b5..8cfcabaa 100644 --- a/packages/vscode-css-languageservice/src/parser/sassParser.ts +++ b/packages/vscode-css-languageservice/src/parser/sassParser.ts @@ -35,6 +35,12 @@ export class SassParser extends cssParser.Parser { super._parseStylesheetAtStatement(isNested) ); } + if (this.peek(TokenType.AtMixinShort)) { + return this._parseMixinDeclaration(); + } + if (this.peek(TokenType.AtIncludeShort)) { + return this._parseMixinReference(); + } return this._parseRuleset(true) || this._parseVariableDeclaration(); } @@ -288,6 +294,12 @@ export class SassParser extends cssParser.Parser { this._parseRuleSetDeclarationAtStatement() ); } + if (this.peek(TokenType.AtMixinShort)) { + return this._parseMixinDeclaration(); + } + if (this.peek(TokenType.AtIncludeShort)) { + return this._parseMixinReference(); + } return ( this._parseVariableDeclaration() || // variable declaration this._tryParseRuleset(true) || // nested ruleset @@ -687,16 +699,17 @@ export class SassParser extends cssParser.Parser { } public _parseMixinReference(): nodes.Node | null { - if (!this.peekKeyword("@include")) { + if (!this.peekKeyword("@include") && !this.peek(TokenType.AtIncludeShort)) { return null; } + let referenceType = this.token.type; const node = this.create(nodes.MixinReference); this.consumeToken(); // Could be module or mixin identifier, set as mixin as default. const firstIdent = this._parseIdent([nodes.ReferenceType.Mixin]); - if (!node.setIdentifier(firstIdent)) { + if (!node.setIdentifier(firstIdent) && referenceType !== TokenType.AtIncludeShort) { return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); } @@ -706,7 +719,9 @@ export class SassParser extends cssParser.Parser { const moduleToken = this.create(nodes.Module); // Re-purpose first matched ident as identifier for module token. - firstIdent.referenceTypes = [nodes.ReferenceType.Module]; + if (firstIdent) { + firstIdent.referenceTypes = [nodes.ReferenceType.Module]; + } moduleToken.setIdentifier(firstIdent); // Override identifier with second ident. diff --git a/packages/vscode-css-languageservice/src/parser/sassScanner.ts b/packages/vscode-css-languageservice/src/parser/sassScanner.ts index 6f6891dc..df7bdf36 100644 --- a/packages/vscode-css-languageservice/src/parser/sassScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/sassScanner.ts @@ -22,6 +22,7 @@ const _LAN = "<".charCodeAt(0); const _RAN = ">".charCodeAt(0); const _DOT = ".".charCodeAt(0); const _ATS = "@".charCodeAt(0); +const _PLS = "+".charCodeAt(0); let customTokenValue = TokenType.CustomToken; @@ -84,6 +85,25 @@ export class SassScanner extends Scanner { return this.finishToken(offset, Ellipsis); } + if (this.syntax === "indented") { + if (this.stream.advanceIfChar(_PLS)) { + let content: string[] = []; + if (this.ident(content)) { + return this.finishToken(offset, TokenType.AtIncludeShort, content.join("")); + } else { + this.stream.goBackTo(offset, depth); + } + } + if (this.stream.advanceIfChar(_EQS)) { + let content: string[] = []; + if (this.ident(content)) { + return this.finishToken(offset, TokenType.AtMixinShort, content.join("")); + } else { + this.stream.goBackTo(offset, depth); + } + } + } + return super.scanNext(offset); } diff --git a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts index 883d89f5..d087fe08 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts @@ -2372,7 +2372,7 @@ figure // This is discouraged in the documentation. Consider not supporting if // implementation is complex (i. e. at-rule parsing can't be patched easily // to consider these valid) - suite.skip("shorthand mixin syntax", () => { + suite("shorthand mixin syntax", () => { test("@include shorthand (+)", () => { assertNode( `p @@ -2382,6 +2382,15 @@ figure ); }); + test("@include shorthand (+) for module", () => { + assertNode( + `p + +foo.double-border(blue)`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + test("@mixin shorthand (=)", () => { assertNode( `=double-border($color) From a3a28d9fb7e3baf25f7a4ecee538fb6d899f3a35 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 21 Jul 2024 10:31:34 +0200 Subject: [PATCH 071/138] refactor: shorthand declare --- .../vscode-css-languageservice/src/parser/sassParser.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/sassParser.ts b/packages/vscode-css-languageservice/src/parser/sassParser.ts index 8cfcabaa..302cdb3b 100644 --- a/packages/vscode-css-languageservice/src/parser/sassParser.ts +++ b/packages/vscode-css-languageservice/src/parser/sassParser.ts @@ -623,14 +623,18 @@ export class SassParser extends cssParser.Parser { } public _parseMixinDeclaration(): nodes.Node | null { - if (!this.peekKeyword("@mixin")) { + if (!this.peekKeyword("@mixin") && !this.peek(TokenType.AtMixinShort)) { return null; } + let declarationType = this.token.type; const node = this.create(nodes.MixinDeclaration); this.consumeToken(); - if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Mixin]))) { + if ( + !node.setIdentifier(this._parseIdent([nodes.ReferenceType.Mixin])) && + declarationType !== TokenType.AtMixinShort + ) { return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR, TokenType.Dedent]); } From 2553c5bc203a50b559230d583e8bed0eff0172c6 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 21 Jul 2024 11:03:35 +0200 Subject: [PATCH 072/138] refactor: fix parser bugs for shorthands --- .../src/parser/cssParser.ts | 2 +- .../src/parser/sassParser.ts | 37 +++++++++++------ .../src/test/sass/parser-indented.test.ts | 13 ++++++ .../src/test/sass/sassSelectionRange.test.ts | 40 +++++++++++++++++++ 4 files changed, 79 insertions(+), 13 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 6202048c..a60bd0ea 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -550,7 +550,7 @@ export class Parser { if (this.accept(TokenType.EOF)) { return this.finish(node); } - if (this.peek(TokenType.AtKeyword) || this.peek(TokenType.AtIncludeShort) || this.peek(TokenType.AtMixinShort)) { + if (this.peek(TokenType.AtKeyword) || this.peek(TokenType.AtIncludeShort)) { return this.finish(node); } // nesting diff --git a/packages/vscode-css-languageservice/src/parser/sassParser.ts b/packages/vscode-css-languageservice/src/parser/sassParser.ts index 302cdb3b..451b246b 100644 --- a/packages/vscode-css-languageservice/src/parser/sassParser.ts +++ b/packages/vscode-css-languageservice/src/parser/sassParser.ts @@ -295,7 +295,7 @@ export class SassParser extends cssParser.Parser { ); } if (this.peek(TokenType.AtMixinShort)) { - return this._parseMixinDeclaration(); + return this._parseMixinDeclaration(); // nested shorthand @mixin } if (this.peek(TokenType.AtIncludeShort)) { return this._parseMixinReference(); @@ -631,11 +631,17 @@ export class SassParser extends cssParser.Parser { const node = this.create(nodes.MixinDeclaration); this.consumeToken(); - if ( - !node.setIdentifier(this._parseIdent([nodes.ReferenceType.Mixin])) && - declarationType !== TokenType.AtMixinShort - ) { - return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR, TokenType.Dedent]); + if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Mixin]))) { + if (declarationType === TokenType.AtMixinShort) { + let ident = this.create(nodes.Identifier); + ident.referenceTypes = [nodes.ReferenceType.Mixin]; + ident.isCustomProperty = false; + ident.offset = node.offset + 1; + ident.length = node.length - 1; + node.setIdentifier(ident); + } else { + return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR, TokenType.Dedent]); + } } if (this.accept(TokenType.ParenthesisL)) { @@ -712,9 +718,18 @@ export class SassParser extends cssParser.Parser { this.consumeToken(); // Could be module or mixin identifier, set as mixin as default. - const firstIdent = this._parseIdent([nodes.ReferenceType.Mixin]); - if (!node.setIdentifier(firstIdent) && referenceType !== TokenType.AtIncludeShort) { - return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); + let firstIdent = this._parseIdent([nodes.ReferenceType.Mixin]); + if (!node.setIdentifier(firstIdent)) { + if (referenceType === TokenType.AtIncludeShort) { + firstIdent = this.create(nodes.Identifier); + firstIdent.referenceTypes = [nodes.ReferenceType.Mixin]; + firstIdent.isCustomProperty = false; + firstIdent.offset = node.offset + 1; + firstIdent.length = node.length - 1; + node.setIdentifier(firstIdent); + } else { + return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); + } } // Is a module accessor. @@ -723,9 +738,7 @@ export class SassParser extends cssParser.Parser { const moduleToken = this.create(nodes.Module); // Re-purpose first matched ident as identifier for module token. - if (firstIdent) { - firstIdent.referenceTypes = [nodes.ReferenceType.Module]; - } + firstIdent.referenceTypes = [nodes.ReferenceType.Module]; moduleToken.setIdentifier(firstIdent); // Override identifier with second ident. diff --git a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts index d087fe08..5fc9ea1b 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts @@ -2399,6 +2399,19 @@ figure parser._parseStylesheet.bind(parser), ); }); + + test("multiple shorthand declarations", () => { + assertNode( + `=size($height, $width) + width: $width + height: $height + +=square($size) + +size($size, $size)`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); }); test("@content", () => { diff --git a/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts index 5325d48e..8f691bd3 100644 --- a/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassSelectionRange.test.ts @@ -266,4 +266,44 @@ body ], ); }); + + test("handles multiple shorthand mixin declarations", () => { + assertRanges( + `=size($height, $width) + width: $width| + height: $height + +=square($size) + +size($size, $size)`, + [ + [31, `$width`], + [24, `width: $width`], + [ + 22, + ` + width: $width + height: $height + +`, + ], + [ + 0, + `=size($height, $width) + width: $width + height: $height + +`, + ], + [ + 0, + `=size($height, $width) + width: $width + height: $height + +=square($size) + +size($size, $size)`, + ], + ], + ); + }); }); From c468b1f2dd0ba8e7e35b6541177e3c32d1a511bc Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 21 Jul 2024 14:01:56 +0200 Subject: [PATCH 073/138] refactor: mark an error if mixing between tabs and spaces --- .../__tests__/do-complete-sassdoc.test.ts | 8 ++++---- .../src/features/do-diagnostics.ts | 15 +++++++++++++- .../src/parser/cssScanner.ts | 20 ++++++++++++++++++- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/packages/language-services/src/features/__tests__/do-complete-sassdoc.test.ts b/packages/language-services/src/features/__tests__/do-complete-sassdoc.test.ts index bd94474e..571066c3 100644 --- a/packages/language-services/src/features/__tests__/do-complete-sassdoc.test.ts +++ b/packages/language-services/src/features/__tests__/do-complete-sassdoc.test.ts @@ -176,10 +176,10 @@ test("sassdoc comment block for mixin with parameters and @content in indented s "", "///", "@mixin apply-to-ie6-only($color: #fff, $visibility: hidden)", - " * html", - " color: $color", - " visibility: $visibility", - " @content", + " * html", + " color: $color", + " visibility: $visibility", + " @content", ], { languageId: "sass" }, ); diff --git a/packages/language-services/src/features/do-diagnostics.ts b/packages/language-services/src/features/do-diagnostics.ts index 163b88a3..575d09b5 100644 --- a/packages/language-services/src/features/do-diagnostics.ts +++ b/packages/language-services/src/features/do-diagnostics.ts @@ -14,7 +14,20 @@ import { export class DoDiagnostics extends LanguageFeature { async doDiagnostics(document: TextDocument): Promise { - return this.doDeprecationDiagnostics(document); + return Promise.all([ + this.doDeprecationDiagnostics(document), + this.doUpstreamDiagnostics(document), + ]).then((diagnostics) => diagnostics.flatMap((diagnostic) => diagnostic)); + } + + private async doUpstreamDiagnostics(document: TextDocument) { + const stylesheet = this.ls.parseStylesheet(document); + const diagnostics = this.getUpstreamLanguageServer().doValidation( + document, + stylesheet, + { validate: true }, + ); + return diagnostics; } private async doDeprecationDiagnostics( diff --git a/packages/vscode-css-languageservice/src/parser/cssScanner.ts b/packages/vscode-css-languageservice/src/parser/cssScanner.ts index 803225dc..83b46afc 100644 --- a/packages/vscode-css-languageservice/src/parser/cssScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/cssScanner.ts @@ -78,6 +78,8 @@ export class MultiLineStream { this._depth = value; } + indentation: "tabs" | "spaces" | undefined = undefined; + constructor(source: string) { this.source = source; this.len = source.length; @@ -439,7 +441,23 @@ export class Scanner { }); if (newlines > 0) { let depth = this.stream.advanceWhileChar((ch) => { - return ch === _TAB || ch === _WSP; + // Make a note the first time we enchounter either _TAB or _WSP. + // Whichever comes first is treated as the correct, expected + // kind of indentation. Mixing between the two is not allowed + // in the indented syntax. + if (!this.stream.indentation && ch === _TAB) { + this.stream.indentation = "tabs"; + } + if (!this.stream.indentation && ch === _WSP) { + this.stream.indentation = "spaces"; + } + if (this.stream.indentation === "tabs") { + return ch === _TAB; + } + if (this.stream.indentation === "spaces") { + return ch === _WSP; + } + return false; }); if (depth > this.stream.depth) { From 957fa2dc31f6e9edf4a6ce13cd8adc5bc93d36cd Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 21 Jul 2024 16:10:37 +0200 Subject: [PATCH 074/138] refactor: sass indented comment syntax An interesting language feature --- .../src/parser/cssParser.ts | 22 ++++- .../src/parser/cssScanner.ts | 7 -- .../src/parser/sassScanner.ts | 86 ++++++++++++++++++- .../src/services/cssFolding.ts | 6 +- .../src/test/sass/parser-indented.test.ts | 64 ++++++++++++-- 5 files changed, 162 insertions(+), 23 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index a60bd0ea..25c4bdff 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -869,7 +869,7 @@ export class Parser { } } - if (!this.peek(TokenType.SemiColon) && !this.peek(TokenType.EOF)) { + if (!this.peek(TokenType.SemiColon) && !this.peek(TokenType.EOF) && !this.peek(TokenType.Newline)) { node.setMedialist(this._parseMediaQueryList()); } @@ -1431,7 +1431,25 @@ export class Parser { const node = this.create(nodes.Document); this.consumeToken(); // @-moz-document - this.resync([], [TokenType.CurlyL, TokenType.Indent]); // ignore all the rules + // ignore all the rules, start back up again at first { (SCSS) or new line (indented) + const _NWL = "\n".charCodeAt(0); + const _CAR = "\r".charCodeAt(0); + const _LFD = "\f".charCodeAt(0); + const _CUL = "{".charCodeAt(0); + + this.scanner.stream.advanceWhileChar((ch) => { + switch (ch) { + case _CUL: + case _NWL: + case _CAR: + case _LFD: + return false; + default: + return true; + } + }); + this.consumeToken(); + return this._parseBody(node, this._parseStylesheetStatement.bind(this)); } diff --git a/packages/vscode-css-languageservice/src/parser/cssScanner.ts b/packages/vscode-css-languageservice/src/parser/cssScanner.ts index 83b46afc..ba0b19c7 100644 --- a/packages/vscode-css-languageservice/src/parser/cssScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/cssScanner.ts @@ -504,13 +504,6 @@ export class Scanner { return false; } - if (this.syntax === "indented") { - // in this dialect multiline comments are notallowed - if (ch === _NWL || ch === _LFD || ch === _CAR) { - return false; - } - } - hot = ch === _MUL; return true; }); diff --git a/packages/vscode-css-languageservice/src/parser/sassScanner.ts b/packages/vscode-css-languageservice/src/parser/sassScanner.ts index df7bdf36..bc353c83 100644 --- a/packages/vscode-css-languageservice/src/parser/sassScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/sassScanner.ts @@ -21,7 +21,7 @@ const _BNG = "!".charCodeAt(0); const _LAN = "<".charCodeAt(0); const _RAN = ">".charCodeAt(0); const _DOT = ".".charCodeAt(0); -const _ATS = "@".charCodeAt(0); +const _MUL = "*".charCodeAt(0); const _PLS = "+".charCodeAt(0); let customTokenValue = TokenType.CustomToken; @@ -148,9 +148,93 @@ export class SassScanner extends Scanner { } protected comment(): boolean { + if (this.syntax === "indented") { + // For comments in indented, any content that is indented + // after opening a comment is considered part of that comment, + // even if the line doesn't start with the usual comment + // syntax. + // https://sass-lang.com/documentation/syntax/comments/#in-sass + + if (this.stream.advanceIfChars([_FSL, _MUL]) || (!this.inURL && this.stream.advanceIfChars([_FSL, _FSL]))) { + let depth = this.stream.depth, + kind = this.stream.lookbackChar(1), + hot = false, + success = false, + mark = this.stream.pos(); + + scan: do { + this.stream.advanceWhileChar((ch) => { + if (kind === _MUL && hot && ch === _FSL) { + // Stop if a CSS-style comment is manually closed with */ + success = true; + return false; + } + + hot = false; + + // Accept all characters up until a new line. + switch (ch) { + case _NWL: + case _CAR: + case _LFD: { + // In the case of a newline we need to see if the next line is indented. + // If it is, keep advancing the stream. + mark = this.stream.pos(); + return false; + } + case _MUL: { + hot = true; + return true; + } + default: + return true; + } + }); + + if (success) { + break scan; + } + + if (this.stream.eos()) { + return true; + } + + this.stream.advanceIfChar(_NWL); // only the one line, blank line == end of comment + this.stream.advanceIfChar(_CAR); + this.stream.advanceIfChar(_LFD); + + let commentDepth = this.stream.advanceWhileChar((ch) => { + return ch === _WSP || ch === _TAB; + }); + + if (commentDepth > depth) { + continue; + } else if (commentDepth === depth) { + // If there's no indentation at this point, we require comment syntax + if (!this.stream.advanceIfChars([_FSL, _FSL]) && !this.stream.advanceIfChars([_MUL])) { + this.stream.goBackTo(mark, depth); + break scan; + } + } else { + this.stream.goBackTo(mark, depth); + break scan; + } + } while (true); + + if (success) { + this.stream.advance(1); + } + + return true; + } + + return false; + } + if (super.comment()) { return true; } + if (!this.inURL && this.stream.advanceIfChars([_FSL, _FSL])) { this.stream.advanceWhileChar((ch: number) => { switch (ch) { diff --git a/packages/vscode-css-languageservice/src/services/cssFolding.ts b/packages/vscode-css-languageservice/src/services/cssFolding.ts index 50cb03e2..00f18ad9 100644 --- a/packages/vscode-css-languageservice/src/services/cssFolding.ts +++ b/packages/vscode-css-languageservice/src/services/cssFolding.ts @@ -130,11 +130,7 @@ function computeFoldingRanges(document: TextDocument): FoldingRange[] { const matches = token.text.match(/^\s*\/\*\s*(#region|#endregion)\b\s*(.*?)\s*\*\//); if (matches) { return commentRegionMarkerToDelimiter(matches[1]); - } else if ( - document.languageId === "scss" || - document.languageId === "sass" || - document.languageId === "less" - ) { + } else if (document.languageId === "scss" || document.languageId === "sass") { const matches = token.text.match(/^\s*\/\/\s*(#region|#endregion)\b\s*(.*?)\s*/); if (matches) { return commentRegionMarkerToDelimiter(matches[1]); diff --git a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts index 5fc9ea1b..1cd858b9 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts @@ -4,6 +4,9 @@ import { SassParser } from "../../parser/sassParser"; import * as nodes from "../../parser/cssNodes"; import { assertError, assertFunction, assertNoNode, assertNode } from "../css/parser.test"; import { SassParseError } from "../../parser/sassErrors"; +import { getSassLanguageService, TextDocument } from "../../cssLanguageService"; +import { getFsProvider } from "../testUtil/fsProvider"; +import { getDocumentContext } from "../testUtil/documentContext"; suite("Sass - Parser", () => { const parser = new SassParser({ syntax: "indented" }); @@ -48,10 +51,10 @@ suite("Sass - Parser", () => { ); }); - test("Sass comment", () => { + test("Sass single-line silent comment", () => { assertNode( `a - // single-line comment + // single-line silent comment b: c `, parser, @@ -59,19 +62,64 @@ suite("Sass - Parser", () => { ); }); - test("Multi-line CSS comment should error", () => { - assertError( + test("Sass multi-line silent comment", () => { + assertNode( `a - b: /* multi -line -comment */ c + // multi-line + silent + comment + b: c +`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("Sass multi-line comment", () => { + assertNode( + `a + /* multi-line + comment + b: c `, parser, parser._parseStylesheet.bind(parser), - ParseError.PropertyValueExpected, ); }); + test("Sass multi-line comment included also in compressed mode", () => { + assertNode( + `a + /*! multi-line + comment + b: c +`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + + test("Sass multi-line comment stops at the expected location", async () => { + let input = `/*! + * Bootstrap Responsive v2.3.2 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + +@import variables +`; + let document = TextDocument.create(`test://test/test.sass`, "sass", 0, input); + + let node = assertNode(input, parser, parser._parseStylesheet.bind(parser)); + + let ls = getSassLanguageService({ fileSystemProvider: getFsProvider() }); + let links = await ls.findDocumentLinks2(document, node, getDocumentContext()); + assert.equal(links.length, 1); + }); + test("stylesheet", () => { assertNode(`$color: #F5F5F5`, parser, parser._parseStylesheet.bind(parser)); assertNode( From 8613c3447fe766d419320a6c3399407926f33883 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Tue, 23 Jul 2024 19:40:05 +0200 Subject: [PATCH 075/138] refactor: allow newline after comma in selector --- packages/vscode-css-languageservice/src/parser/cssParser.ts | 3 +++ .../src/test/sass/parser-indented.test.ts | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 25c4bdff..78c47168 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -423,6 +423,9 @@ export class Parser { } while (this.accept(TokenType.Comma)) { + while (this.accept(TokenType.Newline)) { + // accept any newlines after , and the next selector + } if (!selectors.addChild(this._parseSelector(isNested))) { return this.finish(node, ParseError.SelectorExpected); } diff --git a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts index 1cd858b9..976ced0c 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts @@ -264,7 +264,8 @@ $color: #F5F5F5`, test("selectors", () => { assertNode( ` -#boo, far +#boo, +far a: b .far boo From 90d21300b2e35611933fcd6b2e756be1caa88e61 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Tue, 23 Jul 2024 20:09:53 +0200 Subject: [PATCH 076/138] docs: how to take a performance profile --- docs/src/SUMMARY.md | 1 + .../src/contributing/debugging-performance.md | 25 ++++++++++++++++++ .../debugging-profiling-controls.png | Bin 0 -> 46784 bytes docs/src/images/debugging/debugging.png | Bin 0 -> 44826 bytes 4 files changed, 26 insertions(+) create mode 100644 docs/src/contributing/debugging-performance.md create mode 100644 docs/src/images/debugging/debugging-profiling-controls.png create mode 100644 docs/src/images/debugging/debugging.png diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 172adbef..fe778d42 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -33,5 +33,6 @@ - [Debugging in the browser](contributing/debugging-in-browser.md) - [Debugging unit tests](contributing/debugging-unit-tests.md) - [Debugging end-to-end tests](contributing/debugging-e2e-tests.md) + - [Debugging performance](contributing/debugging-performance.md) - [Releasing new versions](contributing/releases.md) - [Writing documentation](contributing/writing-documentation.md) diff --git a/docs/src/contributing/debugging-performance.md b/docs/src/contributing/debugging-performance.md new file mode 100644 index 00000000..57c59691 --- /dev/null +++ b/docs/src/contributing/debugging-performance.md @@ -0,0 +1,25 @@ +# Debugging performance + +Commonly refered to as performance profiling, this document explains how to run a performance test on Some Sass in Visual Studio Code. + +## Performance Profiling in Visual Studio Code + +To start a performance profile, first [launch the Some Sass extension](./debugging.md) from the Run and Debug pane in Visual Studio Code, then open a file with Sass code. Your debugging pane should look something like the image below. + +![](../images/debugging/debugging.png) + +The Call stack section lists Launch extension, which has four list items. The fourth list item, `node-server.js`, is the Some Sass language server. This is the program we want to profile. + +If you've ever done [performance profiling of JavaScript](https://code.visualstudio.com/docs/nodejs/profiling) in VS Code before, profiling Some Sass works the same way. + +Click or hover over the `node-server.js` row to show additional controls. + +![](../images/debugging/debugging-profiling-controls.png) + +Once the row is active you should see a list of icon buttons. The one we're interested in is the [circle with a dot in the center](https://code.visualstudio.com/docs/nodejs/profiling#_using-the-record-button), Take Performance Profile. Click it, and choose the [type of profile you want](https://code.visualstudio.com/docs/nodejs/profiling#_types-of-profiles). Unless you have specific plans, choose to stop the profile manually when asked. + +Do the operations you want to measure (hover, go to definition, edit some code, or what have you). Then, go back to the debugger and click the button again to stop the performance profiling. + +## Analyzing a performance profile + +You can open the recorded performance profile in Visual Studio Code and dig into the numbers. The VS Code documentation has some tips on [how to analyze a profile](https://code.visualstudio.com/docs/nodejs/profiling#_analyzing-a-profile). diff --git a/docs/src/images/debugging/debugging-profiling-controls.png b/docs/src/images/debugging/debugging-profiling-controls.png new file mode 100644 index 0000000000000000000000000000000000000000..9a3ed62ee343de24010aea93e7e22c786cde66f2 GIT binary patch literal 46784 zcmeFZRa9Nu5;lkg55XZ2T!U@`Aq01KcS*3|?oN;dcXxM!I~z!F_u%gC&i)tY-jj2> z$LN0OG5YPkvFDssYsy+xHNX0b@A9%@sK^A!P*6~)65_&&P*5(E76xlZ&Md&<_R0&F5nC$-vymf%LPHiK!J2`EfItoYd5i zhg^+KhFQi&$molyxSO4klAEluft$Gjhaov1FEY0aC!oO6$l(*Ii=~B?J*Nu~`CocD zfxn-dLFA-=4RJ8%Ay=1?Cl#``Ga~)K#LUD@&WlXSZD(lAsVFS^Ut!=E5BV1d2OCZh z$l2MM$(fbO+Rg;@j)Q{(#LNO>VPOPDFxtCXIecbWm z^Ea{r4&4qoU1iLn1SlxvLQ^3jc?lsQQh7UTV^a$wC@AsoaR~^@F|(LH$B=>`|M*d@ z9T_(iQmPC2Tr~wG2_N~-i2lZ6xN0_x-@f@YWuuTIGf76(lHyu~_`sFEF;tMB>GDI3 zyM_(=(R|!;J)8m=Y}BK+H`#jHmK}Zp#S%w_p8K;3%J8H*;8PoMP2%s51XeHgeK0dg zBQQhKsPz5)U%&`&&435)o1v05tdoX*-CZ9;N_uS@G%TPp(fzSFG!9<+%aL<-;38H- z^AiakG&Rop;kWrQt9--$cJdQnHhuj&zHF+|d$(yC@5iO0`&};* z6N6`jqot7QJT(rN_{YjRZ6%7xr|7#z550(h7T)M;6T`>N7VdmQYbQznjE#fR0HCW| zaIo~Xge75l!nL1V?k~N_L$W?9akQu#`w35~tI|}Pz9owdL8b%LsFt6~@!q}Mu%H!G zn5ZCla$hzZ{z-U$m}3j8r9mRNbb7d3L0vn^4`IFz_zB$`)kAn=rHo~k(22XVjr6BZ zU+VD4L>~Og3ekPKCJ-02f54H=Rbe&qmf%|4iR)B}%W2=GF=B2F&Kl1l-7=EG;Pb=L z@nh}4Q0NCv8l$E9aw%5wA;M#j;xZsuM8dH6pl|q1>BGK-&(r7n4OiUmWrI!#-@}jc z1)-qB)dpW1!E@!63)C-t>=r1j6`n*tQbdF(GUQwsQ-QlkT0_(^GPT|}$o`#~>TjVX z18*V`HBl}7C9_xxkvXB*`1yniGS5d<3Q3v}P=(e<-|Vo4`bjY3O#7hfqa?m!U4i-t zqYF>-Gsy;8`-OD?{i^28+XeWHpo*W)`tKfK(uJX8u_ZC`!dQgH6w(Xzl?myPsD#Tz z;xg&vYYVBBX^IgXQK&>)g*^og#<1U`S_V3a-e*yaSvXL;F}k5NA$z`F3~c+>7CAnh0+6Z~Thi)zb61GFV-x6m}ZzO^8fad8bv! zQP&59<{yv0F%BqS1u1nTt!dkIe8gpXqZT^t*x19(ghBN)D1L@I3v1ze6y&8RR14g};QC#HCDpu0OG}ffYj3ZH?Xuh}J0$%=p**l8d zv=;rM{bc=A{cmxk*m7BliY68J-kE_M1qF0FRLLaCjX3X4g}n9I-B$FIDysrmPF)%)Xf2m?6Hu`l3DRQdCs9Tb@*` zQSPd0uN{)TS==>2H^W(QnVQdUFgk7;R})f$Wa((hXBl+`eW-g#OC*#go+g-PoF>*J ztkYx_ZPjR1f8?JYuN%2!)+*Y1fA_+($t(ED>XG;n4L%Tl3_j)M9BTK=IMk#-xWKSL zw6I=LE72s;TG9Hj%5I1C_v@|Qp430Sr%41$)b`0J{hnNkHEEiu@y0NYS|@+Xo)h_M zOvxIk5w4Ll`=c@2HPbcAwfUAFg(u`=2oWw1?lQw6!vrIm2JesP`m=hvn!Oq~1Iy1t zb>($!mO^!>l^P}!pU&r|C*#eVN1S@YKjRtJ_m$5dm9&0s9c}CLS`Zi^>&WW<6e*h* zG+IAe@$oYu58(_)y3@mk;c@KY9XnO+|4cWx^22tx~)rR zOHoTIOA($99?_lzp41+P9%{Evx1Szh9-MAaS=bUSjA89Nmpi<}AYD+3{4V?|?J@i@ z{Gaqq&%ky`}nPAESIEzR7-!J_*1XEH;v%X2CMYr1_nG zuy}w+=q$@&lDgl?xXvqOCqXAgAmt>nl*FCB7Oxm}7QNk9veqhlDYck!N5=TEjMtKr zhqILfHX|z1HW)M_x2~sM)W~9O1a4QBXFxaMG&<@}MTbY+ugWYn(jb~uHvHH`#)9cd zLojSRR5W~>_$JPsT#hLs!M2U{wWk(%*fXx}=nQ(E@tn8L*Anq5} zEywQc^A=0hw^~RA8=6H%rXsruj0cP}UO(=#&VqK!B(&V?XRYV!iJH^Ek4LZSiGl_; z`jPuZo!}2G=nLW!V!`vZpJ%3!=Yt=czc-6Ccg6X}5xHMnSuCID(dF&Q)`~gWhq(=H zC8N1>xFcLO9-GfqRDvBK+a#`huZT`~Mpez_wQSMdnYULagcO3@ys{ya@&%e|n%A}I zdLUjxr<>1Tg@k@$TvE&vtcohE-;CYlc=oh}JHAh1Bl@Ch!Nl__JS3bC!m+yD%m~f6 zNV7weEW4LcYMNIQUn5|7Yl(G8d$<<9VG9m=4yA371^XNjVhgy zlgqPwWx+5ZbAR)8c9?W;zQte`Rbqu_zK3AMEH^d?d8 zwAbR3+dOLXn=wC)}o)jd`5h7ED@>Z^yTDKrcY~y0{RsYq25v95ot<1LVA0c<|Sb z`Re{IyVg>1OPROHeYrb};o0eZ=(Q4wByX_?_rv$6qHX(&Z4;$NJsUkbFoE~s1MbP; zszdihjSrRKP&mw6;niG9sHI6*{%|;-aReya-34hL`q2f5j|L3n`}Wq6MkT_EIvy0+ z5tNJLP$lo@c-xF>kp|e`-J;p&=f?-7ycAVp&?mjuh=?fIVkCNKPh|G?4|;n~+sKp( zkh&+7;u-kctToYWC@6j?31I7cwR6HkvjN`0ukTpazo>Lt zJaj&Hg&tMK%hB>fBg^~1U}r+Xz145!?{zs{YgOADnCZZeY%A!*`K$duLr8>34Rgh# zmgw0Pb9>gT9^LU9{r)5O-~PAg53twUNBYwiDzjK|OuE=w61&v*MR z4OnozCcbze-F^y3iXd=CZ`5)|-;O=+wVbP!MHM9Wj{5`LTYldWWhk~B68hJV(x9XQ z5ooCwHs^sUA0Hl9j=%M>0!vHhc!&2qj+DyB_zuy+?xkV{2WVOo`tK3u^ZMLe)biEw{wn6#96b^7c(<%|B#Aoz<SZb@p{zsD`A56rG+am zL6WX`SrPEx%>-t*a(KwdsP#U^YO$#|jmvrCps`x9%*+x0dbsAZT zQuTLK0uL^}N96+zUc8DvFw;m2ZYzvZ*Mo5feP@zelpLk>iBt-t-;haPTP~NE{rZNX z)MC95Ngej4?~Rc{nI*&hg*H#EHu~$d!3;jNiVD?Mw@mbVO6c7#sf4`l z2*)(2^%DL<^Mb2rS_AmjHoHrb&4_X$DsSHHCrtI_Rw*&sNWO|v-D*|0>o;%LMNwm3 z;*ZY{%r{v!hZ3_3Y>lK_>1|tO8SU5E9o!(zHrbXTclym%yJ%X)?bS{pHuYRRD&~tb z1%R0-AtgEkMcnT2wJUwRu<)ocQ|2=qNBIft6?#+&OnOntj(c2|Li~#&vqmHFA{ECI z){(zL@WhfI7Dj1FrhCHPh$VlhEAxJOXmohYDX^GpQDKK((LCY2c&cvBl}_q3)#J^2 z{u18)*jR3?vN(p(s58mJ9?@1c^IjD140v^%xcN1~vzYK^#g8x3r)a`NBFS13nGUh- z;=S{ephOCFnBlPD{lepT#kLe5WW6V%uS~1n@FemR<_8P)BzEU;LT(rNXez0EP?^gq zW|iXrZjn|+8?vTBZyddv;c6!w=f^N~vb6-S+cSxjg9N5zdc>-&;pAf2F;N-K-hl*= zOwNZd3)(ZW$+M>ijpv-NyLf|Mk=Qnu&}?2kQ8jOVZ>l&z;_ZgtHBYR9T4cRDl)^q<;_S`g zbl@+Y_Fi_nRGnN3cR221cf5aO$ZM>2uR2k zd(%1P+F`HTj<*)Pru+mf%5THcnFU~qbS6|cJ&qpWz`CpjhlOK5M^2&)Qm5}PUwEGs z4lzI;otyHf*uVvwfyy~Mqh+MJ3k`~zi)?5+`&9PI1?%QB<;}RJ-ba1BWJ14_*MYq< z0P!=fq>!G#Hm`}G44|q^SsTA!o+_DfV6>>-=EpZB7Muz8wR3x*s{U z(awKOc3In<#U5w42SpH0+%jpm7~Wp48E=p0Nkvmza%xoFfEsO9 zbr@>AAzoA5d~!!io-CPCyP$I1#@Vj7h6vzuJLHz?fE83rHFM7OrJ=`HDa0;0RyDZ_&%wD( zZDc&IcgovZH#3-?FI@%`+0<6PUhIq+obIvmXV4GB>b%kg*IDjQ|CE|8ozFa9omn1G zlN6d-`)1&9pOdiAuv%7s>-f?gT5m_NDp!J1B-n7g0D?~Pbv&n-trD_=5l!Z)DG^E9 zwyAb!zOOp2?pQtFW|np8o^t!2*Rk0y_s5syf}K5C}BF%!-d8i z<+qU5gQeDi%K%)q+PLTF*$X=e_3eTQhMj)lDu$OZS_-hw&W-{G?qh2^iWc(MUxq4 zQ<+*=TcU?N$FvnWDc_i&yhZbD~38%z3G6ySAzDqti&Qzb01 ziS=hMvKl=Nn>N+e2yY$pN-is=B0fb#5U>hdW?0j_%NA2BGt4nM`4M$yay{H+4G~Gu zEX6Wg*{U|F3l0iN01aHFH9P9Y22>*2e(s4cR-r3~EULk+qtL~JXdI`)p7MvZlq07F z<3SpvV>$Ei{Y-j|l5pj&aWJt;-yTG$V99sZ6dcqN!!*aD0Z91*T2$c|L!8 z#f;IAW3D<(IO{<2OAL)<*M8}TZS}>@tXPwyRRv6%Ka0_vv}b5hOwX(`iWE_>ZFU%c6l2-j)*VB^I-0V-9YLk&zz`jX^CD-QW z-@Q|;NcS!-%9oX#v09{ZKH_#`>rSsxIA-ORP2$vXCSgAF-dU$X6=yZ+r(FFR5O-c| z)}-(2F@93eBs*iU(8W=vEVtkoQL0cDyi)!eqAppeDYras81QY}}GA~Nxt|-bl zr5x5#DZX3;WqUqt$w|7H1v7PXy1v(uvC|*RO9cOD=uk{`*C^nLlWI85S29yN0i9va zA50)QseE;G8I&uRAxnY)nNcg#sDK@Ba`cKVn|Bu9|8TeoW;HPy4yEOkv$zD~v-bEa z>dYxp%H_{k&R0sQ@xLD0XG~#>Ut5k<%3y;OjU-cY%AM!)?RQAL&aBxu-mEg1u_B6G zyPu4*;cc|v95r?e7ywPiPM53bIP?6>w@Tv@AB$e@pJm-XVDsZ8`k7Yk8Vcm_@Ccp0U%c$qy_8b^&4Qd!3FSYJHu0J$p@OeY)#J(#;mKzN~ z-#=ain^bhMxpQZnd5`;CG*{(0%Et-ASr2&&K*A>W$|>`BXAJwI_u60i7DttrY{4os z_JrV#s2h0;d-pRD%}eB&)lQOJs6%B0gJ1yUku;QHsNezs3yx zSFqA@ZD(F(=z88sk>f0|Jl{V~BKT9^o>GaVzPH=DlW4qM2~7W`C3sUnnQ`5irx|pI zN=RNfdoCA@MN{X=KFI6GkG-3^M;s^m2oYrt_7_{X@BISbgY7CBT)`Wm+u{P*C$seu zKU5COZT7c5H7(zYwwOLsGo8vV68`?VMBgUidYQ)^?hzLSkN&U*pLO~*w5dgceP`@9 z@j|1+3GYa&>7H%j021Y)3gkc?Z~#h&%#%l*Ci6j%DINx429Ng=apaxD%MG-bNEz4Q zIty~xW%xXtes3BSJVup_sH9wt^^Ak#?-90TDkZ8h6k=%mMXBK=d`#X~2UN3FMl%$9 zG%~5A`_pAA>?nCu<{z~3CPvAG99TW~CQX%%G!<~{bFZ#VHwTlXPS%s@)QaeCY-L`n zU%nd$KK3vaLG(C~wG|tuXU)qu3wjky+J%|KLy3|ye6c+4szeM|D&ESkEjE-6De4?8 zmQ_b_pazaGljTkr5nA1yW?+jWx0|bs3^R}iN2PUZ?Ygd=&danfG@45DKR4xV!3kw@ zIXwuQPUl8dDEYZmjkLO7J2rOry-*Uce{iu4D{vc~oS>0|J5RRy-5`OGfWIv38OJWi zl=R*xSz66A7ySb4aVls$qLZ0yQZD&wO8V=IpVpFbk>NT`QV~{ZD&Rzi;{9K20^V=6 zt*`2>-R_|qpYP}Gu)aA7ulm2md2ZabmSFN}3Ysbwm-!z?uH5>jNgxXb7Mc=9a^@XOTU?s_C7O1{QvoNnbUmJsV-)h6?(==-tsY`_ zw}yGsI2)@SrQoL!R^=fk-XQcB1bv8Jr-Tz^)lqjO1i^gKFHz z*<6VnHxjnj0mm}|DEk>2)yBms+4g((zg1~tDJ2wlr(1j6K>|nIt{3d=SsX;~Wc0-| zC`mot*LOE&8dl|+l-Q3HF;Z14G(|=yNAq>W+<6DtIPHFCI$OVFhx62GinVIubKQ&e zYU)RuOCS>J76xUIhCK81omu-VW4n^zjZ%xvurR z`xMNuKDqaFQSFIkCJs4R5*gjbL4vfL5H6yNh7*^}d@3Jb6@uqPCAilKD-#m^K>cU<*eh+S6Vka^k!S-k}o!cvW zThD0$S8d9QTwf3m`r%k4L&~5X{VeMqQ`3FA*!at!k!3zDpW5&N-D`ZZ*OdN8dRp~| z=r5lH*l9g%zZD?)%2Z|tTta@Ar_Cd^aU0!ydv|xrKBb2-wD;POP3jWXaxp*crOE_c zowfPBUMMN14q{8I^f#r3at`j-xOcO@`pJ`?YD7oB}x5{!ynXIvhpJ}Kk8$C#?& z=VMo1e5pJG>o8(hM%49G2uGYh3{o2=ERJK$B5e?pPm{dNbC`h}tT*u5ogX!uD;6=B zBDsz(ZgFLwUNg{}nXNXNss|0lx?Orlf3#iO@k&lm@ixinBg{FFOZ`}WZ;wl_s{O%g zfz+ToG+MQ6$urRV!2Tg|(>vPdy~NtDD$jGyg8fe6gYbQ31s-@hHZys-Ww>>%1npy( z{vs~PhlYGzo~EYqDn1hKlSeLT3t=pHuSjl2=fnOyPH>W5`oUL)qZ`|Vh1te|AZX2- z4!)WdGQZ}>0vyrB!}_PZ#-kE96;y_wd?Yj0gLY~kW}c0z4faRd&2M|Gb7JL7jfNFC zGAX1v2DsjgIaN?!L+MnDc55Jh91|*=0&ufMcM|b*=U<;JZt@LnXKU|C($I9kbZQKs zw5B<;_|Q52*dTxYX*T?2lyvd{g4C(%W~<7d3Ex-j3+#KAr-nQ{-1)}!zcnmVd#Dy= z!CEQxI9Vz)&r9{I6=&7wxGMvnz5@#0mM`(|5h-%fP*QNcX;~XXemXY2qWbkYgF;JT z`6=VQkda2KM`gK1yMCF1LHGXH@yCLEKK4;qrFX$gIxOV{*#y3edGi@pK14XwR8rXP z)_BssE{AidJeepR-0;GvB;5R+0jMQnd*J%7nx6=NF4loOiTuV5C3r|sK6hQFD?!tC z$C5(vWQ$Ir;d9H^#_GmL<%pt~Nxob2nyms;33Z2#mujFaE?;CUHc#!JGs_Q(5JBQcph zh~qBDX?1$cuh?uR9x2#P#HIC1E+5vh@sDb+&;?Hm#L3;?*FE{(5g3OCLq;#O9a97| zeOHDj{3EvT_Ch`jmUPI3_c^sANZx>Q<3ARntY6-g5Rcz;zSnfqmY zpd_Mr?A7n~*;JqHMm`DDqV0m7)W49?6AqMvkBxofnhMo3ds-#0L|=G|SCPN&I(3xa z#ZkNxW zz;K=-uqTtQZ;?HwVP3Bo<{og^^**?s8y=-{6!Te|v90?uLNRLeUsuv~Kk1EAq#S^A z)k3x<0Y^gW^Wwp*3NkwJqqy%$Xyog?(ux6cqGc5%ar>+>f#|`WPavHHz(k>uU$j7p zem9`x!U7-!K4=~+|2wYFXVN~(5Tuo31aC3vbR=fd z%fauBf2pALJz4$P_l8aI@u(G{lonO7f*W)t1SiiL41V)B$ok6c?C;?mzP(WtMMzs2 zJ7{XsT855-5Ke037ayPBG~3Ys!m0`3UPp48+58KZCT$0r;6HMpi32NmhIRh}*xG$26}}Gq3;70=`MhuM zbLIath#y)E0F1*4{(@BggP3C@+5k<^vcs@|`Ttw^BQi6o z?1&@(43ckG0QNBlKH*<;h0*qbHuZ@)_xW2BARG>?mWZuE`k%R?uK-Q3si_Fh82Nvv zVS}yDzgO5{^v_&QZkKfVB zT67_6T_}yt$8FqR59*lI(xe?EpioKS;Xd3?ND9RFU z&dpA2+uC47krm2nG9s1x4TI8jJXe-jl=#_m{%66y?F=UpFp3qtu!aNEmWtaw1mlGQ zp3T;+fnl5daV6u8KaUmfQli}+{X{~Hc1{%BTu*#>qq)>%-Jig?ptw&IqVpLBWKN~( ziuJOLw{6-QtxLU1U@M{`z4|(ylRSsOk0s!1ss$TYxO2F^N zZOVDm()B~R4q28^(1)sBvGoR7`l%QIs1z^3m2q6*(I1u9XtD@Uis#kk*(iazO`ASN z`f~t?gD}U%Z+IH|sY37u?k@Kq1{*)$+G(`9#YNl(%~sx2%H_tKT|e@$u|>cH5`1K#^pHrLL>y&UaHb=6FG^)zUks@@6d@$ zb!#L=%bLsms6`vR$_T(0(TmNHsr|F<0s1J4`Q*iC)E~g*qW$2``ubuDjAnyX&df{V zkJ-#k29j0Grb_d##bh?4^9Br;pJ4s?r*zyN?`oOoJ#J3UR5~8~GOv5basY%r@VY92 zUQ2YwD_=TFFuB%(af#i!Eup6#r-8U9S%cyjIFHLUv}JD^0HT>0=!>R`lS*J3_=|CIgbGw@Y!S4K5O#)-W=*rmeijU1LUDu>>1rcQxqIBX=`y!J^RS}&Tqu8*qlH7VD^IgeFnSv zxHyoHT1`}OyC%~2PG>O16qq%}EXUvzz01)uA+k|rfV672y2)$}YLPa`u4{tt>uPEq z%ck77KOKyS{joOazh6iL=BWM4iLS2hcu`K-EpOtB7}JUTB1cb3FOPX25^!dy@qJjz zhXHo$lv1LUeYtc_Mm#3n@vaLZX7O}B_wob`X2WeQL$(OKlvgrHO9%Ms*fT!URvsYOhE*sJWbu#-Z6^3|=h!(EsNp z5)f!tj%lS3kEWDxl-bRdz*1r66lrmDCl1Xf>@j}Ec=oc>lJ|*{M_XI1`&;j?4}+Gs zxHFz-|Qy`n*1q(kQ#Ab=(w}LxFFh4@Fi1h4M|Fg~f@)##-s71ap zfZ4_WV9}8Lm9;_m^hfvTZ@dW6>Q)6Tv-dLwzwXD41{|l0V!C&uc3k}C#$+-j!$b7v5gRHouns96<%Z&d&D%qv47z9YqXkhdo+I=6AQw(F@4xcC&j!Xf zlN4Rr(_qXU%SIQ-y~D}!Mkt~oGebLu-B1_Pt5rz)pI}fL559(WR3{GlsJQ z-OXzE;kl6)T8a!Y0io32p+hmu6Y(Ura)-;}pIre`qCoIaI~|kv=fTj(iGXuA!wjAM zSENvi12mPL`r8PH_Tqe1Xz{+T`!K63*ITGUJuRlTCTbM>jr?VPQ7BlPe5<%Fz~9=?{DAr7hoF$ zm2%)2z51`wzEBwY`~;+f+=&0WX~+R*`2Tg-iEXT>51|gxM#onKyN6%2--)>LDJ2ud z{SeTuma+h{0mJptazZ4TkobXmWon%P#AGC0D)wy@m2#oH?EDD;l4_l8g^7p09-{&< z(E+E$)vgdcyQc@ZM$heVN{MJ;B2d03-@1DF{fcWMa~73iuDBakf5SgWY-aiHV*})J zpWSZjK%>zc;+rcQCkWuUUqDw=C2Drt!#YU#bh*M|m|bm8TSHoI9CjPxn4At@?%kOF z4te-}K^iMDaJh&_bIyBo?$<;=?g5}r1^!}}3V>%SD}D+B%)h>AORkG1m4T-!1g|>F zR-^myG!_dR@4hIWl;QQ8{pK^3i-|CEwHy3jMBEPQ8CZS-6gUi#a!n3qAPfrONTx;i z{_;i0>y%VtFr$tnXPtG)45NCDIzWrdOF%%|p#bG4I5smhoQ#NJ&-ac zv#AO=i*wkX#Xd|H78wr5Du_nB&aP(AY*D^Lea+ApO&wL46L{~+DB9@p57eE}+Q=1QS|S^)PSo81~rt03_<9aAn= zF3wB{0YGUWy-6FAz!a15=4nGcRlCtnWv=cxcLo(;pq{RGInE8!{Ie#h=QSmh8HnQZ zS(TRRfroqcbsOyh22(k703PVJK{|k6?OTc<<5eN#sE@Un8r+(9Slj}M()+d&)rzqJ z#HCT~FzR!y`Z9>-L<5ivDrCKra8RnL#`uV z+eQz+`o^mq-`wU~xB_2aVNX}NGF!2@>-k1J%52L8<`Cl}c?k&+V_8%;m9tn%;42RAXQ|pJD z2AddHKEU7ajVqZs=$Of8tZT>e#ZNAykxtI8uFze#M=djOHBMkP6bG^9%f@O<&`31b zfh9TF8j#uY0b<@|;oHfFHSMUoSFb52@<+x8_Sr3>gCd9}CJPlD85vZUnl`lu+#dt$ z+nhpwI7?H7%r}XJ5_1VS!`Uo!|C47zfBlU7Htgg|#7KfbprrS zX9^BVfgGd&;nid9hBZJRu8ake17YRX1tD##hlgWi=q?0E+ab^zqH|YH@jj&*Ec+@u zXy%j`T#R}}$wzc2@b=2BGKr3vS_!WdOGH&#^UbgMHg_jXx{d#pY2qhC9|x;N0h|*Z zrMXH2cci@J=2o}sD4=-8Rhdw98c%8Hc>jY%Ea&AL$Q0XuRoBbnH`PiFJ^31T$LT>W7-20*? z!8dNP*}=h$n9IrH{t19O)$FsWC%1!I05apxuN6p-LJ3S-<>vs;eVCY=Li}*@5{>P( zd0H%;hIR&Ceo->FV^gLm8<450vbdFO)tl4zN5iULFY^?gP`hypaFD zq|`t$xm;`J9h%w3^<%e<+dvtu1Bz*k!fa7=wy&?# z!j+{_U$XcqaDyU26|cnKr9~U?yREE{S=1FE^Lbe7b^DovC(6?}3+5kPn=WsV%&Vh`|GT0d z;6SU6WE8U(>+wz9XQW#pkA$6ti4F3O%D|0Y61xgFBb1E7b_qx&0?u+xEp* zzxex&&YRPDsYZZuki7TS%}4w9{!9hj*VJsWh^-4t5okLbl2?A#rk(7JRLZppM{SRN zL5tEAqCUYO#BU^^wt4G979CROe+-3QW;+dGQw{8YLJEF# zJRr>YwG7qb@h4I_DFUEMuRS%No_}yvf#+l{x;N~}*&p1ty#N;|k}A@JCjL)J)N?@d z!WKYG1s+h_2mTSw&;;tM3cTQ4{&@oWuji!oDLWMCA5jl8Ks4hHXXW(I6M)IxWFA+c ztvUT`b}KpU01;yI>X7Sft;?R|{6#lvmSDiN9ukn+RgVB2hg6Vx-dz+Cayp2I6LOG` zWFVB`Ev&00;s31`(Z)CPbtnuz^vCi=>AP%maSZ>fqeZFDJ>iTa%KCRZV-&lil`*-J zadV;2=YO)f&;xLOnnQ&YnX~4QY?$$$2okBAlhw;h@|%mj%Yug|clW+nj%wZdPmgZ9 zQ|EH{tWGWduZT1&>=$}4_M|ktfLvkf({bsq2og0QD|C{VS_d9Ezxl3&$!LZRUprVAM&4C0UVsgFb{fEBu6nzp zvuwV8bDD8J&Am{bj7npiQg;5<|1T^6Mt3CyS^WOuDw;u~S~36`XLAQgPXhkIof)~Q zQzW=|Fo|_zvM(n3*$HwP*KjKh-3+DFyU1O2b`7vNbs82yz?vDC2;)GRTkePtnPRgs z+wEP+!?8|JkEZSMlV=BlSLn_d-!$10{WU<98n+&0mPjORb2wf&YCR;e7nvb$-2~#g zV$HHMV zD!_E4A}HRMOYh*(NEp9kQNCA>ry%eqt^XI=Qq{oAMRh#Gib?XcoF$|IUWqE?67;3J!$Y?9kbz2nt@b&1$Cv;4&CN^OgKJ3oVWVwsD@f54R!5 zQ-X8sclKE!$UX}D^FMSg`J}!~H~1oRvd#ho&T82w%_eOYlYY|MvyJH5efB;e_QhY1);gLqH=@f=tLt<((O&JzbZpx9Uch;AQ~T4Bu>1gZVu6 z9Sp#bl~0ZK*KOFev3lDbypCq7W818*cZ9T&>$PES#wHjlc|&M=N})`D^BO8QCt=8L6=6fP+Ka)0L5Rgyj)lmNUtw@GOD*}~t+ zWs*N60JPdZSe)2w4X^!pMp?dhpMNP2+14Ab2cnKQU0Rc9YX!{Vw*0u^7Yz$G5Onqt ziy-DI$DBM{RND^pe<63MQ&IJWcJ>s;#{=o8cHB_R$nf%lfqM$w z#JuVKM8U|xwvk1<>#UiG9_@V4nYN?2SC*N0qKND`yK3L!Y zTw6A6B^*7xR%5B0hYyWWSzciSYS|u>O|Z9*k_jQk=|G{-(*0z`MEkwxQzupQ zJ0VWf*_x>GTFZ&zBrhFY#@qRd#m46hSGyQaLjf$(eyLry9M30)Y3^t7SjqmIj~@$ZEoN)7ng%3{ zK-Yt__AH*$+B)#6#(l900Be86!P#)66c=4M=XU<}W}s$Ge%PK=Q%%v7&XBjC@$@)V zh*Tc(=7p7#$zT*vcF>=($SH|d@x=L~I}o^%?h^1#K9Kped-W`}CD}4=4tUZVwb5n` zv_G*Lk0eBQ1^4bbzPaFh-?*lEzBOEwu|90wzJehuU>6t6Ctidpa(Q*7GhYIE=CkBlQp_aLuI0z1#cu?2R&LVA*iTpKucJUvOk28Ciz`dum2THL@2^Zjy4 zdy;n!(4eess_t^67)k<*LtgG4Q(;huqR-5H07ynnrW}&7y_|gg;xU|gsT}sQ2r}y5 z^RKB<_oaUSsLO{K6}!5wt~qyL-_1NtR~40yEmrR9C9!zMm-l^0uHR<5d9Ka+;#~na zI63rKd|*QPyd2(7oigxCG&^V(lrQeXyzVj{ANMBMc+~FKtl@Xtvwq$^&iwEq>WiTd zQi!dHFi|e!9Y-t1ofO}pNT#$UzrUYW{|_xALo(ArV%~%}JR;L}VmfUPqV1$qa9&?* z+O;jx`Qp@L0qyaC-Exsk4{vbWfVc;>P&|>vp!~g+e2;Cdr|X4(QMA@0AEZ*0>b?oP zFS<9B1I2Mu{1;-KE$>wrHLhPuuQ{E0Fg9(ji5w`2*(-HES#dIW7$^&CX}Q~J%S^}h zdg^qQi2ijffcWt#;JglvMsC1qFlY`OWk9`?nYm~U7Qg=h-tn@FQC`{6G}vj#Xf}G0 zQ|P!qS{L^U5u+5JO{ZZ3KPw!I-uUIM&ZV#U?12f<$%UH08#D4S|Mn8uGPCiS1pb9;t*XOD#06@ z@L3sHvzQJ$YgWpwO~0ETs!k30Xdenw7T!#97@W#V$CRG4ZJqKh}2NRgP>9s8N|l{}LUuB5^Lt#Ni7s zDmAMDK|D1C^v08?==y4s?2R6BP3;t?Sk~ECMIC>>q%Gc4{hat*%{B9_-fZkQ?>?-V zX^%{5vALImRU+>d6zouAGt+DuQb>MrL)0Ti@+RXT$y>?O&h*d}WJ zdGk|e8Z&%A2R72?r}8D*36uSNvIQH`TPNwXj-iUh;t_N&52+n58-TH?vu^p)r}I|3 z#X)5GVzO9QqZPrUEgzjs@C)AqMOP?6V4ii4!$M#t);&S(KzH5FUf|2)PYW9_HZob) zZ~j67fl_!?R8f^n-8*iMI7zFY|qZh$wDwBjwQjj8k93)@4Ui4wmyW(Sv$j>%n)!lp&6>VP+ zC}MRAdd31;9s}BZ#9z0z+*|75af_|EANZH?A*WMtd?2BeGDCr}HHqV2L%K7}&c96f zK;#f+UssxRc>ujSh3fNv$a>45xVoloJHg!&+$9hQuE7cJ5Znpw?ykYzo#5{7?(XjH z?)GjjyPvPVsws*e!_1zwhqbzoKKpDF-Vv%O9Y_-}Yz&dmkBVUfbvoL)AEfkQ+a*9j z%=>)F^F!q7rGKE7pGE@F_XG>%{_DCbZwW!8k?o1shsii;_7Gcz76d__pyZEzz8^;S z{eu9fl(9dW+m)y$Bt9_0u=&))iSj2ICcGtFj|-W#GjutUu`=8}bfI_Y?>BhP3bSUR zlxzM}Y0IQpYLd;Cod$7x<%dyZ$c7))6M%|Hy+ zhjM>5?DWzG^TImhE~_?2|7c%4XOYrZPC&!ehduN369NHy%S24(Bngn?g|`~pM>4hI zcDBNgJV7z`Nv2TiiJ{k(g`e9mN<4=PaNImUz&2dg%EP>9M;8vpjp zEv0r>hbuyxJ;4TT!fj~h9Z}!%%(ku1e|4aGj``??RMK{Gsn#CfF3P!LB#PuGnyft_ zzGV2ln%olzMbjjW;zl2}hNKQoNA55d^WRGl9q^!63aI{2+J9KS^*(XF*Dh0iF!9T-H^uV~e zY^C{pk(t<}6zL%QCBq3@odUSd9FR6@2_eW4~sm3x#9N z6ih;X3mx0fx9G#$53%sbn9Qp$^aY;P7|yc1As=i2%0#VmpAh)y@x7PjKK5`-+H^no=lUT@_n>+4d)Fw=Zg-#G0_u*b|Tl7T$u~GZSts z$R9{Vj@6=nlL}mavV}_+)hwSOd6aLh;LJ(V_y`(K8#N;Q^^+T_x=`mx$ENws{TG_urny6t0JykKjlbX%>tFtYLuCb-I*G=zC9?z3wIvngkbym_SHxH zK{Te$Nu~|XusKMLu(N?K3p3D8%AdSTYyBBrqbqs{h5D}3l7eA4B9I4ZYExBpEaIhe z2TL|vfCbzZnhP_AHbL6aS{WxO`a8UGCGrYdi=X(IO1H@A);N91Gm`C7kRk1Kp;UvE z^?;N9g?!DOv@z|_K~Hr5txRwz>Byd=Pfi+qI>z^Y?MS3^eFX@&`dMcrCWZH5&vt2u2AGsVxSR!2weulS78A6}kKr9oI3LWGdkU zz~{S!!l9EA-HKXlDR?+Ls-dB5n;+rh22oq*Kn-?B-^3t8_ALyiwHr_OmabyRqYhXg zyv47T1&|u8t`s=S!9Y!SA|FGq>Hqi1IM1$*p++X z9dAw{bC6^j>4tdem5L{gP4h9Gh|=?Jpq*%&Ec5j=+V8J;Orq_F1yvd!Lm7bL!X5IY zSzXtWpYu7Cr`Uf@9GZUk1M6>6RlVh2Is~LiaVP9=R;sW^3FsY_CJ})Jz0$-X`hgF@ zal(PxzjK!B*7CV~8N3sr>2sy>IDbC*&EZB*3&}gwV+8ncwK=~1Q3Zpz^_7uj7}2bj zL!`$V&+`v=s0!P&y_`03d-)3MMsIcH?H-k{XCH65#i(^@i^*l&{Fz#gXRIbT^39Qx zGgWp*y&wZB3<++L7}SP9|q5Y~2|Lr#31n`nrGOlXi7x zA62BdL(OVnV?cS?8)VSw#8`(jlCQ!DW zsiO^@T=|e$Afk*r_3n8yxGf@+nQf!V)5oF2Y%Gq61c6bJ#W&y2>!=RUt6Yeb<)k;c*b z(}niBzCISsl^sE^dC17W3Pb$?N(2S*$Y15Fq_lRfP_aEewB;*<(ZaaY!^A~eYMe7C z4f5r4QxgB_tCehfE8I2gvKyXwpi@WjUa&QiIoj*RUt1!RMC_%Tm_vJUS5J;ir7t2& z*oEZ|gZ4rOnbacgJNT%NArS8SQQd6OA$H=BsS6Gcs=HP8MJm4V4zkqDvQ5K#R|l@Y zvmuKaD8sxh(MX)g(jLUNJ`d(JeqTpGjc&qT5ulYGNkAJiToF=4BLUOdHwP*f#tRVE8YOk|H2IT*?J3&GZ+^j)RjamUXeJ z*w>d5q#;Nx|00m* z825)t)L{(SnuvL(hGwS|IVEnXM-AUkEp#WNcjp+>0#Dh4J}Q0Jw73a%zPIlLcw+0& z`A4x1x!5D@xfdFZ*4GZVk7`5Jhpep#*TV}8Q49vu9odiY*h~N%t~}hmX>qyDKdZo} z(rcso_D!KU0^ejVPP-?_V?1NRG~A2bQ2XWp6m#Y<-;Dp)9pOMeyd5s#Gojw5%`p{6iOtz1ty$i2Y%p|^V*=*3IvU9fMAs;8T6c&8xEw0Q)PN2!sL6+rx`e_l1nsi-dKee$ZDEw-WXvU3-6(s*%b;H05|yG6BX@2m!kJVkDk5b5|Wql-;HqTee!E!*80A_*v&KjSJPA z9Lf-X6@2cg&U=^^p4>!+fBdJB<7#OKK274{>XjNmV{X_QjTIdf9CpAxtF)FDp)mR0`R{63Ku7pQbfIhCeA&KDEDOqYEK zm-_Doyz6;9!LnD4KYaXOg&3$=EklC|^BkVGs|o%0OyD$zq9xdQ`cJe4(CcUzOX(@H zoxP5K-w>)8WGsy{cB$?Rtf3%13RCtyTi;Zk)WB+s%eL3!px9r*+F$E= zUIFf{O$#GC+!AEM5)I-8e{iB&mu)bHXD#HG$JE+#(0#ErS#R};T%p+)lWaHVKlxlP znD=4!JrBLhi5E428{lvDiZuab;_=Lm=L7?Z-|FOw6x2N&Zga%qv+auFqBxvjMB+Fa z+;un=G{0bQH9wt+v;le-wI=tjR|xS`ir;PzIy5Ql9zW6*kCo`SZ!?zTJdSS78tjJb z{XbF235&!r1}?d(iTt-7T>J#%;MrnvlyQLRnMjj8uAJ>FFBN|LEiT*Pug}uZpGaf8 z!P}xzo9qugZ2>+`Z!!yq$)z2`8BVf^6Bc&*IV4-dHIh2Sx_TU`kO#SZ6Y60s_Q+ z(X*QOcM!cLkH^K~jBKUe81>Ig{#iF6T+KzGRefk8O9-I3zbkbwG$*U#?V+z1({*;ny`vz1VpXJCD&4#0JyMxS)aB+ED?S?LUobz=0 z!{)W7GslejCnH-q!yJcQe4iFDm&16fI9+L?8B61ip0ChenA6(0&{6$`$>sXJVZ%*!H9u(- zXk%jEX#kp2uMgRUQAh)50JckLgZ{izohtPnTfQrp$^}29_q>;1(9y`X`3GX?QnQ_w zaL?N_h$$fIsd`{M^u~OC_jr^=g&a%?2W^Y&tZ(R8Z*ChE$h8*BQdVf+IE$1v&gO`u z@_OuUeMj}USLj0%)H1wj`Ttg6lF6jfiNP;-qvXiVRxm3r|4#|#D=%PoM%g6yWd_iw zkT_QxMtu`f7>J~*a%=KH2T|vN2~ef@6O6~{_q_%*%NlX-s1-uS`Up5dhbIkVn}vetd4!(fhoPdQjct-tj8!$RkRoVLl(n? zmSsx{;a+AObfb63^W!G52m7_NN6h)fqK(mpTrt~rVDr%xs9o$=hyHJMCD+ri;wv5A z2+3s&6R5A-81{6hW^rYTR0IIP+?2xU@pH5Qf6nuItp$=qp%{aWfU~S|)=WYDkVq)# zYmVsrCe-KJvvxvt<=>vu-$&KuwPp&H`JTX2SY3XU7Fy9k2}eTiV0}DDbA#MDpV+z- z%BwYg7X98bb^`cU0%mh3AIV3}+Q%oNGuXZAX}P#BzTag`QDky-OkLy)9-m$B8xt4eLqc_S&`K>fpFxIFCTKRPk09|Pu_@*_s#4>fwS zFNQcnj@=J(h0>$9a^NlE|Ei^2)x3bx#v&rE)*vNn%<1&U0=FDUSdSvCakzu2LbOpw zu3avh+hzpEHtK}2K8`SKBvtW5Iz=p&{H^-MRu1P*xq(#T+YWQoK-Zi9c~M^J?4#$7v3c2e=xa3Hc701t{5dqEL7^oi5-@Hkhn3V?o(4O zkj));Ap80*3u){Z$DBN(FuNWxEjAXdD)_)ufR#NBAVf}K!>d|cNq_U%dotWVnGef! z5=m^bb16jm{Io%~k8KQ(^Aa;$m<-v`KoTT&6en^0Asf^mVaY+r?3=N`RGwHCB*g-Y zrPp62bf8U6iW-St7pfyX(Kq)gWudA+0eWg1sK=W-^z3mPYwZ0+fa?#K!;c0RnJ9)P zw#fA6xX;3|f7Ai_7@pXH%$c{*aXO0w!ogZm5x=8q(V zKf5sz=d**uVencXM|-h|1Z@uh9;#Q8eL?tdFHJaEn4A)aF>Lva_V;JUtS#ToH|3x) zJ0JAAaeqdZnY~D57Gg=`guiDj??;^17~ijw!(D$t5ym$B@3wNr3MIcDy9?zXe>C16 zlfk0VL>HpAIW%!Memn*kBv9KU#a*hkP42VO!KIq~Y>-11~x*H;2~Ie7zCo871~SoFA{Sc;)8|9lm9 zLx0;k%U^Hq1=1B!tB_9uvROSG5{V3Y5nV!I1-lQ?QJg0FxFOoK6MiGAcCu`b;Y1lK zxFUC~Gh&$<-Ft-tFU2HMan~nU>G1K7S#zgVvIU7p*B^v2oz1ddzj#ywk zAcj~~fz|og7eO+VhcU-EsqaHpMWK<~RGgVLk3_?`$2pW!W^N!w?kc}~4z2MaMaZDV zQe#6a@d3MK{wZ1n{qK@xt*qVx>$;(fPkFl6^&*=y77=(q&lDS$|84AS&qK|>cf;h= zK@e;r&XI~&!OM@+5MS{9D3~CFGO}mrCGk+}J#-V&_Dz;|BpOc;vZQn=WDmnhlR(p) z^R-85Z%K!Q$-7Omgu;4MD+N9`nKFU94y(PI2k^AnK_9uz7YRjYsdb~RvDkU6r2;nd z`t^hg?Saj<*Ql*N)5vDxP9W~on;$1cAuGLd{S)T&tRk7t96IXV^r;y+qEf#C{yVQ{ z&>sxw4&A&pk63=_t)Xd58(n~!IHyfO+!%{BGS(g>@;{q5hGz(F*O@K~&$Wh0Gvy@c z)bK6MJ|gHHhAcF`QS71@{j7;P-6}*hznxpMVQ#{{`nOGXz0UI}kCUPp>FcO^IX}w= zP;XJknl8!7janvZv)iX`b9j`M>hUc|K5wuP8_gk5GAn@i&TeiqsUn4Me9Ki5G>+$R>i#wHTJ>O)c-SiX`Yck%~rH=PZN zT%dfhwVZEJRZQeC%A77yTvM!Ky+SWo9F34^8C)6=avM2u&oVQHH1YX~1HHrME*A5Y zm8==;UY5W+Sn*c$AX5+dugp##oL1_7FNrpPU|-gFh83dGlTrR~n*LWiK_DKnA9D~c zdu_GJ+7B9?Rq3rGt_l!|6ZIy$Ii?&opJYu80^bJivqJXR2n(U%rUA6Cikvc<9?r5e z=6%7~Lh$#OZ*mA;DE{d01~%{MlBXVsSLMJeJuce+GPh@P&oVNPi_He^yVptwSX7GY z?%=!E=R+=a`9HYR+f(2+oc~_xXDFzTa4NiiWqAwtFg8+Uoa0OjF3uan|I5y}a(NPH zwIpUx+=p!*mWTA=5B6SRE}sA0Y5(vLylV>kHGgT~{r9r>%kP@P(=w#7@PAGnKrCnt zDE_>j!FPB5-M)aMNF5Lh{{LQX{p%CYE6CO5zi)BR_`lSzK{(VXop#7Gk6d^J5JWvx&Ke{=M4@^`mPe2 zEm0A@+$JYczTAEcy-{*AZ~;Vu5-kG>4_#pIvN1$F4!}KB=Dz69(;a?4Alf>9ddy2< zu~^-|^KC&222|9Ilu~INGdY6cX!1Z3rged=g!A_z5?De33773aRFC!ZJ)O~9sZY?T zX8z9BWGkPL%X%cO7B9Kd`L~4zlRO0BI`ajhD*cg&4PwECs{|*bcSoCyF5kHRNTS4r z5wlg7LiUcKc$zPQ*Zbymkh!sZLLsr(tE=?-_XKuVNS)U?C)|!ox#Hv`N?@sM2aW^`l6j7vh6fI_3yCs#zBM@%SUw;ybZSaD>1KasKb^!f{q%)4ZE-Ufy zJcw1Lhvr!W$!#L5i`R2o%hiYIOv6sgKyLz}QP#bb*ssD7l-3|o$l%Dn__UtTV93Zw zNYbAp;G)1k#(wn{MToKq6B=D;Yvs~UTv=#Zf-SbZo4Hy(5tov>8*b8SEhs%)XtHQq zo+*!b*G&36(uh3T8cC!E%H|Hefu^@ZWVSGNB z1Js#c87?@2iJRR|7H__q(Nu#D04G_odX-=tl`6%Yg<3q2b_y3x7wq&$c9pit=1C@9 z2BXsqhRc$QjY+wP06IoK6XtHSGutylgVD5TZ2_3yN_}(iYvEXAHhV8bGzxAVbLx0Y z&g8PW5jcP^4p6uxwnYGZ#bZY(MEwy@X=r9jwH7^v=v$o|qe&!>AG-|B_A9nznhlXf zVkvScUtSQsq=$3e6rDDlwmQ@*E-k$37#&J~bu`-C;VHLn(vT-`rx)Q?C(=7v4p7EN%eFgL%C@;r=RBC`;;8`AcYemug9ODNH?z$%gdF9FUG! zpga}ifn}M!nXV9e6svu4ve~n>$$qJ{ELd)G8LQU2iKa3kb$UF=Q#Z>P!)>}1jT6V5 zCbtpPe`b_|K_51#4Nv_(qsABgMPjqi<5DhSSiM{tW_xIhg57mHfpXepCB>=jumTW~ zL@-NG%-KKj0z$x(q6}&wc0_W&BX)-a3a^_Zk(BRNBrdl&AT;@T;N6H34_EGSxf01D zi`54lEKMa{CQg8Z#um6lCO!m4vqpTG8eJKStrFVm2-wGuy*mCU4yVgAggdgk7=QtO z7*V`>qpR}fAtZ_Y@x1l+z7??olBA`WkEex+rs20z{antHw7GIP-a)KJ$r5ka6UW7# z;P~y`lH@3Q0hl@oB$IN3>s?U%VUL>>lrN$rP%gp=<11S(JBRXKWb_)#keD_bM(En( z=B$+lcCH()tmrP-X+!X!M^0!1dzuJQp7$4D-d<>?HuXG&-%W*OpK5c>dti39eelu; z2HO-Ge5p1=FIKC~7p5oDYv!70BdO3XV(Y%$(xt(owBw$;q@WA;zHLi zA5jS2AAIYXYz)k%i$qL?;OuDx!tq72@5#1IA^%TS%l!bH={EEXM(_x)o&XzMmi$-5T{dau*n&c@p z7pH$TQ6(|bF3njy3)Pyhk+*O;kL`nePGk4b04!NZLb1U#HLXlR+wioyEf@Ag2axUV zL((}`471soiW(jR`+2KBWqVpGd`blL3Z!QaM|c#`m<+i?fH0c3gyAjzH@(RyhaxUi zkpo+e;)SCsmmDD|U_n)6>vTqJG}ZXq{ZCLq{INqKy;+jjg%s-gR(aV$8G5^aZZrn#Vm&DBS(Vj&bjxv|q)48TG(8)|A z;<-@XnnBbre8A)eq?El=&L=eqK0}?a`1UWo@Gaj$PoTb)3nE!g>0lmq5S$zRvw5EiwchLhV_ldToi)qoyQW3CXi55`e{Sy?-w%t@yLz8|NgtW^e|0tBm<)_3BM zXK;tkN!{74L$W6`&S<#L$kPvBF%g!tdU!Vgh%p-Ln& zPdN$=#ktSQxA8X6V;v zTURW+;oZBnY|)OST)pi853WSW`E<>|N33`4mJVkB@2w?Ta5C{pFK|AbXhwjj4 zapVO&q>C=X=e~$UQAT~$B8rBZX%ce4HE%O3B$|LoYrR`L(DkK;X|i=5a@5?I=#gfE z>q9*IqCqtj%h2@PU>rJKAiwhYrN+2~)x*(_aFuAWbf$IBL}_nRc`g1Xwdr<$q!-y} z;aI-Trs@nf{2HC&D{|1evCm_v#GZTvkup3lGoWWrP28w&pgWkUYja)5%QHeun*bUZ z`x7prclr(xn>TJW3(b@ou(m+{THz1Sjz~QB7}O{Al0aLbdJ?1SLYClsQ7DLJ6rS7T z5TG*4(RyjraNxmc?;TOyX~HM%_dm0{RO!=zB2)a;qC)SqXxc~l9g5CZnJyQDN?<82 z_N&5tcfHEdhFhqDx9#yf9R-Ouqt?Nn#94Am3?LMvy@7$-2P9|pN%o(8dyUz`a&VTzZ ztif#p#*g%~y_rudF>Hss*P1SsZWd863lyqPdB%H46+j`Ls5Kv>=p*wJ>2sBHw5G8W z^rCA)p8L7qyQ9}7C@=G@JFYkQbu5Jvx(1AaLLwT$NuhPQJ!j*`IzWeR6zKAw1aZH` zC33%*^3oj6l^mc989j_BGlrsy(1 z_q;%n2i&C+?_CLvNGWFz@+-N%du@zW|MBNn|3H}{XT3RUVJttb?$0Wa2<1QvSrUDB zkN#{qKZ&})^m42yzp4}e?NB;*n_7#te6%?I5HLfji-TfVB#G0-p8O|=k|RMzpZPie z?dgTr1&sN8wK5Gk)u(p7)j%(Nt+&YjNfHXiwqR(YIAl-Q_yt)p^3&2?*cUaf>!Z zD<`E!Ti2o37~LAp)7J)-BgdM`BP$lzAC1CmQ`uM`7tTfcrN;N-IMdAW`{$hC1+cK_ z?~CgR#sO7rhUZ?)tCgWCTCe?Nx4$ps_HfkLU0hG1CYew>6w z*w zJybBGpzX-yN_rLO$;}=y3?(ESpnNo|SZ6iYyfmffm#}dQ$=Bsr@(Q=6tuAnh-dr z?s{HYh}j~Y*IdbTvu>JAqLS|-Br+TKRD49SJLYwR9Gb{Om!<=omvsm;U3?+T1g*a! zjpnNGO*o5fpwF6%190I-*n@OSOP1_~`)*DIbK# z!sHhwFQZ&izCISrw;zRO>mm|>CSHteehGTax*8>WHn6J-Dw5S?R@`W|__zG=t@GtJ z^p)w;E$*uQx4N?9)hfjw;Vc!t{disDPX$vy%&kxpz}%L?QZM6s)wEYdp}u2O?!ux| z5@65R1SUvm!a1uS^)X7^G84E028Mq?$XfzEy35)9W;bRel1KsvQv3t> z(YQ8=kUc3II=Q)@0?~oGZoCJx({^8H(Ia#P4BZfWEiB9lgoJ@;q zV+tLz=dW#tplZJ^m~3!mSvV7BdRkD`-q>qg)PWc$yT+%kVUBi0cwLOi?z2O@3D}Hk z>UvVgTjX(YcfrV;sSPS*tK5*d3F~(9&(cb9XHK(~$_f+COxOvT2oOQ*R2!R=-Wcug zTn=d*=C0=Za$J0Ec-o}Hccd!CgJ0zS;K+G-4UE!BeSu3nzkMJ#F*4nO3a!H_QNB*N zg;rixtvjRjhRB4#XC~=WdQvrWaS^a*-bLoa*@RB*VyrewM_O9gt=VPc?2R6gS2<&I$T`wdOEt0?dic1n5V92G zWi=3NRJj`-$r`-sZgQr(Fwd)=Dmx?wS!3ijU=#lVMHHc`(V!uiQMv$^bZ4JTzLKK-fm0*3@IBK23}r=X}hZyQ)^(cv_wzZ2^d3U zGn!AzkxF9Flrb9Shr?2R`}h%hSVus}{jG(ofGjzM!%0TwNH%5lbbg?SWyBaepg8ah zx&O#$K5Sj%iT^eM4?226Xge}90Nu-p6x-?6q(q0p?|a1-hC`#gzJ~zF*o=wdNQEzH z`^nRNCC;k7mBuWqR;M$^P^-mR_(~(pky`Pf$3B0jqwPKmA4I)XU9C{hH~sJJpvXvw zApW9jRQnrIJbe#COhtPgU@8L4i2s93Yp7DU8`{C|2{h_H~t4j4% z#OZ3yP{0IN`W=(hFABN*@@HD-81Y1o0S>RPExjLROVx#d5e(Xu8Qdoee7#{0KU7*~ z+4%L5tO7Yv(vD**R;UFl;E&SuZrylQYxz|T?T(i^QCb3^d(_3{R2`RUMqx!X6Utj_ zPaZC2D}LKiD{vm!0qFMd!EMlNk<>(0S99ZH*!7KEJfjA6l5UGG$1w;k?HTNcDDgiz z8_KyP!6Nh|4y(;zyO$4%QKjx~h-|Jn`AVb5uegm1@BVVQ3vW(mcVmtm1x!tRu8&Yt z`k~l{fJ3d8oo3hvx{8D_=qY9`jLn~n!*kApLXelnUwMF$H~2k#+jM_NtQ`Cx6?WH` zw`O;`JO1-4#50VeoE&IW{>NRQo_7Dl0l;asu7yLmI!vv|KSTvsYIl+kKjKIwu()sU@9l5aog%}}SkL?a_e!2%csd}Tv%@cv`}oNI z{hS0|`#hfBjBL%p9FYI)EkM%h7y>OF!PRSlhnV_TJo4A6nF;z4W^(_W`V!}#|AzOg zqa$sneCoR&*~*CiI}ZN<&Cv+<{UrKqimL|-ksMDMu!6>0DA#IHYqdR?c-g+}|L?Ce z;k|73M*I2$FX+xF-aq^aXZGG`S}d2Fb8;VxHg&l=yMt(xy~O}*Y>}`3r=zHU-ywph z9;&+<`r^d0)8%wRGGevSPVC%J^vEg-Kt)lTUqnT-1j}I3SUs-ye;!OWMC*EL{KVk8+B3NcB0l??huZ~1>b=iE$Rej-lOaYU{DhRn+it8ae1cOZ)0s)hHZ%p%ik-w@J zo&=b}ppX6dsRyyU^cc_)u zcicEHoQj3MMix8iMY^jru;5xGA1}6sBD~yi?Uer|dvu{Z07;-}Q`2n4`p)y{zA6;- znTi5voMx=Zx@BRWjjqsrx;3MBxur~G-y=J?a?&j8>}nnBQfjr@%C&u~uR|Uki>E2l zW~?v;=A^klI(emV*$ArC89vfBhT10Y(Wu1$hT(%aoRSvapRI_$y{YIuc1U>9C>%Uo zqR?n?45U%bo16@B5zb&T?~A#)o8wfmx;0iueUEG+;Ie=A#bI;hAKQp4V7SUl*Xt+? z&gOY-77RFDXwuXlOSJ&oD}p~{Z%kyqBC`SYf={EPe?r8Gv7zt`MkU{_)r{&69B~F_ zZGw4x$`g+V+(znWHemkw5_HRf0N+II`c|9xQWT}OG+?f(P^G~b;kmc73eWS0MD8;K zqs8jPY>DJin<|}`$(BOD zEEbG?)%WU_L|$E;hF{+&A0iX?^=o>RMdpTT{M5RsChy}8Xa}Gn7!m9SWE`RRhMzg# zQ#_4kBm)*pGNBt=LtzFKGziFY7`oYVqn#=W2~@c~qGHo6;V!XI_y90Q5&n~+PGh;_ zg7s735fp(8)E5b&*fg5FGi6g*{N}C0hAmDGz+8v5?*5_+Utxl_JWcgY z>EU_kM=v|{?ztSb%4Ji`R|Wo6*R0qA)fN@YRPGzj^Uv9kh`7vY%Z0Ju27$JA95c;& z$Dc?PNp-|>ZsKM5Sh!|@5@|2w&M108F195SjNzG0*siUd$I?(oO?m#(CZ5%_(cva+)mp}ZcTUS6Hj&%)|J&q z%|C8Wh{D0Sp&`p;byWrit)EObcO>Q`R=84wHaK}+orl1>hR=Jo@rPa~*o$t>RK-L&@f?3f7tURbwJA1%kvHev6U>#!uN(t6 zzohjXCY-c6_?$?EZyT_eq2Fwl96=wUQ@J6NO4VvEI9Y7g55s=?A>74aSA=}`ljU*e+tJ9r9E1}ju7*=F0A z*vifSs;&oCP&DrU+v)J*?<`WcMn=NfRgd)uUqjbjx& zI_Kt?=_C89Ua!(hpb2LnyPztB6e^%yICn<6`g68W7*3om7hIRYUr>$*c3@q?$Dvf# zpC#u{FM8*y@l#UZMRz$%vzIWxXcS3s&&pliqOiRKUI!Dy%LrkUoLkC zzH~v{Ohu82vql*-woDClf1;L)4CHoX3C{oEQqwV{?}$&AfX)1(6O}T!_QumPn;B@j zR3Y25i^cL|F=OS(`7WlXI@AMoh5=>+Mv9Wd&SVaQnLgvQ2 z)GAOTpf&`U3*iipS+05%HzM~OICQjBujNK8yio!t5&;0lABW56WABrYK>jmHV8 z567)4kTI6fe*(5+ur%jG5j%b-2kZHxg?KvKaNmtHL8%Bld|Rs#O5HM_BOJd)ve)mj zmxdrT)xt`%%@^N#w;BK}>d(`gPj=0JV~p6%xad-=OBXG*$y$h^pqaLm6-$82ZXe=Y z?!CR<;iIoYHWq!O)@FI(UBLaTJzih-*Duh46xOPuMGaK3aLW-t1DieD4FIXv??xd~u=dl>9(&V^+?M^JBFJeIS1<4dO4xCkHanEi zsaq59F%#M1yY2U+aUK9>mP)8zp*@qDT!U&a2-tO<&7*SoTEyl^9J$BK<=T=H3(uL& ze?=XPjcijWL3aFw+y_Gl6)fDO;1Pg3kY)a~hDOEU{kbCT`zmuus)Is>HYZ_Qq2o%0 zPQ2PQ;*H%6f9GkwH0%Ju^D%@J>82yGF}Gy8o;lp$KCKT6UoX4g0*grqd^Z^w_`mnViKuq% z)CB<=%wCa7ry+Zz$@3k{bc3w+_-vu{S9+UKS0kmhry6Lj57;agv1jfH1o*9UC52047do z(YL2$1HLfvC_O&@o}Zj_b@~)8pnV*^k(dB%POeyb6biwyi}qwZGp9}LOsX;2adMTD zi`FU^o7rqOV?7kBtYV-$-I;WWhotk$pW7FVfD0LO3FGp22bBeSB|76*R=dL(LfklV zsTkUf0Tq_C>CWHV?HygE!*1V7>4;7i*-I1}q5|PLEzct)ldtPoHQ>645qivpNqDst zwNMSAof9vE*k8>`Tm?lyIZA0dHI!n$mMPZ7k=MO}>iB+K9}1?0{kNJ*T5j=17%kVN zj{CDlqu8`LBm=qO?u@ff-*>Ratt8Qv>6+cf%ucRebG&)r+oi}Ejon~}0HM6qx4v^O z-`zWIaXB(Ey}8=^yFC*jF7VD-TA7e;eT+?QhNFB^@B=qBp!i5Db}mPyT&bi!q$qdK z(NEf@IdW6!G)3yv;Ql9u@?b?L4p7v}I=oMx5#7u2B`mdF9A>2`oQBP%%MbV@u*=z; zd!Njv-m?w#a6ymja3T-EJ@7^RQfgG*@|(P>a~#*xjgW)#)q~p##)$=F+G-0- zZO3*Jy`XIcWX-=bPi;6mABWJPIrQJ+;VVn;Ju z+Wk}0VxcnemZ=wkL?lN+kvTtFW~~1-$OkDb{@r3_h8$&vy>q(5R1T-kVX4K|uBBP? zFh#gL;wIa2$7T!v{6~GNcAVOIgrhrU#JVk?e)lFZ zmQ)(GJ2?T=3`I>cp2u+Bz z3#UE7Xi?2h)`Qmz+{pzQf79B53E1Hq;{QWyF^q+=>CcAtp(q8#ZHmI6hG>~|0bTR; z;&iC;+tvZB53oHl76R>~L)w?cr(PM1;zM&^S^DX;baXkm!CA@AUL znZGc_4ZkzYXBUBh+S9XFDZfyR91y+61+bq1+n)pAG3YD$B$6mzAQ0$|kXX(fP7D{R zfKhcg)7b?*V)4n{Xmb6DFC93s=hQ^2C{j#VuPcUC+^<@O^;M)Y%6+$wFRWCAejaIW zMFo(NOZ&0TcXw9!i>;c58kPuOZoVs&DDR1KRMrYrt6%Gn(F?g!fRRVlTE5!*Ky^7) zn@owe&MZK-;=Q&kJV!|)AV!)dD$qfcS<5X=Cvsz2J*145?T&CwkXN>QbZ#4!j?`oR zh*Ij`O?_a0J0j3@dkJ-tDsc<`Or2jv;n+r3d3YZC_sTzhI&Ze->HhImGMQB|2#xwz z+UI-0qTHmq^Q>KUN(LUJO}~O46}(&yV_`tvUgM~(81|=>(l7PMs`qp(qwzh zyv?SN>`0ZpVTUx3{I;-5f#6yY=U1^3OSl$d9jhlJBzY7Us(ys5?)Vo@Tz+`RiS{jv zOK@y9)S^pObth(+pEknrIAyR{j3_>lg~TgjF;4gWj*Ip51lz61ByM}tpxv1oh$dq~ zV5n(csfZSPb}~WKa|Dg}X^}3ys)4>>0Jt7punCbO>F_#=dzzqI-@YLGL&Z1m`CiBCRJ7UAiqDiYYgB)`NT7w{yk^f|&f>0AT`*+R>W?FcEMQ5u<39nY5Vl1~A!yzF z;ADWVodUI-pg_o%Y6C<}_FYJ$8ke)c)VkMiq7_agznG3^;@ey zExr<(xhS1DAC1NDExIhxaOercbag>+q*s1cz(&gWiMnU>McC;!@VhEU25i~CY#u7m zle;vY@CH|bwKQHq+(^mJZ3XWH%qT2{Rd2ohM--zneHVe=V9aF66Qn+-jm2V%Jcj$( z#Fgw8`3)+@5oaS9t-3%4$it5Jsyr7QqN!X8E8ZT!5aHIL@8B!~f^HWe{$UOrVPKg% z5ieW!g69eUalTCSj~r>wwapM2jqA(8t z94~eLrAY5jKTk)2vg&{3=A^Kf{0nlP3xBG&yUJB-cKm6+*6<>XZy<-KcATdz1_+Z# z!novq3j!ag229(RpvB=V?Rd$9=e+p=XQAr%W5QT|&(Q4qRTDU)1Q=<5J|pU88D;;? z^^vylB!mn7dHKNxFj5u}N#jV=9f?vtS29rP2RrAmTgKRc_!wU4s${>_3qNJMTu;bd z^`doy8@(8M>kV|u+e|)Pxm#~MgQvn$)&THNY}I&9u&`O0oU~<(L-BnT17twx1{jPX z(PQ^s&f4Av0dNzAS>+zZ>2fE!*&P@Pd3#^6wM#dbBN`Vmr)g542Yupyu0*-D{3|UJ zi|7(4%X$krY|+p|WH?HeT>vB^78u4Ylv{vw*W!8*r8b_7q@;ZlMkai3xeEJzR5BGI zs$QyZqED0z`ogiZO6xAI1!$whO}BSCEFk-05Qf7wJp1Nb@u>8?F4&3pM( z8_z5GaN%fX50({3BB#slqOPBs>j0?o%%9Hga5^}u-lW@^O@68GFL^1s7ba6|7zxGa z4aM(a_*Rg&O!G7+Kiz?djk?NxZS!#X1(>SV2$RvY>v-V;xe;hWsX-XAt|z$9rweZ6 z>vdnBr5>VaZjURpN4(xcsfhk zKy~E*f9;+3SJO>{#RUZcDWOSEK#<$CqL&TImygq=HAcTE04X&ea`$(_j!LRA1e@0EEMkD)h};9 znlScvHb27L=gPBpeZqFO?yz~Zd7VSA0ID16ecpQ_Wc3R)-4Gl#cQGFzHzuEs# zesmpq^N*By9fo9;f5FIMNy^AZi+ku$Q}4LTwja-ScrrV3mf3ELE@)ePHhEQJ9br{-vf zD)Lqk1Bl7j1?UAPv}F*watZWoN@IZzXA`~;JPnuqO3fT* z!r>M@!jTvI*q1}ePGu9Q8C-3QL>4tISY65N(g5dV8?Si)YbgO_dKJ8Dd#1j5zQeWK zM?kT6QiA-(LzXx}zXjZ~S9Reiqxu*hjr^8^nv5@@174G)e7Wo4AO2i&%2>L2LtNU)p@&NqWN`>s5Kfv0l#18X1@jVL(r)EP?wxttJKN zpU)a=cXyQ{N#eGsOqBT~5mO7?MN|i9SvDZH(1Ga|sJP@CbG+phm)*Gr0(NS8hh5wNDukPdDSVa#K+gRvvwqkQ^Oq;j=QAhfLe@QHGK7oM0KzrB zWeH$i+#;kzeFuOzXEv{HV!dyAPY9s|N^QW$5R+)iJKfp57iDMS)w}B4WCe zI5=YnOK`xk;Bn=4OG`0u6C9Aoc3&QcHn<*_Ep${;ebeslkoio|5GUYb$OPP(v;O26 z#u`m%({|%`+^>JgdiZ4_Ow{F|yzu$uQiRp+LkLk&m>_XjYe#@%#8e{oYjUX17u@_*L%67LsRj^c$(cLShHRqb$Z?TYl36sXRmmNCP zEl1}EYa`8Pf!ykj&vQ(~iZHiQM5v4$Y2U=rVObvJLxFwQ7o=brJNPhjt}IDjQjMtS zJFbecsW`cRxsokK~8u&mLXNWp+ zH8~)%JgPFS-e}%W%Z@1dh4d$&y=H8m{_S#9pj_fy3fRGHR@9L+gi-f7yM;Tw@-aROz zfEfel>9{CN$rE{oHn{a4{4jy6^j9veJ{XDKrTh+arOq=CpTNe7^IBt{7KoVYQc`4o z9J_^#2S&o}y>&;cSUANobGF=hZL>DcHtj)o&Uoa5(})^Mj`z8cVCHXL>xwT`?Ltn@ z)4IK)e4u0t+cU<1UKpoOC-n9!cGmA{L>XnD$uY&!z&q&*7NypteOpxx4YUaC4|1V$ z6{Gj18+q{k=3}@nbT;@lhrgH)vsgM}J0bl|4NgMm6tl*6rVkGmX`gY0SMOP{kNr4C z%;cgkH4pC0w2=|F%-wA{n6KEcMjT6}t8h zLav8kNdJK9y)2juVS5y}VL4~tdpyYfi!SxWL+XAE!Ek^VC6j&+1@*QSg6;!o%(`sv z=qq4!q+2_stTc`bYmdB6jXEJxy}b$4{#knG#f~ny%{YPWHAzAx^m)>wq9+E;wDsR3 zI3so9h_sLCCb->wu$4lcK1BibQ!)Y9)%(jxVZVhSN{6$#FGXgvHx-eQTeS+m zRU+ToGezBz-CG&o>}FZ{y`iK4zy-_Me~brGxUi5PuJkpYy}le^3IW8(&aO}DAEg#O zG7~OJPFMe(rTwc?HG#hBXL^YJ^YU97cFJA!4up`x*y=F!d;*~h-EO({P4_-uob5Ju z-zDoafP~DwL`P1q&RR!kVSfP;PhvYIHR#MKd)Dd}`bzn?#&)A;5W8{2pKi>orLBlH zE&t}eGXzjn%ZWEmzmbOk{lrOR^5^3|v89cEi*l?VJV~oe8vu{z`6r_ri~4TV@?!Cj zY3y0KQHvRD)<^U{+B?T8ZO0AmB1gQI*$Cn7N!m?2Yw}h1)NqmX}G8 zOttwc%Id_YqO4XVLvqJ%;qobw*4P|G{@Zm#SIhPN zIHUSb&XdT!cbwH0_3zPT>H-kmu=q0_?TD9mJd13*wNf5d%{IMft_qkI{d&jdSocfT z7FTRhYYEF^{0g|}@=uCSXJ(y98be%M-1cB@5DJ9!V0@Zl(F(?LbyYl8t((shB-M6o zl7n7WHO~BKKoGpfCg@5MXi56pU>vcNL#FuwaXhU$Bi_ilbhWnJDHr)=t+X1WO<&30 z>5Z||=RPyz9z0~j8fPI~?PCX_Ml?zoT3^Be;mw3`^0*$j_X9fK&g@Go6g?LU0P59J5uZF1#8Au;^G0a=NU^ zT0B%l@5XJwuRB9_!)`x6Wr5uzx`*r!+}KqTgFw5^bVd9#&3+rsrH*>w&jgFW+36>oKH2Ox`=$k>&(_obv9p0xlMD>n%TnA8oe#`~-zpWs0S=b*O0ov{S6kk?W5% zsazoA*i`9)l#yFrz=-pB>y_VZ(9szoml<m6C z`P$`nW^VZ%>t{%=EJDHWY3oMENO~r#hyckHC&VZi**Ih+%D{hv$@l3-&y5R2IXAxE z>?{aQ>=7l=Q`?9ZhI}fQsf)KDrT;a&zE-(A>`9^mr6Rrx3>u%0?Old0aYY`I_ybDJ zZ@KnpKPMg&LOo~`vlD1H$~gzvepKP(8F)WE@JkPVL}#XDUCIDA;-XzU z4=G9>Ux=5k+ zF1u$h|Mr+;?i6>TFbo?t{Y?$?FM|Lq%;q zYi_Qi#D#3P*phL+8>3royS0Cvx-@58-$@0$-4gIFj9s$>^V7shD=oZ9x3i$#gH+<^ zI=gD&SYj6ChT?O$dzHuNYf$iLSFkdk^uVg`_Z8Wm%7LFphV{K%!6-C&73L*w=0iSgXI)V zyO05Qai1T9z};3Db~EHs0`_)zoHd3EmH2Mr8Ix1SL>~>Emmx=z^CtOkiO`lHhxYkF zDYFD;{&rz#LpbH@aC9DvmK-BbO6AkJvcLeo5dmCu-gvVrZD+ZdnKjW--nx#P;t)7; zy2Po;>oop6X|P~D0#aZhD<|xOy{#nHfu2~D9-%5H*)sJ5cGB?)EsQl8!UxrUBlUT* zoK**7ZX&Gsmh3lrXX2*{RASOsqBtb0NDq}nJ6wp6n$5*H0=G;1k$yBnb%+v^n;s&O zYrLOi1=1+EZ{iZXtW^;eF~uC@p=mCb3Kug=-c`G{TEjUjBUpv8))Kpp8@5fj zevt@{7s*of)A+-s^B!Y33Ne3)pk2;vpn1X+Ze3-;Y%rC#0e;YR6{C!I=jxx%I3}Ax z>_hL+*Wz}+15LUP>_z*WqcpRHJG_!z=F_CA`@g@dydYZ5P4-MKc43^0fU4QuvKkX* zT!DTG7?e5rc7R&NO6t)$y}axF8A{1t8#I8lytzz;_ms_SCXFEP%3cAQ?6DSk=F*D3jxGp?>Gjo)1uft zeUAjdPxatO;xxMq@syqhKwXqjU3Dv;85N5{Omx`33Ks@Y6^+!ge3=*ze>|cig>O26 zXd(-18Ke@x3-Ywe+{ATezexvjWzz-PY4&nW8@#66z93Ek(Mc%!DX=6Is^?z;o#_Ja zD_ne6+WONt(gx3*a~JO2C+lDh4DQbMXGalDPvydE43X?L>Wer^vJ&4WgUeRRgF^qP z9HRilzUvJ;CBdw`VO45IK#*94q4>{xY*O35>(wk=*5tmgPFp~y4*&kF67lENU+)v6 z>~PyoSR~cFOa0#3n>*CP5$mjed^>O;O<(T4d0?OH2m*bSxFYLA66wNPquwNC5G>Bt zwdsbl?99-+M?a}j5|(UW-y(oybNL^89hTBDs=GHhGucN`^Yohp!05v_c#7Jc*RW4m zz|=PUfZI%)Pw2QEr?~Os?!~5Ku$J$I<<>K7(ew`OX91JK#4;1K2{mQU*>SBqp0@Lu z{{pVaZ4o->t$jrD8<0p0uOIQe6J!mqpIT_uJ5#G5M*V0}RqD4h`>PI6}&i3jG382)*=tImn|VCyva zQ`El0{bVcH->+KqNW%SfIxqW0jzW7Z^4o-o6C%uq)oLQLheErLRuYQ1Y6*%w@%#ns zrj7AF+x>Kv$pkG$h)wp~oZ2rg6h|8jWo2;X)`t7;)#AxeyY`JPYUwepNp`K1*?=v) zfn&TMZrh@ee$r_b5N0i9Kl<4C6Dw0KMHdmn?%z&`qsA#XZ-9n=I2t-OIRqh)8{Xw= zhtZ?Vdaz{sgoo%Cd!&;gZGrc-%f?RX9opW^Lu3_E7pCrCMRC76=HVQ8?uO92d{^I^ zEH5UFbe*i$J&5TvlA)(y6>6am3EdBL82bAM6LPCBJ2)2I6Y&TRCJSr(d#3xQ>4R0S zqh@zP3-|+GUWdO39t+I(6>avbKA(q?eVLB$$hF+@9cVX|Hx@88^&U@(Q4?|e_9G`6 z(dh$1wjAl_d`Tm~Wh8LahNxDVKbL?TeuMSaG?fzR}^2#9vDL{~7KcNh&NV(rCk z5vP=KHMO|IJWdAmHu{bmFF}cQB>4!<%Nl(7ro$c&$u-u9#);SW+e>#H^D_T}D=gh> z5G-9+X}vFc#`3H{&wkVNL`(N-2L^E}wSf79TN#}iR%jPS?g8PkM)=yCs94GS&1vEV z+cwMRl6C0C2^%>*;$d4v&`hlH+Arplnb)6npd@}DtPqu|?kDGtva1;D#a^20h>VY< zTQ+i|iGu-)vWdnQ5A@I) zkB1WF9NE8}9=Z89;8=CVP>F^C85|*t+!#5xr7@43cMBw`iF02zDm8Os-hBrzFe%bh`wT;jD@b)L zN3FR#k?={#OxxeK^!J=7IN@z`LaRv=8-lb1uTU4&$fijwh8135+`>(_Qyt; z<%y3EQc9h%Lxr!BmI1Zcl$Nfc-jT2iy_bQ_;XBA z)%tw_dXx~R*W`aND;i5@`?d*~e6@=vpxTROQ(RC$%wexo|I_#VcmB<8R?1boX?F*` zu+lzGkUvlZP3`xjhCU6%-q_*ibE|Qjao*%Fdy=v z7CAbz@sLz#jb*yd5tx8zIIh%7O6Ql<;mtdLcRq%(%Fs=bS_&@kx|Yb|I%Xu$y!xF>W_i88UKzj*2OK z`AK>f+%ml)Dp&Bv2_&gm6TmP z3r@olv;|bt$P8gsx9_-t@@6MFKYp}XVm!>qht34Z3k)SHCp{>`OY(j&t3$f{<%P($ z@fgnZvXwac>;CJvYtafI&l z!%6?RMK>=6vR(kcPzw5z2=Ka6p i;NNlZ|9%{V-QI;x^~c3vJbCT_k7p_`l&cl3!v7200%_3z literal 0 HcmV?d00001 diff --git a/docs/src/images/debugging/debugging.png b/docs/src/images/debugging/debugging.png new file mode 100644 index 0000000000000000000000000000000000000000..80cd75ae8d318249f45bb9612f28ebdc49dcc610 GIT binary patch literal 44826 zcmeFZWl)`27d1!-76^f$!QI`1YjAg$V8Pv8BDlM|ySoGl5G2^e-QAtb_t5=z(lu2x zQ#Dodum8budCuPJti8?+l9v@jgvW-5fPg@h5EoH|fPl<}fOv5L2Lq%)<#n(D2~=jv z>W=C%(p-i%R`do&HXn`YU9D_^dD`1?7T zftc{GB90ck#OgBggu*uV#)RzjjP#7eeDH)k_C_XLiXx)_s|@_dOZ>^v(UyyW!NtXe z-i3wU#@>{HiIbC)fsvVknVAkKLFeFR?P%aiXYD}p+{E8)h!{H<+MC%rn%P(rKDTM` z(Z93~#%G3CN$orF}Vr>SDm-(-1&mC~do4FcWYKWLw8CyI2)hsLL z|20?rU%BiY?0@R2I2hXt+gJfrfi{&4?TvvpKG`@DGdwRj!~fPC*gS^cy9fO9d;g8C zflapuc2^l?H~|6zzR*lqSYAR{m{8u{#>C9h7y?2(C@uk3Ic65c_xP#cYhe7C){cxj z0wLLje6E_pD+youk8pt|Vi;<+bl<-DHfJLc!_!Mf)e&M?hWJ94;utB&gL?fD0{ju8p8CAZaeLMM%C7Zq;ge9A5%<4W(LHrZdz!>6$6hkWUHG~9G)r9@bfH-b6 zW_P^jn}`U;Gpt4vqtXq+&<$wOZxIlyNjDcg9X%3Yt)C(i(ik)v-43Qn=&hIxQjsHr zPyBlQPw}CSzdYZ(6p^43>R>ebu;>3}TopzxHQg;$<4HsI?d^vV7VaSy>vT@q z3W$NF3o^g+)Shz#$u%PulC7kaO+3EQ?l^;JZ3!W{KAxID1h)@nQ79hp2~$&y$<9;bFz_o^)~TuxJ{UybHF@eqd~W57t}!)w+-&8^H?nb-49wU#DE$m%^$Gnf zeJ5dsTb^+3FPGb+7kNlDKqih9b>lGMMRrx1YCEuGxgkVenrN;XwTZ6;!}3mCr&?T2`!02uFO7k7HwBICmJ3m|-g zE$DKy#nOiLT6yCN(W8&n3W2)9o9Isn2OC8Mp9^Uwco#`!g!qd{?FSBgV0WfEWj1JeHO_tOGk2dI(LL-c&~ShU)sO5f5sP; z%j_`t@y;ofs_)hJo*w;Zrl!P?($M*DCW4y+n*y+UTl+s&1*;2}zMMwF36be7@3!tb z>SZ@(X+-3D3(3J1z|%XCv31XGH!k~Slgd@Jff?2-J4dWv#N zbXpc08yg;*qg;q4XWpiqH)&TyqxfAhL;Q>sHJXAdos`&$Y$~Q%{94>*z^MAgY#OfE z2)PXzMJx7G||3ktU7X@H12NT_IYU2p62BCiZelL(fm8<0_&nOurBZ3fl)yfukhCkAKFeF!ii zW%;5Ju8}ia*_7>;>6Ycza!Z528}dE`4}%wDnf8!&f(}W8uQIyftbw|Auh!kr>f>;I zd40Q;aQ$hOhUtXC`P}qmyhY2X^N;Y4m_`i)?1TcV}OONZ{|E}yWcUI;}2R{@pI7=ai8 z1AS9u+zoMGpCO;UmX2v4N_QnbfR1HeLyLVHq(sV}z zc0`{_mNXyx&Y47Y}(^U0Gseak}C|`+hvhSl$KBNCEHkKi0Mzuhpm`Oia zJix?tk>xZ^-S4Jb=aaIRpq9dxa+X+1;z?hNSByG~-X17fYm>c{TFkg3qI+M)XT`{GQ&SZHA}5Z&8cEqx6R=41iLT!rA1a)UjuW1#LV%1_qL~{Zj3d$HjH) zu?NSzTrD8CUE0_gLlF^rfMOtWrys+xV`LGuXguX;7#k($kOS! zxV_3(7K{=y_cw1h&Pf(&nhCVOHQC;O-05ACdXZ4BebwZd_4Sv-QSeaQgj!m8db4Q9 zwD%&|eIBs|r>tY{ac<6HdER+q)kG}AP|x&f4P5im`sKEb+hVh4irf8N8kx)8vFH*W zb|?WFlHHfSqr7DgFaB-tWWXg76Ot7E0AI0dDY)^9V|_Wf_iByhyY6?6^gw<$#%_GK zQ;+FmJ<*W#=nPe!3HND66JBTc1=H5!+wtuj1}KJnU5qE9Yq4RP95sq3$EWi?k)J&~ z7OVTa99m1otz|x@_vIeUMrWt@q1Q?Tl6=LUJP$$OqHTwZZBwNtJzG8M4s4&p2aJ=$ zRmZ-IT3<4w;c!Swk=0yMh^5Jw0^!iU>X;Bj zM-Z+~!&Q7A-YR^k6j|Eq}KH4!qq zX6>irSRA@JL%RlWTBx?*-}U~^7l03ZvjJX6gZx*Mekl3^*pz%n(tipftG|HB^+VqP z|GyRUtMsGuy;lOpl__w7?)q>O#N&0Pt=r~~?#U3XCi(&ax;yX{$y<`Zgdb5P8tUdJ z48bqVPJF`LK0mh%NoxB9e)$TWtF|ZftxWglnydA-xyzJjM!^M$G)$N5&VJgHwO%Q= zTzB}d|+8lqTn-4F1(;~2zJTNYbo`EAgq^cQNMN& zJm4|8k$3t|vQTVg1Lcsjp~j>MKFfp??Xhk5YSQ@ruY|r8PN-?W5&d6_c=H2#%ye?k z+#Ns)9oyfui|9&yA=2VnwGaeqKFYDmtdl#%OUH3n?opq zz~g=)4s1>~;&hn~35{yGyxumx9BjVBHl2Jx9`VpVsDi9^4+S|@@uWF<D3ld{m{AU2ZDO~Y0UitQ5qBKb3sX>%h8)&60I$!zIB?AGDxK;QUW z&|F;Mv)A^B4!02$8?R99rxB80mAsO%Km4i9`!^cFyA!5o8w038=rs9;7+rV$a`;jQ z>Q$-JEqrb_Cs~8nB5VC2+qDA|_BJIPL;GqKm+eu+AI7)$l_OIq(hts8x?Z|%^^8rS|3X6WYEcQS`9>8rDP>v?*^ zKDpRipOMN6hla6cD-TZRhd|54MpbBotOE@mx(3bRW^<@T!+USDiQM>)dT4P^Q)TPQ z>AV4&Cq@~9!e55=>h^J6CWcZ#712@&72=2Ph-vjinMe5t_M1P;LDx8pA}C~%gu~vJ z2eC-|sN~Xw!zpvq{61jq=BH^bcB`f06bM^7SG+s$lG1p-%z7aJ*4C(Z z@uQuuk(V?sA$02aL>$#E=KK?7$9S0<7E;HS5I&LJus-&wIyBRNMM=(Kn;~|T*(lOd z!z!aCUVXL)9mqB2^0$%-u8 z>AWtwusJi^W2+?AE|<9rOC3%LVHRpX{F_+vOfHL=4aU*1`0Q)v&qCwU50vU8$#m!k z*}c2JczHaXT(8|$8-F*qN+1$;zddwe$Z?&|DJHt4X>&Uly}Q7p><>?iG>hpUN=BD5 z8t86{rdl^WGYrXaq^r@pcfffzesDcO7;@9N+SFDB?6Cpc*XqI5_LtaZrxT;`@mPu7 zCq51vJ9hR;(1^z@Wg0EI(>5~~d`MYCx~+~$xLl4mZ3Np&?|oZTC0w=-&G=mI?;4KI zM?)m94rYy~OOyOZ@007yL)4bNuT+O1q2UJ^4&?)|#opk~Fr;u;MVkz#luXac@xS{# z42vlK{R=|DuNZO(Vke9F;?&W!j+JGLty(-b_ZuuG^U?|Tr}Pyh{JJFcjz>*UZS&T3 z?LwtF8eTIxsbr%3U{TAK;A5Ln_@1NZ;hYnQ_;8eWmcsI#mIDv4RtM09(4MGJ-ui0A zB1}zL#dpJ@TP^OF$>pC9&pnU?hpj2mJJ-TH9vQ51#k2g+mVKT$@xzCnE$IThW8sWe zF?umY1Hn@C@>WAkmBp7IG21p;;R{DSao+F;VAF_)7SoIjx6+yS4tpX+Iwj5MAH2qm zQ@OlElk4t2$flp&I~<=x8FDRu4oRTx&|NstZg!Ml_Ka7Erv*#VYrEng94>e0v5k(# zw74ugJEbxkSCh)bODmGfz0C~f8Di-4hvfexu;&UEQ<>mIfuv=j(wr>x$L{|Sk}!CM z$IUy3o-={L@TfHyOHY(1i2=ILHv7Q@ovVG2!LNs}^D`)>05{~^VRn&^!+K$ot#L=u zmtb;-D?ebb9D`qK67Q&IKq;^5sU7s9*bu)XUM`)lEM=F)JHCqw=PvDPXL_IrAy5g1 z@7Wr!5F)cns~4$Ore9RBh${BVT`!qS;k_xsJbe*PzKvb?M4_izNZH}MN6uk0XHMT> zwM=!$CebR<;7_7(TO?cX`o zB51Pvu4Qw6{O$^p@7HH#Dkbq`)}q|yPJ?!sJTVoEwazROGKc(8QiKwiT7X6ri`E*dJ+8|k zGEkfMR~zM~JO-HDJeDJMTAb&WrTW;F)$uqjy@v1R+4a}O1#f2NOFBg02n0|1gEA4k z7)V4z_3vIY=gLvYq&yHVx?jb+)fC*fJS}MG5j(J=5+8;4>%&TMz{ZdBLXQ}Z4 zltw%6ay0FxfYC=x`i(%ZSgoWK?4pMr%223%ic?f!eZgV1lpHVVMv>z(5bP=zt)H}+{-wTAO4rR_99dd!MQ|`a;Jh?68r6j&b?Mv5Itv$x3|*(; z{aNAZVzW%ZGf{tr$DHQ>u~0uOcXeGqF@n!`dp@dBtq=ArQfUdFROJ`lo49vjo0^o^ zUCJrg7)sEq=$nag-t+p>Uo>H|bM#Ua+{x!X=F6%zU5dh~D8gA$oF9TlPZ{P@c(j_|;MHX+&Oa@#O`LjKVkZM_9yP5$~G#KkX14L*>qV%GciL$7Awu ziKU8OFA^JA|JL4J)?^B`}_NqJ}rjmyzJwtdIWZS2VGFDPtrUiS~= zNzBHR{asI&Di+iGzO~+M$Hx0tc;odtVz|Ti=MmhW60!9CM*|@CO=J@%D|@;7@redu8_W#*F3wnpRKZJcq4x5wI}{Q!_&>d zf~Bo{6VqIBf4ZosCEY+NqC72EB&lNKAj#nhTW~piRdJHy7EREsrJl}ctWrT%a2K%n z#=h{P4kn}2bSZ4^3|)mUzGzA+j~m|{usEHgYE{U_aMf8Zx63eOa5{*#-)?EOJ?#7v z=FGEZ9ZBOQS>mu_n-%3WA*eAPqW8EyQaL8r9(@%048NZpDyZV>Paml6a}4J7pevtU zzm0zifXUTUqbvw0Mx%DMxJA9QNmX?rGrMfcmyA<{b%s+8bJSrY$Ta5!;7}j+J;|Mt z^ZtTh@nm5RxN&p_)IzWS)OP=7=hr>wBtKd%W3j5kY_LjmQ@_(jZowvYzn~I=;+^B} zMGi#=WOamL(o;j2+Uj7U!Fb zT@Lx^qLuid=R1@D6iSactr?0@BnwDxa2S^3f^-RmoRI9ceUxsBi&g)GQE*sSK+zxS z^svR?>Mc~Z__p$O;p@F84u1tw9_$QZQI_}lKUfq$Ub*iN4l?4rpn8I+~@~X;3dU;w%_Wtm=akUvEi8%@+|7qO<7m6LzfO z)B4op#DaBz8mn4- zQ%9>^tL!Ce5{Dt< zHZ}&A(<)BZ=QU1#%I-wLXeRZ9WMLwSm#-X+TzacN0Z6${)2S=MqPw&BI*RX;fVnoZ zf1yA0S<$auL!+B9KX<39><<^8`W(&gvb3A*cl4_|?sB^INtVqCs>&Stul&m))SVN? zJpFArcjj9P@Z~@A0AB;cy|Vrnle0Y)nvR+jz0K^=abq_02|6~0caV#AsN@nMCNdAH zC_OP=M~f{LLu|6-vGB>L6q28KrULTQ`P`@zL5|=1WVjlCuCZLI_&vB8pH1#E3tvVF zjXj<9BHTJ0K~C{FsMwd7V3WS{)~K&_JepQ=zuYOl>oro=p(uZKC%o$JaND|j2)hCK z404g6!gaYQNz|@l_OHt&*VHBQG-j_*h*EFM)xmh?TL+xYEuie%n#rd=k>;oM+<}P; z>m8WS@UNjF;V5p$+2>BB!$xW|=^w)0?4l^s@K9l=(;S6$@d-w!s9jyjTWp_Eu3W z&s+#-q4du4gi)id_*d1Q$3mBKDvLYY<VJO5RI6P0EbSij3lLiygqdl<3_sl$c{RpNjllS$ z(7tX?_MRb;MOX!2IXbO4!)W$wd3W>+!%%FrPRrA;+p)_M^UdJ#yp}SPfkH$PH*q*4 zotV?M6|?ceE{!&iJq&KoBse~oW@oU<;!leDYZAoI-{vV+Ya|izY*V;gJ{9=S@+nqH z(zW3&<^V@OTdR=~>0;%DE()7D+HIYV)}=8MVy@BY$L0*vT}3j-Fq_ZE#e;jBAx_3G zVJyP%xdkJ<`#IU3s-o}{gB320qV0I&rr-{&dA%E84+|cCgmtN=nmX$=}ij|%Oh$g+&AQzmU@4wp-nO0!=_?~8?c+IYCr*5C)k#_mg zwVk)WR$O7y(do#aBoxZ~@~-YpvFH49#U;`th|y5lQ-8u?$_m~O#-Cdbl`mPZkRkY0 z1q=r#`TV7*3h17l(s|)%o*7cjQ>I}Zv$Sz2q^oB%op*|@>8Rczjq^q8=x~ox$de{% zq9Gc(=#6MTrvo_lcz-f(jZ9K}2xEB4H(RLq%R}YCa)X=-ac?xA?xS*;^BBy?78K9u zBS5F|^7C#1hn!NtFG54Ve?TUFIykvawL{a1k@!91K(IbqKS(f7N`H6REsGt*_O6%D zTc<+LnCnXJl7;|M$B{w*juCR%TTLNMH-b!~#(2WZ3ki>>u$jm@PJYjc)8(l2VJ<5N z3{x;wqPm+rPHNs8>jbl7lAlfzaaxOI0b6t*6sR$8N@zxds~k7R^p^;4`a9S}JO#!rZHQZc|+ zSzw?N4Ih+yv%m!6Fz2v|0^tsd=XszNAL;9}?3B-FXw#aEhq zx@xX0A3RiO{}hPkmP`}Y>SV+C0SAD6PS;|hc%BI|g)cV#^jTnILK>#DA0eiGj8|mL zx$nP->2S!`;&^P0|E8}ufTV!bRM@{Z^KV+)7ENDZudvJbUt~GZm;m<11`pw1EAdRj z0k#6-rJ@)j{a=js-@GnV6U57LLW=DdRR3e(fjQtpzl-EDw@m_S{9Q<%@CT3t!^w#x z{&#oJ=_`}~{j3o17eVv9P(TlY^qsz}KL*?c{tV-V7%ts)^9!(dzlQT%@9 zD)4X$hyDJvm5w!lGs%Pk8@BxpIc%1qah8@)fa~=ubm~d&?O>h;>qW)QZ#_Y%TH9S* zBv}1y={OOMy4np<@_k7)+hm%Pl+nz_k`zcJNCdx^PQVu9hVVs^T8Ch@@jiRewHF;= zN3#|i1lRNJHwRUA=XTYou8pzUyt}e{&-%apa(COw^p0k*!S?`nU@*k3lJw7H7!rB6 zx+-?);@w`Qz`M6a6&O6ak5_u@cr2k($VvwE4PQ8b&N~Nfs8wpV*)W(*M@V^_9b-u) zQ1(^y)M__p8=XG~A`vY8$R`L}=PFZ{D^@KcwbEn*`CVrc`TQ6+rb(^7INu}7z@*=( z4VU|8Pcng_yJ{`&bg9s5w5m^6=xy7KpbsSs?M;<<4y$F~V7kw5(aj&Y(d)FA1}KzL zY8PgDJr++{91dAbmp3qQ({BtW=_Iq6k?O_Mm07UeBjOT!T;tjPJR3Q?3rjiJS1r{h zsn9DamgOh{{MaT1x`4?8Z2&H(y>ee@$_{zLaY>~faKAs~Z?ZQ6C8-tf3o$0M%b4$$ z`!5jS%tM~*aRX|C2>g3p!iK-TtFTe*A}BnoeH+3aKY=Mej~4Js`Y6 zH_iAmbu#`2vq#w)=Mh}aT-ifxcb`dRdW~l3t3!T5IOWAWP=(%pHL+MYZ3ZS4DV%!* zE-$r4jgdFSJqP;W{@iG-*|>Pfv{mo@*09UjO&ug?U7f{ZgV%AVQupC#z9P>uNzt8C zr8w4pcih{1t<7jH@YLEuQh|mGpsP;L_9zCT z$*$uNA9>yy5kH=6jJ2ZIfju%{^$%ShE%>Do$qEOOoam~+_(V|zO+ZMeKve}W@ z0qtsW75aB^yBtaW8dYiP4c@cd=V&csa#}!QOF~d-@Qcdn-!Gw4P*V9>oE3MA7s-LH3P z(zu-_VMJwio9iqryFM+0izAFte7q#%De1Ku4w0d<^CaUV54iz`12<(~tz0t)g#IH# z4u~pH`i!J8SWK5CFa%@RQTwdwg=}W{T>sd88rcz)EWD*vC+twd@23zw(GYP${EOzt3+KB$<7lsIC zi}h)=dd>15`@XHljy6!BtHgN54Wb{WtoMeLBq*5d#}DV8B3*6*Tz0kLHa(s#38QEy zA_DnulZ%W3CCxU=?Vl?ir3i|ZP3Mt-&Qa23E1DS+(-)>Pa=<*=kBaOLgrZq>)$ z$rJ{?_BHyHd8aDFevHXdY<^KfUgNpfSis$cM|nb(2+n#cyXf)ZSDNqh#s6;`ABg4$ zF%^mZejtV-VfDLMA?JHxsnNB5RHBR|mJhiqtsi01?i1L{a-+@J#f0SVtTy+m zW#gV|G>_kZo|rj}A(ftCtTr9xTRcXSx!U!*^Vv(AsyOPnbT6Yhj9`AFuQADcC6nYobV^7Gtqmq}4NoGNAR1C&Vk?c^&B;^+< zp-iPgymDjQxK7%PU`qyfA5(m zE2LjJ(c`D`4OHH@ll9pq3IEzpWj_ccbNC|tR_os%pTG+X04Zqh4~+j_CO1)7LjWxJG>}v`9h3KacrV}+0SIG`5<2_an?gXBq60}~6jC{Vzyl){fI3Lu)%**> zsM7#RnsV`?f3S(ZB7pHE7)=WPRIF45lIqfF!+y_AzB3nqLoxb4|AzX1jZVJ|NV4G7 z!2Sb7d?f(5Lf)15FI7ROCXh7kUd{TidB*^tOvayy`(G97fTXfZBeQ?an;cl3WGF-D ze^vZ%!Tv#>zW*)QKY;tcFYNaT`R_>nHYxwvu;0e}KcoNK+x_Pye|!1=V8uUh?>`*& z8-V@?D}Dpe|6s-cU$DZ!i)I9f6fCpb@&0z#0GOjiJz*mZ!{n$phBXlUy$!%2J(2O1 zxoRJ<{a0y$NH|79L=+SJz~5d@zLNvEY5adJcEdHuCmvq@<_r+K{v}|IAW(}YlOfL% z{_4Ix7J<(j?{R%BHB)Io3Q&%+^HonZrlVw{p*zo)%>pY|fFz{@1dODzsWT|llP$(A zM}S-zfzt=yzZ>82>4pPc>mn9I}2z} zx*!#yT<}^P69A3E%;U%2V5)$-%cxPlA$`5CpVK?8qsr!;cbrP%AB~Y7Ea&f@^?1fW z)I6G7O*XCn6a+#q*)6~Um7j2Y<96Mt2Cv;%Au_5DjBu=j8QP zDKB+An$fM$s{B;-!1S?J7oV#zV`C_#pX#_8y~OB8S2 z_@O4^8M4V7Rg!GLqDdiS4Z^+i%qH%3y3n_bu>HWdVpl3 zX-u8HTAR)>_Jq-BG)2rorSvK^YPAF?qo*7tjb2}Ocv?$dgPIRs zC^1}bUCy)%KtLH?%)F9y3!{LWdmAbmj;jGv==-dy?jz^>O~?Dm>2@k3?nd{9keSyO zDgwMJKZ9rQ*(Qo8TSWqu(zI3kI)&^`vI5jcpVwIW6{n%dME;rNwvT zUS|wwrq2x532;MV0Pa`vK3wOb8PK4T+GPjI&foqyN}hX=`R3v*rUS&NeGxI2`=DF* z#ec}+zECW$VTZFxv$itVs_ZL$U}4#9sD0>el-g;P2HTGTqWgl`pMzB`XW(JlYQJNe z)xaOB83v`xWbrP{Xgqe;e(wSc?_C15#)3B=n67lWQNol;Gq;j(OoTc4Rsq4|W{0V* z+;ITq?rjGOnKY#o=5UHhPczhDSde@GUjo$;FiQ_7F=sRFWX)!uMm#Qxo1~J zqY_4~==X4&Ynuh>?l#P&RrC0Ge|_*MYS`z=t^O(rc)yWTv1kV|2?%K~n)L4%J+4nW zMSi1E|1{qsUo^E0YA5sE#excWNs9N-dHd7lRnR$J_fq3V0}p|cN!=Kwx~jv!!gm2K z(0+4|&EurbEQIbv1dyxj1G=fbgA&k2ZtBaOX4U zSuT1}zle}sv0rV6ywDJx+hUOt2tSy;puYIZhH{st zTB<1Qg6Dshr;fV$wX_?|q8JB6d18-`eBfDJy5sVA}7V89zS^@D49Z zXZ!*8sxA&;s#X$bva>*w%~FZW;4x4_>C%^ymW1tQ`8t|Qg?b+KvgO{U?a)}?c+@&A zExkX2V7dJXV1B1k3neaZiEN^Tpifs!g`URphT`{mCdeZiU`OE;TO6Nc5c@PKjE7>! zw?CT|=S!y$6)MO?lUC7cwJRR0wV5099>&-aroL4Ces>8K&?0}&W!+86%NrZ@_0L3C zlrZ6J1%eUe%FR)}A`xVfha;7d=er!C?qa^alW;zKD8YAYH<-Y<|K~79Nq$@H-zQ;^#&1Rf@{cpKT4tARxInQ%okB4BjNY#vfA? z4Z|wfmYc$vsvy9Akdx#1ye)d$M`A*-lLfg06P7%fVIB+K{s1pAf;$_g^RHk7pP8S0 zanOwwPSt3hHo(QoIH7Ti(;omf$zM0JL!tok88M{@%^Ubx=={_0kQfvS&V6%Y|}zYn}nBM?DG z2E(ju{;7&Y*Xes9+yTk+rw6!IpaPyg>vee36+Cg%1ff#I7Aj~)%WMWsPC|nm5%PR(Zc^<&alvGZSrFOY$BMDMPk?pN4AVMu(D4%uUR%yF` zW!2;~GeBi?PzidTHY3CSJV4@?O$zbdTy6{ zQ`}$0W4PibE4230F3V|-xU`xu z?GHkz=v^LC8`!eq-Z)zHh-UF%zs>JXxbynl3WOSCJJd=s77kX)ZDt4=N==?$$qD!& zQJzRZ=UVxIKX5rcsNnOt5+V`sP1fbj*IPNQg#d5K4rZ!!*e$gg0EK;YIpD z{JPo&+wHtffdAogdD6k2E+P30zjaQRvbisXMnes}MT@RAQ`FL}H}pGca@&tzr@lYC zJ;z)w`dn-CdPRr>9U^DC!7iW~96y$AMuP5WQn#(uWRmt!=h$R4l&k^2HA-UvvWZ5e zkdt`gGk=KbTbPL+FGt|}cL)_B3& z_4jcd_k+8A-cMcM?YED-sB~*1M*_U|rpoh2O$S3c6&nR~B@(Um#V!dREt!~ zvL90wvKpiwk9nN#LVLo|rOviSh?*ScG>Z?DAS+Ln=5FF?b!mZjgXh<=s z^L4l!w^L8do81F~+=Fdn%TBjh^ACDd^Iy-joy5Jf%*CsYW?8noJ~oP`!dl{@!o*>L z!VNorHaG&05;yH6oIJf(W6AW=wVUII%<|m*?JKmKunP^F<9F|PY>raL#i0el`Ss#x zaw1;Af>JPB!JnA>oxZ<*HF~_<&;}0QT;J-iw9}4o>eJ`hu=C=_a#PUq0(XD#OEC;a z<&H0}driQXx^DjH{oP{2edNc+E>jx(>rT{Gn-lB~&s*zZmS6`LnQ$K#xeOk!ZLhs$ zvz13apH-BBXwIBk>m?D;T)oAFPzQZT{KMh$(ZX|xYo!f-K`8^t_!-0ob9Z?@fiq3Hu9b*?--L!(y zelq;Z{t#zYvo#QHoZf49<-b7N+JLRdn&fggE0>m~KY&D@#<4?u&Q#N<&<|B|N0beI^B?b|nVSDUILz-9k>unM&orqetpM7*ZqXf^Nhi_N?6Rv5zXG5oeQ z$&L=a<5qIeM?*8g@6i;@sW%u)OJ4%A8rvV+2)3Y$WQgz%=k5>`7UO~*n4T}yasYY@+|7$FbfCRnTV8J@0s?o68{x<0Yc}Y6SW-lMc1d{z zE`h1)RV3s`WNyb>nMHJygp0iq3{V)WDVX??#WFKNBnDq4o<~yeB{Rc`C z;k#P%>U zrMIjwXw+EeJhnx@w<8vmH+de=-3H_h0tc3BMkaq{6!HPb?W)h!b%_n=Li;eJ11tCC z*Ejpudh1^0XG(iQ6N#FvXS$4J_Tv zYJmjb<? zU@>UTBb1SfWlh`8doIlO(J&ATJsh+zOtua4K8(F&)|KESK!HAyw#x zI!oE>2+FsBs`7GojxvvHpZJQ!Z4eBWuTU$@FB{UWGG zBa1pZk>vmU7QoY>vC8*7wdN9YkbKDDO~9P&>tHkRI#RQ<$Drf<;ue$>W)RL-cj_N> z>Vz52qO*Y0&`kND;1gR&)Mt?*eHqkyS#LhAuII7r_dQj1J<~j!N~5)q{jpkFg46z@ z@$n8hlH?toW^UvgE+19D!iMJA#0J|cC9J*}KxI2v%(lOI-VN>K1_xPDl&n?(7EUuy zc;w1gh~Fn!s&VaT24|8lGOZ@R4V^AmP`QQ{wf%No{I>SqN#=&T)8h@t8`q4FoMQE~ z;l1}wo|naC&V&lxfwH&Jg_*b{XrLchPW_;i&}a%UK$|`q)e=>>`u8!SOsX8^N(J z$vtVsUl4W^GBn}*-{nG4e_N<^YJt~1niZQi6o^q^(GqJjxH_1x5Pg?XlThMvaqD!~ z!Q!7U1=R>g8#PxCkWMM``^pa?qf;rh$+)VI@}glWxv<^_^6>sdxsBA60*u(QIt@&7 zcy+pf`-p|#94cP<;ao74scXe)f6!<-U!`SVlq^KTW-UMJt*yL2n#`gH8vqB7ENwvQ zxbWW2aV2c7qPebQe;w-{Ct@eVpWL?p1-A?iM3KcpQT^2)Sj-Qbqi>=AN%+>?UV3;eQ zvz$$ggq|T<$NNx*VX|`(3P^<4LGLkyHNG7x~GJ5w;t9&yJnAO%0tk=b6WKavgG-S7P+=6b|KYxl4w4+3tDx$g&^lb9)7ai9i#t}0umBbCGq zcb~=$S}z;?LmQRQqD~zoGTk*4)D;aFuSd9r5FLysC466!m_()wTmI!Qh%-GNJf(s9M@tW4bHP2)Fi90h+0pa)W_7^XwUv%VIs!tMAkG zaY3QBZ3y}E<}3rJTg(7{%!PMPi`V`^c}<3}`uwle0m_>ap>;Nw+2dSlz8DH2mR?hX zKb(LyM~L*}aEwFNCl4%f5ny(x;Ggp3Ygk#>Rg(#bg7rOD0aUQc}!5t&yvi|(4%MG9GO^o zW5LRA`KaB8{0Sn^(`RBZMIzzpMGyNKBAC9lOeX!zx`--dIP7LHbNGXFSvi4iY-nlZ zA5*zK1)=w_yQTo=^zsdAwQ4{ZzH2VbWu)MzbP2KyacpgL9gWO#K59ah60KZgthOpa zC(mD2W#%r|OPE{wygrVQfj<<96grEX0HHGdt}*Il#?Ev#eD5xd$(e_MYC^EX?OMw{ z(EuiU#{~O2Z`drfR@g~@Ctr=AZX8|yk=53i?(Q)Qv=S?y8C9lec#f}~Y{`D&C_*&I z{{CvoDZOO1t~(9EA3JdO{>FT??d>Z#L?rg>yms4Oz&ONVG={mE=L_q;jv2M07LPK0 zmkD35vAA0LsPL@9Q-ho7)62Aps_k;vZUS6ESyB}8iz}}1-A` z=mY7ph(x@UCPvqV*MQmm3UZQ?ccz2#>O3AVJ!J`L${}SIIIF1f&=tt0V>@x+VlxGNTZYiv96O-55C%VXxwaIe$ z+YJOh8GGJ5)#9k*bf9QQYWQBqA<2Eb+rVS7lV0Jz%Wr9}OK?H@)d7Kpc^ccyZ=8tOv|O~wnaX3Yrp-U_DP3x*=~p_rS>iIq(nskO79o+4G-6;Wn6W+5+v3-f zmEBdeS1HK_jWH9k(tca0&>Ut#RvIo2$hn8$65~jE;H{^e>iX?@ex2GF>uJQR?@hJq z)vCVq2Hb+u0rzCGb)#Kx*Tf?C5GJGWk{0mC-oaNba zPh17^2ePX5J<8lc4DJ?cY&GGZzY_>~y;v11VKr$z`R7eoh<@iqL{$GgEbg+k2g|G` zx4do5(|oAP3i4KfMr&GYp7EDHAsrrj4h*pd8dRE23=34%8lL8tMjs;) zDJws|7!lfBsP`E!TMq1b(ELvN+w$(Dp$mMiTHiAJajK(_7HX!zNbi+NGg4%V7Y>qi zVXta>q^&O3uy%}(rdomr6?)C!)p646>d{sy$I9EToxXABX zryNNGoq7CpWWRcA0ED$@$E4GKX#9>@6V+!v1ZM)Y+e4H-ik1t#nXqRb?9o~>+$WPb zjV5*RgQ(Z94F+ugRBrR?4Qt46(GIT?2ZF{y?5iYQC@4rh?ZW3=$>|4(Sq?h^BEbu0 zD1-GSIlNH6ikshh zbDIfkqCvHA#RYG|Vr?X5CeX8vlL>(|dzCGOO;MN!XIg zB%c*%#R{oM6H{R0zS-~s7#Sg!vD&Jo3ez@00;4IsWX|VX7*YiQF^Sfn#v5R%wc2PL zeoy$oEWcg|SE1Jpch_R6Nuye!_zN-A$Q6D`5UtRPs4q`h)PfAWv5h>FW6Iv|yUsvY z>QH8H_$A&#?Zr)@TjohR3OOm`3JZ3M4?k+}l}GGm1A^8ba;OoQENThF!$-vDa|3=d zq!O}UyJj)#{bXUjYgp}7`W@!MS75xJojgee_H6g2TyZCsIMG*|PJ-6U$6?{NJ)F1K zj_a%Jy4)*ddvzxax&>v|sV;jo?f9+v{e8iVZ|e%7B6SIs9s6%{C_U`+xCu%Cfp`&e z-LISi_)K} zPZQCfekVL9jhoP#48${fdaWYeGC2}_+f>8mV{(5Ovl|A@+P;U7+x(bU-$@`P5Z$N) z&0A_!+)S`+*!B%!&ShjdCSrg(`z<_#4x_Z z@_Q+qIB_p&3mQG0~!grm;Cj2j3_j${J5d1aViRv%i*;bAY9UAPDq$*y8c`@-dJ zpO?R>S*OwY{vl5uv2M4L4xd`dcm5F;152LP8sO?OGwZEoopHNmg==7i!D492SB>?_ zAzec3hyh0}PBJ{dxe;ERy@T znFx;`E{JsM!7m`PWw!!~CN~GrH zo5c4&87XACw*VqfTWhrnYpuYKlX=LTH#mt-Y9@AOAC@bd9@Xr6(+6=C8~>+L`=kC^ zbARUt#2V)$xMIv&Qoen_omi~36hB#M-U_>yF?VyF4BG6?Hk~w*uTV5)l^K%!m(!Ah z^_vnu^ES9%@YNK!2fT()?7ww87Z@z|#z>;5toZWfGIO4f4<+Ln#1{c|g8czo6u@^s z^|%zZNHEzL+^j}>0MUQ12OQV2VM=WWIW_xX2AFv~u=F6A+DA1MbUAc%CH_RA{rHgp({GneY zdNDt&`L4`VCX;iA63BkM+lsX_wOGa}KVPu7Qf)OYoGMTrr~bsC z(L~p%NHGm$9FaQ|eFZXF5c^YElighfuzWNH-~!cYe+3h8hrL?2(;0v?!f1elxlio1 zCrc{z+#i#ch^M0j!06l$qv~~EUpc#F?%H^Oojb56XuOUJNzaZa!kboMY^Vjezz)6* zc<*o7C$wUTQQh_{#fy#uL;SmvfH(kh5T>9v9QVM^1TqsXcPN6O@i}rw8dstuW&SGJ zv@K%&+J-Ej7-#XjMiy*z?%>;LV@#{tcUX-3SFMjz9oZU@0M5RDG2i&|Ym(zHt`m4t z+qk@4%`^s9o&vJm=A##OHmfazBa%=tG8Sm_3^hn=>5g0JT$J-J1 z)Hy!G`)>pxHD)VcD~yMK54bY`R81_9j3u5NCw-Ot{l2K4x^W=dZOaAqv&U3ICW*8{1OOt0c$>nB@O*;+&}b(nv(ThLB3(Qh%GxZ7*P^z2Dxk zS^&ic#HuO2GbYuxP3Lsgf^O1Ar`a_rHq&lyR1FO4^Ct6`x{$ZDOmHJGZBOm@ad$^aP@h|Y(qII^OgM+ z_9|XEE^P7oCB>JflY5P5G4_BbPSap!DA6vKlafC+a>q zHt^4a7o29oa;`sHlH{gR!J*(uIIsKLlF6kH6m0W#yjbu8Xy8K3_^kU=1}_*V>6b@R5W?KeN&00-)QR8JVj zq?6hGIA;`d^Fwd7P#m4=)_A9A0VYfK;X?J`YIJ8xmg!_pY?Jf(eiO4^*Qq0sfdJY^ zC}NUR;npC0T7m9&0c}X2u+)z_LK4Bpym2%k zz6}0E=Zdjd-S`}w*REdEuJqMY#a>w?l;+>Nm?*am;7fpvuPG7b+lrVH!%L793TqwZ z(BouQ`wffJ?N@?;I?E4fv&EW94Oa)HIo2BbMdueN!w)cQz0ye}7z42tyP+Gb+T1-8 zzaBO&b{V$@rp;Zr@4IuP(&)Z`nU)oB4MtOyy2%wKD3-_rVgljmb0O}m#vc$5HxHb(R?k`D^flc z>0@IG`+^ugb2m?c7vxLW?xtUvJu+3Y6IO@GpPIoWVyTT@+O4evI{&+pG-FxoHx21V zHFSvvwYY2sS#!+#1v~auoB1BXZvq+jgYs!}H53YB`m+_!ezSO-!#i)WR)(1q4#7?B zdDWv$RSBpRWNc#%U+0NS$1t_HGgWJE-&fOrh03a1t2^81-)Mb~+U)%n z*+kYcnJa5}OVmj&_-Xe}fZ*b`Hf6sOp6lJQ%&CaLK;$yV8Vne%j~+Q>w5G?CDxkfl zn=~4UHZL~KK7w#faM30A}8`Z2-st{Fz>Pz zWbdj6Ph4mzrI!6h-PLUv-nN^Wv$xG#C)*+}#?hL|Oe7ST{~q5S)g(;+bSdO8jT+*u z5SITlsRC6KIv_NOxY%Q#wD}$}GLRO^Y+=tcY#zJ{U*dD!NGhHZGZv#ZlHIhq*GSLr zR3?{RhA~ng1c>da`uApUD4Nur-O&QOc%IKhT(R0BgxIXY zT+t46r%FH=E(g=t>eQvY^thj(EcT&+;ivj=LjH*-wNQb%f%8x+vkjF1wfupKKWh_l z-8+rC?|g0Xfi&cuc~)^<39;ey%`R6GO;#%t+A!!e6N`**Y`|KVX`uY2@J_xC`k$54=q;1% zZEq4&UjLqg*)%6qqZgaz{UwXZ7;+E+RT#%lBK{V;y*Y74{gp49Aii>N>7!-W%PID3 zF5hHrY+}E9{*wak*fKt{on?b}afO8WrTjpIaLoZ;lmNml+^r&FJ8A&dYi`Y}%g4(Q zyuG4-g@pj~pO*m;IZo|WN(IEBIKR1hu5x?_^mXJCm&cC>Et*PY1XEj!Z*Bkc8^FMU z!K;1J!JI%XnZJdzl`Z9+W1n|*SsxPpw+&j!74U;Wk)B3v8MVEe8#F@L?|p_ld;Ggh z00RA(UNdHfv~{!y|2YHbw{w8S+2gPjc{KdLcIGwmrv35l&i224Z|`tTFR=gnb!*fQ zI?v!2=l|^DmI)llK#E?d`sT#dtkdmZ z-5zlpI0-ZMTg(>tH~*iuZ)CaAg`o_sH(YWuXE@6sf5IpwA^zXPOhARnU!8y?ubd@3 z1fSR)O_$so%Q#x)=C|8J?vMVEK9wh@00b~nylTU0EYu_qrcB9|4yNuyufDn)yKePG zN;mf>aDE3@Dbn3nm#!UJf!#oHs~JZnn|2_t z-}{rsdb6ke7KZLM&I@Q8ka-}1(!9Z-Uysw{F~M$cO#JgM;iKLr9Z zdvoD_!l=~pXm^ADD0hUH-J3AmRM*`R+LE4=JGbK{+m(y78{S>lQMZv3o|B8FT&?AW zC|-4C1>=sbeVes462U&P{HX5jV$5Ou2ODolF|R+{8liXB^7L`B2%Tn~ld3#V>QePB ze>p&$fvL7yrqZd`C<5ZXSM6wwR3fXS(=AHxE|4pq^Z0mo@%lp8U)coMds?NYk&|YI z<*m!aO8E+>ai=4zRnxh^au&yx(eu{b^hzwMK0~XMkf>h9_}lI4(AhfzJ{!dj#TXCn zU*S_FS3af?g#G9T`!dOtp_{fq77*wCE;B5hoCdIqxa>g{7{A}?*P2K5#v#zQ+Xbs{ zS0*0sNFhPT&7OUw?^Prty*`CGjO94`!^@Wm@J(XzRB7_T#wz5>N&?X!U8{_>-(VLh zgX5nI+%ckKXqvP1?pQ?jN>m#KC-P1a6y+qyz)*?7P@UXh1dQj7N)>Vq7mDh^bK&?N zknp3r4r2Nv_&QK9YAgZ{=c+|*9`5HHqd6};BFSa>Xs|678{FexQ=9?GA!tkN!FgT2 z-W^n9I(X2_SQ`$gONYiW@0Y5L)3?t1Gh`AcSvCWl<%`5fD&2?t@^=W6_%zi-p8%O+ z|57@p#QLYV_=ch#FEKw{N|RZwya_>Cw(x+$#pYWrW0p*6PYdpmjn74dUR{e&uMDb2 zZ5R+mtN8eI0n_xWWGA*ma%c2G4)+6MWRYrRy`_U%!0h>`es3riTd@{AlT$N;R)axH z>3$hqQ!-6+Q%Eo3;>+D`93o9RTkl#bGsI7PhxLum&Jv6ffT&;Wc3fYih&W}<9|~-B zs%30OAt^CVino**$1jH6E>-m{)`E34Q1kU&;mvk)>x<+{Bb%4yUado5?GW(hPUS1S z>mZcRwzi$`#v$ zvd^~5N3}WqUWY$1YCEcEM_Qw+kYF&Bu=3Fwkq9;(qqSI0dV>N;QL&y{ERu=LO_RvU z^!6wqBfaO$y?H^jQ0F?p|L7zfW?lzmV{S_kR4oF^s!I3QH-Y=E<=x>XQV4^<`h4q4 z90FdgSf>lhjS9YctCc@Cvqr`@Q&^RN(K_+TP!X5??t?Iojmn5MNE+glap4@h{fWVV z)JEY!gPxFmo5$NT6VT9$bH-2->!=)b$3gS8g(A;NOt@lSTStvWd>D}%9ewhL>30QL zF8y(XTc(xx4{Q5doiayVRK|!3Gk2S#Q<-s+0WL@bfO>wI=>-2OL=VTmW*`=$l%tS4jkfpg63pdj-rxf0K&22!S5B{c9vbDgy#Q< z;6wG8iov2y&^FqeR4*NqRcaG~G42!%`VYv5YBFLv>`Zx#e#HXg@0^qM3GKM$qC_iY9&F0VmxI03K?a_PmbXR=3eOw)` zzp4H%a$j51C3AYOLXBrg@Zb-`JoU{5O=j5#=UnUco<~G(l8cExe8Pp|9(ZKau-KSV zJumucQi^^y+AF?g!VE@_&*%2lO=D^KSY~u|6rd3~4=tl_P;kw%-og&E% zb$Km^+ya(+jscEp=KLpS-^|qR?vqdO!7(sUY!i9z6n2)htR9nMW~m70lBz+&AD(qg z@CnxkFa`=u!n7O1dY0(A>=-N^f|SO$Oj$!6t3K7Ugx$x!kDL7}tKG}BA#kRSPLI!Q z;Ek|5@)^F|?Oc`7^x+tKqQC-rf7;kfCt;;xqx&azxa~;`gyDG9HZEM5e8bJ5g}=xE z=;ZJBCb9P%vH;;{4O<%IW6ZC=^WX_MSGIuhTF_)Yr%fXXyt2>tpsGgN$blp_HzkMT zl~-l<&nkq>#&|~UNZjd@AX7zJG)4J)kMW}xAzrks0Q9rp^Lp6+(8B2{e5fTQCwYfc z`K(6KwT@7%8sWF??N6@e^Jqr5xGVCHYkgz2{#lU`YxU+8*kaGFk{uku8^~O;SaRkgJm{sJ#SxECSdzc;&sZr zv|ybMw^*ZqJMGz>EizPy;A1XXo91q?lX55DdOT?9D1hCzQ+C8+H|2D<=X-4lntQjt z1y`b=Kq;L}V2X+Fhuu+xtq>CT+hR~6md;2|qxh=MgNj;^%V@|112vaTcRwO=hK3S* zcO;$l>f!onKoJ44gsP>bC2W}~_DkGQ;t=xnBcLxO!N}TH%0ib5H&KMLw!IwPtZ>`5 zvXR=9VFbm(We_Pxsu&3f!`AnI93QPA;8t?u4VS)|E-`Hz zPN!1x6LoyOr^{hTQCNw-5^B#9Aa4ejK{yHV-2g7ozmEUJ8WCE4h|7{uw`J`YnALTP|S+|RSvZw&v~wu;W+z!sMvWa zz-838GqGH)Cop9_mr`p+A=O60EeH|^i>y(9D~;Pso9^#HaiQ4T)x*?;l)?FJ3Nl|c zvp^q<9HEm*53U#jdaitvN(yH~&z+qPMO@{K-8dU{RhcmI2^<`J>pXif$zTI`7)nCV z@|dXdTU%RW;6jiI#rDeacp@{F8*oe4a_Ds{EIW+^Sq3j~1NIoap&mMX19h#XQ2d5m zod6@Ksu7Rnb&Y-{kP}w;K|5S=`scXGM4mTF9Po%u5$_ip;bwitZNrzIvDC^_fm7@t4zU@!#Nlq0d^h38{--KX31cB$gKhai*EL=% z7|U@oyD7H#w@d_>Ct$Hquyz*as&QS6j4`OzK0B^g1KrkCfX>+72{wp%PTJvupBYsm zi?VB=1yl`FaJk6}9<+7*eH&we7bl4>ar=j-QG?h_`y^x}ax)x38vcVMZ%|nL=hZY2 z!~uh@V$^yj)knk0MG$XsW+^?JxZ~-VoRQim#!~d_VaxdB56;pJ?kl30bQ(KzVO^%! z6_3GK2u$UL!=G@mcQGJMd*E+A7#jMZW_f#O!_&cpQAlE%7j;sIXUt#9sm{gGpGz$3 zz{1%OT?E`p7{rsAV5=^WAb=!qMqLLbhAk`KO@L-gxEs85evnX3C7FSN!wR*PmxGel zEo*jmua?q#FeeIn*>kJ=uu5kxR z7xuwO7Xfn~I%9Y)PHAg~LKa zRwN|R;1g^)0?s4Gz*|%BNhxW68C{lV+58%BDt1XDs{om@TD16WND8rHv;boiQd?kT z4l3%z6s^NOACUU&T3VCL3B`6sc2(}%}YO$`7bk9 zA(mP;RGCWe@CcJvoT3l~)gep4v}!S`k*ldrs?0A|G3vvSrJxBjZZOdc;hVZGbf0qQ z$+@~;i)Xdyb72*p%(;<)y|eP}fPZ_O)8=Cmu#2^H+7+bhFi8A-h7891Jor1CQZcg% z%AD*{@A9^vQV=eBwUeordf|Xur_5!@zEA5eliukIb1O~g-|dnHtZ<0t%KPpo^iS3c zL-q2%zv{9Em;@Vz8V=pkR0wFVflFp2f)X0`SBe-$ir1c3TdtB(7aChnAO$=X=O>SKoD<@q20ZAhU zM&>(G&#A00!Hl2YDgZ{sH)}_mF1QNMhPKm)Mz6drPx}Qizpc$+Hz60*U&`s>F@~MJ z#wM^wJ#323Me4zi?)Zq?5l0Y0&NmpLpLyn4dGWX3pv0^W0xt^a)GMLp~fvU zNM@8t$>j#mB+0k0>r(zBK)q`6?@_NJ;xHilG6gD;%VUI6&U7hG@ngsN$D=%I{io% zeY*Q2co^C~!sOZ`z!9J1%_zUh=a-}Ohzn3%ceBO4vueK>?ASZUc@_;Q<0b;6C=Ry`b09~N{f8ivn+ zfX5yOyV=`ccy#~*eR>}luOe}wHoo5Jzn|>#xCMmXMyalDT*@?FTQ-}{FhfMLpYeh_ zYa%S7ESabG5l)invfSS`$)lfVB7~Os4%AnD>?s=ArDar@c2+xisoqk(0Ue%gLwK0vI|1r z<|z-p*KPNjpm!;|`-OO#qn8Y)n=4<4Wc)eEJl~qrq?1D z)QN&!+1BDt6)HUrjo95BMU8RuH$j&3s21bXFvdk|v-PM|)rijZQupsmK z6(TDFb*Wvw>B^Q|05nNkrAp1QsHj;!`M{Ib;<(y@uBk3Zz^bM2v({vNSQa^p?eF4- z80-cTHHCOM`a|g@5J5VTEo(J0x3~yc1NY71*3W$U%HYp@mK$!E(&P(6R*}-)_?ZGd z6>j0a$5&nsQLlurQoVkr$xQHP)VMq5yzci!Eqe6nJp>Q@KLh&oN&jl@y&$9Z_O^$U zNBRel${IxrOjBGn=mZhxd*8kRMg7$)0)N|liN8PHvqvL8)dk51%59C2OJY9UPKZuc zUlNzV)rkM;K>qY0NRe+KRRIQvP%r#tx#YN+@x0X)k;mrWUj$r>7bY;ciI>yxUZK}Z z^VjC>C*bA7M)LTR2l}6XfuY_aq5UF$LV9uHvHS0zVla@@VrU(ueYSsp^!4%rUuiV| z65z>0$n~eaeLdt%Nv}!1ZjXiB-VULkD$2);Hv!)ks^l27>Q48x>UmiH`ATm{p|!*L zR-(b6Gv*W85nhJ4HQp@a(UizEUZG(S9y8Wl+HzyIJ*alOzF>NzBgp>mk_ae_h-DxN zYhllhzHhi3!^%Xg)L9Fi0>fVHqadIz{-xM1;qm3f_F#qM2_KDeRoUIshV4fn(Sq$HtoC|1xp!2} z&7cKPWW6^Y5$0j16hF_}I=^x@2n&V&^!K?>BL%wUePBwW0T2x_QE#haAH6!Yy*!Z( z5=G32=XG_+|B~^$NVTyCW&%k57y!U3!_(ST?gO47qRl3M&)3I`j4GvC?4=6@ ziY-M`ZW#@tVN@KuEr+wQEbcQJre`8(G$r5@siA})#AmFQ=y+Y-=zx)c4l3RG-;SHo z4f`uFCd!u=<@0nraxKa==PT?RLsFUMVHSW_!(fEq6EJPdYWt*hwBCX=OP9Al{zLe@ ze~U_y8a>qu7X{!Jjb;(_KGzG~U4DjodAtAwW2eO$O?Ki3GtT`%?GxMY4FRics} zHi)45r7Q@QDhhxq`wr(T4USWYvUahWm*9{d_>lDO7OFuL+tFXhQquDk1-ngf=gVzt zA5S-B&u8~1Cz?z*p~sKhYwvFAFKnLf?GrP5_hlkTEe!w|CWnXzy{jk9JtvA>HcjL4 ziL_~^e4eU+%lE-E`(#wwjKg>|PH7^`0kg}KkeeYO+z-UL#AI*ue46=Kr4xycNNwV` zNA!T$cUXTBy5?4_wJf$Qm3-&x?9~P}i=7AojnuD-(MA!BWxv2^k;G>7w5`Sn!-qyG z8xO=+a89qo{qxI9UVnM_clUs>RPs7Y0&xKLy?xk4aT3%Ve*YtWDE%gr%@Us!h`P32 z#y5TH=nnQFSuav$5yh47`SK7H?V0G#VtgJ(Zn-Y!3nLNBlAR@uMN4%0-W?3d3IpuM0P8hqnM^)kLMt z)Xn1q1ic}`XR-b5ZM!Cu&7RQYD375cfxaqbgxim!u5X$Ko%o-t|XS<%LXPqb(A~o^dv46r3i^Yta)5`Od6{I5$;}u5KFHGqG zRvQZlRr(1LtlyO=Op3pIZ2F$RHN#Zq=KK_*EWmGX-fD83uB?tN{`j!;HeWtJCEwRQ ziKD=nBRr8wX&|14!uj!p(_bcqheFa*lL7&>+!#*?`=T&YAoO-5+0JzG;Hff!OZVqj z&8`RTc3Am{kC{Gnnhlrdy1NYHK8eiw4fy;XJPTZPPWP8YTU)Tcecr_0?U|f=59>XH zL07Lp*VsEQ9&Ymyp2`sZM8&d}*_Ke8B)|#$lSY&-V|nEpCMg0QBsU}; zf0zUF*P#Ss|^;qAgjywoP+b z8}~QWcthV^0sHxIODg}f)Jy*3cAK}5&#gSR|M{xryhfVKL7JE&8|L8KM~EZ-@2`lM zjvp5xCExLqcqiO3FZC}P{Ah}>;v5pK*4co!Cm}e)r;X}@1uL6P&X2qy~yhvn+WgkqFR>9yd;@1o>5s`=$dyJ&E5)&-c*m?1NKMllJfyRZTIryb1 z8DY89q*POw^9pdXl~UHv?;fst4*G|x|aj= z9}L$c*(!g!86V560s%s`ZYs=vQV$Q0^GVr@OddNYG*^##S<3?AZ;K5-vH*L<&QsMF zfODbepG7i8KG=JUHo#z0^I@ zz1Ak zq-sK2cpMhY8;{voFUDcx1^lK5@SD;W%jXETOLIt{aMQ#GxxY-b7Hq<}5Rri~92N{1h4~cj4R)vt#lbkJqRR@oBWsdx^`JJd-UD7&_~TV|$uy>I zv9$e1Emt(zx-Xw8`A#h{Rlu&*npK8Q`Sh4RSj?M6rO+-n|5`Cw5OX+ODAu!0jV88O zyq9)&88BSvNvA!)`SJY+6=qt)6&{%$=6kb=si!J)`!84X1Lr_5cd)Fni`tsDi6yQ?uz~7xA_QAU^{b0+HF@pAgE8cx@fmY#7)-4Z5Psi zF-OE;+}>2_Qbl3ZH!_H>ybmn?$B|4tc3V#SGu4^;MkKBTOS?={e7XZtfH}WujFee5 z+P@Sxd>0sZO$uue;_^v$D$he!DzlkIkV2X-2$ZKMeGrq_w^UMQco@Jtt`ntzM!De($dTYJ`Bk+H^%^-@{s*3ANId8i;>y0lDulot$mceO}=*%;gP@XvHPe}T=F1zok@ykbM15A(F z3al3pa2R%7Yv}Z-zmTXuOm#NOof5Chz=+Nhe|V*R-oTQJfd|_gVKp0KQ;E;jq)y~L zzqjTag=(+PRv+a9Geix| zU=c1_F4C`w?{>f`ZxQP-o5|9R`Hd`ecWY2`U8Es5`k{*}Y8u%*_=)5e#S*O1W3%B- zL=3Vix3uNF0_VB(!bV!b@&%#?2BM9hv4hU!FG?o-gZC3OVsh3(>Oo+^U28$lAT~kd zqNAngLdchTWh=kFSzrGoSMw}5$9G9=E=qg|PZGKK8t6(Gxi;`8LMDiK?1HIuN(77Y zOR1#gSGfj<0^{<;SW!=rolV?om`9&&&o_KSbw83iM@Ux#{5EAVd zl?!Pk&UpZFdzqbB=l#Uf#Y%;|ZzPEr063FzPwUUgqJ`m92tr9K3NwgcOm@9)9rb0i3&dGIcH@JvK(g$vp&d?``|AI3e zEb^p%>=eW{?u%G);;KEGGcEXvMDr0z@}oXi(G8a;X{en8B>a)OKQ%au*7ZF)&s+T8#^Z zuPFyly|!A$agh zt)GDgR_!FZ?YP(tQI242p@C-mb?i3LgsAjuu<(_UTB%-Nf9i2 z$=*@oypbi~-C8KWiw7T02G&T(6n{y5NG67hOaU(k6QaDH3sT5-Z1pxG*w_I$W2we$ zaLGOr=_IwLOO@z3A2{cylVLg76VeU(Gv!RHfH1a)O(cJRZWGd2`)^h&sgtOBx4n1o zq<90Dph{=z8^bTgu9rVu{IG?Y*!)^)=}UIjw?Me+FrLgih<}!koF%rWlVItv#4uiYmtgiNIK6_tOu{P?YWpU^4K0YSi#kr#QmU{+Xwz~7M;V%v8f9w zt|yo5x^vRJsNC|r!+nvg_JGN#ENraw{YA%Mdi}3wK%j$a&1Kgs(DzFUhiyp=wenmO zP1)zlo}^l)&le1FIK(j4om0|rLj4jUH74tPi&`ft8O;yoa>7U@Proe7M#LNRcL@oa zLmR@TDO-ZgoCt(%=4J@OHr5u{*+Y)v;2$mhGKWM7H~|sD-q-Mx4m7SN_g?$mQ-0Z% zTF~_W9{>?H$Bhe^-rJ*V6z8~5?HJga7ly|FhUMT0ZD0{Z`aowIm1Vix2dO6o6At_R z=+j6G{|54Ulw&H>idL@~w9x2FPVJFkwK>0oIZo$-Q-#l$=UN2jqQLbhOC9YiN>L}D z%?W7#_0c>mJg`N%Cz)~!q?H*3TvoX7d<^JstFtZ&Xxap3xgCbDH>%iINFdS@{{*e$ ziKV>xK{|R;oYq5dz0N-;Yd6MA#gmS=TrTVYU1Z z1wepxYe>OK!hm&am7aut0p8*f088!kC#&3Ffq-O(+z;?g&CS`i@g+t$wpI(xO4uGJ zGtB{KF+G^rAl?Q@Ce6>|9bh&x>9jY(2j9ePOYj&IsOqxwTck~ z7~u<+Ck!F$-_S#ZKnJPUZ1ifAGnsCxBHbuukUz6g{{|1gi(TXM0}lE4RgcBrQU##f zK21Otm_YTL-!%HTTW7RPRY&sBdnCGh_b=qglLQ$>;Yur`)ggL8M>rxkE_DC>V;eMsx?BmKi({w5X>#C?m6`M37vul@iZ96bvu zfYp`cUknlqHPGixmZh>;}JE|Fq ze~-ZXBZ|`!Po~31Yu+13Dlx02I+7q%3Y(@uw>Gb5)UUK+xNJ_+X-*rkuMvHAhhthc zyVubg^|p8pDwj_iviwg~shbemEzTD?_&j#PHd}p^{pmlOB_F(r5}>(0dTeBAG&zZ% z@0wvo%^}{Z13tpiZU=yf4OJUi?F`LV+U!VmP3i$imIQ+~(Ss4g-{-AO zUz)fb7JwFa`+(8ES%4+vfTbmWurXV5HXI+E#P#}6KI7A8)k+7T*(y{ni2(u#2G*=v z9jBbJRUmn4nja8iUKewn{tcweLD8CUs}=%E05YNwg1(uB4(hVz8=+UP9h`%W9`+Rj zQrissetAq-t<|32tnMz=U`&*yy#^|OQ?K~;0}z&}$voRjumGdQ41K`2yKzSwn=zm7 zc8T^stf|*K?MF=&Uqk_Br@8zmKql7*B=G{1ufD$`fQkUHXV&s%xvKi0mYwBf_lM6Z zN+~*xfljNI%!AF3zxgaeWALEH!g}yJAmT8~iL2iH%u|oo^BKItW0^~}=3Bl-vx5>M z0T*>5i*elPDOuPk@LQ$s?k;u>2e!l~ntP3-0DDG&i%0zQ`*+NW65S>_X(fEY$~wG!=Cl0lVcN+1>?w&Mq!2v;1V9!|t}m-Qo$0#{`>rJVz+ciw1^wyxrYTB^Ly(Xs=Ii$vs^Ih)nrBv8r`n>FD;}SzTy!$iO z&Jda^749WAz6gp(2h8Cqz|x7J9PmUR+HGtDAKOgH|7%K6S=f`ag6t^`5XbLq%Z;C1dgtOo) zJlop=Y5V>3OAz(D1UKItF_y&F#{CwEEL( zRX8GK*59F`6{6e?teg8J*<@G*fM?Z5IG+GKt6Q~E0_9+@|0c^0aEB#UN7MU_Cq85U zriG8JSbWBIkHWVBEsT1s&Z9JoV!X-*5cSS1nV>6oBt`oHepPg%z_=}Yz<1*_Fbq7p z3zodD>D~<}dgkj#hAfEr`l#8!uLSd9Ji^&(@dk2VC%N?mMBuP^VEd_xE>YXpEW32I z#gIipR>5Ef;^~6NH`I=`S8wn6V{FjazbW333=UT#NI5Hu?r|zd#?N3%BnqEj$m|#q z&v#`MvwNlgm=fFvNR($}MC52y4};oPpZ;y; z1rVaPz=H?lu?7Y=8X}XvDG+Y=l6k#ay^POiRlBH3X-oy4K85zgQp7H)Gc#{7VO(ZB zYKkp;@gi1zAjv$xQv1>Y*q|qPJP?$&2J)`V1>~Y_Av>NMuTDIXU_cv>7PB?SI z43*#U!pGFGef0Fj`LGxsb(34d&4>FamE@TVsP90hJB>H_oZi*MRku)hK4& z2Oz0aLFdnDF#*jvK-`-dK1UVaT+mtJeBGiG279}7Q-{CyK2S8^?kNBsr8rvc6A#F1 z%DSCR1Q8Www7HhIjWs>WOx?P=9BJNi)^m{igU!9A^lm$@@Vo6LhOF$kjXz|?yi|0Y znrnV=x;clMUzlHaK~4gE5&zZ^JH?YL96Wf%UNpLd%~?>vcJ7rq#Q<2wVuq5L+d&)Y zz47b?R>2RgZRo=ZHG6RklqjgP06Q+mj?L@2;^06tzst%6NcRi{__}1dxtK!!)WpsH zzH^3q;AO3WsY~sbL7F^Z{K-n_`p`nmN9E__jN;>3orHVlGX#G@nx%m{!uy8Pk{0gM zU5Ob<(2W~y%xBKtkZQ-wYPg{Y3d?KgqzyG?ge*`k#qq^TKqj{=>DLs^H-#Z#PYNDr z;P<~+Ml8mLeECwbq*$d^)N}2AvSmXS;1UY$a>!KhtSE;tphmo?tFm5=4NvexM3p>(n3a;z;}m<%zHwMp%Io0oN}J)kf#FORI(4JCe+r2@ zpeSi-%n?aIE#Yh48xWNU%)?rrqMVc_M?K9!GuM&JfU3?KpNj9z8ch%_hPT1A3a|R=oOw9>WkF7W>73vW}Y3g5z zqF-|CiWkp_qI!aTzPj!+psJNvI9KN8UuFmZRMM;~%HUBpEWca+kpwIJb zMxB#`AI^spX#8gF1s|e9iT0DYX?0n*o znj{QRDnv*svVhflCqcKy1Sa$i9~wwLTj9C50^Ln@iRifJ$5hTH0|b!<8E*5)x0W#Tw$#B|@x1eeaTesm*_FKNxP5*+-fUN$Rn z9!NG8f^08aOhlyekb)8Gxz9Y<_RL{ppbx~C6Hd3UZbxbv5PC-Y`t4ac>M0A;w2RTi zPcjNmN9TbyZna+n+0;bJ2|k_^>#%3k$C)GRlZ5$(qdUxiRO##yW{z*W8E=vj=%^Ogf2n@iB8~hZ zn1K zskr#R;}3eqtE^4iUr4GRA0KCH8AatMOChXk4r@b1J-CS*q-!{@QR&JvteBim2q5%h zvNj>6T}O2{sOPB;r?fcy=UfcaQ+Ps)N0Ljku-!$jENeSyA!B^PoafQBHa*1Lk^o)Dk-uMns5(H}E z<62W3^6|w541pP%p?6N@2pu6^lEE>D&koaS&IlIOA zslY8*^2;wgRbWsYqI*AA1XWa@JZtvwMcN%dN2XMbPa}VQUqUy$z9;Ot1Io2=_h`%& zvv~WG=R3eVd%`l@-Ll`=yVOrMbXyOtZOfG#%{8Wwa2dBoEgCjF_x<~s?UkXH+ePe9 z5qNOI^%h(IGG5pNKT*MVv6ALwrnhvw{6NAeeJD3Tu;eBD*R zn3&nYqAMbZ{)!*9fs|*CM*7WQgc6JOx_%Q}g3ZhQuKeq@VKbKAUAwqCZ_W}zC#RDV z$nK_oCOKc-GH?ynI`x8hRj$ZK(2i~XQV^{ljU~B~JZg`4za3%nA%IoZLnVpm%CLCr z=I40cL0TSX0uGvkW1~@jkT7Q%7_P33fEqeS0fJ!c+`zQ_Uh-mI4$496^Z8#;P&H${ z_ZAe8g!|vgnkbN7>EauYnlh_M`(EMAh!K^W8LteJO;*6bGjEC{7dfLon+zT~tao&Ejt=3998*R@X< zg{r_KduOU(Ou({#SBjW*PC!8I0G%`-3MMiYs81k^rsj8_HR)T1r|aA>++*Y|MVD9x zEbk;b^~K!VGEssh*X98cVY3@5T_SNk$*#&=Qe)(f*$IKmp{4lbI?+Yal)r!W7cVI^QefdSVF8rPNPZAqL-?1W9I?c0s_3q^PM7vzqX<|43A@{+vfM$& z2)C*a(F!}`BApzy3*!QcZkYS>@z-KxEE{S{j;{(4ci)+P;Xu?tN*^=YiCa@+@av@0 zNg!`=r+SC?@1()WDu^6$__k71kit@gQGHfMZlWGO)BWQeo(shW8_r*COni$4$=W|F z1Z$)UO^})TlEMwniRfQoDM2c9{g?!~+K?#L3azKgt-qlP0rtAa(R5r&mQ;KyYF(sS zt!dVpNxZ@J%3iwCHlH&^bz^;b^3wYEH8B;gN~F&eYgvDta=YCRgRogPVo)84SO+IU z2iso4=C!b+9u$>EBa#U{4zoy3md9Q@Cu2vu2RJt!a&G9|keJ>KO`hne29A6a2qUlZ=2^g)Kq{?Kh>O8f)LV#H}b9Q*OLz35;*Q%+Ki z{2Pk)(V*0EMvlNxU_StOae@O?b$mUCBzPon*PpOkQ;4ylWm8z8cC4{-%a;C#_A~}- zTa%3@^@P&)w^*I&CKTZ#xTjdiMF0k}!T>HYc!d$@Q@z~1XQ{fQNM7xdCe zf$P)UPfWrlm9Qi!R2g48?{vG;Keah3UzEv%<}?jKCQvi~fzA`I3}4Tw`XE)Bdw@L1 zMSdCv_R{6)*9uS(Q}6TN{(W% z-XGSW9?{z`fmWqedX?Ci=EVqEQWd3ZNlNN%y7}Hb_<{BO>GDbk zoS9X8(@~5|#W>`q=kUMe3m5`!-%Yw^Z}0_E+Wb~6eL5w-rl!VueQ1QAbFgc@s?AyU zr#xuEukWD)qB^7j7z>X^9ov)0en)m500p0u%yr6faldH);AZL$;VH+%PU;pFTx=}hQIo_4?L zkZTeJdXKH0utWSmHeCsby{p`;n+7w*+Kc~Ky6DDFz4`X{=e{5rRbM}J%+A Date: Tue, 23 Jul 2024 21:37:24 +0200 Subject: [PATCH 077/138] perf: share the sassdoc parser instance --- .gitignore | 5 ++++- package-lock.json | 8 ++++---- packages/language-services/package.json | 2 +- packages/language-services/src/language-feature.ts | 8 ++++---- packages/language-services/src/language-model-cache.ts | 7 ++++--- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 19cfce18..916938e7 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,7 @@ docs/book/ .eslintcache -.nx/cache \ No newline at end of file +.nx/cache + +*.cpuprofile +*.heapprofile diff --git a/package-lock.json b/package-lock.json index 139e4494..cd1666fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16878,9 +16878,9 @@ } }, "node_modules/sassdoc-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/sassdoc-parser/-/sassdoc-parser-3.3.0.tgz", - "integrity": "sha512-wuRMNC8eLxCjyYwHy6bye74Y3hhGpsmlzhu2pMfsyfZ0v66uZyWlWfgASoptKoUXyKhwyD41mwp84sgEROmDxA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/sassdoc-parser/-/sassdoc-parser-3.4.0.tgz", + "integrity": "sha512-PNrb6w1JCGbg//Vl7x9da9cjtfDf/UZusiI/upUxy797of8822g/qkD7Y3p4JWzJIrUdEADXYGJzeG09qVf9lw==", "license": "MIT", "dependencies": { "cdocparser": "0.15.0" @@ -20396,7 +20396,7 @@ "dependencies": { "@somesass/vscode-css-languageservice": "1.0.1", "colorjs.io": "0.5.0", - "sassdoc-parser": "3.3.0" + "sassdoc-parser": "3.4.0" }, "devDependencies": { "@vitest/coverage-v8": "1.5.3", diff --git a/packages/language-services/package.json b/packages/language-services/package.json index b849f44e..33efa112 100644 --- a/packages/language-services/package.json +++ b/packages/language-services/package.json @@ -50,7 +50,7 @@ "dependencies": { "@somesass/vscode-css-languageservice": "1.0.1", "colorjs.io": "0.5.0", - "sassdoc-parser": "3.3.0" + "sassdoc-parser": "3.4.0" }, "devDependencies": { "@vitest/coverage-v8": "1.5.3", diff --git a/packages/language-services/src/language-feature.ts b/packages/language-services/src/language-feature.ts index f8e9df4c..8f9456bd 100644 --- a/packages/language-services/src/language-feature.ts +++ b/packages/language-services/src/language-feature.ts @@ -307,7 +307,7 @@ export abstract class LanguageFeature { * Looks at {@link position} for a {@link VariableDeclaration} and returns its value as a string (or null if no value was found). * If the value is a reference to another variable this method will find that variable's definition and look for the value there instead. * - * If the value is not found in 20 lookups, assumes a circular reference and returns null. + * If the value is not found in 10 lookups, assumes a circular reference and returns null. */ async findValue( document: TextDocument, @@ -321,13 +321,13 @@ export abstract class LanguageFeature { position: Position, depth = 0, ): Promise { - const MAX_VARIABLE_REFERENCE_LOOKUPS = 20; + const MAX_VARIABLE_REFERENCE_LOOKUPS = 10; if (depth > MAX_VARIABLE_REFERENCE_LOOKUPS) { return null; } - const offset = document.offsetAt(position); const stylesheet = this.ls.parseStylesheet(document); + const offset = document.offsetAt(position); const variable = getNodeAtOffset(stylesheet, offset); if (!(variable instanceof Variable)) { return null; @@ -339,7 +339,7 @@ export abstract class LanguageFeature { } const valueString = variable.getText(); - const dollarIndex = valueString.indexOf("$"); + const dollarIndex = valueString.indexOf("$"); // is this always true in indented? if (dollarIndex !== -1) { // If the variable at position references another variable, // find that variable's definition and look for the real value diff --git a/packages/language-services/src/language-model-cache.ts b/packages/language-services/src/language-model-cache.ts index 1c139762..418802bb 100644 --- a/packages/language-services/src/language-model-cache.ts +++ b/packages/language-services/src/language-model-cache.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { LanguageService as VSCodeLanguageService } from "@somesass/vscode-css-languageservice"; -import { ParseResult, parseSync } from "sassdoc-parser"; +import { ParseResult, Parser as SassdocParser } from "sassdoc-parser"; import { TextDocument, Stylesheet, @@ -34,6 +34,7 @@ export class LanguageModelCache { #nModels = 0; #options: LanguageModelCacheOptions & { sassLs: VSCodeLanguageService }; #cleanupInterval: NodeJS.Timeout | undefined = undefined; + #sassdocParser = new SassdocParser(); constructor( options: LanguageModelCacheOptions & { sassLs: VSCodeLanguageService }, @@ -81,7 +82,7 @@ export class LanguageModelCache { let sassdoc: ParseResult[] = []; try { const text = document.getText(); - sassdoc = parseSync(text); + sassdoc = this.#sassdocParser.parseStringSync(text); } catch { // do nothing } @@ -173,7 +174,7 @@ export class LanguageModelCache { let sassdoc: ParseResult[] = []; try { const text = document.getText(); - sassdoc = parseSync(text); + sassdoc = this.#sassdocParser.parseStringSync(text); } catch { // do nothing } From 72610c87b6c9162f4c073503fbebc7329dcc56a3 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Tue, 23 Jul 2024 21:39:02 +0200 Subject: [PATCH 078/138] refactor: fix an edge case with comments --- .../src/parser/sassScanner.ts | 3 +++ .../src/test/sass/parser-indented.test.ts | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/packages/vscode-css-languageservice/src/parser/sassScanner.ts b/packages/vscode-css-languageservice/src/parser/sassScanner.ts index bc353c83..3d77fa33 100644 --- a/packages/vscode-css-languageservice/src/parser/sassScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/sassScanner.ts @@ -215,6 +215,9 @@ export class SassScanner extends Scanner { this.stream.goBackTo(mark, depth); break scan; } + } else if (commentDepth === 0) { + // This is a "clean dedent", we're back to the root of the stylesheet. Finish the comment. + break scan; } else { this.stream.goBackTo(mark, depth); break scan; diff --git a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts index 976ced0c..4eaa399d 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts @@ -62,6 +62,17 @@ suite("Sass - Parser", () => { ); }); + test("Sass single-line silent comment at end of declaration", () => { + assertNode( + `a + b: c + // single-line silent comment +`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + test("Sass multi-line silent comment", () => { assertNode( `a From f471676f72c6dea27c415aa91a54793596bae778 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Tue, 23 Jul 2024 21:56:07 +0200 Subject: [PATCH 079/138] refactor: fix a bug consuming dedents --- .../src/parser/cssParser.ts | 5 ++++- .../src/test/sass/parser-indented.test.ts | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 78c47168..3dea4ddd 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -542,7 +542,10 @@ export class Parser { // dedents as we get indents. If the depth is zero at this point // we should drop out so we don't end up adding a ruleset as a // child of another ruleset when in reality it's a direct child - // of Stylesheet. + // of Stylesheet + while (this.accept(TokenType.Dedent)) { + // Accept any dedents there may be + } return this.finish(node); } diff --git a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts index 4eaa399d..9fe23449 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts @@ -73,6 +73,19 @@ suite("Sass - Parser", () => { ); }); + test("Sass single-line silent comments after end of declaration and at root of stylesheet", () => { + assertNode( + `=input-block-level + display: block + // Makes inputs behave like true block-level elements + +// Mixin for form field states +`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + test("Sass multi-line silent comment", () => { assertNode( `a From 32afe85fb5d308cd270e4b2eb164d927bba38abd Mon Sep 17 00:00:00 2001 From: William Killerud Date: Tue, 23 Jul 2024 22:05:00 +0200 Subject: [PATCH 080/138] test: failing test case --- .../src/test/sass/parser-indented.test.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts index 9fe23449..ea868836 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts @@ -86,6 +86,34 @@ suite("Sass - Parser", () => { ); }); + // TODO: you are here + test("Comments and nested rulesets", () => { + assertNode( + `// The Grid +=grid-core($gridColumnWidth, $gridGutterWidth) + .row + margin-left: $gridGutterWidth * -1 + +clearfix + [class*="span"] + float: left + min-height: 1px + // prevent collapsing columns + margin-left: $gridGutterWidth + // Set the container width, and override it for fixed navbars in media queries + .container, + .navbar-static-top .container, + .navbar-fixed-top .container, + .navbar-fixed-bottom .container + +grid-core-span($gridColumns, $gridColumnWidth, $gridGutterWidth) + // generate .spanX and .offsetX + +grid-core-span-x($gridColumns, $gridColumnWidth, $gridGutterWidth) + +grid-core-offset-x($gridColumns, $gridColumnWidth, $gridGutterWidth) +`, + parser, + parser._parseStylesheet.bind(parser), + ); + }); + test("Sass multi-line silent comment", () => { assertNode( `a From 1a486eaab200ba312eca6d6211583a7c0f9ba31a Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 4 Aug 2024 11:01:00 +0200 Subject: [PATCH 081/138] test: fix loadPaths test after merge --- .../vscode-css-languageservice/src/parser/sassScanner.ts | 4 ++-- .../linkFixture/loadPaths/shared/my-lib/variables.scss | 0 .../{scss => sass}/linkFixture/loadPaths/src/styles.scss | 0 .../src/test/sass/parser-indented.test.ts | 5 ++--- .../src/test/sass/sassNavigation.test.ts | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) rename packages/vscode-css-languageservice/src/test/{scss => sass}/linkFixture/loadPaths/shared/my-lib/variables.scss (100%) rename packages/vscode-css-languageservice/src/test/{scss => sass}/linkFixture/loadPaths/src/styles.scss (100%) diff --git a/packages/vscode-css-languageservice/src/parser/sassScanner.ts b/packages/vscode-css-languageservice/src/parser/sassScanner.ts index baf70e1a..279e7d93 100644 --- a/packages/vscode-css-languageservice/src/parser/sassScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/sassScanner.ts @@ -178,7 +178,8 @@ export class SassScanner extends Scanner { case _CAR: case _LFD: { // In the case of a newline we need to see if the next line is indented. - // If it is, keep advancing the stream. + // If it is, keep advancing the stream. If not, consume the whitespace up + // to the next non-whitespace character. mark = this.stream.pos(); return false; } @@ -212,7 +213,6 @@ export class SassScanner extends Scanner { } else if (commentDepth === depth) { // If there's no indentation at this point, we require comment syntax if (!this.stream.advanceIfChars([_FSL, _FSL]) && !this.stream.advanceIfChars([_MUL])) { - this.stream.goBackTo(mark, depth); break scan; } } else if (commentDepth === 0) { diff --git a/packages/vscode-css-languageservice/src/test/scss/linkFixture/loadPaths/shared/my-lib/variables.scss b/packages/vscode-css-languageservice/src/test/sass/linkFixture/loadPaths/shared/my-lib/variables.scss similarity index 100% rename from packages/vscode-css-languageservice/src/test/scss/linkFixture/loadPaths/shared/my-lib/variables.scss rename to packages/vscode-css-languageservice/src/test/sass/linkFixture/loadPaths/shared/my-lib/variables.scss diff --git a/packages/vscode-css-languageservice/src/test/scss/linkFixture/loadPaths/src/styles.scss b/packages/vscode-css-languageservice/src/test/sass/linkFixture/loadPaths/src/styles.scss similarity index 100% rename from packages/vscode-css-languageservice/src/test/scss/linkFixture/loadPaths/src/styles.scss rename to packages/vscode-css-languageservice/src/test/sass/linkFixture/loadPaths/src/styles.scss diff --git a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts index 8581b883..cd4d2bb9 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts @@ -86,8 +86,7 @@ suite("Sass - Parser", () => { ); }); - // TODO: you are here - test.skip("Comments and nested rulesets", () => { + test("Comments and nested rulesets", () => { assertNode( `// The Grid =grid-core($gridColumnWidth, $gridGutterWidth) @@ -100,7 +99,7 @@ suite("Sass - Parser", () => { // prevent collapsing columns margin-left: $gridGutterWidth // Set the container width, and override it for fixed navbars in media queries - .container, + .container, .navbar-static-top .container, .navbar-fixed-top .container, .navbar-fixed-bottom .container diff --git a/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts b/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts index f0234e9e..084baad2 100644 --- a/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/sassNavigation.test.ts @@ -904,7 +904,7 @@ foo // Slightly different from aliases, loadPaths act as additional "roots" for import strings. test("load paths", async () => { - const fixtureRoot = path.resolve(__dirname, "../../../../src/test/scss/linkFixture/loadPaths"); + const fixtureRoot = path.resolve(__dirname, "../../../src/test/sass/linkFixture/loadPaths"); const getDocumentUri = (relativePath: string) => { return URI.file(path.resolve(fixtureRoot, relativePath)).toString(true); }; From 3d7077434aedf9b2050886d0abb91e0caf2dc0fe Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 4 Aug 2024 11:25:23 +0200 Subject: [PATCH 082/138] refactor: handle selectors after comment in block --- .../vscode-css-languageservice/src/parser/cssParser.ts | 9 +++++++-- .../vscode-css-languageservice/src/parser/sassScanner.ts | 1 + .../src/test/sass/parser-indented.test.ts | 3 +-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index 98f82314..ac84679e 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -331,6 +331,7 @@ export class Parser { } while ( this.accept(TokenType.Newline) || + this.accept(TokenType.Dedent) || this.accept(TokenType.SemiColon) || this.accept(TokenType.CDO) || this.accept(TokenType.CDC) @@ -556,13 +557,17 @@ export class Parser { if (this.accept(TokenType.EOF)) { return this.finish(node); } + if (this.peek(TokenType.AtKeyword) || this.peek(TokenType.AtIncludeShort)) { return this.finish(node); } - // nesting - if (this.peekDelim("&")) { + + let mark = this.mark(); + if (this._parseSelector(true)) { + this.restoreAtMark(mark); return this.finish(node); } + if (!this.accept(TokenType.Dedent)) { return this.finish(node, ParseError.DedentExpected, [TokenType.Newline, TokenType.Indent, TokenType.EOF]); } diff --git a/packages/vscode-css-languageservice/src/parser/sassScanner.ts b/packages/vscode-css-languageservice/src/parser/sassScanner.ts index 279e7d93..2ea46489 100644 --- a/packages/vscode-css-languageservice/src/parser/sassScanner.ts +++ b/packages/vscode-css-languageservice/src/parser/sassScanner.ts @@ -213,6 +213,7 @@ export class SassScanner extends Scanner { } else if (commentDepth === depth) { // If there's no indentation at this point, we require comment syntax if (!this.stream.advanceIfChars([_FSL, _FSL]) && !this.stream.advanceIfChars([_MUL])) { + this.stream.goBackTo(mark, depth); break scan; } } else if (commentDepth === 0) { diff --git a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts index cd4d2bb9..6fde2b33 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts @@ -106,8 +106,7 @@ suite("Sass - Parser", () => { +grid-core-span($gridColumns, $gridColumnWidth, $gridGutterWidth) // generate .spanX and .offsetX +grid-core-span-x($gridColumns, $gridColumnWidth, $gridGutterWidth) - +grid-core-offset-x($gridColumns, $gridColumnWidth, $gridGutterWidth) -`, + +grid-core-offset-x($gridColumns, $gridColumnWidth, $gridGutterWidth)`, parser, parser._parseStylesheet.bind(parser), ); From 0d188a1a7d03dd307c3c699c23ef5518c993360d Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 4 Aug 2024 16:51:19 +0200 Subject: [PATCH 083/138] refactor: fix multiline selectors for indented --- .../src/parser/cssParser.ts | 3 +++ .../src/test/sass/parser-indented.test.ts | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/vscode-css-languageservice/src/parser/cssParser.ts b/packages/vscode-css-languageservice/src/parser/cssParser.ts index ac84679e..b1148e7c 100644 --- a/packages/vscode-css-languageservice/src/parser/cssParser.ts +++ b/packages/vscode-css-languageservice/src/parser/cssParser.ts @@ -590,6 +590,9 @@ export class Parser { } public _parseSelector(isNested?: boolean): nodes.Selector | null { + while (this.accept(TokenType.Newline)) { + // loop + } const node = this.create(nodes.Selector); let hasContent = false; diff --git a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts index 6fde2b33..e45d629a 100644 --- a/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts +++ b/packages/vscode-css-languageservice/src/test/sass/parser-indented.test.ts @@ -2519,6 +2519,15 @@ figure }); test("@mixin", () => { + assertNode( + `@mixin grid-input($gridColumnWidth, $gridGutterWidth) + input, + textarea, + .uneditable-input + margin-left: 0`, + parser, + parser._parseMixinDeclaration.bind(parser), + ); assertNode( `@mixin large-text font: @@ -3322,4 +3331,18 @@ body parser._parseStylesheet.bind(parser), ); }); + + test("empty selector should be an error", () => { + assertError( + `.hidden-phone + +.hidden-tablet + +.hidden-desktop + display: none !important`, + parser, + parser._parseStylesheet.bind(parser), + ParseError.IndentExpected, + ); + }); }); From bf27fadc05ae77dee93f26e61996ac9e40b348d9 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 4 Aug 2024 20:04:45 +0200 Subject: [PATCH 084/138] refactor: completions for sass indented --- packages/language-server/src/server.ts | 8 + packages/language-server/src/settings.ts | 2 + .../src/features/do-complete.ts | 37 +- vscode-extension/package.json | 355 +++++++++--------- vscode-extension/src/settings.ts | 2 + 5 files changed, 225 insertions(+), 179 deletions(-) diff --git a/packages/language-server/src/server.ts b/packages/language-server/src/server.ts index e7eec8d0..71a3f866 100644 --- a/packages/language-server/src/server.ts +++ b/packages/language-server/src/server.ts @@ -99,6 +99,12 @@ export class SomeSassServer { // For namespaced completions ".", + + // For property values + ":", + + // For custom properties + "-", ], }, signatureHelpProvider: { @@ -173,6 +179,8 @@ export class SomeSassServer { suggestionStyle: settings.suggestionStyle, suggestFunctionsInStringContextAfterSymbols: settings.suggestFunctionsInStringContextAfterSymbols, + triggerPropertyValueCompletion: + settings.triggerPropertyValueCompletion, }, loadPaths: settings.loadPaths, }); diff --git a/packages/language-server/src/settings.ts b/packages/language-server/src/settings.ts index f3a30949..124b431a 100644 --- a/packages/language-server/src/settings.ts +++ b/packages/language-server/src/settings.ts @@ -7,6 +7,7 @@ export interface ISettings { readonly suggestAllFromOpenDocument: boolean; readonly suggestFromUseOnly: boolean; readonly suggestFunctionsInStringContextAfterSymbols: string; + readonly triggerPropertyValueCompletion: boolean; } export interface IEditorSettings { @@ -29,4 +30,5 @@ export const defaultSettings: ISettings = Object.freeze({ suggestAllFromOpenDocument: true, suggestFromUseOnly: true, suggestFunctionsInStringContextAfterSymbols: " (+-*%", + triggerPropertyValueCompletion: true, }); diff --git a/packages/language-services/src/features/do-complete.ts b/packages/language-services/src/features/do-complete.ts index 0285a506..b67ef8d1 100644 --- a/packages/language-services/src/features/do-complete.ts +++ b/packages/language-services/src/features/do-complete.ts @@ -55,7 +55,10 @@ const rePropertyValue = /.*:\s*/; const reEmptyPropertyValue = /.*:\s*$/; const reQuotedValueInString = /["'](?:[^"'\\]|\\.)*["']/g; const reMixinReference = /.*@include\s+(.*)/; +const reIndentedMixinReference = /.*(@include\s+|\+)(.*)/; const reCompletedMixinWithParametersReference = /.*@include\s+(.*)\(/; +const reCompletedIndentedMixinWithParametersReference = + /.*(@include\s+|\+)(.*)\(/; const reComment = /^(.*\/\/|.*\/\*|\s*\*)/; const reSassDoc = /^[\\s]*\/{3}.*$/; const reQuotes = /["']/; @@ -174,7 +177,7 @@ export class DoComplete extends LanguageFeature { ...this.configuration.completionSettings, triggerPropertyValueCompletion: this.configuration.completionSettings - ?.triggerPropertyValueCompletion || false, + ?.triggerPropertyValueCompletion || true, }, ); if (upstreamResult.items.length > 0) { @@ -322,11 +325,9 @@ export class DoComplete extends LanguageFeature { } } } - - return result; } - if (document.languageId === "sass" && result.items.length === 0) { + if (document.languageId === "sass") { const upstreamResult = await upstreamLs.doComplete2( document, position, @@ -343,6 +344,17 @@ export class DoComplete extends LanguageFeature { result.items.push(...upstreamResult.items); } } + + // give suggestions for all @use in case the user is typing one of those + for (const link of links) { + if (link.namespace) { + result.items.push({ + label: link.namespace, + kind: CompletionItemKind.Module, + }); + } + } + return result; } @@ -478,6 +490,23 @@ export class DoComplete extends LanguageFeature { context.isVariableContext = true; context.isFunctionContext = true; } + } else if (document.languageId === "sass") { + // do the same test for the shorthand + to include mixins in this syntax + if ( + !isPropertyValue && + reIndentedMixinReference.test(lineBeforePosition) + ) { + context.isMixinContext = true; + if ( + reCompletedIndentedMixinWithParametersReference.test( + lineBeforePosition, + ) + ) { + context.isMixinContext = false; + context.isVariableContext = true; + context.isFunctionContext = true; + } + } } return context; diff --git a/vscode-extension/package.json b/vscode-extension/package.json index d1ce9285..b601714f 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -1,179 +1,184 @@ { - "name": "some-sass", - "displayName": "Some Sass: extended support for Sass and SassDoc", - "description": "Full support for @use and @forward, including aliases, prefixes and hiding. Rich documentation through SassDoc. Workspace-wide code navigation and refactoring.", - "version": "3.3.0", - "private": true, - "publisher": "SomewhatStationery", - "license": "MIT", - "engines": { - "vscode": "^1.86.0" - }, - "icon": "icon.png", - "homepage": "https://wkillerud.github.io/some-sass/", - "repository": { - "type": "git", - "url": "https://github.com/wkillerud/some-sass" - }, - "keywords": [ - "scss", + "name": "some-sass", + "displayName": "Some Sass: extended support for Sass and SassDoc", + "description": "Full support for @use and @forward, including aliases, prefixes and hiding. Rich documentation through SassDoc. Workspace-wide code navigation and refactoring.", + "version": "3.3.0", + "private": true, + "publisher": "SomewhatStationery", + "license": "MIT", + "engines": { + "vscode": "^1.86.0" + }, + "icon": "icon.png", + "homepage": "https://wkillerud.github.io/some-sass/", + "repository": { + "type": "git", + "url": "https://github.com/wkillerud/some-sass" + }, + "keywords": [ + "scss", "sass", - "sassdoc", - "autocompletion", - "intellisense", - "refactor" - ], - "categories": [ - "Programming Languages" - ], - "activationEvents": [ + "sassdoc", + "autocompletion", + "intellisense", + "refactor" + ], + "categories": [ + "Programming Languages" + ], + "activationEvents": [ "onLanguage:scss", - "onLanguage:sass", - "onLanguage:vue", - "onLanguage:svelte", - "onLanguage:astro", - "onCommand:_somesass.applyExtractCodeAction" - ], - "capabilities": { - "virtualWorkspaces": true - }, - "browser": "./dist/browser-client.js", - "main": "./dist/node-client.js", - "contributes": { - "languages": [ - { - "id": "sass", - "aliases": [ - "Sass", - "sass-indented" - ], - "extensions": [ - ".sass", - ".sass.erb" - ], - "configuration": "./sass.configuration.json" - }, - { - "id": "sass.hover", - "extensions": [ - ".sass.hover" - ], - "configuration": "./sass.configuration.json" - } - ], - "grammars": [ - { - "language": "sass", - "scopeName": "source.sass", - "path": "./syntaxes/sass.tmLanguage.json" - }, - { - "language": "sass.hover", - "scopeName": "source.sass.hover", - "path": "./syntaxes/sass.hover.highlighting.json" - } - ], - "configuration": { - "properties": { - "somesass.loadPaths": { - "type": "array", - "items": { - "type": "string" - }, - "default": [], - "description": "List of paths relative to the workspace root that should be treated as load paths" - }, - "somesass.scannerDepth": { - "type": "number", - "default": 30, - "description": "The maximum number of nested directories to scan." - }, - "somesass.scannerExclude": { - "type": "array", - "items": { - "type": "string" - }, - "default": [ - "**/.git/**", - "**/node_modules/**", - "**/bower_components/**" - ], - "description": "List of glob patterns for directories that are excluded when scanning." - }, - "somesass.scanImportedFiles": { - "type": "boolean", - "default": true, - "deprecationMessage": "Will be removed at some point after `@import` becomes CSS-only.", - "description": "Allows scan imported files. Turning this off will severely limit functionality, and is not recommended." - }, - "somesass.suggestionStyle": { - "type": "string", - "default": "all", - "description": "Controls the style of suggestions for mixins and placeholders.", - "enum": [ - "all", - "nobracket", - "bracket" - ], - "enumItemLabels": [ - "All", - "No brackets", - "Only brackets" - ], - "enumDescriptions": [ - "Show all suggestions", - "Only show suggestions without brackets", - "Where brackets are suggested, omit duplicates without brackets" - ] - }, - "somesass.suggestAllFromOpenDocument": { - "type": "boolean", - "default": false, - "description": "VS Code has built-in code suggestions for symbols declared in the open document. If you prefer the suggestions from Some Sass, you can opt in by turning on this setting. There will be duplicates." - }, - "somesass.suggestFromUseOnly": { - "type": "boolean", - "default": false, - "description": "If your project uses the new module system with @use and @forward, you may want to only include suggestions from your used modules." - }, - "somesass.suggestFunctionsInStringContextAfterSymbols": { - "type": "string", - "default": " (+-*%", - "description": "Allows prompt Functions in String context after specified symbols." - } - } - } - }, - "dependencies": { - "fast-glob": "3.3.2", - "some-sass-language-server": "1.2.8", - "vscode-css-languageservice": "6.3.0", - "vscode-languageclient": "9.0.1", - "vscode-uri": "3.0.7" - }, - "devDependencies": { - "@types/mocha": "10.0.7", - "@types/vscode": "1.86.0", - "@vscode/test-electron": "2.4.1", - "@vscode/test-web": "0.0.56", - "assert": "2.1.0", - "mocha": "10.7.0", - "shx": "0.3.4" - }, - "scripts": { - "vscode:prepublish": "npm run clean && webpack --mode production", - "clean": "shx rm -rf dist", - "build": "webpack --mode development", - "start:web": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=.", - "lint": "eslint \"**/*.ts\" --cache", - "test:e2e": "node ./test/e2e/runTest.js", - "pretest:web": "webpack --config ./webpack.test-web.config.js ", - "test:web": "node ./test/web/runTest.js" - }, - "__metadata": { - "id": "6d35099c-3671-464c-ac0b-34a0c3823927", - "publisherDisplayName": "Somewhat Stationery", - "publisherId": "02638283-c13a-4acf-9f26-24bdcfdfce24", - "isPreReleaseVersion": false - } + "onLanguage:sass", + "onLanguage:vue", + "onLanguage:svelte", + "onLanguage:astro", + "onCommand:_somesass.applyExtractCodeAction" + ], + "capabilities": { + "virtualWorkspaces": true + }, + "browser": "./dist/browser-client.js", + "main": "./dist/node-client.js", + "contributes": { + "languages": [ + { + "id": "sass", + "aliases": [ + "Sass", + "sass-indented" + ], + "extensions": [ + ".sass", + ".sass.erb" + ], + "configuration": "./sass.configuration.json" + }, + { + "id": "sass.hover", + "extensions": [ + ".sass.hover" + ], + "configuration": "./sass.configuration.json" + } + ], + "grammars": [ + { + "language": "sass", + "scopeName": "source.sass", + "path": "./syntaxes/sass.tmLanguage.json" + }, + { + "language": "sass.hover", + "scopeName": "source.sass.hover", + "path": "./syntaxes/sass.hover.highlighting.json" + } + ], + "configuration": { + "properties": { + "somesass.loadPaths": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "description": "List of paths relative to the workspace root that should be treated as load paths" + }, + "somesass.scannerDepth": { + "type": "number", + "default": 30, + "description": "The maximum number of nested directories to scan." + }, + "somesass.scannerExclude": { + "type": "array", + "items": { + "type": "string" + }, + "default": [ + "**/.git/**", + "**/node_modules/**", + "**/bower_components/**" + ], + "description": "List of glob patterns for directories that are excluded when scanning." + }, + "somesass.scanImportedFiles": { + "type": "boolean", + "default": true, + "deprecationMessage": "Will be removed at some point after `@import` becomes CSS-only.", + "description": "Allows scan imported files. Turning this off will severely limit functionality, and is not recommended." + }, + "somesass.suggestionStyle": { + "type": "string", + "default": "all", + "description": "Controls the style of suggestions for mixins and placeholders.", + "enum": [ + "all", + "nobracket", + "bracket" + ], + "enumItemLabels": [ + "All", + "No brackets", + "Only brackets" + ], + "enumDescriptions": [ + "Show all suggestions", + "Only show suggestions without brackets", + "Where brackets are suggested, omit duplicates without brackets" + ] + }, + "somesass.suggestAllFromOpenDocument": { + "type": "boolean", + "default": false, + "description": "VS Code has built-in code suggestions for symbols declared in the open document. If you prefer the suggestions from Some Sass, you can opt in by turning on this setting. There will be duplicates." + }, + "somesass.suggestFromUseOnly": { + "type": "boolean", + "default": false, + "description": "If your project uses the new module system with @use and @forward, you may want to only include suggestions from your used modules." + }, + "somesass.suggestFunctionsInStringContextAfterSymbols": { + "type": "string", + "default": " (+-*%", + "description": "Allows prompt Functions in String context after specified symbols." + }, + "somesass.triggerPropertyValueCompletion": { + "type": "boolean", + "default": true, + "description": "By default, Some Sass triggers property value completion after selecting a CSS property in for Sass. Use this setting to disable this behavior." + } + } + } + }, + "dependencies": { + "fast-glob": "3.3.2", + "some-sass-language-server": "1.2.8", + "vscode-css-languageservice": "6.3.0", + "vscode-languageclient": "9.0.1", + "vscode-uri": "3.0.7" + }, + "devDependencies": { + "@types/mocha": "10.0.7", + "@types/vscode": "1.86.0", + "@vscode/test-electron": "2.4.1", + "@vscode/test-web": "0.0.56", + "assert": "2.1.0", + "mocha": "10.7.0", + "shx": "0.3.4" + }, + "scripts": { + "vscode:prepublish": "npm run clean && webpack --mode production", + "clean": "shx rm -rf dist", + "build": "webpack --mode development", + "start:web": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=.", + "lint": "eslint \"**/*.ts\" --cache", + "test:e2e": "node ./test/e2e/runTest.js", + "pretest:web": "webpack --config ./webpack.test-web.config.js ", + "test:web": "node ./test/web/runTest.js" + }, + "__metadata": { + "id": "6d35099c-3671-464c-ac0b-34a0c3823927", + "publisherDisplayName": "Somewhat Stationery", + "publisherId": "02638283-c13a-4acf-9f26-24bdcfdfce24", + "isPreReleaseVersion": false + } } diff --git a/vscode-extension/src/settings.ts b/vscode-extension/src/settings.ts index f3a30949..26d703d0 100644 --- a/vscode-extension/src/settings.ts +++ b/vscode-extension/src/settings.ts @@ -6,6 +6,7 @@ export interface ISettings { readonly suggestionStyle: "all" | "nobracket" | "bracket"; readonly suggestAllFromOpenDocument: boolean; readonly suggestFromUseOnly: boolean; + readonly triggerPropertyValueCompletion: boolean; readonly suggestFunctionsInStringContextAfterSymbols: string; } @@ -29,4 +30,5 @@ export const defaultSettings: ISettings = Object.freeze({ suggestAllFromOpenDocument: true, suggestFromUseOnly: true, suggestFunctionsInStringContextAfterSymbols: " (+-*%", + triggerPropertyValueCompletion: true, }); From 94d459238a4308f8b557d3236427130dbfb8bce7 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 4 Aug 2024 20:13:40 +0200 Subject: [PATCH 085/138] refactor: color decorators for css colors in sass --- .../src/features/find-colors.ts | 76 ++++++++++++------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/packages/language-services/src/features/find-colors.ts b/packages/language-services/src/features/find-colors.ts index 6868b40e..b58af5d2 100644 --- a/packages/language-services/src/features/find-colors.ts +++ b/packages/language-services/src/features/find-colors.ts @@ -13,8 +13,6 @@ import { export class FindColors extends LanguageFeature { async findColors(document: TextDocument): Promise { - const result: ColorInformation[] = []; - const variables: Variable[] = []; const stylesheet = this.ls.parseStylesheet(document); stylesheet.accept((node) => { @@ -41,36 +39,49 @@ export class FindColors extends LanguageFeature { return []; } - for (const variable of variables) { - const value = await this.findValue( + const result: (ColorInformation | null)[] = await Promise.all( + variables.map(async (variable) => { + const value = await this.findValue( + document, + document.positionAt(variable.offset), + ); + if (value) { + try { + const color = ColorDotJS.parse(value); + const srgba = ColorDotJS.to(color, "srgb"); + const colorInformation: ColorInformation = { + color: { + alpha: srgba.alpha || 1, + red: srgba.coords[0], + green: srgba.coords[1], + blue: srgba.coords[2], + }, + range: { + start: document.positionAt(variable.offset), + end: document.positionAt( + variable.offset + (variable as Variable).getName().length, + ), + }, + }; + + return colorInformation; + } catch (e) { + // do nothing + } + } + return null; + }), + ); + + if (document.languageId === "sass") { + const upstream = this.getUpstreamLanguageServer().findDocumentColors( document, - document.positionAt(variable.offset), + stylesheet, ); - if (value) { - try { - const color = ColorDotJS.parse(value); - const srgba = ColorDotJS.to(color, "srgb"); - const colorInformation: ColorInformation = { - color: { - alpha: srgba.alpha || 1, - red: srgba.coords[0], - green: srgba.coords[1], - blue: srgba.coords[2], - }, - range: { - start: document.positionAt(variable.offset), - end: document.positionAt( - variable.offset + (variable as Variable).getName().length, - ), - }, - }; - result.push(colorInformation); - } catch (e) { - // do nothing - } - } + result.push(...upstream); } - return result; + + return result.filter((c) => c !== null); } getColorPresentations( @@ -93,6 +104,13 @@ export class FindColors extends LanguageFeature { range, ); } + } else if (document.languageId === "sass") { + return this.getUpstreamLanguageServer().getColorPresentations( + document, + stylesheet, + color, + range, + ); } return []; From 4a75ec7c41d89c457941d56528c108f61c427088 Mon Sep 17 00:00:00 2001 From: William Killerud Date: Sun, 4 Aug 2024 21:11:57 +0200 Subject: [PATCH 086/138] refactor: splitt settings per syntax BREAKING CHANGE: JSON keys for suggestion settings have been renamed. Deprecated settings have been removed. For `somesass.suggest` settings, replace them with `somesass.scss.completion.suggest` settings. --- docs/src/user-guide/settings.md | 65 ++++------ packages/language-server/src/server.ts | 16 +-- packages/language-server/src/settings.ts | 43 +++--- .../language-server/src/workspace-scanner.ts | 18 +-- .../__tests__/do-complete-modules.test.ts | 34 ++++- .../src/features/do-complete.ts | 55 +++++--- .../language-services/src/language-feature.ts | 34 ++--- .../src/language-services-types.ts | 122 +++++++++++------- .../src/language-services.ts | 8 +- .../src/cssLanguageTypes.ts | 2 +- vscode-extension/README.md | 11 +- vscode-extension/package.json | 59 ++++++--- vscode-extension/src/settings.ts | 34 ----- .../test/fixtures/.vscode/settings.json | 15 ++- 14 files changed, 275 insertions(+), 241 deletions(-) delete mode 100644 vscode-extension/src/settings.ts diff --git a/docs/src/user-guide/settings.md b/docs/src/user-guide/settings.md index 5f6e1608..86875863 100644 --- a/docs/src/user-guide/settings.md +++ b/docs/src/user-guide/settings.md @@ -9,18 +9,11 @@ These are the recommended settings: ```jsonc { // Recommended if you don't rely on @import - "somesass.suggestFromUseOnly": true, + "somesass.scss.completion.suggestFromUseOnly": true, + "somesass.sass.completion.suggestFromUseOnly": true, // Optional, if you get suggestions from the current document after namespace.$ (you don't need the $ for narrowing down suggestions) "editor.wordBasedSuggestions": false, - - // Optional, for Vue, Svelte, Astro: add `scss` to the list of excluded languages for Emmet to avoid suggestions in Vue, Svelte or Astro files. - // VS Code understands that