From 5451f7b8ff465045bb8dabc688799a444d23f3a8 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Fri, 6 Jun 2025 11:32:14 +0700 Subject: [PATCH 01/14] Restructure example CSS files --- .../syntax-issues/archive/aspect-ratio.css | 3 - .../syntax-issues/archive/at-container.css | 141 ------- .dev-assets/syntax-issues/archive/at-page.css | 14 - .../syntax-issues/archive/at-supports.css | 7 - .../archive/calc-in-at-media.css | 34 -- .../syntax-issues/archive/calc-nesting.css | 37 -- .../archive/clamp-arithmetic-operators.css | 19 - .../syntax-issues/archive/light-dark-func.css | 3 - .dev-assets/syntax-issues/at-import.css | 10 - .dev-assets/syntax-issues/at-layer.css | 15 - .../syntax-issues/at-media-propertie.css | 9 - .../syntax-issues/grid-placement-slash.css | 9 - .dev-assets/syntax-issues/of-not.css | 23 -- demo/css-nesting-demo.css | 303 -------------- demo/css-nesting-invalid.css | 35 -- demo/css-nesting-modules.css | 390 ------------------ examples/demos/at-rules/at-container.css | 0 examples/demos/at-rules/at-counter-style.css | 0 examples/demos/at-rules/at-document.css | 0 examples/demos/at-rules/at-font-face.css | 0 examples/demos/at-rules/at-font-features.css | 0 examples/demos/at-rules/at-import.css | 0 examples/demos/at-rules/at-keyframes.css | 0 examples/demos/at-rules/at-layer.css | 0 examples/demos/at-rules/at-media.css | 0 examples/demos/at-rules/at-namespace.css | 0 examples/demos/at-rules/at-page.css | 0 examples/demos/at-rules/at-supports.css | 0 examples/demos/properties/functions.css | 0 .../demos/properties/nested-properties.css | 0 examples/demos/selectors/nested-selectors.css | 1 + examples/demos/selectors/pseudo-selectors.css | 0 examples/demos/shared/shared-names.css | 0 examples/demos/shared/shared-values.css | 0 34 files changed, 1 insertion(+), 1052 deletions(-) delete mode 100644 .dev-assets/syntax-issues/archive/aspect-ratio.css delete mode 100644 .dev-assets/syntax-issues/archive/at-container.css delete mode 100644 .dev-assets/syntax-issues/archive/at-page.css delete mode 100644 .dev-assets/syntax-issues/archive/at-supports.css delete mode 100644 .dev-assets/syntax-issues/archive/calc-in-at-media.css delete mode 100644 .dev-assets/syntax-issues/archive/calc-nesting.css delete mode 100644 .dev-assets/syntax-issues/archive/clamp-arithmetic-operators.css delete mode 100644 .dev-assets/syntax-issues/archive/light-dark-func.css delete mode 100644 .dev-assets/syntax-issues/at-import.css delete mode 100644 .dev-assets/syntax-issues/at-layer.css delete mode 100644 .dev-assets/syntax-issues/at-media-propertie.css delete mode 100644 .dev-assets/syntax-issues/grid-placement-slash.css delete mode 100644 .dev-assets/syntax-issues/of-not.css delete mode 100644 demo/css-nesting-demo.css delete mode 100644 demo/css-nesting-invalid.css delete mode 100644 demo/css-nesting-modules.css create mode 100644 examples/demos/at-rules/at-container.css create mode 100644 examples/demos/at-rules/at-counter-style.css create mode 100644 examples/demos/at-rules/at-document.css create mode 100644 examples/demos/at-rules/at-font-face.css create mode 100644 examples/demos/at-rules/at-font-features.css create mode 100644 examples/demos/at-rules/at-import.css create mode 100644 examples/demos/at-rules/at-keyframes.css create mode 100644 examples/demos/at-rules/at-layer.css create mode 100644 examples/demos/at-rules/at-media.css create mode 100644 examples/demos/at-rules/at-namespace.css create mode 100644 examples/demos/at-rules/at-page.css create mode 100644 examples/demos/at-rules/at-supports.css create mode 100644 examples/demos/properties/functions.css create mode 100644 examples/demos/properties/nested-properties.css create mode 100644 examples/demos/selectors/nested-selectors.css create mode 100644 examples/demos/selectors/pseudo-selectors.css create mode 100644 examples/demos/shared/shared-names.css create mode 100644 examples/demos/shared/shared-values.css diff --git a/.dev-assets/syntax-issues/archive/aspect-ratio.css b/.dev-assets/syntax-issues/archive/aspect-ratio.css deleted file mode 100644 index 645a57a..0000000 --- a/.dev-assets/syntax-issues/archive/aspect-ratio.css +++ /dev/null @@ -1,3 +0,0 @@ -.class { - aspect-ratio: 1; -} diff --git a/.dev-assets/syntax-issues/archive/at-container.css b/.dev-assets/syntax-issues/archive/at-container.css deleted file mode 100644 index 540ad01..0000000 --- a/.dev-assets/syntax-issues/archive/at-container.css +++ /dev/null @@ -1,141 +0,0 @@ -/** - * AT CONTAINER - */ - -/* AND */ -@container (width > 400px) and (height > 400px) { -} -@container (min-width: 600px) and (orientation: landscape) { -} - -/* NOT */ -@container not (width < 400px) { -} -@container not (width < 400px) not (width > 800px) { -} -@container sidebar not (orientation: landscape) and (min-width: 600px) { -} -@container sidebar not (orientation: landscape) and (min-width: 600px) or (min-width: 600px) { -} - -/* OR */ -@container (width > 400px) or (height > 400px) {'' -} -@container (display: grid) or (display: flex) { -} - -/* CONTAINER-NAME */ -@container name (width > 400px) and (height > 400px) { -} -@container name1 name2 name3 not (width > 400px) not (height > 400px) { -} - -@container default (width > 400px) { -} -@container none default (width > 400px) { -} - -/* CONTAINER-QUERY */ -@container (min-aspect-ratio: 3 / 2) { - .video-player { - border: 5px solid #fff; - } -} - -@container (min-width: calc( var(--width) * 1rem)) { - .card { - margin: 0; - } -} - -/* STYLE-QUERY */ -@container style( --accent-color : currentColor ) { -} - -@container style(small --cards: /*comment*/ small 333px) { -} -@container card (inline-size > 30em) and style(--responsive: true) { -} -@container style( --width : calc( var(--hello) ) ) { -} - -@container style(--themeBackground), not style(--themeColor: blue), style(--themeColor: blue) and style(--themeColor: purple), style(--themeColor: blue) or style(--themeColor: purple) { -} -@container card (inline-size > 30em) and style(--responsive: true) { -} -@container style(--cards: 333px) { - article { - border: thin solid silver; - border-radius: 0.5em; - padding: 1em; - } -} - -/* NESTED @CONTAINER */ -@container summary (width > 400px) { - @container (min-width: 800px) { - .card { - margin: 50px; - } - @container (min-width: calc( var(--width) * 1rem)) { - .card { - margin: 0; - } - } - } -} -@container card (inline-size > 30em) { - @container style(--responsive: true) { - /* styles */ - } -} - -/* EVERYTHING */ -@container /*comment*/ tall /*comment*/ short not invalidName (width > 400px) and invalidName not (min-height: 400px /*comment*/ invalidName) /*comment*/ or style(--accent-color: currentColor) { - .card { - margin: 0; - .example { - margin: 0; - } - } -} - -@container name1 name2 not (max-width: calc( (768 / 16) * 1rem)) invalidName { - invalidName .wp-block-buttons { - margin: calc(var(--wp--custom--element-spacing) * 3) auto; - .wp-block-button, - .wp-element-button { - width: 100%; - } - } -} - -@container (10em <= width <= 20em) { - /* styles */ -} - -@container (inline-size >= 0px) { - /* only applies when an inline-size container is available */ - h2 { - font-size: calc(1.2em + 1cqi); - } -} - -@container (width > 40em) { - h2 { - font-size: 1.5em; - } -} - -@container my-page-layout (block-size > 12em) { - .card { - margin-block: 2em; - } -} - -@container (inline-size >= 0px) { - /* only applies when an inline-size container is available */ - h2 { - font-size: calc(1.2em + 1cqi); - } -} diff --git a/.dev-assets/syntax-issues/archive/at-page.css b/.dev-assets/syntax-issues/archive/at-page.css deleted file mode 100644 index 13d9507..0000000 --- a/.dev-assets/syntax-issues/archive/at-page.css +++ /dev/null @@ -1,14 +0,0 @@ -/** - * AT PAGE - */ - -@page { - content: "Page " counter(pageNumber); -} - -@page { - /* margin box at top right showing page number */ - @top-right { - content: "Page " counter(pageNumber); - } -} diff --git a/.dev-assets/syntax-issues/archive/at-supports.css b/.dev-assets/syntax-issues/archive/at-supports.css deleted file mode 100644 index 1846db0..0000000 --- a/.dev-assets/syntax-issues/archive/at-supports.css +++ /dev/null @@ -1,7 +0,0 @@ -:root { - --supports-red: false; - @supports (color: red) { - --supports-red: true; - } - --x: 1; -} diff --git a/.dev-assets/syntax-issues/archive/calc-in-at-media.css b/.dev-assets/syntax-issues/archive/calc-in-at-media.css deleted file mode 100644 index 433a6dd..0000000 --- a/.dev-assets/syntax-issues/archive/calc-in-at-media.css +++ /dev/null @@ -1,34 +0,0 @@ -/** - * CALC FUNCTION IN @MEDIA - */ - -:root { - --size: 500px; -} - -.example { - max-width: calc(var(--size) * 1rem); -} - -@media screen and (min-width: calc( var(--size) * 1rem)) { - .example { - max-width: calc(var(--size) * 1rem); - } -} - -/* With CSS nesting */ -@media only screen and (max-width: calc( var(--size) * 1rem)) { - .example { - span { - max-width: calc(var(--size) * 1rem); - } - } -} - -.example { - @media only screen and (max-width: calc( var(--size) * 1rem)) { - span { - max-width: calc(var(--size) * 1rem); - } - } -} diff --git a/.dev-assets/syntax-issues/archive/calc-nesting.css b/.dev-assets/syntax-issues/archive/calc-nesting.css deleted file mode 100644 index 9787717..0000000 --- a/.dev-assets/syntax-issues/archive/calc-nesting.css +++ /dev/null @@ -1,37 +0,0 @@ -/** - * NESTED CALC FUNCTIONS - */ - -.example { - margin-top: calc(600 / 16 * 1rem); - margin-bottom: calc( (768 / 16) * 1rem); - margin-right: calc( (768 / 16 * (768 / 16) ) * 1rem); - margin-left: calc( (768 / 16 * (768 / 16 + (768 / 16) ) ) * 1rem); - padding-top: calc( (768 / 16 * (768 / 16 + (768 / 16 - (768 / 16) ) ) ) * 1rem); -} - -/* Without CSS nesting */ -@media screen and (max-width: calc( (1280 / 16) * 1rem)) { - .example { - max-width: calc((768 / 16) * 1rem); - } -} - -/* With CSS nesting */ -@media only screen and (max-width: calc( (768 / 16) * 1rem )) { - .example { - max-width: calc((768 / 16) * 1rem); - span { - max-width: calc((600 / 16) * 1rem); - } - } -} - -.example { - @media only screen and (max-width: calc( var(--size) * 1rem)) { - max-width: calc((768 / 16) * 1rem); - span { - max-width: calc((600 / 16) * 1rem); - } - } -} diff --git a/.dev-assets/syntax-issues/archive/clamp-arithmetic-operators.css b/.dev-assets/syntax-issues/archive/clamp-arithmetic-operators.css deleted file mode 100644 index 08036ad..0000000 --- a/.dev-assets/syntax-issues/archive/clamp-arithmetic-operators.css +++ /dev/null @@ -1,19 +0,0 @@ -/** - * FUNCTION ARITHMETIC OPERATORS - */ - -.example { - margin-block-start: clamp(15px, 0.805rem + 0.64vw, 18px); - margin-block-end: clamp(15px, 0.805rem - 0.64vw, 18px); -} - -.other-examples { - width: clamp(100px, calc(30% / 2rem + 10px), 900px); - width: min(1000px, calc(70% + 100px)); - font-size: clamp(1.1rem, 0.7153rem + 1.6368vw, 1.5rem); - width: max(300px, 50% + 20px); - width: min(300px, 50% + 20px); - height: calc(var(--base-height) + 10%); - font-size: clamp(15px, 0.805rem + 0.64vw, 18px); - width: calc(100% - 20px); -} diff --git a/.dev-assets/syntax-issues/archive/light-dark-func.css b/.dev-assets/syntax-issues/archive/light-dark-func.css deleted file mode 100644 index 0bac515..0000000 --- a/.dev-assets/syntax-issues/archive/light-dark-func.css +++ /dev/null @@ -1,3 +0,0 @@ -.class { - color: light-dark(white, black); -} diff --git a/.dev-assets/syntax-issues/at-import.css b/.dev-assets/syntax-issues/at-import.css deleted file mode 100644 index c7303fc..0000000 --- a/.dev-assets/syntax-issues/at-import.css +++ /dev/null @@ -1,10 +0,0 @@ -/** - * AT IMPORT - */ - -/* new syntax rules for @import have no syntax highlighting */ -@import url("green.css") layer supports(selector(&)) (min-width: calc(10px)); - -/* layer as a function */ -@import url("green.css") layer(bar) supports(selector(&)) - (min-width: calc(10px)); diff --git a/.dev-assets/syntax-issues/at-layer.css b/.dev-assets/syntax-issues/at-layer.css deleted file mode 100644 index d065eaf..0000000 --- a/.dev-assets/syntax-issues/at-layer.css +++ /dev/null @@ -1,15 +0,0 @@ -/** - * AT LAYER - */ - -/* layer name has no syntax highlighting */ -.foo { - @layer base { - block-size: 100%; - @layer support { - & .bar { - min-block-size: 100%; - } - } - } -} diff --git a/.dev-assets/syntax-issues/at-media-propertie.css b/.dev-assets/syntax-issues/at-media-propertie.css deleted file mode 100644 index fcc87c5..0000000 --- a/.dev-assets/syntax-issues/at-media-propertie.css +++ /dev/null @@ -1,9 +0,0 @@ -/** - * AT MEDIA PROPERTY - */ - -/* Missing token for `prefers-reduced-motion: reduce` */ -@media (prefers-reduced-motion: reduce) { -} - -/* NOTE: see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries#descriptors for more media properties */ diff --git a/.dev-assets/syntax-issues/grid-placement-slash.css b/.dev-assets/syntax-issues/grid-placement-slash.css deleted file mode 100644 index 94edcaa..0000000 --- a/.dev-assets/syntax-issues/grid-placement-slash.css +++ /dev/null @@ -1,9 +0,0 @@ -/** - * GRID PLACEMENT SLASH - */ - -/* Property value slash doesn't have its own syntax highlighting */ -.example { - grid-row: 1 / 3; - grid-column: 1/-1; -} diff --git a/.dev-assets/syntax-issues/of-not.css b/.dev-assets/syntax-issues/of-not.css deleted file mode 100644 index 297cb2c..0000000 --- a/.dev-assets/syntax-issues/of-not.css +++ /dev/null @@ -1,23 +0,0 @@ -/** - * OF :NOT - */ - -/* `of` and trailing grammar has no syntax highlighting */ -.item > div:nth-child(1 of :not(.hidden)) { - padding-left: var(--item_margin); -} -.item > div:nth-last-child(1 of :not(.hidden)) { - padding-right: var(--item_margin); -} - -/* Same as above, but using CSS Nesting formatting */ -.item { - & > div { - &:nth-child(1 of :not(.hidden)) { - padding-left: var(--item_margin); - } - &:nth-last-child(1 of :not(.hidden)) { - padding-right: var(--item_margin); - } - } -} diff --git a/demo/css-nesting-demo.css b/demo/css-nesting-demo.css deleted file mode 100644 index 0aae910..0000000 --- a/demo/css-nesting-demo.css +++ /dev/null @@ -1,303 +0,0 @@ -/** -* CSS NESTING DEMO -*/ - -/** -* Nesting without the `&` nesting selector -* See: https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector -*/ - -.parent-rule { - margin: 0; - .child-rule { - margin: 0; - } -} - -.parent-rule { - text-decoration: none; - :hover { - text-decoration: underline; - } -} - -/** -* Nesting with the `&` nesting selector -* See: https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector -*/ - -.parent-rule { - text-decoration: none; - &:hover { - text-decoration: underline; - } -} - -.example { - font-family: system-ui; - font-size: 1.2rem; - & > a { - color: tomato; - &:hover, - &:focus { - color: ivory; - background-color: tomato; - } - } -} - -/** - * Appending the `&` nesting selector - * See: https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector#appending_the_nesting_selector - */ - -.card { - /* .card styles */ - margin: 0; - .featured & { - /* .featured .card styles */ - margin: 0; - } -} - -.card { - /* .card styles */ - margin: 0; - .featured & & & { - /* .featured .card .card .card styles */ - margin: 0; - } -} - -.card { - padding: 0.5rem; - border: 1px solid black; - border-radius: 0.5rem; - & h2 { - /* equivalent to `.card h2` */ - color: slateblue; - .featured & { - /* equivalent to `:is(.card h2):is(.featured h2)` */ - color: tomato; - } - } -} - -/** - * Appending the `&` nesting selector as a compound selector - */ - -.example { - section& { - /* equivalent to `section.example` */ - margin: 0; - } - .class& { - /* equivalent to `.class.example` */ - padding: 0; - } - #id& { - /* equivalent to `#id.example` */ - border: 1px solid black; - } - @media screen and (max-width: 1100px) { - section& { - margin: 0; - } - .class& { - padding: 0; - } - #id& { - margin: 0; - } - } -} - -/** -* Using `&` outside nested rule -* If not used in nested style rule, the & nesting selector represents the scoping root. -* See: https://developer.mozilla.org/en-US/docs/Web/CSS/Nesting_selector#using_outside_nested_rule -*/ - -& { - color: blue; - font-weight: bold; -} - -&:hover { - background-color: wheat; -} - -/** - * Nesting `@media` at-rule - * See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Nesting_at-rules#nesting_media_at-rule - */ - -/* Typical way to write Nested CSS */ -.foo { - display: grid; - @media (orientation: landscape) { - grid-auto-flow: column; - } -} -/* Expanded nested CSS as the browser parses it */ -.foo { - display: grid; - @media (orientation: landscape) { - & { - grid-auto-flow: column; - } - } -} - -/** - * Multiple nested `@media` at-rule - * See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Nesting_at-rules#multiple_nested_media_at-rules - */ - -.foo { - display: grid; - @media (orientation: landscape) { - grid-auto-flow: column; - @media (min-width: 1024px) { - max-inline-size: 1024px; - } - } -} - -/* Equivalent without nesting */ -.foo { - display: grid; -} -@media (orientation: landscape) { - .foo { - grid-auto-flow: column; - } -} -@media (orientation: landscape) and (min-width: 1024px) { - .foo { - max-inline-size: 1024px; - } -} - -/** - * Nesting Cascade Layers (`@layer`) - * See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Nesting_at-rules#nesting_cascade_layers_layer - */ - -@layer base { - @layer support; -} - -.foo { - @layer base { - block-size: 100%; - @layer support { - & .bar { - min-block-size: 100%; - } - } - } -} -/* Equivalent without nesting */ -@layer base { - .foo { - block-size: 100%; - } -} -@layer base.support { - .foo .bar { - min-block-size: 100%; - } -} - -/** -* Creating complex selectors with CSS nesting -* See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_selectors/Selectors_and_combinators#creating_complex_selectors_with_css_nesting -*/ -h2 { - & + p { - & ~ p { - font-style: italic; - } - & + p { - color: red; - } - } -} -.myClass { - & + p { - text-decoration: underline; - } -} -#myId { - & > .myClass { - outline: 3px dashed red; - } - > p #anotherId { - font-size: 1.1rem; - } -} - -/** -* Nesting and compound selectors -* See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting#compound_selectors -*/ - -.notice { - color: black; - padding: 1rem; - .notice-heading:before { - /* equivalent to `.notice .notice-heading:before` */ - content: "ℹ︎ "; - } - &.warning { - /* equivalent to `.notice.warning` */ - background-color: #d81b60; - border-color: #d81b60; - color: white; - .warning-heading:before { - /* equivalent to `.notice.warning .warning-heading:before` */ - content: "! "; - } - } - &.success { - /* equivalent to `.notice.success` */ - background-color: #004d40; - border-color: #004d40; - color: white; - .success-heading:before { - /* equivalent to `.notice.success .success-heading:before` */ - content: "✓ "; - } - } -} - -.notice { - color: black; - padding: 1rem; - .notice-heading:before { - /* equivalent to `.notice .notice-heading:before` */ - content: "ℹ︎ "; - } - .warning { - /* equivalent to `.notice.warning` */ - background-color: #d81b60; - border-color: #d81b60; - color: white; - .warning-heading:before { - /* equivalent to `.notice.warning .warning-heading:before` */ - content: "! "; - } - } - .success { - /* equivalent to `.notice.success` */ - background-color: #004d40; - border-color: #004d40; - color: white; - .success-heading:before { - /* equivalent to `.notice.success .success-heading:before` */ - content: "✓ "; - } - } -} diff --git a/demo/css-nesting-invalid.css b/demo/css-nesting-invalid.css deleted file mode 100644 index fa6a90b..0000000 --- a/demo/css-nesting-invalid.css +++ /dev/null @@ -1,35 +0,0 @@ -/** - * INVALID CSS NESTING - */ - -/** - * Concatenation (is not possible) - * See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting#concatenation_is_not_possible - */ - -.component { - &__child-element { - margin: 0; - } -} -/* In Sass this becomes the CSS below, but CSS nesting doesn't allow this */ -.component__child-element { - margin: 0; -} - -/** -* Invalid nested style rules -* See: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting#invalid_nested_style_rules -*/ - -.parent { - /* .parent styles these work fine */ - & %invalid { - /* %invalid styles all of which are ignored */ - margin: 0; - } - & .valid { - /* .parent .valid styles these work fine */ - margin: 0; - } -} diff --git a/demo/css-nesting-modules.css b/demo/css-nesting-modules.css deleted file mode 100644 index b9f3524..0000000 --- a/demo/css-nesting-modules.css +++ /dev/null @@ -1,390 +0,0 @@ -/** -* CSS NESTING MODULES -* For reviewing CSS nesting and non-nesting syntax For any issues. -*/ - -/** -* At Rules -*/ - -@container (width > 400px) { - h2 { - font-size: 1.5em; - } - .card { - width: 50%; - background-color: gray; - font-size: 1em; - } -} -/* with an optional */ -@container tall (height > 30rem) { - h2 { - line-height: 1.6; - } -} -.post { - container-name: tall; - container-type: inline-size; -} -/* shorthand syntax */ -.post { - container: tall / inline-size; -} - -@counter-style thumbs { - system: cyclic; - symbols: "\1F44D"; - suffix: " "; -} -ul { - list-style: thumbs; -} - -@font-face { - font-family: "MyWebFont"; - src: url("webfont.woff2") format("woff2"), url("webfont.woff") format("woff"); -} - -@font-feature-values MyFont { - @styleset { - nice-style: 1; - } - @stylistic { - nice-style: 1; - } - @character-variant { - nice-style: 1; - } - @swash { - nice-style: 1; - } - @ornaments { - nice-style: 1; - } - @annotation { - nice-style: 1; - } -} - -@import url("https://fonts.googleapis.com/css?family=Open+Sans"); - -@keyframes slidein { - from { - margin-left: 100%; - width: 300%; - } - to { - margin-left: 0%; - width: 100%; - } -} - -@layer base { - h1 { - color: red; - } -} - -@media (max-width: 600px) { - .facet_sidebar { - display: none; - } -} -@media (min-width: 600px), (orientation: portrait) { - /* Styles */ -} - -@page { - size: A4; - margin: 10%; -} -@page :left { - margin-top: 4in; -} -@page wide { - size: a4 landscape; -} -@page { - /* margin box at top right showing page number */ - @top-right { - content: "Page " counter(pageNumber); - } -} - -/* @property is not cross-browser friendly yet */ -/* @property --my-color { - syntax: ''; - inherits: false; - initial-value: black; -} */ - -/* @scope is not cross-browser friendly yet */ -/* @scope (.article-body) to (figure) { - img { - border: 5px solid black; - background-color: goldenrod; - } - :scope { - background: rebeccapurple; - color: antiquewhite; - font-family: sans-serif; - } -} */ - -@supports (display: grid) { - .grid { - display: grid; - } -} - -/** -* FEATURE QUERY OPERATORS (AND, OR, NOT) -*/ - -/* AND */ -@container (width > 400px) and (height > 400px) { -} -@media (min-width: 600px) and (orientation: landscape) { -} -@supports (display: grid) and (gap: 1rem) { -} - -/* NOT */ -@container not (width < 400px) { -} -@media not (orientation: landscape) { -} -@supports not (display: flex) { -} - -/* OR */ -@container (width > 400px) or (height > 400px) { -} -@supports (display: grid) or (display: flex) { -} - -/** -* COMBINATORS -*/ - -.example > li { - margin: 0; -} -div > span { - margin: 0; -} -p ~ span { - color: red; -} -img + p { - color: red; -} - -.example { - div { - > span { - margin: 0; - } - } - p { - ~ span { - color: red; - } - } - img { - + p { - color: red; - } - } -} - -.example { - & div { - & > span { - margin: 0; - } - } - & p { - & ~ span { - color: red; - } - } - & img { - & + p { - color: red; - } - } -} - -/** -* COMMAS -*/ - -html, -body { - margin: 0; - &.a, - &.b { - margin: 0; - } -} - -@media (prefers-reduced-motion: reduce) { - html, - html:focus-within { - scroll-behavior: auto; - &.a, - &.b { - margin: 0; - } - } -} - -.class, -.example { - @media (min-width: 500px) { - &::before, - &::after { - content: '"'; - display: block; - } - } -} - -.class, -.example { - fieldset, - span { - max-width: calc((768 / 16) * 1rem); - &.class_left, - &.class_right { - flex: 0 0 100%; - } - } -} - -@media only screen and (max-width: calc( (768 / 16) * 1rem )) { - .class, - .example { - fieldset, - span { - max-width: calc((768 / 16) * 1rem); - flex: 0 0 100%; - &.class_left, - &.class_right { - flex: 0 0 100%; - } - } - } -} - -.class, -.example { - @media only screen and (max-width: calc( (768 / 16) * 1rem )) { - fieldset, - span { - max-width: calc((768 / 16) * 1rem); - flex: 0 0 100%; - &.class_left, - &.class_right { - flex: 0 0 100%; - } - } - } -} - -/** -* FUNCTIONS -*/ - -.example { - color: rgb(255, 0, 0); - background-color: oklch(70% 0.1 315); - margin-top: calc((100 / 16) * 1rem); - margin-bottom: calc(var(--size, 10px) * 1rem); - max-width: min(100%, 600px); - padding: clamp(1rem, 5%, 2rem); - width: calc(100% - 3em); -} - -/** - * PROPERTY NAMES WHEN SAME AS TAG NAMES - */ - -content, -cursor, -filter, -span { - content: ""; - cursor: pointer; - filter: brightness(1.075); -} - -.class { - &::before { - content: ""; - cursor: pointer; - filter: brightness(1.075); - } - & content, - & cursor, - & filter, - & span { - margin: 0; - } -} - -.class { - span, - header, - nav, - label, - ruby, - mark, - svg { - margin: 0; - } -} - -/** - * PSEUDO CLASS NESTING - */ - -.class { - > :nth-child(1) { - margin: 0; - } - @media screen and (max-width: 1100px) { - > :nth-child(1) { - margin: 0; - > :nth-child(1) { - margin: 0; - } - } - } -} - -/* REFERENCES FOR ADDING ADDITIONAL TYPES */ -/* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_selectors */ -/* https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes */ -/* https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_pseudo-elements */ -/* https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements */ - -/* -- selectors -- tag-names -- combinators -- functional-pseudo-classes -- pseudo-classes -- pseudo-elements -- urls -- color-keywords -- functions -- timing-function -- keyword.operator.shape -- property-names -- property-values -- property-keywords -- string -*/ diff --git a/examples/demos/at-rules/at-container.css b/examples/demos/at-rules/at-container.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/at-rules/at-counter-style.css b/examples/demos/at-rules/at-counter-style.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/at-rules/at-document.css b/examples/demos/at-rules/at-document.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/at-rules/at-font-face.css b/examples/demos/at-rules/at-font-face.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/at-rules/at-font-features.css b/examples/demos/at-rules/at-font-features.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/at-rules/at-import.css b/examples/demos/at-rules/at-import.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/at-rules/at-keyframes.css b/examples/demos/at-rules/at-keyframes.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/at-rules/at-layer.css b/examples/demos/at-rules/at-layer.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/at-rules/at-media.css b/examples/demos/at-rules/at-media.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/at-rules/at-namespace.css b/examples/demos/at-rules/at-namespace.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/at-rules/at-page.css b/examples/demos/at-rules/at-page.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/at-rules/at-supports.css b/examples/demos/at-rules/at-supports.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/properties/functions.css b/examples/demos/properties/functions.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/properties/nested-properties.css b/examples/demos/properties/nested-properties.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/selectors/nested-selectors.css b/examples/demos/selectors/nested-selectors.css new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/demos/selectors/nested-selectors.css @@ -0,0 +1 @@ + diff --git a/examples/demos/selectors/pseudo-selectors.css b/examples/demos/selectors/pseudo-selectors.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/shared/shared-names.css b/examples/demos/shared/shared-names.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/demos/shared/shared-values.css b/examples/demos/shared/shared-values.css new file mode 100644 index 0000000..e69de29 From 4339527cc4a2e389bbdc56d821611627ee0f65ca Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Fri, 6 Jun 2025 11:32:48 +0700 Subject: [PATCH 02/14] Move dev doc files out of main repo --- .../VSCode-CSS-Nesting-Extension-Logo.sketch | Bin 92088 -> 0 bytes .dev-assets/get-shared-names.js | 57 ---------- .dev-assets/rule-lists.jsonc | 104 ------------------ ...ing-syntax-highlighting-logo-no-border.png | Bin 19118 -> 0 bytes 4 files changed, 161 deletions(-) delete mode 100644 .dev-assets/Sketch/VSCode-CSS-Nesting-Extension-Logo.sketch delete mode 100644 .dev-assets/get-shared-names.js delete mode 100644 .dev-assets/rule-lists.jsonc delete mode 100644 images/css-nesting-syntax-highlighting-logo-no-border.png diff --git a/.dev-assets/Sketch/VSCode-CSS-Nesting-Extension-Logo.sketch b/.dev-assets/Sketch/VSCode-CSS-Nesting-Extension-Logo.sketch deleted file mode 100644 index 13754a9798e43e2392a469e578a68a9b0b7eecce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 92088 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 VbH8Nd$S26*UkP0BHx9YG{{i1 testSet.has(item)).sort(); - -console.log(sharedWords); - -// LOGS: -// color-profile -// content -// cursor P -// filter -// font -// header -// image -// label -// line -// mark -// marquee -// mask -// nav -// ruby -// shadow -// span -// style -// text - -// HTML TAGS FROM LIST ABOVE: -// content -// font -// header -// image -// label -// mark -// marquee -// mask -// nav -// ruby -// shadow -// span -// style - -// SVG TAGS FROM LIST ABOVE: -// color-profile -// cursor -// filter -// line -// text - -})(); diff --git a/.dev-assets/rule-lists.jsonc b/.dev-assets/rule-lists.jsonc deleted file mode 100644 index 5687efa..0000000 --- a/.dev-assets/rule-lists.jsonc +++ /dev/null @@ -1,104 +0,0 @@ -// SCSS: -// ------ -// rules - // > selectors - // >> source.css#tag-names - // >> selector_class - // >> selector_id - // >> selector_pseudo_class - // >> tag_parent_reference >>> & - // >> source.css#pseudo-elements - // >> selector_attribute -// property_list - // > {}; - // > ^...rules - // > properties - // >> source.css#property-names - // >> : -> ; >> property_values - // >>> source.css#numeric-values - // >>> source.css#property-keywords - // >>> source.css#property-names - // >>> () >>>> property_values - // > self -// ; - -// CSS: -// ------ -// #selector - // > #selector-innards - // >> #tag-names - // >> entity.other.attribute-name.class.css - // >> entity.other.attribute-name.id.css - // >> #pseudo-classes - // + >> #nesting-selector - // >> #pseudo-elements - // >> #functional-pseudo-classes -// #rule-list - // > {} - // + ^...#selector-innards - // > #rule-list-innards - // >> word >> #property-names - // >> : -> ; >> #property-values - // >> ; - // + > self - - - -// { -// "include": "#at-rules" -// }, -// { -// "include": "#property-names" -// }, -// { -// "include": "#tag-names" -// }, -// { -// "include": "#pseudo-classes" -// }, -// { -// "include": "#pseudo-elements" -// }, -// { -// "include": "#functional-pseudo-classes" -// }, - - - -// { -// "include": "#property-names" -// }, -// { -// "include": "#tag-names" -// }, -// { -// "include": "#pseudo-classes" -// }, -// { -// "include": "#nesting-selector" -// }, -// { -// "include": "#pseudo-elements" -// }, -// { -// "include": "#functional-pseudo-classes" -// }, -// { -// "include": "#rule-list-innards" -// }, -// { -// "include": "$self" -// } - - - - -// { -// "include": "#selector-innards" -// }, -// { -// "include": "#rule-list-innards" -// }, -// { -// "include": "$self" -// } diff --git a/images/css-nesting-syntax-highlighting-logo-no-border.png b/images/css-nesting-syntax-highlighting-logo-no-border.png deleted file mode 100644 index caa44c801ca1a4a748baad697c98e9b2ad448e0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19118 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

Z~~ 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 Date: Fri, 6 Jun 2025 11:33:21 +0700 Subject: [PATCH 03/14] Update package version to 0.4.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7f4fc55..936c697 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "css-nesting-syntax-highlighting", "displayName": "CSS Nesting Syntax Highlighting", "description": "Updates VSCode's CSS TextMate rules to add CSS Nesting", - "version": "0.4.0", + "version": "0.4.1", "icon": "images/css-nesting-syntax-highlighting-logo.png", "publisher": "jacobcassidy", "bugs": { From 600bf2aa9d32988531eea1299d53cb16b50de711 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Fri, 6 Jun 2025 11:44:16 +0700 Subject: [PATCH 04/14] Add official vscode-css source files --- .editorconfig | 15 + src/grammars/css.cson | 2139 ++++++++++ src/language-configuration.json | 28 + src/spec/css-spec.mjs | 3693 +++++++++++++++++ .../prepare-extension-debugging.mjs | 14 + src/testing-util/test.mjs | 69 + 6 files changed, 5958 insertions(+) create mode 100644 .editorconfig create mode 100644 src/grammars/css.cson create mode 100644 src/language-configuration.json create mode 100644 src/spec/css-spec.mjs create mode 100644 src/testing-util/prepare-extension-debugging.mjs create mode 100644 src/testing-util/test.mjs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..cd6061a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Tab indentation +[*] +indent_style = tab +trim_trailing_whitespace = true + +# The indent size used in the `package.json` file cannot be changed +# https://github.com/npm/npm/pull/3180#issuecomment-16336516 +[{*.yml,*.yaml,*.cson,package.json}] +indent_style = space +indent_size = 2 diff --git a/src/grammars/css.cson b/src/grammars/css.cson new file mode 100644 index 0000000..6256e55 --- /dev/null +++ b/src/grammars/css.cson @@ -0,0 +1,2139 @@ +'scopeName': 'source.css' +'name': 'CSS' +'fileTypes': [ + 'css' + 'css.erb' +] +'firstLineMatch': '''(?xi) + # Emacs modeline + -\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*) + css + (?=[\\s;]|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*= + css + (?=\\s|:|$) +''' +'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#combinators' + } + { + 'include': '#selector' + } + { + 'include': '#at-rules' + } + { + 'include': '#rule-list' + } +] +'repository': + 'at-rules': + 'patterns': [ + { + # @charset, with possible preceding BOM sequence + 'begin': '\\A(?:\\xEF\\xBB\\xBF)?(?i:(?=\\s*@charset\\b))' + 'end': ';|(?=$)' + 'endCaptures': + '0': + 'name': 'punctuation.terminator.rule.css' + 'name': 'meta.at-rule.charset.css' + 'patterns': [ + { + 'captures': + '1': + 'name': 'invalid.illegal.not-lowercase.charset.css' + '2': + 'name': 'invalid.illegal.leading-whitespace.charset.css' + '3': + 'name': 'invalid.illegal.no-whitespace.charset.css' + '4': + 'name': 'invalid.illegal.whitespace.charset.css' + '5': + 'name': 'invalid.illegal.not-double-quoted.charset.css' + '6': + 'name': 'invalid.illegal.unclosed-string.charset.css' + '7': + 'name': 'invalid.illegal.unexpected-characters.charset.css' + 'match': '''(?x) # Possible errors: + \\G + ((?!@charset)@\\w+) # Not lowercase (@charset is case-sensitive) + | + \\G(\\s+) # Preceding whitespace + | + (@charset\\S[^;]*) # No whitespace after @charset + | + (?<=@charset) # Before quoted charset name + (\\x20{2,}|\\t+) # More than one space used, or a tab + | + (?<=@charset\\x20) # Beginning of charset name + ([^";]+) # Not double-quoted + | + ("[^"]+$) # Unclosed quote + | + (?<=") # After charset name + ([^;]+) # Unexpected junk instead of semicolon + ''' + } + { + 'captures': + '1': + 'name': 'keyword.control.at-rule.charset.css' + '2': + 'name': 'punctuation.definition.keyword.css' + 'match': '((@)charset)(?=\\s)' + } + { + 'begin': '"' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.string.begin.css' + 'end': '"|$' + 'endCaptures': + '0': + 'name': 'punctuation.definition.string.end.css' + 'name': 'string.quoted.double.css' + 'patterns': [ + { + 'begin': '(?:\\G|^)(?=(?:[^"])+$)' + 'end': '$' + 'name': 'invalid.illegal.unclosed.string.css' + } + ] + } + ] + } + { + # @import + 'begin': '(?i)((@)import)(?:\\s+|$|(?=[\'"]|/\\*))' + 'beginCaptures': + '1': + 'name': 'keyword.control.at-rule.import.css' + '2': + 'name': 'punctuation.definition.keyword.css' + 'end': ';' + 'endCaptures': + '0': + 'name': 'punctuation.terminator.rule.css' + 'name': 'meta.at-rule.import.css' + 'patterns': [ + { + 'begin': '\\G\\s*(?=/\\*)' + 'end': '(?<=\\*/)\\s*' + 'patterns': [ + { + 'include': '#comment-block' + } + ] + } + { + 'include': '#string' + } + { + 'include': '#url' + } + { + 'include': '#media-query-list' + } + ] + } + { + # @font-face + 'begin': '(?i)((@)font-face)(?=\\s*|{|/\\*|$)' + 'beginCaptures': + '1': + 'name': 'keyword.control.at-rule.font-face.css' + '2': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?!\\G)' + 'name': 'meta.at-rule.font-face.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#rule-list' + } + ] + } + { + # @page + 'begin': '(?i)(@)page(?=[\\s:{]|/\\*|$)' + 'captures': + '0': + 'name': 'keyword.control.at-rule.page.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*($|[:{;]))' + 'name': 'meta.at-rule.page.css' + 'patterns': [ + { + 'include': '#rule-list' + } + ] + } + { + # @media + 'begin': '(?i)(?=@media(\\s|\\(|/\\*|$))' + 'end': '(?<=})(?!\\G)' + 'patterns': [ + { + 'begin': '(?i)\\G(@)media' + 'beginCaptures': + '0': + 'name': 'keyword.control.at-rule.media.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*[{;])' + 'name': 'meta.at-rule.media.header.css' + 'patterns': [ + { + 'include': '#media-query-list' + } + ] + } + { + 'begin': '{' + 'beginCaptures': + '0': + 'name': 'punctuation.section.media.begin.bracket.curly.css' + 'end': '}' + 'endCaptures': + '0': + 'name': 'punctuation.section.media.end.bracket.curly.css' + 'name': 'meta.at-rule.media.body.css' + 'patterns': [ + { + 'include': '$self' + } + ] + } + ] + } + { + # @counter-style + 'begin': '(?i)(?=@counter-style([\\s\'"{;]|/\\*|$))' + 'end': '(?<=})(?!\\G)' + 'patterns': [ + { + 'begin': '(?i)\\G(@)counter-style' + 'beginCaptures': + '0': + 'name': 'keyword.control.at-rule.counter-style.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*{)' + 'name': 'meta.at-rule.counter-style.header.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'captures': + '0': + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + (?:[-a-zA-Z_] | [^\\x00-\\x7F]) # First letter + (?:[-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier + |\\\\(?:[0-9a-fA-F]{1,6}|.) + )* + ''' + 'name': 'variable.parameter.style-name.css' + } + ] + } + { + 'begin': '{' + 'beginCaptures': + '0': + 'name': 'punctuation.section.property-list.begin.bracket.curly.css' + 'end': '}' + 'endCaptures': + '0': + 'name': 'punctuation.section.property-list.end.bracket.curly.css' + 'name': 'meta.at-rule.counter-style.body.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#rule-list-innards' + } + ] + } + ] + } + { + # @document + 'begin': '(?i)(?=@document([\\s\'"{;]|/\\*|$))' + 'end': '(?<=})(?!\\G)' + 'patterns': [ + { + 'begin': '(?i)\\G(@)document' + 'beginCaptures': + '0': + 'name': 'keyword.control.at-rule.document.css' + '1': + 'name': 'punctuation.definition.keyword.css' + 'end': '(?=\\s*[{;])' + 'name': 'meta.at-rule.document.header.css' + 'patterns': [ + { + 'begin': '(?i)(?>>' + 'name': 'invalid.deprecated.combinator.css' + } + { + 'match': '>>|>|\\+|~' + 'name': 'keyword.operator.combinator.css' + } + ] + 'commas': + 'match': ',' + 'name': 'punctuation.separator.list.comma.css' + 'comment-block': + 'begin': '/\\*' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.comment.begin.css' + 'end': '\\*/' + 'endCaptures': + '0': + 'name': 'punctuation.definition.comment.end.css' + 'name': 'comment.block.css' + 'escapes': + 'patterns': [ + { + 'match': '\\\\[0-9a-fA-F]{1,6}' + 'name': 'constant.character.escape.codepoint.css' + } + { + 'begin': '\\\\$\\s*' + 'end': '^(?<:=]|\\)|/\\*) # Terminates cleanly + ''' + 'media-feature-keywords': + 'match': '''(?xi) + (?<=^|\\s|:|\\*/) + (?: portrait # Orientation + | landscape + | progressive # Scan types + | interlace + | fullscreen # Display modes + | standalone + | minimal-ui + | browser + | hover + ) + (?=\\s|\\)|$) + ''' + 'name': 'support.constant.property-value.css' + 'media-query': + 'begin': '\\G' + 'end': '(?=\\s*[{;])' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#escapes' + } + { + 'include': '#media-types' + } + { + 'match': '(?i)(?<=\\s|^|,|\\*/)(only|not)(?=\\s|{|/\\*|$)' + 'name': 'keyword.operator.logical.$1.media.css' + } + { + 'match': '(?i)(?<=\\s|^|\\*/|\\))and(?=\\s|/\\*|$)' + 'name': 'keyword.operator.logical.and.media.css' + } + { + 'match': ',(?:(?:\\s*,)+|(?=\\s*[;){]))' + 'name': 'invalid.illegal.comma.css' + } + { + 'include': '#commas' + } + { + 'begin': '\\(' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.parameters.begin.bracket.round.css' + 'end': '\\)' + 'endCaptures': + '0': + 'name': 'punctuation.definition.parameters.end.bracket.round.css' + 'patterns': [ + { + 'include': '#media-features' + } + { + 'include': '#media-feature-keywords' + } + { + 'match': ':' + 'name': 'punctuation.separator.key-value.css' + } + { + 'match': '>=|<=|=|<|>' + 'name': 'keyword.operator.comparison.css' + } + { + 'captures': + '1': + 'name': 'constant.numeric.css' + '2': + 'name': 'keyword.operator.arithmetic.css' + '3': + 'name': 'constant.numeric.css' + 'match': '(\\d+)\\s*(/)\\s*(\\d+)' + 'name': 'meta.ratio.css' + } + { + 'include': '#numeric-values' + } + { + 'include': '#comment-block' + } + ] + } + ] + 'media-query-list': + 'begin': '(?=\\s*[^{;])' + 'end': '(?=\\s*[{;])' + 'patterns': [ + { + 'include': '#media-query' + } + ] + 'media-types': + 'captures': + '1': + 'name': 'support.constant.media.css' + '2': + 'name': 'invalid.deprecated.constant.media.css' + 'match': '''(?xi) + (?<=^|\\s|,|\\*/) + (?: + # Valid media types + (all|print|screen|speech) + | + # Deprecated in Media Queries 4: http://dev.w3.org/csswg/mediaqueries/#media-types + (aural|braille|embossed|handheld|projection|tty|tv) + ) + (?=$|[{,\\s;]|/\\*) + ''' + 'numeric-values': + 'patterns': [ + { + 'captures': + '1': + 'name': 'punctuation.definition.constant.css' + 'match': '(#)(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\\b' + 'name': 'constant.other.color.rgb-value.hex.css' + } + { + 'captures': + '1': + 'name': 'keyword.other.unit.percentage.css' + '2': + 'name': 'keyword.other.unit.${2:/downcase}.css' + 'match': '''(?xi) (?+~|] # - Followed by another selector + | /\\* # - Followed by a block comment + ) + | + # Name contains unescaped ASCII symbol + (?: # Check for acceptable preceding characters + [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character + | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence + )* + (?: # Invalid punctuation + [!"'%&(*;+~|] # - Another selector + | /\\* # - A block comment + ) + ''' + 'name': 'entity.other.attribute-name.class.css' + } + { + 'captures': + '1': + 'name': 'punctuation.definition.entity.css' + '2': + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + (\\#) + ( + -? + (?![0-9]) + (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ + ) + (?=$|[\\s,.\\#)\\[:{>+~|]|/\\*) + ''' + 'name': 'entity.other.attribute-name.id.css' + } + { + 'begin': '\\[' + 'beginCaptures': + '0': + 'name': 'punctuation.definition.entity.begin.bracket.square.css' + 'end': '\\]' + 'endCaptures': + '0': + 'name': 'punctuation.definition.entity.end.bracket.square.css' + 'name': 'meta.attribute-selector.css' + 'patterns': [ + { + 'include': '#comment-block' + } + { + 'include': '#string' + } + { + 'captures': + '1': + 'name': 'storage.modifier.ignore-case.css' + 'match': '(?<=["\'\\s]|^|\\*/)\\s*([iI])\\s*(?=[\\s\\]]|/\\*|$)' + } + { + 'captures': + '1': + 'name': 'string.unquoted.attribute-value.css' + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '(?x)(?<==)\\s*((?!/\\*)(?:[^\\\\"\'\\s\\]]|\\\\.)+)' + } + { + 'include': '#escapes' + } + { + 'match': '[~|^$*]?=' + 'name': 'keyword.operator.pattern.css' + } + { + 'match': '\\|' + 'name': 'punctuation.separator.css' + } + { + 'captures': + '1': + 'name': 'entity.other.namespace-prefix.css' + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + # Qualified namespace prefix + ( -?(?!\\d)(?:[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+ + | \\* + ) + # Lookahead to ensure there's a valid identifier ahead + (?= + \\| (?!\\s|=|$|\\]) + (?: -?(?!\\d) + | [\\\\\\w-] + | [^\\x00-\\x7F] + ) + ) + ''' + } + { + 'captures': + '1': + 'name': 'entity.other.attribute-name.css' + 'patterns': [ + { + 'include': '#escapes' + } + ] + 'match': '''(?x) + (-?(?!\\d)(?>[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+) + \\s* + (?=[~|^\\]$*=]|/\\*) + ''' + } + ] + } + { + 'include': '#pseudo-classes' + } + { + 'include': '#pseudo-elements' + } + { + 'include': '#functional-pseudo-classes' + } + # Custom HTML elements + { + 'match': '''(?x) (?\\s,.\\#|){:\\[]|/\\*|$) + ''' + 'name': 'entity.name.tag.css' + 'unicode-range': + 'captures': + '0': + 'name': 'constant.other.unicode-range.css' + '1': + 'name': 'punctuation.separator.dash.unicode-range.css' + 'match': '(? b + * ~ :not(.nah)').tokens; + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '>' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '+' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '~' }); + }); + + it('highlights deprecated combinators', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.sooo /deep/ >>>_.>>>').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'invalid.deprecated.combinator.css'], value: '/deep/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'invalid.deprecated.combinator.css'], value: '>>>' }); + }); + + it('tokenizes complex selectors', function () { + var lines, tokens; + tokens = testGrammar.tokenizeLine('[disabled], [disabled] + p').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ["source.css", "meta.selector.css", "meta.attribute-selector.css", "punctuation.definition.entity.begin.bracket.square.css"], value: '[' }); + assert.deepStrictEqual(tokens[1], { scopes: ["source.css", "meta.selector.css", "meta.attribute-selector.css", "entity.other.attribute-name.css"], value: 'disabled' }); + assert.deepStrictEqual(tokens[2], { scopes: ["source.css", "meta.selector.css", "meta.attribute-selector.css", "punctuation.definition.entity.end.bracket.square.css"], value: ']' }); + assert.deepStrictEqual(tokens[3], { scopes: ["source.css", "meta.selector.css", "punctuation.separator.list.comma.css"], value: ',' }); + assert.deepStrictEqual(tokens[5], { scopes: ["source.css", "meta.selector.css", "meta.attribute-selector.css", "punctuation.definition.entity.begin.bracket.square.css"], value: '[' }); + assert.deepStrictEqual(tokens[6], { scopes: ["source.css", "meta.selector.css", "meta.attribute-selector.css", "entity.other.attribute-name.css"], value: 'disabled' }); + assert.deepStrictEqual(tokens[7], { scopes: ["source.css", "meta.selector.css", "meta.attribute-selector.css", "punctuation.definition.entity.end.bracket.square.css"], value: ']' }); + assert.deepStrictEqual(tokens[9], { scopes: ["source.css", "meta.selector.css", "keyword.operator.combinator.css"], value: '+' }); + assert.deepStrictEqual(tokens[11], { scopes: ["source.css", "meta.selector.css", "entity.name.tag.css"], value: 'p' }); + + lines = testGrammar.tokenizeLines("[disabled]:not(:first-child)::before:hover\n ~ div.object\n + #id.thing:hover > strong ~ p::before,\na::last-of-type,/*Comment*/::selection > html[lang^=en-AU],\n*>em.i.ly[data-name|=\"Life\"] { }"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'disabled' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'not' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'first-child' }); + assert.deepStrictEqual(lines[0][8], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[0][9], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(lines[0][10], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'before' }); + assert.deepStrictEqual(lines[0][11], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[0][12], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'hover' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '~' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'div' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'object' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '+' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'id' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'thing' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'hover' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '>' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'strong' }); + assert.deepStrictEqual(lines[2][14], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '~' }); + assert.deepStrictEqual(lines[2][16], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'p' }); + assert.deepStrictEqual(lines[2][17], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(lines[2][18], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'before' }); + assert.deepStrictEqual(lines[2][19], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'invalid.illegal.colon.css'], value: ':' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'last-of-type' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'comment.block.css'], value: 'Comment' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(lines[3][9], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'selection' }); + assert.deepStrictEqual(lines[3][11], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '>' }); + assert.deepStrictEqual(lines[3][13], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'html' }); + assert.deepStrictEqual(lines[3][14], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(lines[3][15], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'lang' }); + assert.deepStrictEqual(lines[3][16], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '^=' }); + assert.deepStrictEqual(lines[3][17], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'en-AU' }); + assert.deepStrictEqual(lines[3][18], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(lines[3][19], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.wildcard.css'], value: '*' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '>' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'em' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'i' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'ly' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(lines[4][8], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'data-name' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '|=' }); + assert.deepStrictEqual(lines[4][10], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[4][11], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: 'Life' }); + assert.deepStrictEqual(lines[4][12], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[4][13], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(lines[4][15], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[4][17], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + describe('custom elements (as type selectors)', function () { + it('only tokenizes identifiers beginning with [a-z]', function () { + var tokens; + tokens = testGrammar.tokenizeLine('pearl-1941 1941-pearl -pearl-1941').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'pearl-1941' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css'], value: ' 1941-pearl -pearl-1941' }); + }); + + it('tokenizes custom elements containing non-ASCII letters', function () { + var tokens; + tokens = testGrammar.tokenizeLine('pokémon-ピカチュウ').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'pokémon-ピカチュウ' }); + }); + + it('does not tokenize identifiers containing [A-Z]', function () { + var tokens; + tokens = testGrammar.tokenizeLine('Basecamp-schedule basecamp-Schedule').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css'], value: 'Basecamp-schedule basecamp-Schedule' }); + }); + + it('does not tokenize identifiers containing no hyphens', function () { + var tokens; + tokens = testGrammar.tokenizeLine('halo_night').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css'], value: 'halo_night' }); + }); + + it('does not tokenise identifiers following an @ symbol', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@some-weird-new-feature').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'some-weird-new-feature' }); + }); + + it('does not tokenise identifiers in unfamiliar functions', function () { + var tokens; + tokens = testGrammar.tokenizeLine('some-edgy-new-function()').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css'], value: 'some-edgy-new-function(' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css'], value: ')' }); + }); + }); + + describe('attribute selectors', function () { + it('tokenizes attribute selectors without values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('[title]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'title' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenizes attribute selectors with identifier values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('[hreflang|=fr]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'hreflang' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '|=' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'fr' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenizes attribute selectors with string values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('[href^="http://www.w3.org/"]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'href' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '^=' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: 'http://www.w3.org/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenizes CSS qualified attribute names with wildcard prefix', function () { + var tokens; + tokens = testGrammar.tokenizeLine('[*|title]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: '*' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'title' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenizes CSS qualified attribute names with namespace prefix', function () { + var tokens; + tokens = testGrammar.tokenizeLine('[marvel|origin=radiation]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: 'marvel' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'origin' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'radiation' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenizes CSS qualified attribute names without namespace prefix', function () { + var tokens; + tokens = testGrammar.tokenizeLine('[|data-hp="75"]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'data-hp' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: '75' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises compound ID/attribute selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#div[id="0"]{ }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'div' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'id' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + + tokens = testGrammar.tokenizeLine('.bar#div[id="0"]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'bar' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'div' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'id' }); + }); + + it('tokenises compound class/attribute selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.div[id="0"]{ }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'div' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'id' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + + tokens = testGrammar.tokenizeLine('#bar.div[id]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'bar' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'div' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'id' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('allows whitespace to be inserted between tokens', function () { + var tokens; + tokens = testGrammar.tokenizeLine('span[ er|lang |= "%%" ]').tokens; + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: 'er' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'lang' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '|=' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: '%%' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises escape sequences inside attribute selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a[name\\[0\\]="value"]').tokens; + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'name' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css', 'constant.character.escape.css'], value: '\\[' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: '0' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css', 'constant.character.escape.css'], value: '\\]' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises escape sequences inside namespace prefixes', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a[name\\ space|Get\\ It\\?="kek"]').tokens; + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: 'name' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css', 'constant.character.escape.css'], value: '\\ ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: 'space' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'Get' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css', 'constant.character.escape.css'], value: '\\ ' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'It' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css', 'constant.character.escape.css'], value: '\\?' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises comments inside attribute selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('span[/*]*/lang]').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'span' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css'], value: ']' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'lang' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises quoted strings in attribute selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a[href^="#"] a[href^= "#"] a[href^="#" ]').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: '#' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '^=' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: '#' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[24], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: '#' }); + assert.deepStrictEqual(tokens[25], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[26], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[27], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + + tokens = testGrammar.tokenizeLine("a[href^='#'] a[href^= '#'] a[href^='#' ]").tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css'], value: '#' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '^=' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css'], value: '#' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[24], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css'], value: '#' }); + assert.deepStrictEqual(tokens[25], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + assert.deepStrictEqual(tokens[26], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[27], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises unquoted strings in attribute selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('span[class~=Java]').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '~=' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'Java' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + + tokens = testGrammar.tokenizeLine('span[class^= 0xDEADCAFE=|~BEEFBABE ]').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '^=' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: '0xDEADCAFE=|~BEEFBABE' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises escape sequences in unquoted strings', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a[name\\[0\\]=a\\BAD\\AF\\]a\\ i] {}').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'a' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css', 'constant.character.escape.codepoint.css'], value: '\\BAD' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css', 'constant.character.escape.codepoint.css'], value: '\\AF' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css', 'constant.character.escape.css'], value: '\\]' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'a' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css', 'constant.character.escape.css'], value: '\\ ' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'i' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('tokenises the ignore-case modifier at the end of a selector', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a[attr=val i] a[attr="val" i] a[attr=\'val\'I] a[val^= \'"\'i] a[attr= i] a[attr= i i]').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'storage.modifier.ignore-case.css'], value: 'i' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'storage.modifier.ignore-case.css'], value: 'i' }); + assert.deepStrictEqual(tokens[26], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + assert.deepStrictEqual(tokens[27], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'storage.modifier.ignore-case.css'], value: 'I' }); + assert.deepStrictEqual(tokens[28], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(tokens[34], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[35], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[36], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css'], value: '"' }); + assert.deepStrictEqual(tokens[37], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + assert.deepStrictEqual(tokens[38], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'storage.modifier.ignore-case.css'], value: 'i' }); + assert.deepStrictEqual(tokens[39], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(tokens[44], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(tokens[45], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[46], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'i' }); + assert.deepStrictEqual(tokens[47], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(tokens[52], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(tokens[53], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[54], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.unquoted.attribute-value.css'], value: 'i' }); + assert.deepStrictEqual(tokens[55], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[56], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'storage.modifier.ignore-case.css'], value: 'i' }); + assert.deepStrictEqual(tokens[57], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + + it('tokenises attribute selectors spanning multiple lines', function () { + var lines; + lines = testGrammar.tokenizeLines("span[\n \\x20{2}\n ns|lang/**/\n |=\n\"pt\"]"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'span' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css'], value: ' ' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: 'ns' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'lang' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '|=' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: 'pt' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + + lines = testGrammar.tokenizeLines("span[/*===\n==|span[/*}\n====*/*|lang/*]=*/~=/*\"|\"*/\"en-AU\"/*\n |\n*/\ni]"); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css'], value: '===' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css'], value: '==|span[/*}' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css'], value: '====' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: '*' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'lang' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css'], value: ']=' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '~=' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css'], value: '"|"' }); + assert.deepStrictEqual(lines[2][11], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[2][13], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: 'en-AU' }); + assert.deepStrictEqual(lines[2][14], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[2][15], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css'], value: ' |' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[5][0], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'storage.modifier.ignore-case.css'], value: 'i' }); + assert.deepStrictEqual(lines[5][1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + }); + }); + + describe('class selectors', function () { + it('tokenizes class selectors containing non-ASCII letters', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.étendard').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'étendard' }); + + tokens = testGrammar.tokenizeLine('.スポンサー').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'スポンサー' }); + }); + + it('tokenizes a class selector consisting of two hypens', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.--').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: '--' }); + }); + + it('tokenizes class selectors consisting of one (valid) character', function () { + var tokens; + tokens = testGrammar.tokenizeLine('._').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: '_' }); + }); + + it('tokenises class selectors starting with an escape sequence', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.\\33\\44-model {').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'constant.character.escape.codepoint.css'], value: '\\33' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'constant.character.escape.codepoint.css'], value: '\\44' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: '-model' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('tokenises class selectors ending with an escape sequence', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.la\\{tex\\} {').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'la' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'constant.character.escape.css'], value: '\\{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'tex' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'constant.character.escape.css'], value: '\\}' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('marks a class invalid if it contains unescaped ASCII punctuation or symbols other than "-" and "_"', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.B&W{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: 'B&W' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('marks a class invalid if it starts with ASCII digits ([0-9])', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.666{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: '666' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('marks a class invalid if it starts with "-" followed by ASCII digits', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.-911-{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: '-911-' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('marks a class invalid if it consists of only one hyphen', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.-{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: '-' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + }); + + describe('id selectors', function () { + it('tokenizes id selectors consisting of ASCII letters', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#unicorn').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'unicorn' }); + }); + + it('tokenizes id selectors containing non-ASCII letters', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#洪荒之力').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: '洪荒之力' }); + }); + + it('tokenizes id selectors containing [0-9], "-", or "_"', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#_zer0-day').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: '_zer0-day' }); + }); + + it('tokenizes id selectors beginning with two hyphens', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#--d3bug--').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: '--d3bug--' }); + }); + + it('marks an id invalid if it contains ASCII punctuation or symbols other than "-" and "_"', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#sort!{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: 'sort!' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('marks an id invalid if it starts with ASCII digits ([0-9])', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#666{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: '666' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('marks an id invalid if it starts with "-" followed by ASCII digits', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#-911-{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: '-911-' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('marks an id invalid if it consists of one hyphen only', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#-{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'invalid.illegal.bad-identifier.css'], value: '-' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('tokenises ID selectors starting with an escape sequence', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#\\33\\44-model {').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'constant.character.escape.codepoint.css'], value: '\\33' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'constant.character.escape.codepoint.css'], value: '\\44' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: '-model' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('tokenises ID selectors ending with an escape sequence', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#la\\{tex\\} {').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'la' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'constant.character.escape.css'], value: '\\{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'tex' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'constant.character.escape.css'], value: '\\}' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + }); + + describe('namespace prefixes', function () { + it('tokenises arbitrary namespace prefixes', function () { + var tokens; + tokens = testGrammar.tokenizeLine('foo|h1 { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.namespace-prefix.css'], value: 'foo' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'h1' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenises anonymous namespace prefixes', function () { + var tokens; + tokens = testGrammar.tokenizeLine('*|abbr {}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.namespace-prefix.css'], value: '*' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'abbr' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('*|* {}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.namespace-prefix.css'], value: '*' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.wildcard.css'], value: '*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('foo|* { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.namespace-prefix.css'], value: 'foo' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.wildcard.css'], value: '*' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('|[svg|attr=name]{}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.namespace-prefix.css'], value: 'svg' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'attr' }); + }); + + it('tokenises the "no-namespace" prefix', function () { + var tokens; + tokens = testGrammar.tokenizeLine('|h1 { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.css'], value: '|' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'h1' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it("doesn't tokenise prefixes without a selector", function () { + var tokens; + tokens = testGrammar.tokenizeLine('*| { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.wildcard.css'], value: '*' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css'], value: '|' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + tokens = testGrammar.tokenizeLine('*|{ }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.wildcard.css'], value: '*' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css'], value: '|' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('at-rules', function () { + describe('@charset', function () { + it('tokenises @charset rules at the start of a file', function () { + var lines; + lines = testGrammar.tokenizeLines('@charset "US-ASCII";'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css'], value: 'charset' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.charset.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css'], value: 'US-ASCII' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.charset.css', 'punctuation.terminator.rule.css'], value: ';' }); + + lines = testGrammar.tokenizeLines('/* Not the first line */\n@charset "UTF-8";'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'comment.block.css'], value: ' Not the first line ' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'charset' }); + }); + + it('highlights invalid @charset statements', function () { + var lines; + lines = testGrammar.tokenizeLines(" @charset 'US-ASCII';"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.leading-whitespace.charset.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css'], value: 'charset' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.not-double-quoted.charset.css'], value: "'US-ASCII'" }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.charset.css', 'punctuation.terminator.rule.css'], value: ';' }); + + lines = testGrammar.tokenizeLines('@charset "iso-8859-15";'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css'], value: 'charset' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.whitespace.charset.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css'], value: 'iso-8859-15' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.charset.css', 'punctuation.terminator.rule.css'], value: ';' }); + + lines = testGrammar.tokenizeLines('@charset"US-ASCII";'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.no-whitespace.charset.css'], value: '@charset"US-ASCII"' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css', 'punctuation.terminator.rule.css'], value: ';' }); + + lines = testGrammar.tokenizeLines('@charset "UTF-8" ;'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css'], value: 'charset' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.charset.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css'], value: 'UTF-8' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.unexpected-characters.charset.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.charset.css', 'punctuation.terminator.rule.css'], value: ';' }); + + lines = testGrammar.tokenizeLines('@charset "WTF-8" /* Nope */ ;'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css'], value: 'charset' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.charset.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css'], value: 'WTF-8' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.charset.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.unexpected-characters.charset.css'], value: ' /* Nope */ ' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.charset.css', 'punctuation.terminator.rule.css'], value: ';' }); + + lines = testGrammar.tokenizeLines('@charset "UTF-8'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css', 'keyword.control.at-rule.charset.css'], value: 'charset' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.charset.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.unclosed-string.charset.css'], value: '"UTF-8' }); + + lines = testGrammar.tokenizeLines("@CHARSET 'US-ASCII';"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.charset.css', 'invalid.illegal.not-lowercase.charset.css'], value: '@CHARSET' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.charset.css'], value: " 'US-ASCII'" }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.charset.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + }); + describe('@import', function () { + it('tokenises @import statements', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@import url("file.css");').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css'], value: 'file.css' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('@import "file.css";').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css'], value: 'file.css' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine("@import 'file.css';").tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.single.css'], value: 'file.css' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + }); + it("doesn't let injected comments impact parameter matching", function () { + var tokens; + tokens = testGrammar.tokenizeLine('@import /* url("name"); */ "1.css";').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css'], value: ' url("name"); ' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css'], value: '1.css' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('@import/* Comment */"2.css";').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css'], value: ' Comment ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css'], value: '2.css' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + it('correctly handles word boundaries', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@import"file.css";').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css'], value: 'file.css' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('@import-file.css;').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'import-file' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.header.css'], value: '.css' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.header.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it('matches a URL that starts on the next line', function () { + var lines; + lines = testGrammar.tokenizeLines('@import\nurl("file.css");'); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css'], value: 'file.css' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + // Skipped because `vscode-textmate` does not produce this token + it.skip('matches a URL that starts on the next line and produces a token for whitespace', function () { + var lines; + lines = testGrammar.tokenizeLines('@import\nurl("file.css");'); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.import.css'], value: '' }); + }); + + it('matches comments inside query lists', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@import url("1.css") print /* url(";"); */ all;').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css'], value: '1.css' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.import.css', 'support.constant.media.css'], value: 'print' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css'], value: ' url(";"); ' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.at-rule.import.css', 'support.constant.media.css'], value: 'all' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it('highlights deprecated media types', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@import "astral.css" projection;').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css'], value: 'astral.css' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'invalid.deprecated.constant.media.css'], value: 'projection' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it('highlights media features in query lists', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@import url(\'landscape.css\') screen and (orientation:landscape);').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: '\'' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.single.css'], value: 'landscape.css' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: '\'' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.import.css', 'support.constant.media.css'], value: 'screen' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.import.css', 'support.type.property-name.media.css'], value: 'orientation' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.at-rule.import.css', 'support.constant.property-value.css'], value: 'landscape' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + }); + + describe('@media', function () { + it('tokenises @media keywords correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media(max-width: 37.5em) { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'max-width' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '37.5' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('@media not print and (max-width: 37.5em){ }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.not.media.css'], value: 'not' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'print' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'max-width' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '37.5' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + }); + it('highlights deprecated media types', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media (max-device-width: 2px){ }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'max-device-width' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + }); + + it('highlights vendored media features', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media (-webkit-foo: bar){ b{ } }').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.vendored.property-name.media.css'], value: '-webkit-foo' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css'], value: ' bar' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + + tokens = testGrammar.tokenizeLine('@media screen and (-ms-high-contrast:black-on-white){ }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'screen' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.vendored.property-name.media.css'], value: '-ms-high-contrast' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css'], value: 'black-on-white' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('@media (_moz-a:b){}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.vendored.property-name.media.css'], value: '_moz-a' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css'], value: 'b' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + + tokens = testGrammar.tokenizeLine('@media (-hp-foo:bar){}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.vendored.property-name.media.css'], value: '-hp-foo' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css'], value: 'bar' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + + tokens = testGrammar.tokenizeLine('@media (mso-page-size:wide){}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.vendored.property-name.media.css'], value: 'mso-page-size' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css'], value: 'wide' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + }); + + it('tokenises @media immediately following a closing brace', function () { + var tokens; + tokens = testGrammar.tokenizeLine('h1 { }@media only screen { } h2 { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'h1' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.only.media.css'], value: 'only' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'screen' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'h2' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('h1 { }@media only screen { }h2 { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'h1' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.only.media.css'], value: 'only' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'screen' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'h2' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenises level 4 media-query syntax', function () { + var lines; + lines = testGrammar.tokenizeLines("@media (min-width >= 0px)\n and (max-width <= 400)\n and (min-height > 400)\n and (max-height < 200)"); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.comparison.css'], value: '>=' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.comparison.css'], value: '<=' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.comparison.css'], value: '>' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.comparison.css'], value: '<' }); + }); + + it('tokenises comments between media types', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media/* */only/* */screen/* */and (min-width:1100px){}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.only.media.css'], value: 'only' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'screen' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'min-width' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '1100' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[22], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenises comments between media features', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media/*=*/(max-width:/**/37.5em)/*=*/and/*=*/(/*=*/min-height/*:*/:/*=*/1.2em/*;*/){}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'max-width' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '37.5' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[22], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[24], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'min-height' }); + assert.deepStrictEqual(tokens[25], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[26], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ':' }); + assert.deepStrictEqual(tokens[27], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[28], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[29], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[30], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[31], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[32], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '1.2' }); + assert.deepStrictEqual(tokens[33], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(tokens[34], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[35], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ';' }); + assert.deepStrictEqual(tokens[36], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[37], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[38], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[39], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + }); + }); + + it('matches media queries across lines', function () { + var lines; + lines = testGrammar.tokenizeLines("@media only screen and (min-width : /* 40 */\n 320px),\n not print and (max-width: 480px) /* kek */ and (-webkit-min-device-pixel-ratio /*:*/ : 2),\nonly speech and (min-width: 10em), /* wat */ (-webkit-min-device-pixel-ratio: 2) { }"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.only.media.css'], value: 'only' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'screen' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(lines[0][9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[0][10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'min-width' }); + assert.deepStrictEqual(lines[0][12], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[0][14], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][15], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ' 40 ' }); + assert.deepStrictEqual(lines[0][16], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '320' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.not.media.css'], value: 'not' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'print' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'max-width' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][11], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '480' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[2][13], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][15], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][16], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ' kek ' }); + assert.deepStrictEqual(lines[2][17], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][19], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(lines[2][21], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][22], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.vendored.property-name.media.css'], value: '-webkit-min-device-pixel-ratio' }); + assert.deepStrictEqual(lines[2][24], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][25], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ':' }); + assert.deepStrictEqual(lines[2][26], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][28], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][30], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[2][31], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][32], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.only.media.css'], value: 'only' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'speech' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.operator.logical.and.media.css'], value: 'and' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'min-width' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '10' }); + assert.deepStrictEqual(lines[3][11], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[3][12], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][13], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][15], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[3][16], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ' wat ' }); + assert.deepStrictEqual(lines[3][17], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[3][19], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][20], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.vendored.property-name.media.css'], value: '-webkit-min-device-pixel-ratio' }); + assert.deepStrictEqual(lines[3][21], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][23], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[3][24], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][26], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[3][28], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + }); + + it('highlights invalid commas', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media , {}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'invalid.illegal.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('@media , ,screen {}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'invalid.illegal.comma.css'], value: ', ,' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.constant.media.css'], value: 'screen' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.body.css', 'punctuation.section.media.end.bracket.curly.css'], value: '}' }); + }); + + it('allows spaces inside ratio values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media (min-aspect-ratio: 3 / 4) and (max-aspect-ratio: 20 / 17) {}').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css', 'constant.numeric.css'], value: '3' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css'], value: ' ' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css', 'keyword.operator.arithmetic.css'], value: '/' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css'], value: ' ' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css', 'constant.numeric.css'], value: '4' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css', 'constant.numeric.css'], value: '20' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css'], value: ' ' }); + assert.deepStrictEqual(tokens[22], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css', 'keyword.operator.arithmetic.css'], value: '/' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css'], value: ' ' }); + assert.deepStrictEqual(tokens[24], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'meta.ratio.css', 'constant.numeric.css'], value: '17' }); + }); + + describe('@keyframes', function () { + it('tokenises keyframe lists correctly', function () { + var lines; + lines = testGrammar.tokenizeLines("@keyframes important1 {\n from { margin-top: 50px;\n margin-bottom: 100px }\n 50% { margin-top: 150px !important; } /* Ignored */\n to { margin-top: 100px; }\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css'], value: 'keyframes' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'variable.parameter.keyframe-list.css'], value: 'important1' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'punctuation.section.keyframes.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.css'], value: 'from' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-top' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '50' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[1][10], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-bottom' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '100' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.percentage.css'], value: '50%' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-top' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '150' }); + assert.deepStrictEqual(lines[3][9], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[3][11], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'keyword.other.important.css'], value: '!important' }); + assert.deepStrictEqual(lines[3][12], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[3][14], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[3][16], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[3][17], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'comment.block.css'], value: ' Ignored ' }); + assert.deepStrictEqual(lines[3][18], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.css'], value: 'to' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-top' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[4][8], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '100' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[4][10], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[4][12], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[5][0], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'punctuation.section.keyframes.end.bracket.curly.css'], value: '}' }); + }); + + it('matches injected comments', function () { + var lines; + lines = testGrammar.tokenizeLines("@keyframes/*{*/___IDENT__/*}\n { Nah { margin-top: 2em; }\n*/{ from"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css'], value: 'keyframes' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'comment.block.css'], value: '{' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'variable.parameter.keyframe-list.css'], value: '___IDENT__' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'comment.block.css'], value: '}' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'comment.block.css'], value: ' { Nah { margin-top: 2em; }' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'punctuation.section.keyframes.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.css'], value: 'from' }); + }); + + it('matches offset keywords case-insensitively', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@keyframes Give-them-both { fROm { } To {} }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css'], value: 'keyframes' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'variable.parameter.keyframe-list.css'], value: 'Give-them-both' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'punctuation.section.keyframes.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.css'], value: 'fROm' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.css'], value: 'To' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'punctuation.section.keyframes.end.bracket.curly.css'], value: '}' }); + }); + + it('matches percentile offsets', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@keyframes identifier { -50.2% } @keyframes ident2 { .25%}').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.percentage.css'], value: '-50.2%' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.percentage.css'], value: '.25%' }); + }); + + it('highlights escape sequences inside identifiers', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@keyframes A\\1F602Z').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css'], value: 'keyframes' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'variable.parameter.keyframe-list.css'], value: 'A' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'variable.parameter.keyframe-list.css', 'constant.character.escape.codepoint.css'], value: '\\1F602' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.keyframes.header.css', 'variable.parameter.keyframe-list.css'], value: 'Z' }); + }); + }); + + describe('@supports', function () { + it('tokenises feature queries', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@supports (font-size: 1em) { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css'], value: 'supports' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.supports.header.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'font-size' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.end.bracket.curly.css'], value: '}' }); + }); + + it('matches logical operators', function () { + var lines; + lines = testGrammar.tokenizeLines("@supports not (font-size: 1em){ }\n@supports (font-size: 1em) and (font-size: 1em){ }\n@supports (font-size: 1em) or (font-size: 1em){ }"); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.not.css'], value: 'not' }); + assert.deepStrictEqual(lines[1][11], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.and.css'], value: 'and' }); + assert.deepStrictEqual(lines[2][11], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.or.css'], value: 'or' }); + }); + + it('matches custom variables in feature queries', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@supports (--foo: green){}').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'variable.css'], value: '--foo' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'green' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + }); + + it("doesn't mistake brackets in string literals for feature queries", function () { + var lines; + lines = testGrammar.tokenizeLines("@supports not ((tab-size:4) or (-moz-tab-size:4)){\n body::before{content: \"Come on, Microsoft (Get it together already)…\"; }\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css'], value: 'supports' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.not.css'], value: 'not' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'tab-size' }); + assert.deepStrictEqual(lines[0][12], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'keyword.operator.logical.feature.or.css'], value: 'or' }); + assert.deepStrictEqual(lines[0][15], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.vendored.property-name.css'], value: '-moz-tab-size' }); + assert.deepStrictEqual(lines[0][20], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'body' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'before' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'content' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'], value: 'Come on, Microsoft (Get it together already)…' }); + assert.deepStrictEqual(lines[1][10], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[1][11], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[1][13], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenises multiple feature queries', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@supports (display:table-cell) or ((display:list-item) and (display:run-in)){').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css'], value: 'supports' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'display' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'table-cell' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.or.css'], value: 'or' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'display' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'list-item' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'keyword.operator.logical.feature.and.css'], value: 'and' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'display' }); + assert.deepStrictEqual(tokens[22], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'run-in' }); + assert.deepStrictEqual(tokens[24], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[25], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[26], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.begin.bracket.curly.css'], value: '{' }); + }); + + it('embeds rulesets and other at-rules', function () { + var lines; + lines = testGrammar.tokenizeLines("@supports (animation-name: test) {\n #node {\n animation-name: test;\n }\n body > header[data-name=\"attr\"] ~ *:not(:first-child){\n content: \"😂👌\"\n }\n @keyframes important1 {\n from {\n margin-top: 50px;\n margin-bottom: 100px\n }\n 50% { margin-top: 150px !important; } /* Ignored */\n to { margin-top: 100px; }\n }\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css'], value: 'supports' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'animation-name' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css'], value: 'test' }); + assert.deepStrictEqual(lines[0][8], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[0][10], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.id.css', 'punctuation.definition.entity.css'], value: '#' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.id.css'], value: 'node' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'animation-name' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'test' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'body' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '>' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'header' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.begin.bracket.square.css'], value: '[' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'meta.attribute-selector.css', 'entity.other.attribute-name.css'], value: 'data-name' }); + assert.deepStrictEqual(lines[4][8], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'meta.attribute-selector.css', 'keyword.operator.pattern.css'], value: '=' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[4][10], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css'], value: 'attr' }); + assert.deepStrictEqual(lines[4][11], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'meta.attribute-selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[4][12], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'meta.attribute-selector.css', 'punctuation.definition.entity.end.bracket.square.css'], value: ']' }); + assert.deepStrictEqual(lines[4][14], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'keyword.operator.combinator.css'], value: '~' }); + assert.deepStrictEqual(lines[4][16], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.name.tag.wildcard.css'], value: '*' }); + assert.deepStrictEqual(lines[4][17], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[4][18], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'not' }); + assert.deepStrictEqual(lines[4][19], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[4][20], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[4][21], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'first-child' }); + assert.deepStrictEqual(lines[4][22], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[4][23], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[5][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'content' }); + assert.deepStrictEqual(lines[5][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[5][4], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[5][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'], value: '😂👌' }); + assert.deepStrictEqual(lines[5][6], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[6][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[7][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[7][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.header.css', 'keyword.control.at-rule.keyframes.css'], value: 'keyframes' }); + assert.deepStrictEqual(lines[7][4], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.header.css', 'variable.parameter.keyframe-list.css'], value: 'important1' }); + assert.deepStrictEqual(lines[7][6], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'punctuation.section.keyframes.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[8][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.css'], value: 'from' }); + assert.deepStrictEqual(lines[8][3], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[9][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-top' }); + assert.deepStrictEqual(lines[9][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[9][4], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '50' }); + assert.deepStrictEqual(lines[9][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[9][6], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[10][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-bottom' }); + assert.deepStrictEqual(lines[10][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[10][4], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '100' }); + assert.deepStrictEqual(lines[10][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[11][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[12][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.percentage.css'], value: '50%' }); + assert.deepStrictEqual(lines[12][3], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[12][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-top' }); + assert.deepStrictEqual(lines[12][6], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[12][8], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '150' }); + assert.deepStrictEqual(lines[12][9], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[12][11], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'keyword.other.important.css'], value: '!important' }); + assert.deepStrictEqual(lines[12][12], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[12][14], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[12][16], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[12][17], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'comment.block.css'], value: ' Ignored ' }); + assert.deepStrictEqual(lines[12][18], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[13][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'entity.other.keyframe-offset.css'], value: 'to' }); + assert.deepStrictEqual(lines[13][3], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[13][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'margin-top' }); + assert.deepStrictEqual(lines[13][6], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[13][8], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '100' }); + assert.deepStrictEqual(lines[13][9], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[13][10], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[13][12], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[14][1], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.at-rule.keyframes.body.css', 'punctuation.section.keyframes.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[15][0], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.end.bracket.curly.css'], value: '}' }); + }); + + it('matches injected comments', function () { + var lines; + lines = testGrammar.tokenizeLines("@supports/*===*/not/*==****************|\n==*/(display:table-cell)/*============*/ and (display: list-item)/*}*/{}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css'], value: 'supports' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css'], value: '===' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.not.css'], value: 'not' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css'], value: '==****************|' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css'], value: '==' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'display' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'table-cell' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css'], value: '============' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][11], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.and.css'], value: 'and' }); + assert.deepStrictEqual(lines[1][13], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][19], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[1][20], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css'], value: '}' }); + assert.deepStrictEqual(lines[1][21], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][22], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][23], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.end.bracket.curly.css'], value: '}' }); + }); + + it('matches feature queries across multiple lines', function () { + var lines; + lines = testGrammar.tokenizeLines("@supports\n (box-shadow: 0 0 2px rgba(0,0,0,.5) inset) or\n (-moz-box-shadow: 0 0 2px black inset) or\n (-webkit-box-shadow: 0 0 2px black inset) or\n (-o-box-shadow: 0 0 2px black inset)\n{ .noticebox { } }"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.control.at-rule.supports.css'], value: 'supports' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'box-shadow' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[1][10], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[1][12], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgba' }); + assert.deepStrictEqual(lines[1][13], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][14], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[1][15], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[1][16], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[1][17], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[1][18], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[1][19], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[1][20], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '.5' }); + assert.deepStrictEqual(lines[1][21], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][23], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'inset' }); + assert.deepStrictEqual(lines[1][24], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][26], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.or.css'], value: 'or' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.vendored.property-name.css'], value: '-moz-box-shadow' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'black' }); + assert.deepStrictEqual(lines[2][14], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'inset' }); + assert.deepStrictEqual(lines[2][15], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][17], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.or.css'], value: 'or' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.vendored.property-name.css'], value: '-webkit-box-shadow' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[3][9], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[3][10], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[3][12], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'black' }); + assert.deepStrictEqual(lines[3][14], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'inset' }); + assert.deepStrictEqual(lines[3][15], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][17], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'keyword.operator.logical.feature.or.css'], value: 'or' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-name.css', 'support.type.vendored.property-name.css'], value: '-o-box-shadow' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[4][10], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[4][12], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'black' }); + assert.deepStrictEqual(lines[4][14], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'inset' }); + assert.deepStrictEqual(lines[4][15], { scopes: ['source.css', 'meta.at-rule.supports.header.css', 'meta.feature-query.css', 'punctuation.definition.condition.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[5][0], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[5][2], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(lines[5][3], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'noticebox' }); + assert.deepStrictEqual(lines[5][5], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[5][7], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[5][9], { scopes: ['source.css', 'meta.at-rule.supports.body.css', 'punctuation.section.supports.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('@namespace', function () { + it('tokenises @namespace statements correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@namespace "XML";').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.namespace.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css'], value: 'XML' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('@namespace prefix "XML" ;').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.namespace.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css'], value: 'prefix' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.namespace.css'], value: ' ' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css'], value: 'XML' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.namespace.css'], value: ' ' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('@namespace url("http://a.bc/");').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.namespace.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css'], value: 'http://a.bc/' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it("doesn't confuse a prefix of 'url' as a function", function () { + var tokens; + tokens = testGrammar.tokenizeLine('@namespace url url("http://a.bc/");').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css'], value: 'url' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css'], value: 'http://a.bc/' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it('permits injected comments between tokens', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@namespace/*=*/pre/*=*/"url"/*=*/;').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css'], value: 'pre' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css'], value: 'url' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it('allows no spaces between "@namespace" and quoted URLs', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@namespace"XML";').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css'], value: 'XML' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it('tokenises escape sequences in prefixes', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@namespace pre\\ fix "http://url/";').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css'], value: 'pre' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css', 'constant.character.escape.css'], value: '\\ ' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css'], value: 'fix' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + }); + + it('allows arguments to span multiple lines', function () { + var lines; + lines = testGrammar.tokenizeLines("@namespace\nprefix\"XML\";"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css'], value: 'prefix' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css'], value: 'XML' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + + lines = testGrammar.tokenizeLines("@namespace\n\n prefix\n\nurl(\"http://a.bc/\");"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'namespace' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'entity.name.function.namespace-prefix.css'], value: 'prefix' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css'], value: 'http://a.bc/' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + }); + + describe('font-feature declarations', function () { + it('tokenises font-feature blocks', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@font-feature-values Font name 2 { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css'], value: 'font-feature-values' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.font-features.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css'], value: 'Font name 2' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('allows font-feature names to start on a different line', function () { + var lines; + lines = testGrammar.tokenizeLines("@font-feature-values\nFont name 2\n{"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css'], value: 'font-feature-values' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css'], value: 'Font name 2' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('matches injected comments', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@font-feature-values/*{*/Font/*}*/name/*{*/2{').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css'], value: 'font-feature-values' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css'], value: '{' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css'], value: 'Font' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css'], value: '}' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css'], value: 'name' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css'], value: '{' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css'], value: '2' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('tokenises at-rules for feature names', function () { + var lines; + lines = testGrammar.tokenizeLines("@swash{ swashy: 2; }\n@ornaments{ ident: 2; }\n@annotation{ ident: 1; }\n@stylistic{ stylish: 2; }\n@styleset{ sets: 2 3 4; }\n@character-variant{ charvar: 2 }"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.swash.css', 'keyword.control.at-rule.swash.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.swash.css', 'keyword.control.at-rule.swash.css'], value: 'swash' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'swashy' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[0][8], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[0][10], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'keyword.control.at-rule.ornaments.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'keyword.control.at-rule.ornaments.css'], value: 'ornaments' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'ident' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'meta.property-list.font-feature.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'meta.property-list.font-feature.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[1][10], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'keyword.control.at-rule.annotation.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'keyword.control.at-rule.annotation.css'], value: 'annotation' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'ident' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'meta.property-list.font-feature.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'meta.property-list.font-feature.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'keyword.control.at-rule.stylistic.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'keyword.control.at-rule.stylistic.css'], value: 'stylistic' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'stylish' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'meta.property-list.font-feature.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'meta.property-list.font-feature.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[3][10], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'keyword.control.at-rule.styleset.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'keyword.control.at-rule.styleset.css'], value: 'styleset' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'sets' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '3' }); + assert.deepStrictEqual(lines[4][11], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '4' }); + assert.deepStrictEqual(lines[4][12], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[4][14], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[5][0], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'keyword.control.at-rule.character-variant.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[5][1], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'keyword.control.at-rule.character-variant.css'], value: 'character-variant' }); + assert.deepStrictEqual(lines[5][2], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[5][4], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'charvar' }); + assert.deepStrictEqual(lines[5][5], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'meta.property-list.font-feature.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[5][7], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[5][9], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('matches feature-name rules case-insensitively', function () { + var lines; + lines = testGrammar.tokenizeLines("@sWASH{ swashy: 2; }\n@ornaMENts{ ident: 2; }\n@anNOTatION{ ident: 1; }\n@styLISTic{ stylish: 2; }\n@STYLEset{ sets: 2 3 4; }\n@CHARacter-VARiant{ charvar: 2 }"); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.swash.css', 'keyword.control.at-rule.swash.css'], value: 'sWASH' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.ornaments.css', 'keyword.control.at-rule.ornaments.css'], value: 'ornaMENts' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.annotation.css', 'keyword.control.at-rule.annotation.css'], value: 'anNOTatION' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.stylistic.css', 'keyword.control.at-rule.stylistic.css'], value: 'styLISTic' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.styleset.css', 'keyword.control.at-rule.styleset.css'], value: 'STYLEset' }); + assert.deepStrictEqual(lines[5][1], { scopes: ['source.css', 'meta.at-rule.character-variant.css', 'keyword.control.at-rule.character-variant.css'], value: 'CHARacter-VARiant' }); + }); + + it('matches comments inside feature-name rules', function () { + var lines; + lines = testGrammar.tokenizeLines("@font-feature-values Font name 2 {\n@swash{/*\n========*/swashy:/**/2;/**/}\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'keyword.control.at-rule.font-feature-values.css'], value: 'font-feature-values' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.font-features.css', 'variable.parameter.font-name.css'], value: 'Font name 2' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'keyword.control.at-rule.swash.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'keyword.control.at-rule.swash.css'], value: 'swash' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'comment.block.css'], value: '========' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'swashy' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'meta.property-value.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('highlights escape sequences inside feature-names', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@swash{ s\\000077a\\73hy: 1; }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.swash.css', 'keyword.control.at-rule.swash.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.swash.css', 'keyword.control.at-rule.swash.css'], value: 'swash' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 's' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css', 'constant.character.escape.codepoint.css'], value: '\\000077' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'a' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css', 'constant.character.escape.codepoint.css'], value: '\\73' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.swash.css', 'meta.property-list.font-feature.css', 'variable.font-feature.css'], value: 'hy' }); + }); + }); + + describe('@page', function () { + it('tokenises @page blocks correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@page :first { }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css'], value: 'page' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'first' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it.skip('tokenizes @page:right {} correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@page:right{}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css'], value: 'page' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'right' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenizes @page {} correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@page {}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css'], value: 'page' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it.skip('tokenizes @page{} correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@page{}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css'], value: 'page' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('@counter-style', function () { + it('tokenises them and their contents correctly', function () { + var lines; + lines = testGrammar.tokenizeLines("@counter-style winners-list {\n system: fixed;\n symbols: url(gold-medal.svg) url(silver-medal.svg) url(bronze-medal.svg);\n suffix: \" \";\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css'], value: 'counter-style' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'variable.parameter.style-name.css'], value: 'winners-list' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'system' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'fixed' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'symbols' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'variable.parameter.url.css'], value: 'gold-medal.svg' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][11], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'variable.parameter.url.css'], value: 'silver-medal.svg' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][14], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(lines[2][15], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][16], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'variable.parameter.url.css'], value: 'bronze-medal.svg' }); + assert.deepStrictEqual(lines[2][17], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][18], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'suffix' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('matches injected comments', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@counter-style/*{*/winners-list/*}*/{ system: fixed; }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css'], value: 'counter-style' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'comment.block.css'], value: '{' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'variable.parameter.style-name.css'], value: 'winners-list' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'comment.block.css'], value: '}' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'system' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'fixed' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.at-rule.counter-style.body.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it("allows the counter-style's name to start on a different line", function () { + var lines; + lines = testGrammar.tokenizeLines("@counter-style\nwinners-list"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css'], value: 'counter-style' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'variable.parameter.style-name.css'], value: 'winners-list' }); + }); + + it("highlights escape sequences inside the style's name", function () { + var tokens; + tokens = testGrammar.tokenizeLine('@counter-style A\\01F602z').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'keyword.control.at-rule.counter-style.css'], value: 'counter-style' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'variable.parameter.style-name.css'], value: 'A' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'variable.parameter.style-name.css', 'constant.character.escape.codepoint.css'], value: '\\01F602' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.counter-style.header.css', 'variable.parameter.style-name.css'], value: 'z' }); + }); + }); + + describe('@document', function () { + it('correctly tokenises @document rules', function () { + var lines; + lines = testGrammar.tokenizeLines("@document url(http://www.w3.org/),\n url-prefix(http://www.w3.org/Style/), /* Comment */\n domain(/**/mozilla.org),\n regexp(\"https:.*\") {\n body{ color: #f00; }\n }"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'keyword.control.at-rule.document.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'keyword.control.at-rule.document.css'], value: 'document' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.url.css', 'variable.parameter.url.css'], value: 'http://www.w3.org/' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[0][7], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'support.function.document-rule.css'], value: 'url-prefix' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'variable.parameter.document-rule.css'], value: 'http://www.w3.org/Style/' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'comment.block.css'], value: ' Comment ' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'support.function.document-rule.css'], value: 'domain' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'variable.parameter.document-rule.css'], value: 'mozilla.org' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'support.function.document-rule.css'], value: 'regexp' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'string.quoted.double.css'], value: 'https:.*' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.at-rule.document.header.css', 'meta.function.document-rule.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'punctuation.section.document.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'body' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(lines[4][8], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.color.rgb-value.hex.css'], value: 'f00' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[4][11], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(lines[5][1], { scopes: ['source.css', 'meta.at-rule.document.body.css', 'punctuation.section.document.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('@viewport', function () { + it('tokenises @viewport blocks correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@viewport { min-width: 640px; max-width: 800px; }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'keyword.control.at-rule.viewport.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'keyword.control.at-rule.viewport.css'], value: 'viewport' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'min-width' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '640' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'max-width' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '800' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenises them across lines', function () { + var lines; + lines = testGrammar.tokenizeLines("@-O-VIEWPORT\n{\n zoom: 0.75;\n min-zoom: 0.5;\n max-zoom: 0.9;\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'keyword.control.at-rule.viewport.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'keyword.control.at-rule.viewport.css'], value: '-O-VIEWPORT' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'zoom' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0.75' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'min-zoom' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0.5' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'max-zoom' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0.9' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[5][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenises injected comments', function () { + var lines; + lines = testGrammar.tokenizeLines("@-ms-viewport/*{*/{/*\n==*/orientation: landscape;\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'keyword.control.at-rule.viewport.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'keyword.control.at-rule.viewport.css'], value: '-ms-viewport' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'comment.block.css'], value: '{' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.at-rule.viewport.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.property-list.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.property-list.css', 'comment.block.css'], value: '==' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.property-list.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'orientation' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'landscape' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('unknown at-rules', function () { + it('correctly parses single-line unknown at-rules closing with semicolons', function () { + var lines; + lines = testGrammar.tokenizeLines("@foo;\n@foo ;\n@foo a;\n@foo ();\n@foo (a);"); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'foo' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'foo' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'foo' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.at-rule.header.css'], value: ' a' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'foo' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.at-rule.header.css'], value: ' ()' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'foo' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.at-rule.header.css'], value: ' (a)' }); + }); + + it('correctly parses single-line unknown at-rules closing with ;', function () { + var lines; + lines = testGrammar.tokenizeLines("@foo bar;\n.foo"); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.header.css', 'keyword.control.at-rule.css'], value: 'foo' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'foo' }); + }); + }); + }); + + describe('capitalisation', function () { + it('ignores case in at-rules', function () { + var lines; + lines = testGrammar.tokenizeLines("@IMPoRT url(\"file.css\");\n@MEdIA (MAX-WIDTH: 2px){ }\n@pAgE :fIRST { }\n@NAMEspace \"A\";\n@foNT-FacE {}"); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'IMPoRT' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'MEdIA' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'MAX-WIDTH' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.at-rule.page.css', 'keyword.control.at-rule.page.css'], value: 'pAgE' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'fIRST' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.at-rule.namespace.css', 'keyword.control.at-rule.namespace.css'], value: 'NAMEspace' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.at-rule.font-face.css', 'keyword.control.at-rule.font-face.css'], value: 'foNT-FacE' }); + }); + + it('ignores case in property names', function () { + var lines; + lines = testGrammar.tokenizeLines("a{ COLOR: #fff; }\na{ gRId-tEMPLaTe: none; }\na{ bACkgrOUND-iMAGE: none; }\na{ -MOZ-IMAGE: none; }"); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'COLOR' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'gRId-tEMPLaTe' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'bACkgrOUND-iMAGE' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.vendored.property-name.css'], value: '-MOZ-IMAGE' }); + }); + + it('ignores case in property keywords', function () { + var lines; + lines = testGrammar.tokenizeLines("a{ color: INItIaL; }\na{ color: trAnsPAREnT; }\na{ color: rED; }\na{ color: unSET; }\na{ color: NONe; }\na{ style: lOWER-lATIN; }\na{ color: -WebkIT-foo; }\na{ font: HelVETica; }"); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'INItIaL' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'trAnsPAREnT' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'rED' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'unSET' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'NONe' }); + assert.deepStrictEqual(lines[5][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.list-style-type.css'], value: 'lOWER-lATIN' }); + assert.deepStrictEqual(lines[6][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.vendored.property-value.css'], value: '-WebkIT-foo' }); + assert.deepStrictEqual(lines[7][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.font-name.css'], value: 'HelVETica' }); + }); + + it('ignores case in selectors', function () { + var lines; + lines = testGrammar.tokenizeLines("DIV:HOVER { }\n#id::BefORE { }\n#id::aFTEr { }\nTABle:nTH-cHILD(2N+1) {}\nhtML:NOT(.htiml) {}\nI::BACKDROP\nI::-mOZ-thing {}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'DIV' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'HOVER' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'BefORE' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'aFTEr' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'TABle' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'nTH-cHILD' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '2N+1' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'htML' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'NOT' }); + assert.deepStrictEqual(lines[5][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'I' }); + assert.deepStrictEqual(lines[5][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'BACKDROP' }); + assert.deepStrictEqual(lines[6][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: '-mOZ-thing' }); + }); + + it('ignores case in function names', function () { + var lines; + lines = testGrammar.tokenizeLines("a{ color: RGBa(); }\na{ color: hslA(); }\na{ color: URL(); }\na{ content: ATTr(); }\na{ content: CoUNTer(); }\na{ content: cuBIC-beZIER()}\na{ content: sTePs()}\na{ content: cALc(2 + 2)}"); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'RGBa' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'hslA' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.url.css', 'support.function.url.css'], value: 'URL' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'ATTr' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'CoUNTer' }); + assert.deepStrictEqual(lines[5][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'support.function.timing-function.css'], value: 'cuBIC-beZIER' }); + assert.deepStrictEqual(lines[6][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'support.function.timing-function.css'], value: 'sTePs' }); + assert.deepStrictEqual(lines[7][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'support.function.calc.css'], value: 'cALc' }); + }); + + it('ignores case in unit names', function () { + var lines; + lines = testGrammar.tokenizeLines("a{width: 20EM; }\na{width: 20ReM; }\na{width: 8tURN; }\na{width: 20S; }\na{width: 20CM}\na{width: 2gRAd}"); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '20' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'EM' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.rem.css'], value: 'ReM' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'width' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.turn.css'], value: 'tURN' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.s.css'], value: 'S' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '20' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.cm.css'], value: 'CM' }); + assert.deepStrictEqual(lines[5][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.grad.css'], value: 'gRAd' }); + }); + }); + + describe('pseudo-classes', function () { + it('tokenizes regular pseudo-classes', function () { + var tokens; + tokens = testGrammar.tokenizeLine('p:first-child').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'p' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'first-child' }); + }); + + it("doesn't tokenise pseudo-classes if followed by a semicolon or closed bracket", function () { + var tokens; + tokens = testGrammar.tokenizeLine('p{ left:left }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'p' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'left' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'left' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + describe(':dir()', function () { + it('tokenises :dir() and its keywords', function () { + var lines; + lines = testGrammar.tokenizeLines("a:dir(ltr ){ }\n*:dir( rtl){ }"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'dir' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.selector.css', 'support.constant.text-direction.css'], value: 'ltr' }); + assert.deepStrictEqual(lines[0][5], { scopes: ['source.css', 'meta.selector.css'], value: ' ' }); + assert.deepStrictEqual(lines[0][6], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.wildcard.css'], value: '*' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'dir' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.selector.css'], value: ' ' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.selector.css', 'support.constant.text-direction.css'], value: 'rtl' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('allows :dir() to include comments and newlines', function () { + var lines; + lines = testGrammar.tokenizeLines(":DIR(/**\n==*/ltr/*\n*/)"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'DIR' }); + assert.deepStrictEqual(lines[0][2], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[0][3], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[0][4], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css'], value: '*' }); + assert.deepStrictEqual(lines[1][0], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css'], value: '==' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.selector.css', 'support.constant.text-direction.css'], value: 'ltr' }); + assert.deepStrictEqual(lines[1][3], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + }); + + describe(':lang()', function () { + it('tokenizes :lang()', function () { + var tokens; + tokens = testGrammar.tokenizeLine(':lang(zh-Hans-CN,es-419)').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'lang' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'support.constant.language-range.css'], value: 'zh-Hans-CN' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'support.constant.language-range.css'], value: 'es-419' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('does not tokenize unquoted language ranges containing asterisks', function () { + var tokens; + tokens = testGrammar.tokenizeLine(':lang(zh-*-CN)').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css'], value: 'zh-*-CN' }); + }); + + it('tokenizes language ranges containing asterisks quoted as strings', function () { + var tokens; + tokens = testGrammar.tokenizeLine(':lang("zh-*-CN",\'*-ab-\')').tokens; + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'string.quoted.double.css', 'support.constant.language-range.css'], value: 'zh-*-CN' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'string.quoted.single.css', 'support.constant.language-range.css'], value: '*-ab-' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.selector.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + }); + }); + + describe(':not()', function () { + it('tokenises other selectors inside :not()', function () { + var tokens; + tokens = testGrammar.tokenizeLine('*:not(.class-name):not(div) {}').tokens; + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'not' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'class-name' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'not' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'div' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('tokenises injected comments', function () { + var tokens; + tokens = testGrammar.tokenizeLine('*:not(/*(*/.class-name/*)*/):not(/*b*/) {}').tokens; + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'not' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css'], value: '(' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'class-name' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css'], value: ')' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'not' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css'], value: 'b' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.selector.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + }); + + describe(':nth-*()', function () { + it('tokenizes :nth-child()', function () { + var tokens; + tokens = testGrammar.tokenizeLines(':nth-child(2n+1)\n:nth-child(2n -1)\n:nth-child(-2n+ 1)\n:nth-child(-2n - 1)\n:nth-child(odd)\n:nth-child(even)\n:nth-child( odd )\n:nth-child( even )'); + assert.deepStrictEqual(tokens[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'nth-child' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '2n+1' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[1][3], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '2n -1' }); + assert.deepStrictEqual(tokens[2][3], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '-2n+ 1' }); + assert.deepStrictEqual(tokens[3][3], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '-2n - 1' }); + assert.deepStrictEqual(tokens[4][3], { scopes: ['source.css', 'meta.selector.css', 'support.constant.parity.css'], value: 'odd' }); + assert.deepStrictEqual(tokens[5][3], { scopes: ['source.css', 'meta.selector.css', 'support.constant.parity.css'], value: 'even' }); + assert.deepStrictEqual(tokens[6][3], { scopes: ['source.css', 'meta.selector.css'], value: ' ' }); + assert.deepStrictEqual(tokens[6][4], { scopes: ['source.css', 'meta.selector.css', 'support.constant.parity.css'], value: 'odd' }); + assert.deepStrictEqual(tokens[7][4], { scopes: ['source.css', 'meta.selector.css', 'support.constant.parity.css'], value: 'even' }); + assert.deepStrictEqual(tokens[7][5], { scopes: ['source.css', 'meta.selector.css'], value: ' ' }); + }); + + it('tokenizes :nth-last-child()', function () { + var tokens; + tokens = testGrammar.tokenizeLines(':nth-last-child(2n)\n:nth-last-child( -2n)\n:nth-last-child( 2n )\n:nth-last-child(even)'); + assert.deepStrictEqual(tokens[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'nth-last-child' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '2n' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[1][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '-2n' }); + assert.deepStrictEqual(tokens[2][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '2n' }); + assert.deepStrictEqual(tokens[2][6], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[3][3], { scopes: ['source.css', 'meta.selector.css', 'support.constant.parity.css'], value: 'even' }); + }); + + it('tokenizes :nth-of-type()', function () { + var tokens; + tokens = testGrammar.tokenizeLines('img:nth-of-type(+n+1)\nimg:nth-of-type(-n+1)\nimg:nth-of-type(n+1)'); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'nth-of-type' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '+n+1' }); + assert.deepStrictEqual(tokens[0][5], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[1][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '-n+1' }); + assert.deepStrictEqual(tokens[2][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: 'n+1' }); + }); + + it('tokenizes ::nth-last-of-type()', function () { + var tokens; + tokens = testGrammar.tokenizeLines('h1:nth-last-of-type(-1)\nh1:nth-last-of-type(+2)\nh1:nth-last-of-type(3)'); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'nth-last-of-type' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '-1' }); + assert.deepStrictEqual(tokens[0][5], { scopes: ['source.css', 'meta.selector.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[1][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '+2' }); + assert.deepStrictEqual(tokens[2][4], { scopes: ['source.css', 'meta.selector.css', 'constant.numeric.css'], value: '3' }); + }); + }); + }); + + describe('pseudo-elements', function () { + it('tokenizes both : and :: notations for pseudo-elements introduced in CSS 1 and 2', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.opening:first-letter').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'opening' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'first-letter' }); + + tokens = testGrammar.tokenizeLine('q::after').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'q' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'after' }); + }); + + it('tokenizes both : and :: notations for vendor-prefixed pseudo-elements', function () { + var tokens; + tokens = testGrammar.tokenizeLine(':-ms-input-placeholder').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: '-ms-input-placeholder' }); + + tokens = testGrammar.tokenizeLine('::-webkit-input-placeholder').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: '-webkit-input-placeholder' }); + }); + + it('only tokenizes the :: notation for other pseudo-elements', function () { + var tokens; + tokens = testGrammar.tokenizeLine('::selection').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'selection' }); + + tokens = testGrammar.tokenizeLine(':selection').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css'], value: ':selection' }); + }); + }); + describe('compound selectors', function () { + it('tokenizes the combination of type selectors followed by class selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('very-custom.class').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'very-custom' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css', 'punctuation.definition.entity.css'], value: '.' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.class.css'], value: 'class' }); + }); + + it('tokenizes the combination of type selectors followed by pseudo-classes', function () { + var tokens; + tokens = testGrammar.tokenizeLine('very-custom:hover').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'very-custom' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css', 'punctuation.definition.entity.css'], value: ':' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-class.css'], value: 'hover' }); + }); + + it('tokenizes the combination of type selectors followed by pseudo-elements', function () { + var tokens; + tokens = testGrammar.tokenizeLine('very-custom::shadow').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'very-custom' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css', 'punctuation.definition.entity.css'], value: '::' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.selector.css', 'entity.other.attribute-name.pseudo-element.css'], value: 'shadow' }); + }); + }); + }); + + describe('property lists (declaration blocks)', function () { + it('tokenizes inline property lists', function () { + var tokens; + tokens = testGrammar.tokenizeLine('div { font-size: inherit; }').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'font-size' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'inherit' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenizes compact inline property lists', function () { + var tokens; + tokens = testGrammar.tokenizeLine('div{color:inherit;float:left}').tokens; + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'inherit' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'float' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'left' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenizes multiple inline property lists', function () { + var tokens; + tokens = testGrammar.tokenizeLines('very-custom { color: inherit }\nanother-one { display : none ; }'); + assert.deepStrictEqual(tokens[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'very-custom' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + assert.deepStrictEqual(tokens[0][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'inherit' }); + assert.deepStrictEqual(tokens[0][8], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[1][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'another-one' }); + assert.deepStrictEqual(tokens[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'display' }); + assert.deepStrictEqual(tokens[1][5], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[1][6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[1][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'none' }); + assert.deepStrictEqual(tokens[1][9], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[1][10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[1][12], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenizes custom properties', function () { + var tokens; + tokens = testGrammar.tokenizeLine(':root { --white: #FFF; }').tokens; + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'variable.css'], value: '--white' }); + }); + + it('tokenises commas between property values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ text-shadow: a, b; }').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.separator.list.comma.css'], value: ',' }); + }); + + it('tokenises superfluous semicolons', function () { + var i, j, lines; + lines = testGrammar.tokenizeLines('.test{ width: 20em;;;;;;;;;\n;;;;;;;;;height: 10em; }'); + for (i = j = 0; j <= 8; i = ++j) { + assert.deepStrictEqual(lines[0][i + 9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[1][i], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + } + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'height' }); + }); + + describe('values', function () { + it('tokenizes color keywords', function () { + var tokens; + tokens = testGrammar.tokenizeLine('#jon { color: snow; }').tokens; + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.color.w3c-extended-color-name.css'], value: 'snow' }); + }); + + it('tokenises RGBA values in hex notation', function () { + var tokens; + tokens = testGrammar.tokenizeLine('p{ color: #f030; }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.color.rgb-value.hex.css'], value: 'f030' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('a{ color: #CAFEBABE; }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.color.rgb-value.hex.css'], value: 'CAFEBABE' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('a{ color: #CAFEBABEF; }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: '#CAFEBABEF' }); + }); + + it('tokenizes common font names', function () { + var tokens; + tokens = testGrammar.tokenizeLine('p { font-family: Verdana, Helvetica, sans-serif; }').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.font-name.css'], value: 'Verdana' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.font-name.css'], value: 'Helvetica' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.font-name.css'], value: 'sans-serif' }); + }); + + it('tokenizes predefined list style types', function () { + var tokens; + tokens = testGrammar.tokenizeLine('ol.myth { list-style-type: cjk-earthly-branch }').tokens; + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.list-style-type.css'], value: 'cjk-earthly-branch' }); + }); + + it('tokenizes numeric values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('div { font-size: 14px; }').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '14' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + }); + + it('does not tokenize invalid numeric values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('div { font-size: test14px; }').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'test14px' }); + + tokens = testGrammar.tokenizeLine('div { font-size: test-14px; }').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'test-14px' }); + }); + + it('tokenizes vendor-prefixed values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.edge { cursor: -webkit-zoom-in; }').tokens; + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.vendored.property-value.css'], value: '-webkit-zoom-in' }); + + tokens = testGrammar.tokenizeLine('.edge { width: -moz-min-content; }').tokens; + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.vendored.property-value.css'], value: '-moz-min-content' }); + + tokens = testGrammar.tokenizeLine('.edge { display: -ms-grid; }').tokens; + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.vendored.property-value.css'], value: '-ms-grid' }); + }); + + it('tokenizes custom variables', function () { + var tokens; + tokens = testGrammar.tokenizeLine('div { color: var(--primary-color) }').tokens; + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--primary-color' }); + }); + + it('tokenises numeric values correctly', function () { + var lines; + lines = testGrammar.tokenizeLines(".a { a: 12em }\n.a { a: 4.01ex }\n.a { a: -456.8ch }\n.a { a: 0.0REM }\n.a { a: +0.0vh }\n.a { a: -0.0vw }\n.a { a: .6px }\n.a { a: 10e3mm }\n.a { a: 10E3cm }\n.a { a: -3.4e+2In }\n.a { a: -3.4e-2ch }\n.a { a: +.5E-2% }\n.a { a: -3.4e-2% }"); + assert.deepStrictEqual(lines[0][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '12' }); + assert.deepStrictEqual(lines[0][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '4.01' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.ex.css'], value: 'ex' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '-456.8' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.ch.css'], value: 'ch' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0.0' }); + assert.deepStrictEqual(lines[3][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.rem.css'], value: 'REM' }); + assert.deepStrictEqual(lines[4][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '+0.0' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.vh.css'], value: 'vh' }); + assert.deepStrictEqual(lines[5][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '-0.0' }); + assert.deepStrictEqual(lines[5][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.vw.css'], value: 'vw' }); + assert.deepStrictEqual(lines[6][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '.6' }); + assert.deepStrictEqual(lines[6][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[7][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '10e3' }); + assert.deepStrictEqual(lines[7][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.mm.css'], value: 'mm' }); + assert.deepStrictEqual(lines[8][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '10E3' }); + assert.deepStrictEqual(lines[8][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.cm.css'], value: 'cm' }); + assert.deepStrictEqual(lines[9][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '-3.4e+2' }); + assert.deepStrictEqual(lines[9][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.in.css'], value: 'In' }); + assert.deepStrictEqual(lines[10][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '-3.4e-2' }); + assert.deepStrictEqual(lines[10][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.ch.css'], value: 'ch' }); + assert.deepStrictEqual(lines[11][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '+.5E-2' }); + assert.deepStrictEqual(lines[11][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(lines[12][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '-3.4e-2' }); + assert.deepStrictEqual(lines[12][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + }); + + describe('functional notation', function () { + describe('attr()', function () { + it('tokenises parameters correctly and case-insensitively', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{content:aTTr(data-width px, inherit)}').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'aTTr' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'data-width' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'px' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.constant.property-value.css'], value: 'inherit' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('matches variables', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{content:ATTR(VAR(--name) px, "N/A")}').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'ATTR' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'VAR' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--name' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'px' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css'], value: 'N/A' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + }); + + describe('calc()', function () { + it('tokenises calculations', function () { + var lines; + lines = testGrammar.tokenizeLines("a{\n width: calc(3px + -1em);\n width: calc(3px - -1em);\n width: calc(3px * 2);\n width: calc(3px / 2);\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'support.function.calc.css'], value: 'calc' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '3' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'keyword.operator.arithmetic.css'], value: '+' }); + assert.deepStrictEqual(lines[1][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '-1' }); + assert.deepStrictEqual(lines[1][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[1][13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'keyword.operator.arithmetic.css'], value: '-' }); + assert.deepStrictEqual(lines[2][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '-1' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[3][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'keyword.operator.arithmetic.css'], value: '*' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'keyword.operator.arithmetic.css'], value: '/' }); + assert.deepStrictEqual(lines[4][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '2' }); + }); + + it('requires whitespace around + and - operators', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ width: calc(3px+1em); }').tokens; + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css'], value: '+' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + + tokens = testGrammar.tokenizeLine('a{ width: calc(3px--1em); height: calc(10-1em);}').tokens; + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css'], value: '--1em' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '10' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css'], value: '-1em' }); + }); + + it('does not require whitespace around * and / operators', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ width: calc(3px*2); }').tokens; + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'keyword.operator.arithmetic.css'], value: '*' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '2' }); + + tokens = testGrammar.tokenizeLine('a{ width: calc(3px/2); }').tokens; + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'keyword.operator.arithmetic.css'], value: '/' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '2' }); + }); + + it('matches variable expansions inside calculations', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.foo { margin-top: calc(var(--gap) + 1px); }').tokens; + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'support.function.calc.css'], value: 'calc' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--gap' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'keyword.operator.arithmetic.css'], value: '+' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.calc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[22], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('colours', function () { + it('tokenises colour functions correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ color: rgb(187,255,221); }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgb' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '187' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '255' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '221' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + + tokens = testGrammar.tokenizeLine('a{ color: RGBa( 100%, 0% ,20.17% ,.5 ); }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'RGBa' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '100' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '20.17' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '.5' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + + tokens = testGrammar.tokenizeLine('a{color:HSL(0, 00100%,50%)}').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'HSL' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '00100' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '50' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + + tokens = testGrammar.tokenizeLine('a{color:HSLa(2,.0%,1%,.7)}').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'HSLa' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '.0' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '.7' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('matches variables as colour components', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ color: RGBA(var(--red), 0% , 20%, .2)}').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--red' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + }); + + it('matches comments between colour components', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ color: rgba(/**/255/*=*/,0,/*2.2%*/51/*,*/0.2)}').tokens; + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '255' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '51' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[22], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: ',' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[24], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0.2' }); + }); + + it('allows colour components to be split across lines', function () { + var lines; + lines = testGrammar.tokenizeLines(".frost{\n background-color: rgba(\n var(--red), /* Red */\n var(--green), /* Green */\n var(--blue), /* Blue */\n /* var(--test),\n /**/var(--opacity) /* Transparency */\n );\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgba' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--red' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: ' Red ' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--green' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: ' Green ' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--blue' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[4][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: ' Blue ' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[5][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[5][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: ' var(--test),' }); + assert.deepStrictEqual(lines[6][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: ' /*' }); + assert.deepStrictEqual(lines[6][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[6][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(lines[6][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[6][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--opacity' }); + assert.deepStrictEqual(lines[6][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[6][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[6][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css'], value: ' Transparency ' }); + assert.deepStrictEqual(lines[6][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[7][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + }); + + describe('gradients', function () { + it('tokenises linear gradients', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ background-image: linear-gradient( 45deg, blue, red ); }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: 'linear-gradient' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '45' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.deg.css'], value: 'deg' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'blue' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'red' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('a{ background-image: LINear-graDIEnt( ellipse to left top, blue, red);').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: 'LINear-graDIEnt' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'ellipse' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'keyword.operator.gradient.css'], value: 'to' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'left' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'top' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'blue' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'red' }); + }); + + it('tokenises radial gradients', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ background-image: radial-gradient(farthest-corner at 45px 45px , #f00 0%, #00f 100%);}').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: 'radial-gradient' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'farthest-corner' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'keyword.operator.gradient.css'], value: 'at' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '45' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '45' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css'], value: 'f00' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[24], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + + tokens = testGrammar.tokenizeLine('a{ background-image: RADial-gradiENT(16px at 60px 50%,#000 0%, #000 14px, rgba(0,0,0,.3) 18px, transparent 19px)}').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: 'RADial-gradiENT' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '16' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'keyword.operator.gradient.css'], value: 'at' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '60' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '50' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css'], value: '000' }); + assert.deepStrictEqual(tokens[33], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgba' }); + assert.deepStrictEqual(tokens[34], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[35], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[36], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[41], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '.3' }); + assert.deepStrictEqual(tokens[42], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[48], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'transparent' }); + }); + + it('matches gradients that span multiple lines with injected comments', function () { + var lines; + lines = testGrammar.tokenizeLines("a{\n background-image: raDIAL-gradiENT(\n ellipse farthest-corner/*@*/at/*@*/470px 47px,/*===\n========*/#FFFF80 20%, rgba(204, 153, 153, 0.4) 30%,/*))))))))}*/#E6E6FF 60%); }"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: 'raDIAL-gradiENT' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'ellipse' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'farthest-corner' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css'], value: '@' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'keyword.operator.gradient.css'], value: 'at' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css'], value: '470' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(lines[2][16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[2][18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css'], value: '===' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css'], value: '========' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css'], value: 'FFFF80' }); + assert.deepStrictEqual(lines[3][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgba' }); + assert.deepStrictEqual(lines[3][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0.4' }); + assert.deepStrictEqual(lines[3][21], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][26], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[3][27], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css'], value: '))))))))}' }); + assert.deepStrictEqual(lines[3][28], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[3][29], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(lines[3][30], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css'], value: 'E6E6FF' }); + }); + + it('highlights vendored gradient functions', function () { + var lines; + lines = testGrammar.tokenizeLines(".grad {\n background-image: -webkit-linear-gradient(top, /* For Chrome 25 and Safari 6, iOS 6.1, Android 4.3 */ hsl(0, 80%, 70%), #bada55);\n background-image: -moz-linear-gradient(top, /* For Firefox (3.6 to 15) */ hsl(0, 80%, 70%), #bada55);\n background-image: -o-linear-gradient(top, /* For old Opera (11.1 to 12.0) */ hsl(0, 80%, 70%), #bada55);\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: '-webkit-linear-gradient' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'top' }); + assert.deepStrictEqual(lines[1][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css'], value: ' For Chrome 25 and Safari 6, iOS 6.1, Android 4.3 ' }); + assert.deepStrictEqual(lines[1][13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'hsl' }); + assert.deepStrictEqual(lines[1][22], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '70' }); + assert.deepStrictEqual(lines[1][23], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(lines[1][24], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[1][27], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css', 'punctuation.definition.constant.css'], value: '#' }); + assert.deepStrictEqual(lines[1][28], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'constant.other.color.rgb-value.hex.css'], value: 'bada55' }); + assert.deepStrictEqual(lines[1][29], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: '-moz-linear-gradient' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.constant.property-value.css'], value: 'top' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css'], value: ' For Firefox (3.6 to 15) ' }); + assert.deepStrictEqual(lines[2][13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'hsl' }); + assert.deepStrictEqual(lines[2][14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][24], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][29], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'support.function.gradient.css'], value: '-o-linear-gradient' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'comment.block.css'], value: ' For old Opera (11.1 to 12.0) ' }); + assert.deepStrictEqual(lines[3][13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'hsl' }); + assert.deepStrictEqual(lines[3][14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + }); + + it('highlights antique Webkit syntax as deprecated', function () { + var lines; + lines = testGrammar.tokenizeLines(".grad {\n background-image: -webkit-gradient(linear, 0% 0%, 0% 100%,\n from( rgb(0, 171, 235)),\n color-stop(0.5, rgb(255, 255, 255)),\n color-stop(0.5, rgb(102, 204, 0)),\n to(rgb(255, 255, 255))),\n -webkit-gradient(radial, 45 45, 10, 52 50, 30, from(#A7D30C), to(rgba(1,159,98,0)), color-stop(90%, #019F62)),\n -webkit-gradient(radial, 105 105, 20, 112 120, 50, from(#ff5f98), to(rgba(255,1,136,0)), color-stop(75%, #ff0188)),\n -webkit-gradient(radial, 95 15, 15, 102 20, 40, from(#00c9ff), to(rgba(0,201,255,0)), color-stop(80%, #00b5e2)),\n -webkit-gradient(radial, 0 150, 50, 0 140, 90, from(#f4f201), to(rgba(228, 199,0,0)), color-stop(80%, #e4c700));\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.gradient.function.css'], value: '-webkit-gradient' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'support.constant.property-value.css'], value: 'linear' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[1][19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'constant.numeric.css'], value: '100' }); + assert.deepStrictEqual(lines[1][20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.function.css'], value: 'from' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgb' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '171' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.function.css'], value: 'color-stop' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'constant.numeric.css'], value: '0.5' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[4][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.function.css'], value: 'color-stop' }); + assert.deepStrictEqual(lines[4][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[4][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'constant.numeric.css'], value: '0.5' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgb' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[4][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '102' }); + assert.deepStrictEqual(lines[4][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[4][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '204' }); + assert.deepStrictEqual(lines[4][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[4][14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[4][15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[4][16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[4][17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[5][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.function.css'], value: 'to' }); + assert.deepStrictEqual(lines[5][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[5][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[5][13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[5][14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[5][15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[6][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.gradient.function.css'], value: '-webkit-gradient' }); + assert.deepStrictEqual(lines[6][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[6][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'support.constant.property-value.css'], value: 'radial' }); + assert.deepStrictEqual(lines[6][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[6][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'constant.numeric.css'], value: '45' }); + assert.deepStrictEqual(lines[6][31], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'meta.function.color.css', 'support.function.misc.css'], value: 'rgba' }); + assert.deepStrictEqual(lines[7][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.gradient.function.css'], value: '-webkit-gradient' }); + assert.deepStrictEqual(lines[7][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[9][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'invalid.deprecated.gradient.function.css'], value: '-webkit-gradient' }); + assert.deepStrictEqual(lines[9][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[9][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'support.constant.property-value.css'], value: 'radial' }); + assert.deepStrictEqual(lines[9][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[9][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(lines[9][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'constant.numeric.css'], value: '150' }); + assert.deepStrictEqual(lines[9][54], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.gradient.invalid.deprecated.gradient.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[9][55], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[10][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('other functions', function () { + it('tokenises basic-shape functions', function () { + var lines; + lines = testGrammar.tokenizeLines("a{\n shape-outside: circle(20em/*=*/at 50% 50%);\n shape-outside: inset(1em, 1em, 1em, 1em);\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'support.function.shape.css'], value: 'circle' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css'], value: '20' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(lines[1][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[1][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'keyword.operator.shape.css'], value: 'at' }); + assert.deepStrictEqual(lines[1][13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css'], value: '50' }); + assert.deepStrictEqual(lines[1][14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(lines[1][16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css'], value: '50' }); + assert.deepStrictEqual(lines[1][17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css', 'keyword.other.unit.percentage.css'], value: '%' }); + assert.deepStrictEqual(lines[1][18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'support.function.shape.css'], value: 'inset' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[2][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(lines[2][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[2][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(lines[2][15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[2][16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css'], value: '1' }); + assert.deepStrictEqual(lines[2][19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(lines[2][20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.shape.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + it('tokenises OpenType feature functions', function () { + var lines; + lines = testGrammar.tokenizeLines(".font{\n font-variant-alternates: stylistic(user-defined-ident);\n font-variant-alternates: styleset(user-defined-ident);\n font-variant-alternates: character-variant(user-defined-ident);\n font-variant-alternates: swash(user-defined-ident);\n font-variant-alternates: ornaments(user-defined-ident);\n font-variant-alternates: annotation(user-defined-ident);\n font-variant-alternates: swash(ident1) annotation(ident2);\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'stylistic' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'user-defined-ident' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'styleset' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'user-defined-ident' }); + assert.deepStrictEqual(lines[2][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'character-variant' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'user-defined-ident' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[4][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'swash' }); + assert.deepStrictEqual(lines[4][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[4][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'user-defined-ident' }); + assert.deepStrictEqual(lines[4][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[5][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'ornaments' }); + assert.deepStrictEqual(lines[5][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[5][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'user-defined-ident' }); + assert.deepStrictEqual(lines[5][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[6][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'annotation' }); + assert.deepStrictEqual(lines[6][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[6][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'user-defined-ident' }); + assert.deepStrictEqual(lines[6][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[7][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'swash' }); + assert.deepStrictEqual(lines[7][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[7][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'ident1' }); + assert.deepStrictEqual(lines[7][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[7][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'annotation' }); + assert.deepStrictEqual(lines[7][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[7][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'variable.parameter.misc.css'], value: 'ident2' }); + assert.deepStrictEqual(lines[7][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('tokenises image-set()', function () { + var lines; + lines = testGrammar.tokenizeLines("a{\n background-image: image-set( \"foo.png\" 1x,\n \"foo-2x.png\" 2x,\n \"foo-print.png\" 600dpi );\n}"); + assert.deepStrictEqual(lines[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(lines[0][1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(lines[1][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'background-image' }); + assert.deepStrictEqual(lines[1][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'support.function.misc.css'], value: 'image-set' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css'], value: 'foo.png' }); + assert.deepStrictEqual(lines[1][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[1][11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'constant.numeric.other.density.css'], value: '1x' }); + assert.deepStrictEqual(lines[1][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css'], value: 'foo-2x.png' }); + assert.deepStrictEqual(lines[2][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[2][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'constant.numeric.other.density.css'], value: '2x' }); + assert.deepStrictEqual(lines[2][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[3][2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css'], value: 'foo-print.png' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[3][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'constant.numeric.css'], value: '600' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'constant.numeric.css', 'keyword.other.unit.dpi.css'], value: 'dpi' }); + assert.deepStrictEqual(lines[3][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.misc.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(lines[3][9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('timing-functions', function () { + it('tokenises them correctly', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ zoom: cubic-bezier(/**/1.2,/*=*/0,0,0/**/)}').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'support.function.timing-function.css'], value: 'cubic-bezier' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'constant.numeric.css'], value: '1.2' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[19], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[20], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[21], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[22], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('highlights the "start" and "end" keywords', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ before: steps(0, start); after: steps(1, end); }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'support.function.timing-function.css'], value: 'steps' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'support.constant.step-direction.css'], value: 'start' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[23], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.timing-function.css', 'support.constant.step-direction.css'], value: 'end' }); + }); + }); + + describe('variables', function () { + it('scopes var() statements as variables', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{color: var(--name)}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--name' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('a{color: var( --name )}').tokens; + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--name' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('allows injected comments', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ color: var( /*=*/ --something ) }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'comment.block.css'], value: '=' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--something' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + }); + + it('tokenises fallback values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('.bar{ width: var(--page-width, /*;;;);*/ 2); }').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'support.function.misc.css'], value: 'var' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'variable.argument.css'], value: '--page-width' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'comment.block.css'], value: ';;;);' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'constant.numeric.css'], value: '2' }); + assert.deepStrictEqual(tokens[17], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'meta.function.variable.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[18], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + }); + + it('does not tokenise functions with whitespace between name and parameters', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ p: attr (title); }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css'], value: 'p' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'attr (title' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css'], value: ')' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('a{url:url (s)}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css'], value: 'url' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'url (s' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css'], value: ')' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('a{content:url ("http://github.com/");}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'content' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'url (' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'], value: 'http://github.com/' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css'], value: ')' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('a{content: url (http://a.pl/)}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'content' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'url (http://a.pl/' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css'], value: ')' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + + tokens = testGrammar.tokenizeLine('a{ color: rgb (187,255,221); }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'rgb (' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '187' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '255' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '221' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.property-list.css'], value: ')' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + describe('Unicode ranges', function () { + it('tokenises single codepoints', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ a: U+A5 }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: 'U+A5' }); + }); + + it('tokenises codepoint ranges', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ a: U+0025-00FF }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: 'U+0025' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css', 'punctuation.separator.dash.unicode-range.css'], value: '-' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: '00FF' }); + + tokens = testGrammar.tokenizeLine('a{ unicode-range: u+0-7F }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: 'u+0' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css', 'punctuation.separator.dash.unicode-range.css'], value: '-' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: '7F' }); + }); + + it('tokenises wildcard ranges', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ unicode-range: U+4?? }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: 'U+4??' }); + + tokens = testGrammar.tokenizeLine('a{ unicode-range: U+0025-00FF, U+4?? }').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: 'U+0025' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css', 'punctuation.separator.dash.unicode-range.css'], value: '-' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: '00FF' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.other.unicode-range.css'], value: 'U+4??' }); + }); + }); + }); + }); + + describe('escape sequences', function () { + it('tokenizes escape sequences in single-quoted strings', function () { + var tokens; + tokens = testGrammar.tokenizeLine("very-custom { content: '\\c0ffee' }").tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'very-custom' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'content' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'constant.character.escape.codepoint.css'], value: '\\c0ffee' }); + }); + + it('tokenizes escape sequences in double-quoted strings', function () { + var tokens; + tokens = testGrammar.tokenizeLine('very-custom { content: "\\c0ffee" }').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.custom.css'], value: 'very-custom' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'content' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'constant.character.escape.codepoint.css'], value: '\\c0ffee' }); + }); + + it('tokenises escape sequences in selectors', function () { + var tokens; + tokens = testGrammar.tokenizeLine('\\61 \\{ { } \\}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'constant.character.escape.codepoint.css'], value: '\\61' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'constant.character.escape.css'], value: '\\{' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'constant.character.escape.css'], value: '\\}' }); + + tokens = testGrammar.tokenizeLine('\\61\\ \\. \\@media {}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'constant.character.escape.codepoint.css'], value: '\\61' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'constant.character.escape.css'], value: '\\ ' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'constant.character.escape.css'], value: '\\.' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'constant.character.escape.css'], value: '\\@' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.selector.css'], value: 'media' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + }); + + it('tokenises escape sequences in property lists', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a { \\77\\69\\64\\74\\68: 20px; }').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'constant.character.escape.codepoint.css'], value: '\\77' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'constant.character.escape.codepoint.css'], value: '\\69' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'constant.character.escape.codepoint.css'], value: '\\64' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'constant.character.escape.codepoint.css'], value: '\\74' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'constant.character.escape.codepoint.css'], value: '\\68' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + }); + + it('tokenises escape sequences in property values', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a { content: \\1F764; }').tokens; + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.character.escape.codepoint.css'], value: '\\1F764' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('unclosed strings', function () { + it('highlights an unterminated string as an error', function () { + var tokens; + tokens = testGrammar.tokenizeLine("a{ content: 'aaaa").tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'invalid.illegal.unclosed.string.css'], value: 'aaaa' }); + + tokens = testGrammar.tokenizeLine('a{ content: "aaaa').tokens; + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'invalid.illegal.unclosed.string.css'], value: 'aaaa' }); + }); + + it.skip("knows when a string is line-wrapped - a", function () { + var lines; + lines = testGrammar.tokenizeLines("a{\n content: \"aaaaa\\\\\\\naaa\"; color: red;\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'], value: 'aaaaa' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'constant.character.escape.css'], value: '\\\\' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'constant.character.escape.newline.css'], value: '\\' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'], value: 'aaa' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + }); + + it.skip("knows when a string is line-wrapped - b", function () { + var lines; + lines = testGrammar.tokenizeLines("a{\n content: 'aaaaa\\\\\\\naaa'; color: red;\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css'], value: 'aaaaa' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'constant.character.escape.css'], value: '\\\\' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'constant.character.escape.newline.css'], value: '\\' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css'], value: 'aaa' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'punctuation.definition.string.end.css'], value: "'" }); + assert.deepStrictEqual(lines[2][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + }); + + it('highlights escape sequences inside invalid strings', function () { + var tokens; + tokens = testGrammar.tokenizeLine('a{ content: "aaa\\"aa').tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'invalid.illegal.unclosed.string.css'], value: 'aaa' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'invalid.illegal.unclosed.string.css', 'constant.character.escape.css'], value: '\\"' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'invalid.illegal.unclosed.string.css'], value: 'aa' }); + + tokens = testGrammar.tokenizeLine("a{ content: 'aaa\\'aa").tokens; + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'punctuation.definition.string.begin.css'], value: "'" }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'invalid.illegal.unclosed.string.css'], value: 'aaa' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'invalid.illegal.unclosed.string.css', 'constant.character.escape.css'], value: "\\'" }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.single.css', 'invalid.illegal.unclosed.string.css'], value: 'aa' }); + }); + + it.skip('highlights unclosed lines in line-wrapped strings', function () { + var lines; + lines = testGrammar.tokenizeLines("a{\n content: \"aaa\\\"aa\\\naaaa\naaaa; color: red;\n}"); + assert.deepStrictEqual(lines[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'], value: 'aaa' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'constant.character.escape.css'], value: '\\"' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css'], value: 'aa' }); + assert.deepStrictEqual(lines[1][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'constant.character.escape.newline.css'], value: '\\' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'string.quoted.double.css', 'invalid.illegal.unclosed.string.css'], value: 'aaaa' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'aaaa' }); + assert.deepStrictEqual(lines[3][1], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[3][3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'color' }); + assert.deepStrictEqual(lines[3][4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(lines[3][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.color.w3c-standard-color-name.css'], value: 'red' }); + assert.deepStrictEqual(lines[3][7], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(lines[4][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('comments', function () { + it('tokenises comments inside @import statements', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@import /* url("name"); */ "1.css";').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css'], value: ' url("name"); ' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css'], value: '1.css' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.import.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('@import/*";"*/ url("2.css");').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css'], value: '";"' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css'], value: '2.css' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + + tokens = testGrammar.tokenizeLine('@import url("3.css") print /* url(";"); */;').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.import.css', 'keyword.control.at-rule.import.css'], value: 'import' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'support.function.url.css'], value: 'url' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.begin.css'], value: '"' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css'], value: '3.css' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'string.quoted.double.css', 'punctuation.definition.string.end.css'], value: '"' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.import.css', 'meta.function.url.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.import.css', 'support.constant.media.css'], value: 'print' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[13], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css'], value: ' url(";"); ' }); + assert.deepStrictEqual(tokens[14], { scopes: ['source.css', 'meta.at-rule.import.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[15], { scopes: ['source.css', 'meta.at-rule.import.css', 'punctuation.terminator.rule.css'], value: ';' }); + }); + + it('tokenises comments inside @font-face statements', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@font-face/*"{;}"*/{}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.font-face.css', 'keyword.control.at-rule.font-face.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.font-face.css', 'keyword.control.at-rule.font-face.css'], value: 'font-face' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.font-face.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.font-face.css', 'comment.block.css'], value: '"{;}"' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.font-face.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenizes comments before media queries', function () { + var tokens; + tokens = testGrammar.tokenizeLine('/* comment */ @media').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'comment.block.css'], value: ' comment ' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + }); + + it('tokenizes comments after media queries', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media/* comment */ ()').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ' comment ' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + }); + + it('tokenizes comments inside query lists', function () { + var tokens; + tokens = testGrammar.tokenizeLine('@media (max-height: 40em/* comment */)').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css', 'punctuation.definition.keyword.css'], value: '@' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'keyword.control.at-rule.media.css'], value: 'media' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'support.type.property-name.media.css'], value: 'max-height' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css'], value: '40' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'constant.numeric.css', 'keyword.other.unit.em.css'], value: 'em' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css'], value: ' comment ' }); + assert.deepStrictEqual(tokens[11], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[12], { scopes: ['source.css', 'meta.at-rule.media.header.css', 'punctuation.definition.parameters.end.bracket.round.css'], value: ')' }); + }); + + it('tokenizes inline comments', function () { + var tokens; + tokens = testGrammar.tokenizeLine('section {border:4px/*padding:1px*/}').tokens; + assert.deepStrictEqual(tokens[0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'section' }); + assert.deepStrictEqual(tokens[1], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[3], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'border' }); + assert.deepStrictEqual(tokens[4], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '4' }); + assert.deepStrictEqual(tokens[6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(tokens[8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'comment.block.css'], value: 'padding:1px' }); + assert.deepStrictEqual(tokens[9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(tokens[10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it('tokenizes multi-line comments', function () { + var lines; + lines = testGrammar.tokenizeLines(" section {\n border:4px /*1px;\n padding:1px*/\n}"); + assert.deepStrictEqual(lines[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: ' ' }); + assert.deepStrictEqual(lines[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'comment.block.css', 'punctuation.definition.comment.begin.css'], value: '/*' }); + assert.deepStrictEqual(lines[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'comment.block.css'], value: '1px;' }); + assert.deepStrictEqual(lines[2][0], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'comment.block.css'], value: ' padding:1px' }); + assert.deepStrictEqual(lines[2][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'comment.block.css', 'punctuation.definition.comment.end.css'], value: '*/' }); + assert.deepStrictEqual(lines[3][0], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); + + describe('Animations', function () { + it('does not confuse animation names with predefined keywords', function () { + var tokens; + tokens = testGrammar.tokenizeLines('.animated {\n animation-name: orphan-black;\n animation-name: line-scale;\n}'); + assert.deepStrictEqual(tokens[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'orphan-black' }); + assert.deepStrictEqual(tokens[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: 'line-scale' }); + }); + }); + + describe('Transforms', function () { + it('tokenizes transform functions', function () { + var tokens; + tokens = testGrammar.tokenizeLines('.transformed {\n transform: matrix(0, 1.5, -1.5, 0, 0, 100px);\n transform: rotate(90deg) translateX(100px) scale(1.5);\n}'); + assert.deepStrictEqual(tokens[1][1], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'transform' }); + assert.deepStrictEqual(tokens[1][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[1][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.function.transform.css'], value: 'matrix' }); + assert.deepStrictEqual(tokens[1][5], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.section.function.begin.bracket.round.css'], value: '(' }); + assert.deepStrictEqual(tokens[1][6], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '0' }); + assert.deepStrictEqual(tokens[1][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.separator.list.comma.css'], value: ',' }); + assert.deepStrictEqual(tokens[1][12], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '-1.5' }); + assert.deepStrictEqual(tokens[1][22], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[1][23], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'punctuation.section.function.end.bracket.round.css'], value: ')' }); + assert.deepStrictEqual(tokens[2][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.function.transform.css'], value: 'rotate' }); + assert.deepStrictEqual(tokens[2][10], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.function.transform.css'], value: 'translateX' }); + assert.deepStrictEqual(tokens[2][16], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.function.transform.css'], value: 'scale' }); + }); + }); + + describe("performance regressions", function () { + it("does not hang when tokenizing invalid input preceding an equals sign", function () { + var start; + start = Date.now(); + testGrammar.tokenizeLine('\n [}~" + ('ÁÂÃÄÅÆÇÈÊËÍÎ'.repeat(100)) + "\n"); + assert.ok(Date.now() - start < 5000); + }); + }); + + // Skipped because `firstLineRegex.scanner` does not exist in `vscode-textmate` + describe.skip("firstLineMatch", function () { + it("recognises Emacs modelines", function () { + var invalid, j, k, len, len1, line, ref, ref1, valid; + valid = "#-*- CSS -*-\n#-*- mode: CSS -*-\n/* -*-css-*- */\n// -*- CSS -*-\n/* -*- mode:CSS -*- */\n// -*- font:bar;mode:CSS -*-\n// -*- font:bar;mode:CSS;foo:bar; -*-\n// -*-font:mode;mode:CSS-*-\n// -*- foo:bar mode: css bar:baz -*-\n\" -*-foo:bar;mode:css;bar:foo-*- \";\n\" -*-font-mode:foo;mode:css;foo-bar:quux-*-\"\n\"-*-font:x;foo:bar; mode : CsS; bar:foo;foooooo:baaaaar;fo:ba;-*-\";\n\"-*- font:x;foo : bar ; mode : cSS ; bar : foo ; foooooo:baaaaar;fo:ba-*-\";"; + ref = valid.split(/\n/); + for (j = 0, len = ref.length; j < len; j++) { + line = ref[j]; + assert.notEqual(testGrammar.grammar.firstLineRegex.scanner.findNextMatchSync(line), null); + } + invalid = "/* --*css-*- */\n/* -*-- CSS -*-\n/* -*- -- CSS -*-\n/* -*- CSS -;- -*-\n// -*- CCSS -*-\n// -*- CSS; -*-\n// -*- css-stuff -*-\n/* -*- model:css -*-\n/* -*- indent-mode:css -*-\n// -*- font:mode;CSS -*-\n// -*- mode: -*- CSS\n// -*- mode: I-miss-plain-old-css -*-\n// -*-font:mode;mode:css--*-"; + ref1 = invalid.split(/\n/); + for (k = 0, len1 = ref1.length; k < len1; k++) { + line = ref1[k]; + assert.equal(testGrammar.grammar.firstLineRegex.scanner.findNextMatchSync(line), null) + } + }); + + it("recognises Vim modelines", function () { + var invalid, j, k, len, len1, line, ref, ref1, valid; + valid = "vim: se filetype=css:\n# vim: se ft=css:\n# vim: set ft=CSS:\n# vim: set filetype=CSS:\n# vim: ft=CSS\n# vim: syntax=CSS\n# vim: se syntax=css:\n# ex: syntax=CSS\n# vim:ft=css\n# vim600: ft=css\n# vim>600: set ft=css:\n# vi:noai:sw=3 ts=6 ft=CSS\n# vi::::::::::noai:::::::::::: ft=CSS\n# vim:ts=4:sts=4:sw=4:noexpandtab:ft=cSS\n# vi:: noai : : : : sw =3 ts =6 ft =Css\n# vim: ts=4: pi sts=4: ft=CSS: noexpandtab: sw=4:\n# vim: ts=4 sts=4: ft=css noexpandtab:\n# vim:noexpandtab sts=4 ft=css ts=4\n# vim:noexpandtab:ft=css\n# vim:ts=4:sts=4 ft=css:noexpandtab:\x20\n# vim:noexpandtab titlestring=hi\|there\\\\ ft=css ts=4"; + ref = valid.split(/\n/); + for (j = 0, len = ref.length; j < len; j++) { + line = ref[j]; + assert.notEqual(testGrammar.grammar.firstLineRegex.scanner.findNextMatchSync(line), null); + } + invalid = "ex: se filetype=css:\n_vi: se filetype=CSS:\n vi: se filetype=CSS\n# vim set ft=css3\n# vim: soft=css\n# vim: clean-syntax=css:\n# vim set ft=css:\n# vim: setft=CSS:\n# vim: se ft=css backupdir=tmp\n# vim: set ft=css set cmdheight=1\n# vim:noexpandtab sts:4 ft:CSS ts:4\n# vim:noexpandtab titlestring=hi\\|there\\ ft=CSS ts=4\n# vim:noexpandtab titlestring=hi\\|there\\\\\\ ft=CSS ts=4"; + ref1 = invalid.split(/\n/); + for (k = 0, len1 = ref1.length; k < len1; k++) { + line = ref1[k]; + assert.equal(testGrammar.grammar.firstLineRegex.scanner.findNextMatchSync(line), null); + } + }); + }); + + describe("Missing supported properties regressions", function () { + it("recognises place-items property as supported", function () { + var tokens; + tokens = testGrammar.tokenizeLines('a { place-items: center center; }'); + assert.deepStrictEqual(tokens[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'place-items' }); + assert.deepStrictEqual(tokens[0][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][6], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'center' }); + assert.deepStrictEqual(tokens[0][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'center' }); + assert.deepStrictEqual(tokens[0][10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[0][11], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][12], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it("recognises place-self property as supported", function () { + var tokens; + tokens = testGrammar.tokenizeLines('a { place-self: center center; }'); + assert.deepStrictEqual(tokens[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'place-self' }); + assert.deepStrictEqual(tokens[0][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][6], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'center' }); + assert.deepStrictEqual(tokens[0][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'center' }); + assert.deepStrictEqual(tokens[0][10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[0][11], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][12], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it("recognises place-content property as supported", function () { + var tokens; + tokens = testGrammar.tokenizeLines('a { place-content: center center; }'); + assert.deepStrictEqual(tokens[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'place-content' }); + assert.deepStrictEqual(tokens[0][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][6], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'center' }); + assert.deepStrictEqual(tokens[0][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][9], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'support.constant.property-value.css'], value: 'center' }); + assert.deepStrictEqual(tokens[0][10], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[0][11], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][12], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + + it("recognises row-gap property as supported", function () { + var tokens; + tokens = testGrammar.tokenizeLines('a { row-gap: 5px; }'); + assert.deepStrictEqual(tokens[0][0], { scopes: ['source.css', 'meta.selector.css', 'entity.name.tag.css'], value: 'a' }); + assert.deepStrictEqual(tokens[0][1], { scopes: ['source.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][2], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.begin.bracket.curly.css'], value: '{' }); + assert.deepStrictEqual(tokens[0][3], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][4], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-name.css', 'support.type.property-name.css'], value: 'row-gap' }); + assert.deepStrictEqual(tokens[0][5], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.separator.key-value.css'], value: ':' }); + assert.deepStrictEqual(tokens[0][6], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][7], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css'], value: '5' }); + assert.deepStrictEqual(tokens[0][8], { scopes: ['source.css', 'meta.property-list.css', 'meta.property-value.css', 'constant.numeric.css', 'keyword.other.unit.px.css'], value: 'px' }); + assert.deepStrictEqual(tokens[0][9], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.terminator.rule.css'], value: ';' }); + assert.deepStrictEqual(tokens[0][10], { scopes: ['source.css', 'meta.property-list.css'], value: ' ' }); + assert.deepStrictEqual(tokens[0][11], { scopes: ['source.css', 'meta.property-list.css', 'punctuation.section.property-list.end.bracket.curly.css'], value: '}' }); + }); + }); +}); diff --git a/src/testing-util/prepare-extension-debugging.mjs b/src/testing-util/prepare-extension-debugging.mjs new file mode 100644 index 0000000..1d33213 --- /dev/null +++ b/src/testing-util/prepare-extension-debugging.mjs @@ -0,0 +1,14 @@ +import fs from 'fs'; +import path from 'path'; +import cson from 'cson'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const file = await fs.promises.readFile(path.join(__dirname, '..', 'grammars', 'css.cson')); +await fs.promises.writeFile(path.join(__dirname, '..', 'grammars', 'css.tmLanguage.json'), cson.createJSONString(cson.parse(file.toString()))); + +if (!fs.existsSync(path.join(__dirname, '..', 'testing-util', 'example.css'))) { + await fs.promises.writeFile(path.join(__dirname, '..', 'testing-util', 'example.css'), `.foo {\n\tcolor: lime;\n}\n`); +} diff --git a/src/testing-util/test.mjs b/src/testing-util/test.mjs new file mode 100644 index 0000000..26abebf --- /dev/null +++ b/src/testing-util/test.mjs @@ -0,0 +1,69 @@ +// from https://github.com/microsoft/vscode-textmate/blob/677f741f5b5ef69589e0e5d2a6e556f94514cbfd/README.md +import fs from 'fs'; +import path from 'path'; +import vsctm from 'vscode-textmate'; +import oniguruma from 'vscode-oniguruma'; +import cson from 'cson'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const wasmBin = fs.readFileSync(path.join(__dirname, '..', 'node_modules', 'vscode-oniguruma', 'release', 'onig.wasm')).buffer; +const vscodeOnigurumaLib = oniguruma.loadWASM(wasmBin).then(() => { + return { + createOnigScanner(patterns) { return new oniguruma.OnigScanner(patterns); }, + createOnigString(s) { return new oniguruma.OnigString(s); } + }; +}); + +// Create a registry that can create a grammar from a scope name. +const registry = new vsctm.Registry({ + onigLib: vscodeOnigurumaLib, + loadGrammar: async (scopeName) => { + if (scopeName === 'source.css') { + const file = await fs.promises.readFile(path.join(__dirname, '..', 'grammars', 'css.cson')); + return vsctm.parseRawGrammar(cson.createJSONString(cson.parse(file.toString())), 'css.json'); + } + console.log(`Unknown scope name: ${scopeName}`); + return null; + } +}); + +const grammar = await registry.loadGrammar('source.css'); + +export default { + grammar: grammar, + tokenizeLine: function tokenizeLine(line) { + const lineTokens = grammar.tokenizeLine(line, vsctm.INITIAL); + + lineTokens.tokens.forEach((token) => { + token.value = line.slice(token.startIndex, token.endIndex); + delete token.startIndex; + delete token.endIndex; + }); + + return lineTokens; + }, + tokenizeLines: function tokenizeLines(text) { + const lines = text.split(/\r\n|\r|\n/g); + const tokenizedLines = []; + + let ruleStack = vsctm.INITIAL; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const lineTokens = grammar.tokenizeLine(line, ruleStack); + + lineTokens.tokens.forEach((token) => { + token.value = line.slice(token.startIndex, token.endIndex); + delete token.startIndex; + delete token.endIndex; + }); + + tokenizedLines.push(lineTokens.tokens); + ruleStack = lineTokens.ruleStack; + } + + return tokenizedLines; + } +} From ac1243d1531e9db4e38b723bbf15f1ecf8edaac9 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Fri, 6 Jun 2025 11:48:03 +0700 Subject: [PATCH 05/14] Add `\*.md` to list of files indented by spaces instead of tabs, in `.editorconfig` --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index cd6061a..e3fe4dc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,6 @@ trim_trailing_whitespace = true # The indent size used in the `package.json` file cannot be changed # https://github.com/npm/npm/pull/3180#issuecomment-16336516 -[{*.yml,*.yaml,*.cson,package.json}] +[{*.md,*.yml,*.yaml,*.cson,package.json}] indent_style = space indent_size = 2 From c157981ecadb90331df3b5777d4358ee283862c2 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Fri, 6 Jun 2025 12:07:03 +0700 Subject: [PATCH 06/14] Update official vscode-css source file structure --- src/vscode-css/.editorconfig | 15 + src/vscode-css/.nvmrc | 1 + src/vscode-css/CONTRIBUTING.md | 1 + src/vscode-css/LICENSE.txt | 33 ++ src/vscode-css/README.md | 10 + src/vscode-css/SECURITY.md | 41 +++ src/{ => vscode-css}/grammars/css.cson | 0 .../language-configuration.json | 0 src/vscode-css/package-lock.json | 323 ++++++++++++++++++ src/vscode-css/package.json | 39 +++ src/{ => vscode-css}/spec/css-spec.mjs | 0 .../prepare-extension-debugging.mjs | 0 src/{ => vscode-css}/testing-util/test.mjs | 0 13 files changed, 463 insertions(+) create mode 100644 src/vscode-css/.editorconfig create mode 100644 src/vscode-css/.nvmrc create mode 100644 src/vscode-css/CONTRIBUTING.md create mode 100644 src/vscode-css/LICENSE.txt create mode 100644 src/vscode-css/README.md create mode 100644 src/vscode-css/SECURITY.md rename src/{ => vscode-css}/grammars/css.cson (100%) rename src/{ => vscode-css}/language-configuration.json (100%) create mode 100644 src/vscode-css/package-lock.json create mode 100644 src/vscode-css/package.json rename src/{ => vscode-css}/spec/css-spec.mjs (100%) rename src/{ => vscode-css}/testing-util/prepare-extension-debugging.mjs (100%) rename src/{ => vscode-css}/testing-util/test.mjs (100%) diff --git a/src/vscode-css/.editorconfig b/src/vscode-css/.editorconfig new file mode 100644 index 0000000..cd6061a --- /dev/null +++ b/src/vscode-css/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Tab indentation +[*] +indent_style = tab +trim_trailing_whitespace = true + +# The indent size used in the `package.json` file cannot be changed +# https://github.com/npm/npm/pull/3180#issuecomment-16336516 +[{*.yml,*.yaml,*.cson,package.json}] +indent_style = space +indent_size = 2 diff --git a/src/vscode-css/.nvmrc b/src/vscode-css/.nvmrc new file mode 100644 index 0000000..2a393af --- /dev/null +++ b/src/vscode-css/.nvmrc @@ -0,0 +1 @@ +20.18.0 diff --git a/src/vscode-css/CONTRIBUTING.md b/src/vscode-css/CONTRIBUTING.md new file mode 100644 index 0000000..a8471f0 --- /dev/null +++ b/src/vscode-css/CONTRIBUTING.md @@ -0,0 +1 @@ +See the [VS Code contributing guide](https://github.com/microsoft/vscode/blob/main/CONTRIBUTING.md) diff --git a/src/vscode-css/LICENSE.txt b/src/vscode-css/LICENSE.txt new file mode 100644 index 0000000..c3fa4ed --- /dev/null +++ b/src/vscode-css/LICENSE.txt @@ -0,0 +1,33 @@ +MIT License + +Copyright (c) Microsoft Corporation. + +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. + +-------------------------------------------------------------------- + +This package was derived from a TextMate bundle located at +https://github.com/textmate/css.tmbundle and distributed under the following +license, located in `README.mdown`: + +Permission to copy, use, modify, sell and distribute this +software is granted. This software is provided "as is" without +express or implied warranty, and with no claim as to its +suitability for any purpose. diff --git a/src/vscode-css/README.md b/src/vscode-css/README.md new file mode 100644 index 0000000..54d9dd8 --- /dev/null +++ b/src/vscode-css/README.md @@ -0,0 +1,10 @@ +# CSS syntax highlighting in VS Code + +Adds syntax highlighting to CSS files in VS Code + +Derived from https://github.com/atom/language-css. +Originally [converted](http://flight-manual.atom.io/hacking-atom/sections/converting-from-textmate) +from the [CSS TextMate bundle](https://github.com/textmate/css.tmbundle). + +Contributions are greatly appreciated. Please fork this repository and open a +pull request to add snippets, make grammar tweaks, etc. diff --git a/src/vscode-css/SECURITY.md b/src/vscode-css/SECURITY.md new file mode 100644 index 0000000..9dc6316 --- /dev/null +++ b/src/vscode-css/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). + + diff --git a/src/grammars/css.cson b/src/vscode-css/grammars/css.cson similarity index 100% rename from src/grammars/css.cson rename to src/vscode-css/grammars/css.cson diff --git a/src/language-configuration.json b/src/vscode-css/language-configuration.json similarity index 100% rename from src/language-configuration.json rename to src/vscode-css/language-configuration.json diff --git a/src/vscode-css/package-lock.json b/src/vscode-css/package-lock.json new file mode 100644 index 0000000..fb1c16f --- /dev/null +++ b/src/vscode-css/package-lock.json @@ -0,0 +1,323 @@ +{ + "name": "vscode-css", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "vscode-css", + "version": "0.0.0", + "devDependencies": { + "cson": "^8.2.0", + "vscode-oniguruma": "^2.0.1", + "vscode-textmate": "^9.0.0" + }, + "engines": { + "vscode": "^1.96.0" + } + }, + "node_modules/cson": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cson/-/cson-8.2.0.tgz", + "integrity": "sha512-Hkf2J3i9VOGqlIYbjHaaHW9Vz9kJXebG1VrCivK76J1vr8uj4HG+z+UmJFxl8xVanpWfybUdk8l293dSDxwIWg==", + "dev": true, + "dependencies": { + "cson-parser": "^4.0.9", + "extract-opts": "^5.3.0", + "requirefresh": "^5.9.0", + "safefs": "^8.4.0" + }, + "bin": { + "cson": "bin.cjs", + "cson2json": "bin.cjs", + "json2cson": "bin.cjs" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/cson-parser": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-4.0.9.tgz", + "integrity": "sha512-I79SAcCYquWnEfXYj8hBqOOWKj6eH6zX1hhX3yqmS4K3bYp7jME3UFpHPzu3rUew0oyfc0s8T6IlWGXRAheHag==", + "dev": true, + "dependencies": { + "coffeescript": "1.12.7" + }, + "engines": { + "node": ">=10.13" + } + }, + "node_modules/cson-parser/node_modules/coffeescript": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", + "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==", + "dev": true, + "bin": { + "cake": "bin/cake", + "coffee": "bin/coffee" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eachr": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eachr/-/eachr-7.2.0.tgz", + "integrity": "sha512-zv0Vtm0c0mvTvamW3duiGqadnu4w9oIpG7p8YLb2z7bDj5wGRvzNJTLyMeWiuOvYXj9kT411ox0UAa8fziUIeg==", + "dev": true, + "dependencies": { + "editions": "^6.19.0", + "typechecker": "^9.2.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/editions": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.19.0.tgz", + "integrity": "sha512-mJabiGaqoKWjgz0kqrVPzLBsk4sh11zcNXKFXBZ3eLwwHBWylz5OcrBrZRRaLPWoRpm9m428CLzKcituabUuFg==", + "dev": true, + "dependencies": { + "version-range": "^4.11.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/extract-opts": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/extract-opts/-/extract-opts-5.5.0.tgz", + "integrity": "sha512-idbs8/KK4ZFB+Q5m5mCK8rMOOfwtEvfn/60q3zN8HzIlvn/kGdcqV+IbAqqJwtYPm1TvbbSJZxcyOYEXvn2zuA==", + "dev": true, + "dependencies": { + "eachr": "^7.2.0", + "editions": "^6.19.0", + "typechecker": "^9.2.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/requirefresh": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/requirefresh/-/requirefresh-5.11.0.tgz", + "integrity": "sha512-S1yRp4++ZZEAJv0ecPZotN62XXH38s/WDqoqWK3JZ3zqEmUb1RoqsYGCF+xN8DRGE3oecfdRF2AgF0nnuvUD3g==", + "dev": true, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/safefs": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/safefs/-/safefs-8.6.0.tgz", + "integrity": "sha512-CfelnCnGgCHWylnMTG68qYgFxu6mrnamaiCBQe6YDpnzrooLFfROMrkJBkQGlTU83GUcn+VBkYfZyEMt4ygPVA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.11", + "version-compare": "^3.8.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/typechecker": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/typechecker/-/typechecker-9.2.0.tgz", + "integrity": "sha512-WrIeSNqD6gW0Ss8OPHB0xHLyihoCsE57NZ5aLeYkLG75SOwP2uE9MtAnqKyca48X70vuJ3SNiSMRtop7Dnmg8A==", + "dev": true, + "dependencies": { + "editions": "^6.19.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/version-compare": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/version-compare/-/version-compare-3.8.0.tgz", + "integrity": "sha512-90rv+kdT8VyU45h6hOQeP9w8EuxHDdBOzHbHZZKrhw3zpaK3nJXgKDSCkV/OlUgvmt7vprCB1R9VuHrgjffpIg==", + "dev": true, + "dependencies": { + "editions": "^6.18.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/version-range": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.11.0.tgz", + "integrity": "sha512-h6ZOpIbN1Dk6hfDAmni/lOMncSUcIU1/6XsDc54kQe9/lMzzzv53ovpTr0CbNDJz2GLbmr/yyrQjD8vntyxXxg==", + "dev": true, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/vscode-oniguruma": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-2.0.1.tgz", + "integrity": "sha512-poJU8iHIWnC3vgphJnrLZyI3YdqRlR27xzqDmpPXYzA93R4Gk8z7T6oqDzDoHjoikA2aS82crdXFkjELCdJsjQ==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.0.0.tgz", + "integrity": "sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg==", + "dev": true + } + }, + "dependencies": { + "cson": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/cson/-/cson-8.2.0.tgz", + "integrity": "sha512-Hkf2J3i9VOGqlIYbjHaaHW9Vz9kJXebG1VrCivK76J1vr8uj4HG+z+UmJFxl8xVanpWfybUdk8l293dSDxwIWg==", + "dev": true, + "requires": { + "cson-parser": "^4.0.9", + "extract-opts": "^5.3.0", + "requirefresh": "^5.9.0", + "safefs": "^8.4.0" + } + }, + "cson-parser": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-4.0.9.tgz", + "integrity": "sha512-I79SAcCYquWnEfXYj8hBqOOWKj6eH6zX1hhX3yqmS4K3bYp7jME3UFpHPzu3rUew0oyfc0s8T6IlWGXRAheHag==", + "dev": true, + "requires": { + "coffeescript": "1.12.7" + }, + "dependencies": { + "coffeescript": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", + "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==", + "dev": true + } + } + }, + "eachr": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eachr/-/eachr-7.2.0.tgz", + "integrity": "sha512-zv0Vtm0c0mvTvamW3duiGqadnu4w9oIpG7p8YLb2z7bDj5wGRvzNJTLyMeWiuOvYXj9kT411ox0UAa8fziUIeg==", + "dev": true, + "requires": { + "editions": "^6.19.0", + "typechecker": "^9.2.0" + } + }, + "editions": { + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.19.0.tgz", + "integrity": "sha512-mJabiGaqoKWjgz0kqrVPzLBsk4sh11zcNXKFXBZ3eLwwHBWylz5OcrBrZRRaLPWoRpm9m428CLzKcituabUuFg==", + "dev": true, + "requires": { + "version-range": "^4.11.0" + } + }, + "extract-opts": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/extract-opts/-/extract-opts-5.5.0.tgz", + "integrity": "sha512-idbs8/KK4ZFB+Q5m5mCK8rMOOfwtEvfn/60q3zN8HzIlvn/kGdcqV+IbAqqJwtYPm1TvbbSJZxcyOYEXvn2zuA==", + "dev": true, + "requires": { + "eachr": "^7.2.0", + "editions": "^6.19.0", + "typechecker": "^9.2.0" + } + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "requirefresh": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/requirefresh/-/requirefresh-5.11.0.tgz", + "integrity": "sha512-S1yRp4++ZZEAJv0ecPZotN62XXH38s/WDqoqWK3JZ3zqEmUb1RoqsYGCF+xN8DRGE3oecfdRF2AgF0nnuvUD3g==", + "dev": true + }, + "safefs": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/safefs/-/safefs-8.6.0.tgz", + "integrity": "sha512-CfelnCnGgCHWylnMTG68qYgFxu6mrnamaiCBQe6YDpnzrooLFfROMrkJBkQGlTU83GUcn+VBkYfZyEMt4ygPVA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.11", + "version-compare": "^3.8.0" + } + }, + "typechecker": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/typechecker/-/typechecker-9.2.0.tgz", + "integrity": "sha512-WrIeSNqD6gW0Ss8OPHB0xHLyihoCsE57NZ5aLeYkLG75SOwP2uE9MtAnqKyca48X70vuJ3SNiSMRtop7Dnmg8A==", + "dev": true, + "requires": { + "editions": "^6.19.0" + } + }, + "version-compare": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/version-compare/-/version-compare-3.8.0.tgz", + "integrity": "sha512-90rv+kdT8VyU45h6hOQeP9w8EuxHDdBOzHbHZZKrhw3zpaK3nJXgKDSCkV/OlUgvmt7vprCB1R9VuHrgjffpIg==", + "dev": true, + "requires": { + "editions": "^6.18.0" + } + }, + "version-range": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.11.0.tgz", + "integrity": "sha512-h6ZOpIbN1Dk6hfDAmni/lOMncSUcIU1/6XsDc54kQe9/lMzzzv53ovpTr0CbNDJz2GLbmr/yyrQjD8vntyxXxg==", + "dev": true + }, + "vscode-oniguruma": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-2.0.1.tgz", + "integrity": "sha512-poJU8iHIWnC3vgphJnrLZyI3YdqRlR27xzqDmpPXYzA93R4Gk8z7T6oqDzDoHjoikA2aS82crdXFkjELCdJsjQ==", + "dev": true + }, + "vscode-textmate": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.0.0.tgz", + "integrity": "sha512-Cl65diFGxz7gpwbav10HqiY/eVYTO1sjQpmRmV991Bj7wAoOAjGQ97PpQcXorDE2Uc4hnGWLY17xme+5t6MlSg==", + "dev": true + } + } +} diff --git a/src/vscode-css/package.json b/src/vscode-css/package.json new file mode 100644 index 0000000..5e7acdc --- /dev/null +++ b/src/vscode-css/package.json @@ -0,0 +1,39 @@ +{ + "name": "vscode-css", + "description": "CSS support in VS Code", + "version": "0.0.0", + "scripts": { + "prepare-extension-debugging": "node ./testing-util/prepare-extension-debugging.mjs ", + "test": "node --test ./spec/css-spec.mjs" + }, + "devDependencies": { + "cson": "^8.2.0", + "vscode-oniguruma": "^2.0.1", + "vscode-textmate": "^9.0.0" + }, + "engines": { + "vscode": "^1.96.0" + }, + "contributes": { + "languages": [ + { + "id": "css", + "aliases": [ + "CSS", + "css" + ], + "extensions": [ + ".css" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "css", + "scopeName": "source.css", + "path": "./grammars/css.tmLanguage.json" + } + ] + } +} diff --git a/src/spec/css-spec.mjs b/src/vscode-css/spec/css-spec.mjs similarity index 100% rename from src/spec/css-spec.mjs rename to src/vscode-css/spec/css-spec.mjs diff --git a/src/testing-util/prepare-extension-debugging.mjs b/src/vscode-css/testing-util/prepare-extension-debugging.mjs similarity index 100% rename from src/testing-util/prepare-extension-debugging.mjs rename to src/vscode-css/testing-util/prepare-extension-debugging.mjs diff --git a/src/testing-util/test.mjs b/src/vscode-css/testing-util/test.mjs similarity index 100% rename from src/testing-util/test.mjs rename to src/vscode-css/testing-util/test.mjs From ff669070777bab12a908027adaf485aea88345be Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Fri, 6 Jun 2025 12:08:25 +0700 Subject: [PATCH 07/14] Reorganize package.json details --- package.json | 62 ++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 936c697..0b0863c 100644 --- a/package.json +++ b/package.json @@ -1,21 +1,32 @@ { "name": "css-nesting-syntax-highlighting", "displayName": "CSS Nesting Syntax Highlighting", - "description": "Updates VSCode's CSS TextMate rules to add CSS Nesting", "version": "0.4.1", - "icon": "images/css-nesting-syntax-highlighting-logo.png", "publisher": "jacobcassidy", - "bugs": { - "url": "https://github.com/jacobcassidy/vscode-css-nesting-syntax-highlighting/issues" + "description": "Updates VSCode's CSS TextMate rules to add CSS Nesting", + "author": { + "name": "Jacob Cassidy" }, - "repository": { - "type": "git", - "url": "https://github.com/jacobcassidy/vscode-css-nesting-syntax-highlighting" + "categories": [ + "Programming Languages" + ], + "contributes": { + "grammars": [ + { + "language": "css", + "scopeName": "source.css", + "path": "./syntaxes/css.tmLanguage.json" + } + ] + }, + "engines": { + "vscode": "^1.84.0" }, "galleryBanner": { "color": "#292929", "theme": "dark" }, + "icon": "images/css-nesting-syntax-highlighting-logo.png", "keywords": [ "CSS Nesting", "Nested CSS", @@ -25,34 +36,17 @@ "Highlighting", "Colorizer" ], - "engines": { - "vscode": "^1.84.0" + "devDependencies": { + "cson": "^8.4.0" }, - "categories": [ - "Programming Languages" - ], - "contributes": { - "grammars": [ - { - "language": "css", - "scopeName": "source.css", - "path": "./syntaxes/css.tmLanguage.json" - } - ] + "bugs": { + "url": "https://github.com/jacobcassidy/vscode-css-nesting-syntax-highlighting/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/jacobcassidy/vscode-css-nesting-syntax-highlighting" }, - "__metadata": { - "id": "dc07370c-e087-43f1-91fe-4f44363dc5d7", - "publisherId": "aee9e75c-ea26-4af1-b5e6-815e33288c4b", - "publisherDisplayName": "Jacob Cassidy", - "targetPlatform": "undefined", - "updated": false, - "isPreReleaseVersion": false, - "hasPreReleaseVersion": false, - "installedTimestamp": 1736203061589, - "pinned": false, - "size": 146700, - "isApplicationScoped": false, - "preRelease": false, - "source": "gallery" + "scripts": { + "build": "cson2json src/vscode-css/grammars/css.cson > syntaxes/css.tmLanguage.json" } } From 722907e02e010c7c70fec7e88468918b8e0e2672 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Fri, 6 Jun 2025 12:12:28 +0700 Subject: [PATCH 08/14] Add CSS syntax test demo files --- examples/tests/added/at-container-tests.css | 0 examples/tests/added/nested-tests.css | 0 examples/tests/original/original-tests.css | 349 +++++++++++ .../original-tests_replaced-newline.css | 561 ++++++++++++++++++ 4 files changed, 910 insertions(+) create mode 100644 examples/tests/added/at-container-tests.css create mode 100644 examples/tests/added/nested-tests.css create mode 100644 examples/tests/original/original-tests.css create mode 100644 examples/tests/original/original-tests_replaced-newline.css diff --git a/examples/tests/added/at-container-tests.css b/examples/tests/added/at-container-tests.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/tests/added/nested-tests.css b/examples/tests/added/nested-tests.css new file mode 100644 index 0000000..e69de29 diff --git a/examples/tests/original/original-tests.css b/examples/tests/original/original-tests.css new file mode 100644 index 0000000..e76ccd9 --- /dev/null +++ b/examples/tests/original/original-tests.css @@ -0,0 +1,349 @@ +/* SELECTORS */ + +/* SELECTORS > TYPE */ +p {gap:0} + +/* SELECTORS > UNIVERSAL SELECTOR */ +* {gap:0} + +/* SELECTORS > COMBINATOR */ +a > b + * ~ :not(.nah) {gap:0} + +/* SELECTORS > DEPRECATED COMBINATOR */ +.sooo /deep/ >>>_.>> {gap:0} + +/* SELECTORS > COMPLEX */ +[disabled], [disabled] + p {gap:0} +[disabled]:not(:first-child)::before:hover + ~ div.object + + #id.thing:hover > strong ~ p::before, + a::last-of-type,/*Comment*/::selection > html[lang^=en-AU], + *>em.i.ly[data-name|=\"Life\"] {gap:0} + +/* SELECTORS > CUSTOM ELEMENT */ +pearl-1941 1941-pearl -pearl-1941 {gap:0} +pokémon-ピカチュウ {gap:0} +Basecamp-schedule basecamp-Schedule {gap:0} +halo_night {gap:0} +@some-weird-new-feature {gap:0} +some-edgy-new-function() {gap:0} + +/* SELECTORS > ATTRIBUTE */ +[title] {gap:0} +[hreflang|=fr] {gap:0} +[href^="http://www.w3.org/"] {gap:0} +[*|title] {gap:0} +[marvel|origin=radiation] {gap:0} +[|data-hp="75"] {gap:0} +#div[id="0"] {gap:0} +.bar#div[id="0"] {gap:0} +.div[id="0"] {gap:0} +#bar.div[id] {gap:0} +span[ er|lang |= "%%" ] {gap:0} +a[name\\[0\\]="value"] {gap:0} +a[name\\ space|Get\\ It\\?="kek"] {gap:0} +span[/*]*/lang] {gap:0} +a[href^="#"] a[href^= "#"] a[href^="#" ] {gap:0} +a[href^='#'] a[href^= '#'] a[href^='#' ] {gap:0} +span[class~=Java] {gap:0} +span[class^= 0xDEADCAFE=|~BEEFBABE ] {gap:0} +a[name\\[0\\]=a\\BAD\\AF\\]a\\ i] {gap:0} +a[attr=val i] a[attr="val" i] a[attr=\'val\'I] a[val^= \'"\'i] a[attr= i] a[attr= i i] {gap:0} +span[\n \\x20{2}\n ns|lang/**/\n |=\n\"pt\"] {gap:0} +span[/*===\n==|span[/*}\n====*/*|lang/*]=*/~=/*\"|\"*/\"en-AU\"/*\n |\n*/\ni] {gap:0} + +/* SELECTORS > CLASS */ +.étendard {gap:0} +.スポンサー {gap:0} +.-- {gap:0} +._ {gap:0} +.\\33\\44-model {gap:0} +.la\\{tex\\} {gap:0} +.B!W{gap:0} +.666{gap:0} +.-911-{gap:0} +.-{gap:0} + +/* SELECTORS > ID */ +#unicorn {gap:0} +#洪荒之力 {gap:0} +#_zer0-day {gap:0} +#--d3bug-- {gap:0} +#sort!{gap:0} +#666{gap:0} +#-911-{gap:0} +#-{gap:0} +#\\33\\44-model {gap:0} +#la\\{tex\\} {gap:0} + +/* SELECTORS > NAMESPACE */ +foo|h1 {gap:0} +*|abbr {gap:0} +*|* {gap:0} +foo|* {gap:0} +|[svg|attr=name]{gap:0} +|h1 {gap:0} +*| {gap:0} +*|{gap:0} + + +/* AT RULES */ + +/* AT RULES > @CHARSET */ +@charset "US-ASCII"; +/* Not the first line */\n@charset "UTF-8"; + @charset 'US-ASCII'; +@charset "iso-8859-15"; +@charset"US-ASCII"; +@charset "UTF-8" ; +@charset "WTF-8" /* Nope */ ; +/* @charset "UTF-8 */ +@CHARSET 'US-ASCII'; + +/* AT RULES > @IMPORT */ +@import url("file.css"); +@import "file.css"; +@import 'file.css'; +@import /* url("name"); */ "1.css"; +@import/* Comment */"2.css"; +@import"file.css"; +@import-file.css; +@import\nurl("file.css"); +@import\nurl("file.css"); +@import url("1.css") print /* url(";"); */ all; +@import "astral.css" projection; +/* @import url(\'landscape.css\') screen and (orientation:landscape); */ + +/* AT RULES > @MEDIA */ +@media(max-width: 37.5em) { } +@media not print and (max-width: 37.5em){ } +@media (max-device-width: 2px){ } +@media (-webkit-foo: bar){ b{ } } +@media screen and (-ms-high-contrast:black-on-white){ } +@media (_moz-a:b){} +@media (-hp-foo:bar){} +@media (mso-page-size:wide){} +h1 { }@media only screen { } h2 { } +h1 { }@media only screen { }h2 { } +/* @media (min-width >= 0px)\n and (max-width <= 400)\n and (min-height > 400)\n and (max-height < 200) */ +@media/* */only/* */screen/* */and (min-width:1100px){} +@media/*=*/(max-width:/**/37.5em)/*=*/and/*=*/(/*=*/min-height/*:*/:/*=*/1.2em/*;*/){} +@media only screen and (min-width : /* 40 */\n 320px),\n not print and (max-width: 480px) /* kek */ and (-webkit-min-device-pixel-ratio /*:*/ : 2),\nonly speech and (min-width: 10em), /* wat */ (-webkit-min-device-pixel-ratio: 2) { } +@media , {} +@media (min-aspect-ratio: 3 / 4) and (max-aspect-ratio: 20 / 17) {} + +/* AT RULES > @KEYFRAMES */ +@keyframes important1 {\n from { margin-top: 50px;\n margin-bottom: 100px }\n 50% { margin-top: 150px !important; } /* Ignored */\n to { margin-top: 100px; }\n} +/* broke, so removed */ +@keyframes Give-them-both { fROm { } To {} } +@keyframes identifier { -50.2% } @keyframes ident2 { .25%} +@keyframes A\\1F602Z {} + +/* AT RULES > @SUPPORTS */ +@supports (font-size: 1em) { } +@supports not (font-size: 1em){ }\n@supports (font-size: 1em) and (font-size: 1em){ }\n@supports (font-size: 1em) or (font-size: 1em){ } +@supports (--foo: green){} +@supports not ((tab-size:4) or (-moz-tab-size:4)){\n body::before{content: \"Come on, Microsoft (Get it together already)…\"; }\n} +@supports (display:table-cell) or ((display:list-item) and (display:run-in)){} +@supports (animation-name: test) {\n #node {\n animation-name: test;\n }\n body > header[data-name=\"attr\"] ~ *:not(:first-child){\n content: \"😂👌\"\n }\n @keyframes important1 {\n from {\n margin-top: 50px;\n margin-bottom: 100px\n }\n 50% { margin-top: 150px !important; } /* Ignored */\n to { margin-top: 100px; }\n }\n} +@supports/*===*/not/*==****************|\n==*/(display:table-cell)/*============*/ and (display: list-item)/*}*/{} +/* @supports\n (box-shadow: 0 0 2px rgba(0,0,0,.5) inset) or\n (-moz-box-shadow: 0 0 2px black inset) or\n (-webkit-box-shadow: 0 0 2px black inset) or\n (-o-box-shadow: 0 0 2px black inset)\n{ .noticebox { } } */ + +/* AT RULES > @NAMESPACE */ +@namespace "XML"; +@namespace prefix "XML" ; +@namespace url("http://a.bc/"); +@namespace url url("http://a.bc/"); +@namespace/*=*/pre/*=*/"url"/*=*/; +@namespace"XML"; +@namespace pre\\ fix "http://url/"; +@namespace\nprefix\"XML\"; +@namespace\n\n prefix\n\nurl(\"http://a.bc/\"); + +/* AT RULES > @FONT-FEATURE */ +@font-feature-values Font name 2 { } +@font-feature-values\nFont name 2\n{} +@font-feature-values/*{*/Font/*}*/name/*{*/2{} +@swash{ swashy: 2; }\n@ornaments{ ident: 2; }\n@annotation{ ident: 1; }\n@stylistic{ stylish: 2; }\n@styleset{ sets: 2 3 4; }\n@character-variant{ charvar: 2 } +@sWASH{ swashy: 2; }\n@ornaMENts{ ident: 2; }\n@anNOTatION{ ident: 1; }\n@styLISTic{ stylish: 2; }\n@STYLEset{ sets: 2 3 4; }\n@CHARacter-VARiant{ charvar: 2 } +@font-feature-values Font name 2 {\n@swash{/*\n========*/swashy:/**/2;/**/}\n} +@swash{ s\\000077a\\73hy: 1; } + +/* AT RULES > @PAGE */ +@page :first { } +@page:right{} +@page {} +@page{} + +/* AT RULES > @COUNTER-STYLE */ +@counter-style winners-list {\n system: fixed;\n symbols: url(gold-medal.svg) url(silver-medal.svg) url(bronze-medal.svg);\n suffix: \" \";\n} +@counter-style/*{*/winners-list/*}*/{ system: fixed; } +@counter-style\nwinners-list {} +@counter-style A\\01F602z {} + +/* AT RULES > @DOCUMENT */ +/* broke, so removed */ + +/* AT RULES > @VIEWPORT */ +@viewport { min-width: 640px; max-width: 800px; } +@-O-VIEWPORT\n{\n zoom: 0.75;\n min-zoom: 0.5;\n max-zoom: 0.9;\n} +@-ms-viewport/*{*/{/*\n==*/orientation: landscape;\n} + +/* AT RULES > UNKNOWN */ +/* @foo;\n@foo ;\n@foo a;\n@foo ();\n@foo (a); */ +/* @foo bar;\n.foo */ + +/* CAPITALIZATION */ +/* @IMPoRT url(\"file.css\");\n@MEdIA (MAX-WIDTH: 2px){ }\n@pAgE :fIRST { }\n@NAMEspace \"A\";\n@foNT-FacE {} */ +a{ COLOR: #fff; }\na{ gRId-tEMPLaTe: none; }\na{ bACkgrOUND-iMAGE: none; }\na{ -MOZ-IMAGE: none; } +a{ color: INItIaL; }\na{ color: trAnsPAREnT; }\na{ color: rED; }\na{ color: unSET; }\na{ color: NONe; }\na{ style: lOWER-lATIN; }\na{ color: -WebkIT-foo; }\na{ font: HelVETica; } +DIV:HOVER { }\n#id::BefORE { }\n#id::aFTEr { }\nTABle:nTH-cHILD(2N+1) {}\nhtML:NOT(.htiml) {}\nI::BACKDROP\nI::-mOZ-thing {} +a{ color: RGBa(); }\na{ color: hslA(); }\na{ color: URL(); }\na{ content: ATTr(); }\na{ content: CoUNTer(); }\na{ content: cuBIC-beZIER()}\na{ content: sTePs()}\na{ content: cALc(2 + 2)} +a{width: 20EM; }\na{width: 20ReM; }\na{width: 8tURN; }\na{width: 20S; }\na{width: 20CM}\na{width: 2gRAd} + +/* PSEUDO-CLASSES */ +p:first-child {} +p{ left:left } +a:dir(ltr ){ }\n*:dir( rtl){ } +:DIR(/**\n==*/ltr/*\n*/) {} +:lang(zh-Hans-CN,es-419) {} +:lang(zh-*-CN) {} +/* :lang("zh-*-CN",\'*-ab-\') {} */ +*:not(.class-name):not(div) {} +*:not(/*(*/.class-name/*)*/):not(/*b*/) {} +:nth-child(2n+1)\n:nth-child(2n -1)\n:nth-child(-2n+ 1)\n:nth-child(-2n - 1)\n:nth-child(odd)\n:nth-child(even)\n:nth-child( odd )\n:nth-child( even ) {} +:nth-last-child(2n)\n:nth-last-child( -2n)\n:nth-last-child( 2n )\n:nth-last-child(even) {} +img:nth-of-type(+n+1)\nimg:nth-of-type(-n+1)\nimg:nth-of-type(n+1) {} +h1:nth-last-of-type(-1)\nh1:nth-last-of-type(+2)\nh1:nth-last-of-type(3) {} + +/* PSEUDO-ELEMENTS */ +.opening:first-letter {} +q::after {} +:-ms-input-placeholder {} +::-webkit-input-placeholder {} +::selection {} +:selection {} + +/* COMPOUND SELECTOR */ +very-custom.class +very-custom:hover +very-custom::shadow + +/* PROPERTY LISTS */ +div { font-size: inherit; } +div{color:inherit;float:left} +very-custom { color: inherit }\nanother-one { display : none ; } +:root { --white: #FFF; } +a{ text-shadow: a, b; } +.test{ width: 20em;;;;;;;;;\n;;;;;;;;;height: 10em; } +p{ color: #f030; } +a{ color: #CAFEBABE; } +/* a{ color: #CAFEBABEF; } */ +p { font-family: Verdana, Helvetica, sans-serif; } +ol.myth { list-style-type: cjk-earthly-branch } +div { font-size: 14px; } +div { font-size: test14px; } +div { font-size: test-14px; } +.edge { cursor: -webkit-zoom-in; } +.edge { width: -moz-min-content; } +.edge { display: -ms-grid; } +div { color: var(--primary-color) } +.a { a: 12em }\n.a { a: 4.01ex }\n.a { a: -456.8ch }\n.a { a: 0.0REM }\n.a { a: +0.0vh }\n.a { a: -0.0vw }\n.a { a: .6px }\n.a { a: 10e3mm }\n.a { a: 10E3cm }\n.a { a: -3.4e+2In }\n.a { a: -3.4e-2ch }\n.a { a: +.5E-2% }\n.a { a: -3.4e-2% } + +/* FUNCTIONAL NOTATION */ +a{content:aTTr(data-width px, inherit)} +a{content:ATTR(VAR(--name) px, "N/A")} +a{\n width: calc(3px + -1em);\n width: calc(3px - -1em);\n width: calc(3px * 2);\n width: calc(3px / 2);\n} +a{ width: calc(3px+1em); } +a{ width: calc(3px--1em); height: calc(10-1em);} +a{ width: calc(3px*2); } +a{ width: calc(3px/2); } +.foo { margin-top: calc(var(--gap) + 1px); } + +/* COLORS */ +a{ color: rgb(187,255,221); } +a{ color: RGBa( 100%, 0% ,20.17% ,.5 ); } +a{color:HSL(0, 00100%,50%)} +a{color:HSLa(2,.0%,1%,.7)} +a{ color: RGBA(var(--red), 0% , 20%, .2)} +a{ color: rgba(/**/255/*=*/,0,/*2.2%*/51/*,*/0.2)} +.frost{\n background-color: rgba(\n var(--red), /* Red */\n var(--green), /* Green */\n var(--blue), /* Blue */\n /* var(--test),\n /**/var(--opacity) /* Transparency */\n );\n} + +/* GRADIENTS */ +a{ background-image: linear-gradient( 45deg, blue, red ); } +a{ background-image: LINear-graDIEnt( ellipse to left top, blue, red); +a{ background-image: radial-gradient(farthest-corner at 45px 45px , #f00 0%, #00f 100%);} +a{ background-image: RADial-gradiENT(16px at 60px 50%,#000 0%, #000 14px, rgba(0,0,0,.3) 18px, transparent 19px)} +a{\n background-image: raDIAL-gradiENT(\n ellipse farthest-corner/*@*/at/*@*/470px 47px,/*===\n========*/#FFFF80 20%, rgba(204, 153, 153, 0.4) 30%,/*))))))))}*/#E6E6FF 60%); } +.grad {\n background-image: -webkit-linear-gradient(top, /* For Chrome 25 and Safari 6, iOS 6.1, Android 4.3 */ hsl(0, 80%, 70%), #bada55);\n background-image: -moz-linear-gradient(top, /* For Firefox (3.6 to 15) */ hsl(0, 80%, 70%), #bada55);\n background-image: -o-linear-gradient(top, /* For old Opera (11.1 to 12.0) */ hsl(0, 80%, 70%), #bada55);\n} +.grad {\n background-image: -webkit-gradient(linear, 0% 0%, 0% 100%,\n from( rgb(0, 171, 235)),\n color-stop(0.5, rgb(255, 255, 255)),\n color-stop(0.5, rgb(102, 204, 0)),\n to(rgb(255, 255, 255))),\n -webkit-gradient(radial, 45 45, 10, 52 50, 30, from(#A7D30C), to(rgba(1,159,98,0)), color-stop(90%, #019F62)),\n -webkit-gradient(radial, 105 105, 20, 112 120, 50, from(#ff5f98), to(rgba(255,1,136,0)), color-stop(75%, #ff0188)),\n -webkit-gradient(radial, 95 15, 15, 102 20, 40, from(#00c9ff), to(rgba(0,201,255,0)), color-stop(80%, #00b5e2)),\n -webkit-gradient(radial, 0 150, 50, 0 140, 90, from(#f4f201), to(rgba(228, 199,0,0)), color-stop(80%, #e4c700));\n} + +/* OTHER FUNCTIONS */ +a{\n shape-outside: circle(20em/*=*/at 50% 50%);\n shape-outside: inset(1em, 1em, 1em, 1em);\n} +.font{\n font-variant-alternates: stylistic(user-defined-ident);\n font-variant-alternates: styleset(user-defined-ident);\n font-variant-alternates: character-variant(user-defined-ident);\n font-variant-alternates: swash(user-defined-ident);\n font-variant-alternates: ornaments(user-defined-ident);\n font-variant-alternates: annotation(user-defined-ident);\n font-variant-alternates: swash(ident1) annotation(ident2);\n} +/* a{\n background-image: image-set( \"foo.png\" 1x,\n \"foo-2x.png\" 2x,\n \"foo-print.png\" 600dpi );\n} */ + +/* TIMING FUNCTIONS */ +a{ zoom: cubic-bezier(/**/1.2,/*=*/0,0,0/**/)} +a{ before: steps(0, start); after: steps(1, end); } + +/* VARIABLES */ +a{color: var(--name)} +a{color: var( --name )} +a{ color: var( /*=*/ --something ) } +.bar{ width: var(--page-width, /*;;;);*/ 2); } + +a{ p: attr (title); } +a{url:url (s)} +a{content:url ("http://github.com/");} +a{content: url (http://a.pl/)} +a{ color: rgb (187,255,221); } + +/* UNICODE RANGES */ +a{ a: U+A5 } +a{ unicode-range: u+0-7F } +a{ unicode-range: U+4?? } +a{ unicode-range: U+0025-00FF, U+4?? } + +/* ESCAPE SEQUENCES */ +very-custom { content: '\\c0ffee' } +very-custom { content: "\\c0ffee" } +\\61 \\{ { } \\} +a { \\77\\69\\64\\74\\68: 20px; } +a { content: \\1F764; } + +/* UNCLOSED STRINGS */ +/* a{ content: 'aaaa */ +/* a{ content: "aaaa */ +/* a{ content: \"aaaaa\\\\\\\naaa\"; color: red;\n} */ +/* a{\n content: 'aaaaa\\\\\\\naaa'; color: red;\n} */ +/* a{ content: "aaa\\"aa */ +/* a{ content: 'aaa\\'aa */ +a{\n content: \"aaa\\\"aa\\\naaaa\naaaa; color: red;\n} + +/* COMMENTS */ +@import /* url("name"); */ "1.css"; +@import/*";"*/ url("2.css"); +@import url("3.css") print /* url(";"); */; +@font-face/*"{;}"*/{} +/* comment */ @media {} +@media/* comment */ () {} +@media (max-height: 40em/* comment */) {} +section {border:4px/*padding:1px*/} +section {\n border:4px /*1px;\n padding:1px*/\n} + +/* ANIMATIONS */ +.animated {\n animation-name: orphan-black;\n animation-name: line-scale;\n} + +/* TRANSFORMS */ +.transformed {\n transform: matrix(0, 1.5, -1.5, 0, 0, 100px);\n transform: rotate(90deg) translateX(100px) scale(1.5);\n} + +/* PERFORMANCE REGRESSIONS */ +/* \n [}~" + ('ÁÂÃÄÅÆÇÈÊËÍÎ'.repeat(100)) + "\n */ + +/* MISSING SUPPORTED PROPERTIES REGRESSIONS */ +a { place-items: center center; } +a { place-self: center center; } +a { place-content: center center; } +a { row-gap: 5px; } diff --git a/examples/tests/original/original-tests_replaced-newline.css b/examples/tests/original/original-tests_replaced-newline.css new file mode 100644 index 0000000..e1c2231 --- /dev/null +++ b/examples/tests/original/original-tests_replaced-newline.css @@ -0,0 +1,561 @@ +/* SELECTORS */ + +/* SELECTORS > TYPE */ +p {gap:0} + +/* SELECTORS > UNIVERSAL SELECTOR */ +* {gap:0} + +/* SELECTORS > COMBINATOR */ +a > b + * ~ :not(.nah) {gap:0} + +/* SELECTORS > DEPRECATED COMBINATOR */ +/* .sooo /deep/ >>>_.>> {gap:0} */ + +/* SELECTORS > COMPLEX */ +[disabled], [disabled] + p {gap:0} +[disabled]:not(:first-child)::before:hover + ~ div.object + + #id.thing:hover > strong ~ p::before, + a::last-of-type,/*Comment*/::selection > html[lang^=en-AU], + *>em.i.ly[data-name|=\"Life\"] {gap:0} + +/* SELECTORS > CUSTOM ELEMENT */ +pearl-1941 1941-pearl -pearl-1941 {gap:0} +pokémon-ピカチュウ {gap:0} +Basecamp-schedule basecamp-Schedule {gap:0} +halo_night {gap:0} +@some-weird-new-feature {gap:0} +some-edgy-new-function() {gap:0} + +/* SELECTORS > ATTRIBUTE */ +[title] {gap:0} +[hreflang|=fr] {gap:0} +[href^="http://www.w3.org/"] {gap:0} +[*|title] {gap:0} +[marvel|origin=radiation] {gap:0} +[|data-hp="75"] {gap:0} +#div[id="0"] {gap:0} +.bar#div[id="0"] {gap:0} +.div[id="0"] {gap:0} +#bar.div[id] {gap:0} +span[ er|lang |= "%%" ] {gap:0} +a[name\\[0\\]="value"] {gap:0} +a[name\\ space|Get\\ It\\?="kek"] {gap:0} +span[/*]*/lang] {gap:0} +a[href^="#"] a[href^= "#"] a[href^="#" ] {gap:0} +a[href^='#'] a[href^= '#'] a[href^='#' ] {gap:0} +span[class~=Java] {gap:0} +span[class^= 0xDEADCAFE=|~BEEFBABE ] {gap:0} +a[name\\[0\\]=a\\BAD\\AF\\]a\\ i] {gap:0} +a[attr=val i] a[attr="val" i] a[attr=\'val\'I] a[val^= \'"\'i] a[attr= i] a[attr= i i] {gap:0} +span[ + \\x20{2} + ns|lang/**/ + |= +\"pt\"] {gap:0} +span[/*=== +==|span[/*} +====*/*|lang/*]=*/~=/*\"|\"*/\"en-AU\"/* + | +*/ +i] {gap:0} + +/* SELECTORS > CLASS */ +.étendard {gap:0} +.スポンサー {gap:0} +.-- {gap:0} +._ {gap:0} +.\\33\\44-model {gap:0} +.la\\{tex\\} {gap:0} +.B!W{gap:0} +.666{gap:0} +.-911-{gap:0} +.-{gap:0} + +/* SELECTORS > ID */ +#unicorn {gap:0} +#洪荒之力 {gap:0} +#_zer0-day {gap:0} +#--d3bug-- {gap:0} +#sort!{gap:0} +#666{gap:0} +#-911-{gap:0} +#-{gap:0} +#\\33\\44-model {gap:0} +#la\\{tex\\} {gap:0} + +/* SELECTORS > NAMESPACE */ +foo|h1 {gap:0} +*|abbr {gap:0} +*|* {gap:0} +foo|* {gap:0} +|[svg|attr=name]{gap:0} +|h1 {gap:0} +*| {gap:0} +*|{gap:0} + +/* AT RULES */ + +/* AT RULES > @CHARSET */ +@charset "US-ASCII"; +/* Not the first line */ +@charset "UTF-8"; + @charset 'US-ASCII'; +@charset "iso-8859-15"; +@charset"US-ASCII"; +@charset "UTF-8" ; +@charset "WTF-8" /* Nope */ ; +/* @charset "UTF-8 */ +@CHARSET 'US-ASCII'; + +/* AT RULES > @IMPORT */ +@import url("file.css"); +@import "file.css"; +@import 'file.css'; +@import /* url("name"); */ "1.css"; +@import/* Comment */"2.css"; +@import"file.css"; +@import-file.css; +@import +url("file.css"); +@import +url("file.css"); +@import url("1.css") print /* url(";"); */ all; +@import "astral.css" projection; +/* @import url(\'landscape.css\') screen and (orientation:landscape); */ + +/* AT RULES > @MEDIA */ +@media(max-width: 37.5em) { } +@media not print and (max-width: 37.5em){ } +@media (max-device-width: 2px){ } +@media (-webkit-foo: bar){ b{ } } +@media screen and (-ms-high-contrast:black-on-white){ } +@media (_moz-a:b){} +@media (-hp-foo:bar){} +@media (mso-page-size:wide){} +h1 { }@media only screen { } h2 { } +h1 { }@media only screen { }h2 { } +@media (min-width >= 0px) + and (max-width <= 400) + and (min-height > 400) + and (max-height < 200) {} +@media/* */only/* */screen/* */and (min-width:1100px){} +@media/*=*/(max-width:/**/37.5em)/*=*/and/*=*/(/*=*/min-height/*:*/:/*=*/1.2em/*;*/){} +@media only screen and (min-width : /* 40 */ + 320px), + not print and (max-width: 480px) /* kek */ and (-webkit-min-device-pixel-ratio /*:*/ : 2), +only speech and (min-width: 10em), /* wat */ (-webkit-min-device-pixel-ratio: 2) { } +@media , {} +@media (min-aspect-ratio: 3 / 4) and (max-aspect-ratio: 20 / 17) {} + +/* AT RULES > @KEYFRAMES */ +@keyframes important1 { + from { margin-top: 50px; + margin-bottom: 100px } + 50% { margin-top: 150px !important; } /* Ignored */ + to { margin-top: 100px; } +} +/* broke, so removed */ +@keyframes Give-them-both { fROm { } To {} } +@keyframes identifier { -50.2% } @keyframes ident2 { .25%} +@keyframes A\\1F602Z {} + +/* AT RULES > @SUPPORTS */ +@supports (font-size: 1em) { } +@supports not (font-size: 1em){ } +@supports (font-size: 1em) and (font-size: 1em){ } +@supports (font-size: 1em) or (font-size: 1em){ } +@supports (--foo: green){} +@supports not ((tab-size:4) or (-moz-tab-size:4)){ + body::before{content: \"Come on, Microsoft (Get it together already)…\"; } +} +@supports (display:table-cell) or ((display:list-item) and (display:run-in)){} +@supports (animation-name: test) { + #node { + animation-name: test; + } + body > header[data-name=\"attr\"] ~ *:not(:first-child){ + content: \"😂👌\" + } + @keyframes important1 { + from { + margin-top: 50px; + margin-bottom: 100px + } + 50% { margin-top: 150px !important; } /* Ignored */ + to { margin-top: 100px; } + } +} +@supports/*===*/not/*==****************| +==*/(display:table-cell)/*============*/ and (display: list-item)/*}*/{} +/* @supports + (box-shadow: 0 0 2px rgba(0,0,0,.5) inset) or + (-moz-box-shadow: 0 0 2px black inset) or + (-webkit-box-shadow: 0 0 2px black inset) or + (-o-box-shadow: 0 0 2px black inset) +{ .noticebox { } } */ + +/* AT RULES > @NAMESPACE */ +@namespace "XML"; +@namespace prefix "XML" ; +@namespace url("http://a.bc/"); +@namespace url url("http://a.bc/"); +@namespace/*=*/pre/*=*/"url"/*=*/; +@namespace"XML"; +@namespace pre\\ fix "http://url/"; +@namespace +prefix\"XML\"; +/* @namespace + + prefix + +url(\"http://a.bc/\"); */ + +/* AT RULES > @FONT-FEATURE */ +@font-feature-values Font name 2 { } +@font-feature-values +Font name 2 +{} +@font-feature-values/*{*/Font/*}*/name/*{*/2{} +@swash{ swashy: 2; } +@ornaments{ ident: 2; } +@annotation{ ident: 1; } +@stylistic{ stylish: 2; } +@styleset{ sets: 2 3 4; } +@character-variant{ charvar: 2 } +@sWASH{ swashy: 2; } +@ornaMENts{ ident: 2; } +@anNOTatION{ ident: 1; } +@styLISTic{ stylish: 2; } +@STYLEset{ sets: 2 3 4; } +@CHARacter-VARiant{ charvar: 2 } +@font-feature-values Font name 2 { +@swash{/* +========*/swashy:/**/2;/**/} +} +@swash{ s\\000077a\\73hy: 1; } + +/* AT RULES > @PAGE */ +@page :first { } +@page:right{} +@page {} +@page{} + +/* AT RULES > @COUNTER-STYLE */ +@counter-style winners-list { + system: fixed; + symbols: url(gold-medal.svg) url(silver-medal.svg) url(bronze-medal.svg); + suffix: \" \"; +} +@counter-style/*{*/winners-list/*}*/{ system: fixed; } +@counter-style +winners-list {} +@counter-style A\\01F602z {} + +/* AT RULES > @DOCUMENT */ +/* broke, so removed */ + +/* AT RULES > @VIEWPORT */ +@viewport { min-width: 640px; max-width: 800px; } +@-O-VIEWPORT +{ + zoom: 0.75; + min-zoom: 0.5; + max-zoom: 0.9; +} +@-ms-viewport/*{*/{/* +==*/orientation: landscape; +} + +/* AT RULES > UNKNOWN */ +/* @foo; +@foo ; +@foo a; +@foo (); +@foo (a); */ +/* @foo bar; +.foo */ + +/* CAPITALIZATION */ +/* @IMPoRT url(\"file.css\"); +@MEdIA (MAX-WIDTH: 2px){ } +@pAgE :fIRST { } +@NAMEspace \"A\"; +@foNT-FacE {} */ +a{ COLOR: #fff; } +a{ gRId-tEMPLaTe: none; } +a{ bACkgrOUND-iMAGE: none; } +a{ -MOZ-IMAGE: none; } +a{ color: INItIaL; } +a{ color: trAnsPAREnT; } +a{ color: rED; } +a{ color: unSET; } +a{ color: NONe; } +a{ style: lOWER-lATIN; } +a{ color: -WebkIT-foo; } +a{ font: HelVETica; } +DIV:HOVER { } +#id::BefORE { } +#id::aFTEr { } +TABle:nTH-cHILD(2N+1) {} +htML:NOT(.htiml) {} +I::BACKDROP +I::-mOZ-thing {} +a{ color: RGBa(); } +a{ color: hslA(); } +a{ color: URL(); } +a{ content: ATTr(); } +a{ content: CoUNTer(); } +a{ content: cuBIC-beZIER()} +a{ content: sTePs()} +a{ content: cALc(2 + 2)} +a{width: 20EM; } +a{width: 20ReM; } +a{width: 8tURN; } +a{width: 20S; } +a{width: 20CM} +a{width: 2gRAd} + +/* PSEUDO-CLASSES */ +p:first-child {} +p{ left:left } +a:dir(ltr ){gap:0} +*:dir( rtl){gap:0} +:DIR(/** +==*/ltr/* +*/) {gap:0} +:lang(zh-Hans-CN,es-419) {gap:0} +:lang(zh-*-CN) {gap:0} +/* :lang("zh-*-CN",\'*-ab-\') {} */ +*:not(.class-name):not(div) {gap:0} +*:not(/*(*/.class-name/*)*/):not(/*b*/) {gap:0} +:nth-child(2n+1) +:nth-child(2n -1) +:nth-child(-2n+ 1) +:nth-child(-2n - 1) +:nth-child(odd) +:nth-child(even) +:nth-child( odd ) +:nth-child( even ) {gap:0} +:nth-last-child(2n) {gap:0} +:nth-last-child( -2n) {gap:0} +:nth-last-child( 2n ) {gap:0} +:nth-last-child(even) {gap:0} +img:nth-of-type(+n+1) {gap:0} +img:nth-of-type(-n+1) {gap:0} +img:nth-of-type(n+1) {gap:0} +h1:nth-last-of-type(-1) {gap:0} +h1:nth-last-of-type(+2) {gap:0} +h1:nth-last-of-type(3) {gap:0} + +/* PSEUDO-ELEMENTS */ +.opening:first-letter {gap:0} +q::after {gap:0} +:-ms-input-placeholder {gap:0} +::-webkit-input-placeholder {gap:0} +::selection {gap:0} +:selection {gap:0} + +/* COMPOUND SELECTOR */ +very-custom.class +very-custom:hover +very-custom::shadow + +/* PROPERTY LISTS */ +div { font-size: inherit; } +div{color:inherit;float:left} +very-custom { color: inherit } +another-one { display : none ; } +:root { --white: #FFF; } +a{ text-shadow: a, b; } +.test{ width: 20em;;;;;;;;; +;;;;;;;;;height: 10em; } +p{ color: #f030; } +a{ color: #CAFEBABE; } +/* a{ color: #CAFEBABEF; } */ +p { font-family: Verdana, Helvetica, sans-serif; } +ol.myth { list-style-type: cjk-earthly-branch } +div { font-size: 14px; } +div { font-size: test14px; } +div { font-size: test-14px; } +.edge { cursor: -webkit-zoom-in; } +.edge { width: -moz-min-content; } +.edge { display: -ms-grid; } +div { color: var(--primary-color) } +.a { a: 12em } +.a { a: 4.01ex } +.a { a: -456.8ch } +.a { a: 0.0REM } +.a { a: +0.0vh } +.a { a: -0.0vw } +.a { a: .6px } +.a { a: 10e3mm } +.a { a: 10E3cm } +.a { a: -3.4e+2In } +.a { a: -3.4e-2ch } +.a { a: +.5E-2% } +.a { a: -3.4e-2% } + +/* FUNCTIONAL NOTATION */ +a{content:aTTr(data-width px, inherit)} +a{content:ATTR(VAR(--name) px, "N/A")} +a{ + width: calc(3px + -1em); + width: calc(3px - -1em); + width: calc(3px * 2); + width: calc(3px / 2); +} +a{ width: calc(3px+1em); } +a{ width: calc(3px--1em); height: calc(10-1em);} +a{ width: calc(3px*2); } +a{ width: calc(3px/2); } +.foo { margin-top: calc(var(--gap) + 1px); } + +/* COLORS */ +a{ color: rgb(187,255,221); } +a{ color: RGBa( 100%, 0% ,20.17% ,.5 ); } +a{color:HSL(0, 00100%,50%)} +a{color:HSLa(2,.0%,1%,.7)} +a{ color: RGBA(var(--red), 0% , 20%, .2)} +a{ color: rgba(/**/255/*=*/,0,/*2.2%*/51/*,*/0.2)} +.frost{ + background-color: rgba( + var(--red), /* Red */ + var(--green), /* Green */ + var(--blue), /* Blue */ + /* var(--test), + /**/var(--opacity) /* Transparency */ + ); +} + +/* GRADIENTS */ +a{ background-image: linear-gradient( 45deg, blue, red ); } +a{ background-image: LINear-graDIEnt( ellipse to left top, blue, red); +a{ background-image: radial-gradient(farthest-corner at 45px 45px , #f00 0%, #00f 100%);} +a{ background-image: RADial-gradiENT(16px at 60px 50%,#000 0%, #000 14px, rgba(0,0,0,.3) 18px, transparent 19px)} +a{ + background-image: raDIAL-gradiENT( + ellipse farthest-corner/*@*/at/*@*/470px 47px,/*=== +========*/#FFFF80 20%, rgba(204, 153, 153, 0.4) 30%,/*))))))))}*/#E6E6FF 60%); } +.grad { + background-image: -webkit-linear-gradient(top, /* For Chrome 25 and Safari 6, iOS 6.1, Android 4.3 */ hsl(0, 80%, 70%), #bada55); + background-image: -moz-linear-gradient(top, /* For Firefox (3.6 to 15) */ hsl(0, 80%, 70%), #bada55); + background-image: -o-linear-gradient(top, /* For old Opera (11.1 to 12.0) */ hsl(0, 80%, 70%), #bada55); +} +.grad { + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, + from( rgb(0, 171, 235)), + color-stop(0.5, rgb(255, 255, 255)), + color-stop(0.5, rgb(102, 204, 0)), + to(rgb(255, 255, 255))), + -webkit-gradient(radial, 45 45, 10, 52 50, 30, from(#A7D30C), to(rgba(1,159,98,0)), color-stop(90%, #019F62)), + -webkit-gradient(radial, 105 105, 20, 112 120, 50, from(#ff5f98), to(rgba(255,1,136,0)), color-stop(75%, #ff0188)), + -webkit-gradient(radial, 95 15, 15, 102 20, 40, from(#00c9ff), to(rgba(0,201,255,0)), color-stop(80%, #00b5e2)), + -webkit-gradient(radial, 0 150, 50, 0 140, 90, from(#f4f201), to(rgba(228, 199,0,0)), color-stop(80%, #e4c700)); +} + +/* OTHER FUNCTIONS */ +a{ + shape-outside: circle(20em/*=*/at 50% 50%); + shape-outside: inset(1em, 1em, 1em, 1em); +} +.font{ + font-variant-alternates: stylistic(user-defined-ident); + font-variant-alternates: styleset(user-defined-ident); + font-variant-alternates: character-variant(user-defined-ident); + font-variant-alternates: swash(user-defined-ident); + font-variant-alternates: ornaments(user-defined-ident); + font-variant-alternates: annotation(user-defined-ident); + font-variant-alternates: swash(ident1) annotation(ident2); +} +/* a{ + background-image: image-set( \"foo.png\" 1x, + \"foo-2x.png\" 2x, + \"foo-print.png\" 600dpi ); +} */ + +/* TIMING FUNCTIONS */ +a{ zoom: cubic-bezier(/**/1.2,/*=*/0,0,0/**/)} +a{ before: steps(0, start); after: steps(1, end); } + +/* VARIABLES */ +a{color: var(--name)} +a{color: var( --name )} +a{ color: var( /*=*/ --something ) } +.bar{ width: var(--page-width, /*;;;);*/ 2); } + +a{ p: attr (title); } +a{url:url (s)} +a{content:url ("http://github.com/");} +a{content: url (http://a.pl/)} +a{ color: rgb (187,255,221); } + +/* UNICODE RANGES */ +a{ a: U+A5 } +a{ unicode-range: u+0-7F } +a{ unicode-range: U+4?? } +a{ unicode-range: U+0025-00FF, U+4?? } + +/* ESCAPE SEQUENCES */ +very-custom { content: '\\c0ffee' } +very-custom { content: "\\c0ffee" } +\\61 \\{ { } \\} +a { \\77\\69\\64\\74\\68: 20px; } +a { content: \\1F764; } + +/* UNCLOSED STRINGS */ +/* a{ content: 'aaaa */ +/* a{ content: "aaaa */ +/* a{ content: \"aaaaa\\\\\\ +aaa\"; color: red; +} */ +/* a{ + content: 'aaaaa\\\\\\ +aaa'; color: red; +} */ +/* a{ content: "aaa\\"aa */ +/* a{ content: 'aaa\\'aa */ +a{ + content: \"aaa\\\"aa\\ +aaaa +aaaa; color: red; +} + +/* COMMENTS */ +@import /* url("name"); */ "1.css"; +@import/*";"*/ url("2.css"); +@import url("3.css") print /* url(";"); */; +@font-face/*"{;}"*/ +/* comment */ @media {} +@media/* comment */ () {} +@media (max-height: 40em/* comment */) {} +section {border:4px/*padding:1px*/} +section { + border:4px /*1px; + padding:1px*/ +} + +/* ANIMATIONS */ +.animated { + animation-name: orphan-black; + animation-name: line-scale; +} + +/* TRANSFORMS */ +.transformed { + transform: matrix(0, 1.5, -1.5, 0, 0, 100px); + transform: rotate(90deg) translateX(100px) scale(1.5); +} + +/* PERFORMANCE REGRESSIONS */ +/* + [}~" + ('ÁÂÃÄÅÆÇÈÊËÍÎ'.repeat(100)) + " + */ + +/* MISSING SUPPORTED PROPERTIES REGRESSIONS */ +a { place-items: center center; } +a { place-self: center center; } +a { place-content: center center; } +a { row-gap: 5px; } From 73e6127e07f852518e92e84fdc8ee6378e3ae0a3 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Fri, 6 Jun 2025 12:21:00 +0700 Subject: [PATCH 09/14] Add "issues" CSS files --- examples/issues/bugs/aspect-ratio-issue.css | 3 + examples/issues/bugs/at-page-issue.css | 14 ++ examples/issues/bugs/at-supports-issue.css | 28 ++++ .../issues/bugs/calc-in-at-media-issue.css | 34 +++++ examples/issues/bugs/calc-nesting-issue.css | 37 +++++ .../bugs/clamp-arithmetic-operators-issue.css | 19 +++ .../bugs/property-value-slash-issue.css | 12 ++ examples/issues/bugs/scss-issues.scss | 67 +++++++++ .../issues/missing/at-container-issue.css | 141 ++++++++++++++++++ examples/issues/missing/at-import-issue.css | 11 ++ examples/issues/missing/at-layer-issue.css | 15 ++ .../missing/at-media-properties-issue.css | 15 ++ .../issues/missing/light-dark-func-issue.css | 3 + examples/issues/missing/of-not-issue.css | 23 +++ 14 files changed, 422 insertions(+) create mode 100644 examples/issues/bugs/aspect-ratio-issue.css create mode 100644 examples/issues/bugs/at-page-issue.css create mode 100644 examples/issues/bugs/at-supports-issue.css create mode 100644 examples/issues/bugs/calc-in-at-media-issue.css create mode 100644 examples/issues/bugs/calc-nesting-issue.css create mode 100644 examples/issues/bugs/clamp-arithmetic-operators-issue.css create mode 100644 examples/issues/bugs/property-value-slash-issue.css create mode 100644 examples/issues/bugs/scss-issues.scss create mode 100644 examples/issues/missing/at-container-issue.css create mode 100644 examples/issues/missing/at-import-issue.css create mode 100644 examples/issues/missing/at-layer-issue.css create mode 100644 examples/issues/missing/at-media-properties-issue.css create mode 100644 examples/issues/missing/light-dark-func-issue.css create mode 100644 examples/issues/missing/of-not-issue.css diff --git a/examples/issues/bugs/aspect-ratio-issue.css b/examples/issues/bugs/aspect-ratio-issue.css new file mode 100644 index 0000000..645a57a --- /dev/null +++ b/examples/issues/bugs/aspect-ratio-issue.css @@ -0,0 +1,3 @@ +.class { + aspect-ratio: 1; +} diff --git a/examples/issues/bugs/at-page-issue.css b/examples/issues/bugs/at-page-issue.css new file mode 100644 index 0000000..13d9507 --- /dev/null +++ b/examples/issues/bugs/at-page-issue.css @@ -0,0 +1,14 @@ +/** + * AT PAGE + */ + +@page { + content: "Page " counter(pageNumber); +} + +@page { + /* margin box at top right showing page number */ + @top-right { + content: "Page " counter(pageNumber); + } +} diff --git a/examples/issues/bugs/at-supports-issue.css b/examples/issues/bugs/at-supports-issue.css new file mode 100644 index 0000000..513b661 --- /dev/null +++ b/examples/issues/bugs/at-supports-issue.css @@ -0,0 +1,28 @@ +/* Correctly nested */ +:root { + --supports-red: false; + @supports (color: red) { + --supports-red: true; + width: 100%; + row-gap: 1rem; + section { + display: flex; + row-gap: 1rem; + } + } + --x: 1; +} + +@supports (color: red) { + section { + display: flex; + row-gap: 1rem; + } +} + +/* Invalid first-level properties not nested within a selector should not have syntax highlighting */ +@supports (color: red) { + --supports-red: true; + width: 100%; + row-gap: 1rem; +} diff --git a/examples/issues/bugs/calc-in-at-media-issue.css b/examples/issues/bugs/calc-in-at-media-issue.css new file mode 100644 index 0000000..433a6dd --- /dev/null +++ b/examples/issues/bugs/calc-in-at-media-issue.css @@ -0,0 +1,34 @@ +/** + * CALC FUNCTION IN @MEDIA + */ + +:root { + --size: 500px; +} + +.example { + max-width: calc(var(--size) * 1rem); +} + +@media screen and (min-width: calc( var(--size) * 1rem)) { + .example { + max-width: calc(var(--size) * 1rem); + } +} + +/* With CSS nesting */ +@media only screen and (max-width: calc( var(--size) * 1rem)) { + .example { + span { + max-width: calc(var(--size) * 1rem); + } + } +} + +.example { + @media only screen and (max-width: calc( var(--size) * 1rem)) { + span { + max-width: calc(var(--size) * 1rem); + } + } +} diff --git a/examples/issues/bugs/calc-nesting-issue.css b/examples/issues/bugs/calc-nesting-issue.css new file mode 100644 index 0000000..9787717 --- /dev/null +++ b/examples/issues/bugs/calc-nesting-issue.css @@ -0,0 +1,37 @@ +/** + * NESTED CALC FUNCTIONS + */ + +.example { + margin-top: calc(600 / 16 * 1rem); + margin-bottom: calc( (768 / 16) * 1rem); + margin-right: calc( (768 / 16 * (768 / 16) ) * 1rem); + margin-left: calc( (768 / 16 * (768 / 16 + (768 / 16) ) ) * 1rem); + padding-top: calc( (768 / 16 * (768 / 16 + (768 / 16 - (768 / 16) ) ) ) * 1rem); +} + +/* Without CSS nesting */ +@media screen and (max-width: calc( (1280 / 16) * 1rem)) { + .example { + max-width: calc((768 / 16) * 1rem); + } +} + +/* With CSS nesting */ +@media only screen and (max-width: calc( (768 / 16) * 1rem )) { + .example { + max-width: calc((768 / 16) * 1rem); + span { + max-width: calc((600 / 16) * 1rem); + } + } +} + +.example { + @media only screen and (max-width: calc( var(--size) * 1rem)) { + max-width: calc((768 / 16) * 1rem); + span { + max-width: calc((600 / 16) * 1rem); + } + } +} diff --git a/examples/issues/bugs/clamp-arithmetic-operators-issue.css b/examples/issues/bugs/clamp-arithmetic-operators-issue.css new file mode 100644 index 0000000..08036ad --- /dev/null +++ b/examples/issues/bugs/clamp-arithmetic-operators-issue.css @@ -0,0 +1,19 @@ +/** + * FUNCTION ARITHMETIC OPERATORS + */ + +.example { + margin-block-start: clamp(15px, 0.805rem + 0.64vw, 18px); + margin-block-end: clamp(15px, 0.805rem - 0.64vw, 18px); +} + +.other-examples { + width: clamp(100px, calc(30% / 2rem + 10px), 900px); + width: min(1000px, calc(70% + 100px)); + font-size: clamp(1.1rem, 0.7153rem + 1.6368vw, 1.5rem); + width: max(300px, 50% + 20px); + width: min(300px, 50% + 20px); + height: calc(var(--base-height) + 10%); + font-size: clamp(15px, 0.805rem + 0.64vw, 18px); + width: calc(100% - 20px); +} diff --git a/examples/issues/bugs/property-value-slash-issue.css b/examples/issues/bugs/property-value-slash-issue.css new file mode 100644 index 0000000..12e5bfb --- /dev/null +++ b/examples/issues/bugs/property-value-slash-issue.css @@ -0,0 +1,12 @@ +/** + * GRID PLACEMENT SLASH + */ + +/* Property value slash doesn't have its own syntax highlighting */ +.example { + grid-column: 1/-1; + grid-row: 1 / 3; + + width: clamp(100px, calc(30% / 2rem - 10px), 900px); + width: some-edgy-new-function(30% / 2rem + 10px); +} diff --git a/examples/issues/bugs/scss-issues.scss b/examples/issues/bugs/scss-issues.scss new file mode 100644 index 0000000..a9249a8 --- /dev/null +++ b/examples/issues/bugs/scss-issues.scss @@ -0,0 +1,67 @@ +#footer { + p { + color: var(--color-re); + margin-block-end: 16px; + } +} + +.re-wrap #footer p { + color: var(--color-mc-100); +} + +.footer-blurb { + max-inline-size: 584px; +} + +.footer-copyright { + margin-block-end: 0; + font-size: 16px; +} + +.footer-license { + font-size: 16px; +} + +.footer-notice { + display: flex; + flex-direction: column; + + a { + color: var(--color-mc-100); + margin-block-end: 8px; + font-size: 16px; + text-decoration: none; + } +} + +.connect-btn-wrap { + display: flex; + gap: 16px; +} + +.connect-btn { + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--color-re); + border-radius: var(--border-radius-box); + block-size: 3.125rem; + inline-size: 3.125rem; + margin-block-end: 16px; + text-decoration: none; + + .logo { + opacity: 0.8; + transition: opacity 300ms; + } + + &:hover { + .logo { + opacity: 1; + } + } + + .re-wrap & { + border: 1px solid var(--color-mc-100); + } +} diff --git a/examples/issues/missing/at-container-issue.css b/examples/issues/missing/at-container-issue.css new file mode 100644 index 0000000..540ad01 --- /dev/null +++ b/examples/issues/missing/at-container-issue.css @@ -0,0 +1,141 @@ +/** + * AT CONTAINER + */ + +/* AND */ +@container (width > 400px) and (height > 400px) { +} +@container (min-width: 600px) and (orientation: landscape) { +} + +/* NOT */ +@container not (width < 400px) { +} +@container not (width < 400px) not (width > 800px) { +} +@container sidebar not (orientation: landscape) and (min-width: 600px) { +} +@container sidebar not (orientation: landscape) and (min-width: 600px) or (min-width: 600px) { +} + +/* OR */ +@container (width > 400px) or (height > 400px) {'' +} +@container (display: grid) or (display: flex) { +} + +/* CONTAINER-NAME */ +@container name (width > 400px) and (height > 400px) { +} +@container name1 name2 name3 not (width > 400px) not (height > 400px) { +} + +@container default (width > 400px) { +} +@container none default (width > 400px) { +} + +/* CONTAINER-QUERY */ +@container (min-aspect-ratio: 3 / 2) { + .video-player { + border: 5px solid #fff; + } +} + +@container (min-width: calc( var(--width) * 1rem)) { + .card { + margin: 0; + } +} + +/* STYLE-QUERY */ +@container style( --accent-color : currentColor ) { +} + +@container style(small --cards: /*comment*/ small 333px) { +} +@container card (inline-size > 30em) and style(--responsive: true) { +} +@container style( --width : calc( var(--hello) ) ) { +} + +@container style(--themeBackground), not style(--themeColor: blue), style(--themeColor: blue) and style(--themeColor: purple), style(--themeColor: blue) or style(--themeColor: purple) { +} +@container card (inline-size > 30em) and style(--responsive: true) { +} +@container style(--cards: 333px) { + article { + border: thin solid silver; + border-radius: 0.5em; + padding: 1em; + } +} + +/* NESTED @CONTAINER */ +@container summary (width > 400px) { + @container (min-width: 800px) { + .card { + margin: 50px; + } + @container (min-width: calc( var(--width) * 1rem)) { + .card { + margin: 0; + } + } + } +} +@container card (inline-size > 30em) { + @container style(--responsive: true) { + /* styles */ + } +} + +/* EVERYTHING */ +@container /*comment*/ tall /*comment*/ short not invalidName (width > 400px) and invalidName not (min-height: 400px /*comment*/ invalidName) /*comment*/ or style(--accent-color: currentColor) { + .card { + margin: 0; + .example { + margin: 0; + } + } +} + +@container name1 name2 not (max-width: calc( (768 / 16) * 1rem)) invalidName { + invalidName .wp-block-buttons { + margin: calc(var(--wp--custom--element-spacing) * 3) auto; + .wp-block-button, + .wp-element-button { + width: 100%; + } + } +} + +@container (10em <= width <= 20em) { + /* styles */ +} + +@container (inline-size >= 0px) { + /* only applies when an inline-size container is available */ + h2 { + font-size: calc(1.2em + 1cqi); + } +} + +@container (width > 40em) { + h2 { + font-size: 1.5em; + } +} + +@container my-page-layout (block-size > 12em) { + .card { + margin-block: 2em; + } +} + +@container (inline-size >= 0px) { + /* only applies when an inline-size container is available */ + h2 { + font-size: calc(1.2em + 1cqi); + } +} diff --git a/examples/issues/missing/at-import-issue.css b/examples/issues/missing/at-import-issue.css new file mode 100644 index 0000000..9bda8da --- /dev/null +++ b/examples/issues/missing/at-import-issue.css @@ -0,0 +1,11 @@ +/** + * AT IMPORT + */ + +/* new syntax rules for @import have no syntax highlighting */ +@import "/css/styles.css" supports(color: red); +@import url("green.css") layer supports(selector(&)) (min-width: calc(10px)); + +/* layer as a function */ +@import url("green.css") layer(bar) supports(selector(&)) + (min-width: calc(10px)); diff --git a/examples/issues/missing/at-layer-issue.css b/examples/issues/missing/at-layer-issue.css new file mode 100644 index 0000000..150115d --- /dev/null +++ b/examples/issues/missing/at-layer-issue.css @@ -0,0 +1,15 @@ +/** + * AT LAYER + */ + +/* layer name has no unique syntax highlighting scope */ +.foo { + @layer base { + block-size: 100%; + @layer support { + & .bar { + min-block-size: 100%; + } + } + } +} diff --git a/examples/issues/missing/at-media-properties-issue.css b/examples/issues/missing/at-media-properties-issue.css new file mode 100644 index 0000000..8f40324 --- /dev/null +++ b/examples/issues/missing/at-media-properties-issue.css @@ -0,0 +1,15 @@ +/** + * AT MEDIA PROPERTY + */ + +/* Missing tokens */ +@media (any-hover: 0) { +} +@media (any-pointer: 0) { +} +@media (color-gamut: 0) { +} +@media (prefers-reduced-motion: reduce) { + + +/* NOTE: see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries#descriptors for more media properties */ diff --git a/examples/issues/missing/light-dark-func-issue.css b/examples/issues/missing/light-dark-func-issue.css new file mode 100644 index 0000000..0bac515 --- /dev/null +++ b/examples/issues/missing/light-dark-func-issue.css @@ -0,0 +1,3 @@ +.class { + color: light-dark(white, black); +} diff --git a/examples/issues/missing/of-not-issue.css b/examples/issues/missing/of-not-issue.css new file mode 100644 index 0000000..297cb2c --- /dev/null +++ b/examples/issues/missing/of-not-issue.css @@ -0,0 +1,23 @@ +/** + * OF :NOT + */ + +/* `of` and trailing grammar has no syntax highlighting */ +.item > div:nth-child(1 of :not(.hidden)) { + padding-left: var(--item_margin); +} +.item > div:nth-last-child(1 of :not(.hidden)) { + padding-right: var(--item_margin); +} + +/* Same as above, but using CSS Nesting formatting */ +.item { + & > div { + &:nth-child(1 of :not(.hidden)) { + padding-left: var(--item_margin); + } + &:nth-last-child(1 of :not(.hidden)) { + padding-right: var(--item_margin); + } + } +} From f7498ea25e7b7223772cde6f98a1497d3dcf30eb Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Fri, 6 Jun 2025 12:21:36 +0700 Subject: [PATCH 10/14] Add dev config files --- .prettierignore | 2 + .vscodeignore | 13 ++- package-lock.json | 202 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+), 4 deletions(-) create mode 100644 .prettierignore create mode 100644 package-lock.json diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..b574a5b --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +examples/* +src/vscode-css/spec/* diff --git a/.vscodeignore b/.vscodeignore index f6adf93..f387a11 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,5 +1,10 @@ -.dev-assets/** -.vscode/** -.vscode-test/** +.dev-assets .gitignore -vsc-extension-quickstart.md +.package-lock.json +.prettierignore +.prettierrc +.vscode +.vscodeignore +examples +node_modules +src diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..cfded48 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,202 @@ +{ + "name": "css-nesting-syntax-highlighting", + "version": "0.4.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "css-nesting-syntax-highlighting", + "version": "0.4.1", + "devDependencies": { + "cson": "^8.4.0" + }, + "engines": { + "vscode": "^1.84.0" + } + }, + "node_modules/coffeescript": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-1.12.7.tgz", + "integrity": "sha512-pLXHFxQMPklVoEekowk8b3erNynC+DVJzChxS/LCBBgR6/8AJkHivkm//zbowcfc7BTCAjryuhx6gPqPRfsFoA==", + "dev": true, + "license": "MIT", + "bin": { + "cake": "bin/cake", + "coffee": "bin/coffee" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/cson": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/cson/-/cson-8.4.0.tgz", + "integrity": "sha512-QwXDbiJodA3DFEKA888zTED28EZo2JC7EK2K3qVsUVbE0oKQVRZ4K+Ch/I8QaTYPZ9Q0GXG1BFK5dtcxd6C3mg==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "cson-parser": "^4.0.9", + "extract-opts": "^5.8.0", + "requirefresh": "^5.13.0", + "safefs": "^8.9.0" + }, + "bin": { + "cson": "bin.cjs", + "cson2json": "bin.cjs", + "json2cson": "bin.cjs" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/cson-parser": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-4.0.9.tgz", + "integrity": "sha512-I79SAcCYquWnEfXYj8hBqOOWKj6eH6zX1hhX3yqmS4K3bYp7jME3UFpHPzu3rUew0oyfc0s8T6IlWGXRAheHag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "coffeescript": "1.12.7" + }, + "engines": { + "node": ">=10.13" + } + }, + "node_modules/eachr": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/eachr/-/eachr-7.4.0.tgz", + "integrity": "sha512-d6v/ERRrwxZ5oNHJz2Z5Oouge0Xc3rFxeaGNHjAhTDUCkLhy8t583ieH9/Qop1UNDTcZXOSEs8dqawmbHaEEkA==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0", + "typechecker": "^9.3.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/editions": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/editions/-/editions-6.21.0.tgz", + "integrity": "sha512-ofkXJtn7z0urokN62DI3SBo/5xAtF0rR7tn+S/bSYV79Ka8pTajIIl+fFQ1q88DQEImymmo97M4azY3WX/nUdg==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "version-range": "^4.13.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/extract-opts": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/extract-opts/-/extract-opts-5.9.0.tgz", + "integrity": "sha512-UN5d1xtD3cQQNmE2OVsP5ij1dCDgpK5bmOxyMEXc8KaZ/YLCV0ge/RxBGG3fy7KVnxsjekwutzmzRVInQtaB4Q==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "eachr": "^7.4.0", + "editions": "^6.21.0", + "typechecker": "^9.3.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/requirefresh": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/requirefresh/-/requirefresh-5.13.0.tgz", + "integrity": "sha512-v3BuU/AAKjDqgIig1xyekcBHtujCAghuFTOQ7YNAngEKtTksSS1vT9Abt1ilbmQFAD4ChHHGJ3JSNbqvEOIxkQ==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/safefs": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/safefs/-/safefs-8.10.0.tgz", + "integrity": "sha512-ZHnuws4iLc5vB6kxU/MpGEZO7Fz80zIEOJw6VgRAroa/PeJB/jcc2KEXxWA0Izpx+W728j398DxQRx1RU+KXIg==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "graceful-fs": "^4.2.11", + "version-compare": "^3.10.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/typechecker": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/typechecker/-/typechecker-9.3.0.tgz", + "integrity": "sha512-7NKr0EkLaL5fkYE56DPwqgQx1FjepvDRZ64trUgb1NgeFqLkaZThI2L33vFJzN4plVyAN5zWcov57QcZIU3bjg==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.20.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/version-compare": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/version-compare/-/version-compare-3.11.0.tgz", + "integrity": "sha512-sufLk/bD3oq+iLzWk8FOHfYwVIaC3NLJcvMqnggmakwNskyKyM+u8x1F8NdwjlLVgC8oYSTgB0RK68VCT6wQOg==", + "dev": true, + "license": "Artistic-2.0", + "dependencies": { + "editions": "^6.21.0" + }, + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/version-range": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.14.0.tgz", + "integrity": "sha512-gjb0ARm9qlcBAonU4zPwkl9ecKkas+tC2CGwFfptTCWWIVTWY1YUbT2zZKsOAF1jR/tNxxyLwwG0cb42XlYcTg==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=4" + }, + "funding": { + "url": "https://bevry.me/fund" + } + } + } +} From 347984305a009d7bbfaf84f6d22692c40972b58a Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Fri, 6 Jun 2025 12:29:05 +0700 Subject: [PATCH 11/14] Replace extension's `css.tmLanguage.json` with official vscode-css file --- syntaxes/css.tmLanguage.json | 314 +---------------------------------- 1 file changed, 8 insertions(+), 306 deletions(-) diff --git a/syntaxes/css.tmLanguage.json b/syntaxes/css.tmLanguage.json index e93902e..e207468 100644 --- a/syntaxes/css.tmLanguage.json +++ b/syntaxes/css.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/microsoft/vscode-css/commit/c216f777497265700ff336f739328e5197e012cd", + "version": "https://github.com/microsoft/vscode-css/commit/a927fe2f73927bf5c25d0b0c4dd0e63d69fd8887", "name": "CSS", "scopeName": "source.css", "patterns": [ @@ -25,65 +25,11 @@ }, { "include": "#rule-list" - }, - { - "include": "#nesting-selector" } ], "repository": { - "arithmetic-operators": { - "match": "[*/]|(?<=\\s|^)[-+](?=\\s|$)", - "name": "keyword.operator.arithmetic.css" - }, "at-rules": { "patterns": [ - { - "begin": "(?i)(?=@container(\\s|\\(|/\\*|$))", - "end": "(?<=})(?!\\G)", - "patterns": [ - { - "begin": "(?i)\\G(@)container", - "beginCaptures": { - "0": { - "name": "keyword.control.at-rule.container.css" - }, - "1": { - "name": "punctuation.definition.keyword.css" - } - }, - "end": "(?=\\s*[{;])", - "name": "meta.at-rule.container.header.css", - "patterns": [ - { - "include": "#container-condition" - } - ] - }, - { - "begin": "{", - "beginCaptures": { - "0": { - "name": "punctuation.section.container.begin.bracket.curly.css" - } - }, - "end": "}", - "endCaptures": { - "0": { - "name": "punctuation.section.container.end.bracket.curly.css" - } - }, - "name": "meta.at-rule.container.body.css", - "patterns": [ - { - "include": "#nesting-at-rules" - }, - { - "include": "$self" - } - ] - } - ] - }, { "begin": "\\A(?:\\xEF\\xBB\\xBF)?(?i:(?=\\s*@charset\\b))", "end": ";|(?=$)", @@ -272,9 +218,6 @@ }, "name": "meta.at-rule.media.body.css", "patterns": [ - { - "include": "#nesting-at-rules" - }, { "include": "$self" } @@ -557,9 +500,6 @@ }, "name": "meta.at-rule.supports.body.css", "patterns": [ - { - "include": "#nesting-at-rules" - }, { "include": "$self" } @@ -721,9 +661,6 @@ }, "name": "meta.at-rule.body.css", "patterns": [ - { - "include": "#nesting-at-rules" - }, { "include": "$self" } @@ -784,164 +721,6 @@ }, "name": "comment.block.css" }, - "container-condition": { - "begin": "\\G", - "end": "(?=\\s*[{;])", - "patterns": [ - { - "include": "#comment-block" - }, - { - "include": "#escapes" - }, - { - "match": "(?xi)\n (?<=not.*)not\n # Only one `not` in allowed", - "name": "invalid.illegal.multiple-not.container.css" - }, - { - "match": "(?i)(?<=\\s|^|,|\\*/)(and|or|not)(?=\\s|{|/\\*|$)", - "name": "keyword.operator.logical.$1.container.css" - }, - { - "include": "#container-name" - }, - { - "include": "#container-query" - } - ] - }, - "container-name": { - "begin": "\\G", - "end": "(?xi)\n (?=(?:\\(|not)|style\\()\n # Ends when `(`, `not`, or `style(` is reached", - "patterns": [ - { - "include": "#comment-block" - }, - { - "captures": { - "1": { - "name": "invalid.illegal.constant.container-name.container.css" - }, - "2": { - "name": "support.constant.container-name.container.css" - } - }, - "match": "(?xi)\n (?<=^|\\s|\\*/)\n(?:\n # Invalid name\n (default|none)\n |\n # Valid names\n ([a-zA-Z0-9\\-_\\\\]+)\n )\n(?=$|[({\\s;]|/\\*)" - } - ] - }, - "container-query": { - "begin": "((?<=\\s)\\()|(style)(\\()", - "beginCaptures": { - "1": { - "name": "punctuation.definition.parameters.begin.bracket.round.css" - }, - "2": { - "name": "support.style-query.container.css" - }, - "3": { - "name": "punctuation.definition.parameters.begin.bracket.round.css" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.definition.parameters.end.bracket.round.css" - } - }, - "patterns": [ - { - "begin": "(?xi)\n (?<=\\s\\()\n # Rules for size ", - "end": "(?=\\))", - "patterns": [ - { - "include": "#container-query-features" - }, - { - "include": "#container-size-features" - }, - { - "include": "#container-size-feature-keywords" - } - ] - }, - { - "begin": "(?xi)\n (?<=style\\()\n # Rules for container-query ", - "end": "(?=\\))", - "name": "style-query.container.css", - "patterns": [ - { - "include": "#container-query-features" - }, - { - "include": "#container-style-features" - }, - { - "include": "#container-style-feature-keywords" - } - ] - } - ] - }, - "container-query-features": { - "patterns": [ - { - "match": ":", - "name": "punctuation.separator.key-value.css" - }, - { - "match": ">=|<=|=|<|>", - "name": "keyword.operator.comparison.css" - }, - { - "include": "#numeric-values" - }, - { - "include": "#comment-block" - } - ] - }, - "container-size-features": { - "match": "(?xi)\n (?<=^|\\s|\\(|\\*/) # Preceded by whitespace, bracket or comment\n (?:\n # Standardised features\n (\n (?:min-|max-)? # Range features\n (?: aspect-ratio\n | block-size\n | height\n | inline-size\n | width\n )\n | orientation\n )\n )\n (?=\\s|$|[><:=]|\\)|/\\*) # Terminates cleanly", - "name": "support.size.property-name.container.css" - }, - "container-size-feature-keywords": { - "patterns": [ - { - "match": "(?xi)\n (?<=^|\\s|:|\\*/)\n # Preceded by whitespace, colon, or comment\n (?: portrait\n | landscape\n # Orientation\n )\n (?=\\s|\\)|$)", - "name": "support.constant.property-value.container.css" - }, - { - "include": "#functions" - } - ] - }, - "container-style-features": { - "match": "(?x)\n # Custom Property Name\n (?<=^|\\s|\\(|\\*/) # Preceded by whitespace, bracket or comment\n (?\\s,.\\#|&){:\\[]|/\\*|$)", - "name": "entity.name.tag.css" - }, - { - "include": "#property-names" - }, - { - "include": "#selector-innards" - } - ] - }, - "nesting-selector": { - "match": "&", - "name": "entity.name.tag.nesting.selector.css" - }, "numeric-values": { "patterns": [ { @@ -1793,14 +1504,8 @@ }, "name": "meta.property-list.css", "patterns": [ - { - "include": "#nesting-rules" - }, { "include": "#rule-list-innards" - }, - { - "include": "$self" } ] }, @@ -1870,9 +1575,6 @@ }, "selector-innards": { "patterns": [ - { - "include": "#nesting-selector" - }, { "include": "#comment-block" }, @@ -1916,7 +1618,7 @@ ] } }, - "match": "(?x) (?+~|&] # - Followed by another selector\n | /\\* # - Followed by a block comment\n )\n |\n # Name contains unescaped ASCII symbol\n (?: # Check for acceptable preceding characters\n [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence\n )*\n (?: # Invalid punctuation\n [!\"'%(*;+~|] # - Followed by another selector\n | /\\* # - Followed by a block comment\n )\n |\n # Name contains unescaped ASCII symbol\n (?: # Check for acceptable preceding characters\n [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence\n )*\n (?: # Invalid punctuation\n [!\"'%&(*;+~|&] # - Another selector\n | /\\* # - A block comment\n)", + "match": "(?x)\n(\\.) # Valid class-name\n(\n (?: [-a-zA-Z_0-9]|[^\\x00-\\x7F] # Valid identifier characters\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # Escape sequence\n )+\n) # Followed by either:\n(?= $ # - End of the line\n | [\\s,.\\#)\\[:{>+~|] # - Another selector\n | /\\* # - A block comment\n)", "name": "entity.other.attribute-name.class.css" }, { @@ -1948,7 +1650,7 @@ ] } }, - "match": "(?x)\n(\\#)\n(\n -?\n (?![0-9])\n (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n)\n(?=$|[\\s,.\\#)\\[:{>+~|&]|/\\*)", + "match": "(?x)\n(\\#)\n(\n -?\n (?![0-9])\n (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n)\n(?=$|[\\s,.\\#)\\[:{>+~|]|/\\*)", "name": "entity.other.attribute-name.id.css" }, { @@ -2112,7 +1814,7 @@ ] }, "tag-names": { - "match": "(?xi) (?\\s,.\\#|&){:\\[]|/\\*|$)", + "match": "(?xi) (?\\s,.\\#|){:\\[]|/\\*|$)", "name": "entity.name.tag.css" }, "unicode-range": { From d7d532d199842b20527dd45d744ebd65e1b68569 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Fri, 6 Jun 2025 12:30:52 +0700 Subject: [PATCH 12/14] Update official `css.tmLanguage.json` with src converted file --- syntaxes/css.tmLanguage.json | 3727 +++++++++++++++++----------------- 1 file changed, 1863 insertions(+), 1864 deletions(-) diff --git a/syntaxes/css.tmLanguage.json b/syntaxes/css.tmLanguage.json index e207468..5a898a6 100644 --- a/syntaxes/css.tmLanguage.json +++ b/syntaxes/css.tmLanguage.json @@ -1,1865 +1,1864 @@ { - "information_for_contributors": [ - "This file has been converted from https://github.com/microsoft/vscode-css/blob/master/grammars/css.cson", - "If you want to provide a fix or improvement, please create a pull request against the original repository.", - "Once accepted there, we are happy to receive an update request." - ], - "version": "https://github.com/microsoft/vscode-css/commit/a927fe2f73927bf5c25d0b0c4dd0e63d69fd8887", - "name": "CSS", - "scopeName": "source.css", - "patterns": [ - { - "include": "#comment-block" - }, - { - "include": "#escapes" - }, - { - "include": "#combinators" - }, - { - "include": "#selector" - }, - { - "include": "#at-rules" - }, - { - "include": "#rule-list" - } - ], - "repository": { - "at-rules": { - "patterns": [ - { - "begin": "\\A(?:\\xEF\\xBB\\xBF)?(?i:(?=\\s*@charset\\b))", - "end": ";|(?=$)", - "endCaptures": { - "0": { - "name": "punctuation.terminator.rule.css" - } - }, - "name": "meta.at-rule.charset.css", - "patterns": [ - { - "captures": { - "1": { - "name": "invalid.illegal.not-lowercase.charset.css" - }, - "2": { - "name": "invalid.illegal.leading-whitespace.charset.css" - }, - "3": { - "name": "invalid.illegal.no-whitespace.charset.css" - }, - "4": { - "name": "invalid.illegal.whitespace.charset.css" - }, - "5": { - "name": "invalid.illegal.not-double-quoted.charset.css" - }, - "6": { - "name": "invalid.illegal.unclosed-string.charset.css" - }, - "7": { - "name": "invalid.illegal.unexpected-characters.charset.css" - } - }, - "match": "(?x) # Possible errors:\n\\G\n((?!@charset)@\\w+) # Not lowercase (@charset is case-sensitive)\n|\n\\G(\\s+) # Preceding whitespace\n|\n(@charset\\S[^;]*) # No whitespace after @charset\n|\n(?<=@charset) # Before quoted charset name\n(\\x20{2,}|\\t+) # More than one space used, or a tab\n|\n(?<=@charset\\x20) # Beginning of charset name\n([^\";]+) # Not double-quoted\n|\n(\"[^\"]+$) # Unclosed quote\n|\n(?<=\") # After charset name\n([^;]+) # Unexpected junk instead of semicolon" - }, - { - "captures": { - "1": { - "name": "keyword.control.at-rule.charset.css" - }, - "2": { - "name": "punctuation.definition.keyword.css" - } - }, - "match": "((@)charset)(?=\\s)" - }, - { - "begin": "\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.css" - } - }, - "end": "\"|$", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.css" - } - }, - "name": "string.quoted.double.css", - "patterns": [ - { - "begin": "(?:\\G|^)(?=(?:[^\"])+$)", - "end": "$", - "name": "invalid.illegal.unclosed.string.css" - } - ] - } - ] - }, - { - "begin": "(?i)((@)import)(?:\\s+|$|(?=['\"]|/\\*))", - "beginCaptures": { - "1": { - "name": "keyword.control.at-rule.import.css" - }, - "2": { - "name": "punctuation.definition.keyword.css" - } - }, - "end": ";", - "endCaptures": { - "0": { - "name": "punctuation.terminator.rule.css" - } - }, - "name": "meta.at-rule.import.css", - "patterns": [ - { - "begin": "\\G\\s*(?=/\\*)", - "end": "(?<=\\*/)\\s*", - "patterns": [ - { - "include": "#comment-block" - } - ] - }, - { - "include": "#string" - }, - { - "include": "#url" - }, - { - "include": "#media-query-list" - } - ] - }, - { - "begin": "(?i)((@)font-face)(?=\\s*|{|/\\*|$)", - "beginCaptures": { - "1": { - "name": "keyword.control.at-rule.font-face.css" - }, - "2": { - "name": "punctuation.definition.keyword.css" - } - }, - "end": "(?!\\G)", - "name": "meta.at-rule.font-face.css", - "patterns": [ - { - "include": "#comment-block" - }, - { - "include": "#escapes" - }, - { - "include": "#rule-list" - } - ] - }, - { - "begin": "(?i)(@)page(?=[\\s:{]|/\\*|$)", - "captures": { - "0": { - "name": "keyword.control.at-rule.page.css" - }, - "1": { - "name": "punctuation.definition.keyword.css" - } - }, - "end": "(?=\\s*($|[:{;]))", - "name": "meta.at-rule.page.css", - "patterns": [ - { - "include": "#rule-list" - } - ] - }, - { - "begin": "(?i)(?=@media(\\s|\\(|/\\*|$))", - "end": "(?<=})(?!\\G)", - "patterns": [ - { - "begin": "(?i)\\G(@)media", - "beginCaptures": { - "0": { - "name": "keyword.control.at-rule.media.css" - }, - "1": { - "name": "punctuation.definition.keyword.css" - } - }, - "end": "(?=\\s*[{;])", - "name": "meta.at-rule.media.header.css", - "patterns": [ - { - "include": "#media-query-list" - } - ] - }, - { - "begin": "{", - "beginCaptures": { - "0": { - "name": "punctuation.section.media.begin.bracket.curly.css" - } - }, - "end": "}", - "endCaptures": { - "0": { - "name": "punctuation.section.media.end.bracket.curly.css" - } - }, - "name": "meta.at-rule.media.body.css", - "patterns": [ - { - "include": "$self" - } - ] - } - ] - }, - { - "begin": "(?i)(?=@counter-style([\\s'\"{;]|/\\*|$))", - "end": "(?<=})(?!\\G)", - "patterns": [ - { - "begin": "(?i)\\G(@)counter-style", - "beginCaptures": { - "0": { - "name": "keyword.control.at-rule.counter-style.css" - }, - "1": { - "name": "punctuation.definition.keyword.css" - } - }, - "end": "(?=\\s*{)", - "name": "meta.at-rule.counter-style.header.css", - "patterns": [ - { - "include": "#comment-block" - }, - { - "include": "#escapes" - }, - { - "captures": { - "0": { - "patterns": [ - { - "include": "#escapes" - } - ] - } - }, - "match": "(?x)\n(?:[-a-zA-Z_] | [^\\x00-\\x7F]) # First letter\n(?:[-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier\n |\\\\(?:[0-9a-fA-F]{1,6}|.)\n)*", - "name": "variable.parameter.style-name.css" - } - ] - }, - { - "begin": "{", - "beginCaptures": { - "0": { - "name": "punctuation.section.property-list.begin.bracket.curly.css" - } - }, - "end": "}", - "endCaptures": { - "0": { - "name": "punctuation.section.property-list.end.bracket.curly.css" - } - }, - "name": "meta.at-rule.counter-style.body.css", - "patterns": [ - { - "include": "#comment-block" - }, - { - "include": "#escapes" - }, - { - "include": "#rule-list-innards" - } - ] - } - ] - }, - { - "begin": "(?i)(?=@document([\\s'\"{;]|/\\*|$))", - "end": "(?<=})(?!\\G)", - "patterns": [ - { - "begin": "(?i)\\G(@)document", - "beginCaptures": { - "0": { - "name": "keyword.control.at-rule.document.css" - }, - "1": { - "name": "punctuation.definition.keyword.css" - } - }, - "end": "(?=\\s*[{;])", - "name": "meta.at-rule.document.header.css", - "patterns": [ - { - "begin": "(?i)(?>>", - "name": "invalid.deprecated.combinator.css" - }, - { - "match": ">>|>|\\+|~", - "name": "keyword.operator.combinator.css" - } - ] - }, - "commas": { - "match": ",", - "name": "punctuation.separator.list.comma.css" - }, - "comment-block": { - "begin": "/\\*", - "beginCaptures": { - "0": { - "name": "punctuation.definition.comment.begin.css" - } - }, - "end": "\\*/", - "endCaptures": { - "0": { - "name": "punctuation.definition.comment.end.css" - } - }, - "name": "comment.block.css" - }, - "escapes": { - "patterns": [ - { - "match": "\\\\[0-9a-fA-F]{1,6}", - "name": "constant.character.escape.codepoint.css" - }, - { - "begin": "\\\\$\\s*", - "end": "^(?<:=]|\\)|/\\*) # Terminates cleanly" - }, - "media-feature-keywords": { - "match": "(?xi)\n(?<=^|\\s|:|\\*/)\n(?: portrait # Orientation\n | landscape\n | progressive # Scan types\n | interlace\n | fullscreen # Display modes\n | standalone\n | minimal-ui\n | browser\n | hover\n)\n(?=\\s|\\)|$)", - "name": "support.constant.property-value.css" - }, - "media-query": { - "begin": "\\G", - "end": "(?=\\s*[{;])", - "patterns": [ - { - "include": "#comment-block" - }, - { - "include": "#escapes" - }, - { - "include": "#media-types" - }, - { - "match": "(?i)(?<=\\s|^|,|\\*/)(only|not)(?=\\s|{|/\\*|$)", - "name": "keyword.operator.logical.$1.media.css" - }, - { - "match": "(?i)(?<=\\s|^|\\*/|\\))and(?=\\s|/\\*|$)", - "name": "keyword.operator.logical.and.media.css" - }, - { - "match": ",(?:(?:\\s*,)+|(?=\\s*[;){]))", - "name": "invalid.illegal.comma.css" - }, - { - "include": "#commas" - }, - { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.definition.parameters.begin.bracket.round.css" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.definition.parameters.end.bracket.round.css" - } - }, - "patterns": [ - { - "include": "#media-features" - }, - { - "include": "#media-feature-keywords" - }, - { - "match": ":", - "name": "punctuation.separator.key-value.css" - }, - { - "match": ">=|<=|=|<|>", - "name": "keyword.operator.comparison.css" - }, - { - "captures": { - "1": { - "name": "constant.numeric.css" - }, - "2": { - "name": "keyword.operator.arithmetic.css" - }, - "3": { - "name": "constant.numeric.css" - } - }, - "match": "(\\d+)\\s*(/)\\s*(\\d+)", - "name": "meta.ratio.css" - }, - { - "include": "#numeric-values" - }, - { - "include": "#comment-block" - } - ] - } - ] - }, - "media-query-list": { - "begin": "(?=\\s*[^{;])", - "end": "(?=\\s*[{;])", - "patterns": [ - { - "include": "#media-query" - } - ] - }, - "media-types": { - "captures": { - "1": { - "name": "support.constant.media.css" - }, - "2": { - "name": "invalid.deprecated.constant.media.css" - } - }, - "match": "(?xi)\n(?<=^|\\s|,|\\*/)\n(?:\n # Valid media types\n (all|print|screen|speech)\n |\n # Deprecated in Media Queries 4: http://dev.w3.org/csswg/mediaqueries/#media-types\n (aural|braille|embossed|handheld|projection|tty|tv)\n)\n(?=$|[{,\\s;]|/\\*)" - }, - "numeric-values": { - "patterns": [ - { - "captures": { - "1": { - "name": "punctuation.definition.constant.css" - } - }, - "match": "(#)(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\\b", - "name": "constant.other.color.rgb-value.hex.css" - }, - { - "captures": { - "1": { - "name": "keyword.other.unit.percentage.css" - }, - "2": { - "name": "keyword.other.unit.${2:/downcase}.css" - } - }, - "match": "(?xi) (?+~|] # - Followed by another selector\n | /\\* # - Followed by a block comment\n )\n |\n # Name contains unescaped ASCII symbol\n (?: # Check for acceptable preceding characters\n [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence\n )*\n (?: # Invalid punctuation\n [!\"'%&(*;+~|] # - Another selector\n | /\\* # - A block comment\n)", - "name": "entity.other.attribute-name.class.css" - }, - { - "captures": { - "1": { - "name": "punctuation.definition.entity.css" - }, - "2": { - "patterns": [ - { - "include": "#escapes" - } - ] - } - }, - "match": "(?x)\n(\\#)\n(\n -?\n (?![0-9])\n (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n)\n(?=$|[\\s,.\\#)\\[:{>+~|]|/\\*)", - "name": "entity.other.attribute-name.id.css" - }, - { - "begin": "\\[", - "beginCaptures": { - "0": { - "name": "punctuation.definition.entity.begin.bracket.square.css" - } - }, - "end": "\\]", - "endCaptures": { - "0": { - "name": "punctuation.definition.entity.end.bracket.square.css" - } - }, - "name": "meta.attribute-selector.css", - "patterns": [ - { - "include": "#comment-block" - }, - { - "include": "#string" - }, - { - "captures": { - "1": { - "name": "storage.modifier.ignore-case.css" - } - }, - "match": "(?<=[\"'\\s]|^|\\*/)\\s*([iI])\\s*(?=[\\s\\]]|/\\*|$)" - }, - { - "captures": { - "1": { - "name": "string.unquoted.attribute-value.css", - "patterns": [ - { - "include": "#escapes" - } - ] - } - }, - "match": "(?x)(?<==)\\s*((?!/\\*)(?:[^\\\\\"'\\s\\]]|\\\\.)+)" - }, - { - "include": "#escapes" - }, - { - "match": "[~|^$*]?=", - "name": "keyword.operator.pattern.css" - }, - { - "match": "\\|", - "name": "punctuation.separator.css" - }, - { - "captures": { - "1": { - "name": "entity.other.namespace-prefix.css", - "patterns": [ - { - "include": "#escapes" - } - ] - } - }, - "match": "(?x)\n# Qualified namespace prefix\n( -?(?!\\d)(?:[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n| \\*\n)\n# Lookahead to ensure there's a valid identifier ahead\n(?=\n \\| (?!\\s|=|$|\\])\n (?: -?(?!\\d)\n | [\\\\\\w-]\n | [^\\x00-\\x7F]\n )\n)" - }, - { - "captures": { - "1": { - "name": "entity.other.attribute-name.css", - "patterns": [ - { - "include": "#escapes" - } - ] - } - }, - "match": "(?x)\n(-?(?!\\d)(?>[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+)\n\\s*\n(?=[~|^\\]$*=]|/\\*)" - } - ] - }, - { - "include": "#pseudo-classes" - }, - { - "include": "#pseudo-elements" - }, - { - "include": "#functional-pseudo-classes" - }, - { - "match": "(?x) (?\\s,.\\#|){:\\[]|/\\*|$)", - "name": "entity.name.tag.css" - }, - "unicode-range": { - "captures": { - "0": { - "name": "constant.other.unicode-range.css" - }, - "1": { - "name": "punctuation.separator.dash.unicode-range.css" - } - }, - "match": "(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n css\n(?=\\s|:|$)", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "include": "#combinators" + }, + { + "include": "#selector" + }, + { + "include": "#at-rules" + }, + { + "include": "#rule-list" + } + ], + "repository": { + "at-rules": { + "patterns": [ + { + "begin": "\\A(?:\\xEF\\xBB\\xBF)?(?i:(?=\\s*@charset\\b))", + "end": ";|(?=$)", + "endCaptures": { + "0": { + "name": "punctuation.terminator.rule.css" + } + }, + "name": "meta.at-rule.charset.css", + "patterns": [ + { + "captures": { + "1": { + "name": "invalid.illegal.not-lowercase.charset.css" + }, + "2": { + "name": "invalid.illegal.leading-whitespace.charset.css" + }, + "3": { + "name": "invalid.illegal.no-whitespace.charset.css" + }, + "4": { + "name": "invalid.illegal.whitespace.charset.css" + }, + "5": { + "name": "invalid.illegal.not-double-quoted.charset.css" + }, + "6": { + "name": "invalid.illegal.unclosed-string.charset.css" + }, + "7": { + "name": "invalid.illegal.unexpected-characters.charset.css" + } + }, + "match": "(?x) # Possible errors:\n\\G\n((?!@charset)@\\w+) # Not lowercase (@charset is case-sensitive)\n|\n\\G(\\s+) # Preceding whitespace\n|\n(@charset\\S[^;]*) # No whitespace after @charset\n|\n(?<=@charset) # Before quoted charset name\n(\\x20{2,}|\\t+) # More than one space used, or a tab\n|\n(?<=@charset\\x20) # Beginning of charset name\n([^\";]+) # Not double-quoted\n|\n(\"[^\"]+$) # Unclosed quote\n|\n(?<=\") # After charset name\n([^;]+) # Unexpected junk instead of semicolon" + }, + { + "captures": { + "1": { + "name": "keyword.control.at-rule.charset.css" + }, + "2": { + "name": "punctuation.definition.keyword.css" + } + }, + "match": "((@)charset)(?=\\s)" + }, + { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.css" + } + }, + "end": "\"|$", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.css" + } + }, + "name": "string.quoted.double.css", + "patterns": [ + { + "begin": "(?:\\G|^)(?=(?:[^\"])+$)", + "end": "$", + "name": "invalid.illegal.unclosed.string.css" + } + ] + } + ] + }, + { + "begin": "(?i)((@)import)(?:\\s+|$|(?=['\"]|/\\*))", + "beginCaptures": { + "1": { + "name": "keyword.control.at-rule.import.css" + }, + "2": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": ";", + "endCaptures": { + "0": { + "name": "punctuation.terminator.rule.css" + } + }, + "name": "meta.at-rule.import.css", + "patterns": [ + { + "begin": "\\G\\s*(?=/\\*)", + "end": "(?<=\\*/)\\s*", + "patterns": [ + { + "include": "#comment-block" + } + ] + }, + { + "include": "#string" + }, + { + "include": "#url" + }, + { + "include": "#media-query-list" + } + ] + }, + { + "begin": "(?i)((@)font-face)(?=\\s*|{|/\\*|$)", + "beginCaptures": { + "1": { + "name": "keyword.control.at-rule.font-face.css" + }, + "2": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?!\\G)", + "name": "meta.at-rule.font-face.css", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "include": "#rule-list" + } + ] + }, + { + "begin": "(?i)(@)page(?=[\\s:{]|/\\*|$)", + "captures": { + "0": { + "name": "keyword.control.at-rule.page.css" + }, + "1": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?=\\s*($|[:{;]))", + "name": "meta.at-rule.page.css", + "patterns": [ + { + "include": "#rule-list" + } + ] + }, + { + "begin": "(?i)(?=@media(\\s|\\(|/\\*|$))", + "end": "(?<=})(?!\\G)", + "patterns": [ + { + "begin": "(?i)\\G(@)media", + "beginCaptures": { + "0": { + "name": "keyword.control.at-rule.media.css" + }, + "1": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?=\\s*[{;])", + "name": "meta.at-rule.media.header.css", + "patterns": [ + { + "include": "#media-query-list" + } + ] + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.media.begin.bracket.curly.css" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.media.end.bracket.curly.css" + } + }, + "name": "meta.at-rule.media.body.css", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + { + "begin": "(?i)(?=@counter-style([\\s'\"{;]|/\\*|$))", + "end": "(?<=})(?!\\G)", + "patterns": [ + { + "begin": "(?i)\\G(@)counter-style", + "beginCaptures": { + "0": { + "name": "keyword.control.at-rule.counter-style.css" + }, + "1": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?=\\s*{)", + "name": "meta.at-rule.counter-style.header.css", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "captures": { + "0": { + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)\n(?:[-a-zA-Z_] | [^\\x00-\\x7F]) # First letter\n(?:[-a-zA-Z0-9_] | [^\\x00-\\x7F] # Remainder of identifier\n |\\\\(?:[0-9a-fA-F]{1,6}|.)\n)*", + "name": "variable.parameter.style-name.css" + } + ] + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.property-list.begin.bracket.curly.css" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.property-list.end.bracket.curly.css" + } + }, + "name": "meta.at-rule.counter-style.body.css", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "include": "#rule-list-innards" + } + ] + } + ] + }, + { + "begin": "(?i)(?=@document([\\s'\"{;]|/\\*|$))", + "end": "(?<=})(?!\\G)", + "patterns": [ + { + "begin": "(?i)\\G(@)document", + "beginCaptures": { + "0": { + "name": "keyword.control.at-rule.document.css" + }, + "1": { + "name": "punctuation.definition.keyword.css" + } + }, + "end": "(?=\\s*[{;])", + "name": "meta.at-rule.document.header.css", + "patterns": [ + { + "begin": "(?i)(?>>", + "name": "invalid.deprecated.combinator.css" + }, + { + "match": ">>|>|\\+|~", + "name": "keyword.operator.combinator.css" + } + ] + }, + "commas": { + "match": ",", + "name": "punctuation.separator.list.comma.css" + }, + "comment-block": { + "begin": "/\\*", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.begin.css" + } + }, + "end": "\\*/", + "endCaptures": { + "0": { + "name": "punctuation.definition.comment.end.css" + } + }, + "name": "comment.block.css" + }, + "escapes": { + "patterns": [ + { + "match": "\\\\[0-9a-fA-F]{1,6}", + "name": "constant.character.escape.codepoint.css" + }, + { + "begin": "\\\\$\\s*", + "end": "^(?<:=]|\\)|/\\*) # Terminates cleanly" + }, + "media-feature-keywords": { + "match": "(?xi)\n(?<=^|\\s|:|\\*/)\n(?: portrait # Orientation\n | landscape\n | progressive # Scan types\n | interlace\n | fullscreen # Display modes\n | standalone\n | minimal-ui\n | browser\n | hover\n)\n(?=\\s|\\)|$)", + "name": "support.constant.property-value.css" + }, + "media-query": { + "begin": "\\G", + "end": "(?=\\s*[{;])", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#escapes" + }, + { + "include": "#media-types" + }, + { + "match": "(?i)(?<=\\s|^|,|\\*/)(only|not)(?=\\s|{|/\\*|$)", + "name": "keyword.operator.logical.$1.media.css" + }, + { + "match": "(?i)(?<=\\s|^|\\*/|\\))and(?=\\s|/\\*|$)", + "name": "keyword.operator.logical.and.media.css" + }, + { + "match": ",(?:(?:\\s*,)+|(?=\\s*[;){]))", + "name": "invalid.illegal.comma.css" + }, + { + "include": "#commas" + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.parameters.begin.bracket.round.css" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.css" + } + }, + "patterns": [ + { + "include": "#media-features" + }, + { + "include": "#media-feature-keywords" + }, + { + "match": ":", + "name": "punctuation.separator.key-value.css" + }, + { + "match": ">=|<=|=|<|>", + "name": "keyword.operator.comparison.css" + }, + { + "captures": { + "1": { + "name": "constant.numeric.css" + }, + "2": { + "name": "keyword.operator.arithmetic.css" + }, + "3": { + "name": "constant.numeric.css" + } + }, + "match": "(\\d+)\\s*(/)\\s*(\\d+)", + "name": "meta.ratio.css" + }, + { + "include": "#numeric-values" + }, + { + "include": "#comment-block" + } + ] + } + ] + }, + "media-query-list": { + "begin": "(?=\\s*[^{;])", + "end": "(?=\\s*[{;])", + "patterns": [ + { + "include": "#media-query" + } + ] + }, + "media-types": { + "captures": { + "1": { + "name": "support.constant.media.css" + }, + "2": { + "name": "invalid.deprecated.constant.media.css" + } + }, + "match": "(?xi)\n(?<=^|\\s|,|\\*/)\n(?:\n # Valid media types\n (all|print|screen|speech)\n |\n # Deprecated in Media Queries 4: http://dev.w3.org/csswg/mediaqueries/#media-types\n (aural|braille|embossed|handheld|projection|tty|tv)\n)\n(?=$|[{,\\s;]|/\\*)" + }, + "numeric-values": { + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.definition.constant.css" + } + }, + "match": "(#)(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\\b", + "name": "constant.other.color.rgb-value.hex.css" + }, + { + "captures": { + "1": { + "name": "keyword.other.unit.percentage.css" + }, + "2": { + "name": "keyword.other.unit.${2:/downcase}.css" + } + }, + "match": "(?xi) (?+~|] # - Followed by another selector\n | /\\* # - Followed by a block comment\n )\n |\n # Name contains unescaped ASCII symbol\n (?: # Check for acceptable preceding characters\n [-a-zA-Z_0-9]|[^\\x00-\\x7F] # - Valid selector character\n | \\\\(?:[0-9a-fA-F]{1,6}|.) # - Escape sequence\n )*\n (?: # Invalid punctuation\n [!\"'%&(*;+~|] # - Another selector\n | /\\* # - A block comment\n)", + "name": "entity.other.attribute-name.class.css" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.entity.css" + }, + "2": { + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)\n(\\#)\n(\n -?\n (?![0-9])\n (?:[-a-zA-Z0-9_]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n)\n(?=$|[\\s,.\\#)\\[:{>+~|]|/\\*)", + "name": "entity.other.attribute-name.id.css" + }, + { + "begin": "\\[", + "beginCaptures": { + "0": { + "name": "punctuation.definition.entity.begin.bracket.square.css" + } + }, + "end": "\\]", + "endCaptures": { + "0": { + "name": "punctuation.definition.entity.end.bracket.square.css" + } + }, + "name": "meta.attribute-selector.css", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#string" + }, + { + "captures": { + "1": { + "name": "storage.modifier.ignore-case.css" + } + }, + "match": "(?<=[\"'\\s]|^|\\*/)\\s*([iI])\\s*(?=[\\s\\]]|/\\*|$)" + }, + { + "captures": { + "1": { + "name": "string.unquoted.attribute-value.css", + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)(?<==)\\s*((?!/\\*)(?:[^\\\\\"'\\s\\]]|\\\\.)+)" + }, + { + "include": "#escapes" + }, + { + "match": "[~|^$*]?=", + "name": "keyword.operator.pattern.css" + }, + { + "match": "\\|", + "name": "punctuation.separator.css" + }, + { + "captures": { + "1": { + "name": "entity.other.namespace-prefix.css", + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)\n# Qualified namespace prefix\n( -?(?!\\d)(?:[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+\n| \\*\n)\n# Lookahead to ensure there's a valid identifier ahead\n(?=\n \\| (?!\\s|=|$|\\])\n (?: -?(?!\\d)\n | [\\\\\\w-]\n | [^\\x00-\\x7F]\n )\n)" + }, + { + "captures": { + "1": { + "name": "entity.other.attribute-name.css", + "patterns": [ + { + "include": "#escapes" + } + ] + } + }, + "match": "(?x)\n(-?(?!\\d)(?>[\\w-]|[^\\x00-\\x7F]|\\\\(?:[0-9a-fA-F]{1,6}|.))+)\n\\s*\n(?=[~|^\\]$*=]|/\\*)" + } + ] + }, + { + "include": "#pseudo-classes" + }, + { + "include": "#pseudo-elements" + }, + { + "include": "#functional-pseudo-classes" + }, + { + "match": "(?x) (?\\s,.\\#|){:\\[]|/\\*|$)", + "name": "entity.name.tag.css" + }, + "unicode-range": { + "captures": { + "0": { + "name": "constant.other.unicode-range.css" + }, + "1": { + "name": "punctuation.separator.dash.unicode-range.css" + } + }, + "match": "(? Date: Fri, 6 Jun 2025 12:32:33 +0700 Subject: [PATCH 13/14] Add CHANGELOG v0.4.1 notes --- CHANGELOG.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3962fd..f266c3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,28 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.4.1] - 2025-06-06 + +### Added + +- Added "issues" CSS files. +- Added `\*.md` to list of files indented by spaces instead of tabs, in `.editorconfig`. +- Added CSS syntax test demo files. +- Added development config files. +- Added official vscode-css source files. + +### Changed + +- Moved dev doc files out of main repo. +- Reorganize `package.json` details. +- Replaced extension's `css.tmLanguage.json` with official vscode-css file. +- Restructured example CSS files. + ## [0.4.0] - 2025-01-31 ### Added -- Added sheild.io badges to top of `README.md`. +- Added shield.io badges to top of `README.md`. ## [0.3.0] - 2025-01-07 From c966e143f8600999e7827dfe7eea17c6e33af5c1 Mon Sep 17 00:00:00 2001 From: Jacob Cassidy Date: Fri, 6 Jun 2025 12:35:08 +0700 Subject: [PATCH 14/14] Add test script command to root --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0b0863c..07a6af9 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "url": "https://github.com/jacobcassidy/vscode-css-nesting-syntax-highlighting" }, "scripts": { - "build": "cson2json src/vscode-css/grammars/css.cson > syntaxes/css.tmLanguage.json" + "build": "cson2json src/vscode-css/grammars/css.cson > syntaxes/css.tmLanguage.json", + "test": "node --test ./src/vscode-css/spec/css-spec.mjs" } }