Merge commit '02b4ee757654b7d54fe35352fd8e53a8a4385d42'

pull/708/head
Wilfred Hughes 2024-04-28 17:32:23 +07:00
commit 688aaa83f8
29 changed files with 10970 additions and 5920 deletions

@ -0,0 +1,20 @@
module.exports = {
'env': {
'commonjs': true,
'es2021': true,
},
'extends': 'google',
'overrides': [
],
'parserOptions': {
'ecmaVersion': 'latest',
'sourceType': 'module',
},
'rules': {
'indent': ['error', 2, {'SwitchCase': 1}],
'max-len': [
'error',
{'code': 120, 'ignoreComments': true, 'ignoreUrls': true, 'ignoreStrings': true},
],
},
};

@ -1,2 +1,10 @@
/src/** linguist-vendored
/examples/* linguist-vendored
src/grammar.json linguist-generated
src/node-types.json linguist-generated
src/parser.c linguist-generated
src/grammar.json -diff
src/node-types.json -diff
src/parser.c -diff

@ -0,0 +1,60 @@
name: Bug Report
description: File a bug or issue
title: "bug: "
labels: [bug]
body:
- type: markdown
attributes:
value: |
**Before** reporting an issue, make sure to search [existing issues](https://github.com/tree-sitter/tree-sitter-css/issues). Usage questions such as ***"How do I...?"*** either belong in [Discussions](https://github.com/tree-sitter/tree-sitter/discussions) upstream or in our [Discord server](https://discord.gg/w7nTvsVJhm) and will be closed.
If your issue is related to a bug in your editor-experience because your editor *leverages* tree-sitter and this parser, then it is likely your issue does *NOT* belong here and belongs in the relevant editor's repository.
- type: checkboxes
attributes:
label: Did you check existing issues?
description: Make sure you've checked all of the below before submitting an issue
options:
- label: I have read all the [tree-sitter docs](https://tree-sitter.github.io/tree-sitter/using-parsers) if it relates to using the parser
required: false
- label: I have searched the existing issues of tree-sitter-css
required: true
- type: input
attributes:
label: "Tree-Sitter CLI Version, if relevant (output of `tree-sitter --version`)"
placeholder: "tree-sitter 0.20.8 (6bbb50bef8249e6460e7d69e42cc8146622fa4fd)"
validations:
required: false
- type: textarea
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is. Please include any related errors you see such as parsing errors or tree-sitter cli errors.
validations:
required: true
- type: textarea
attributes:
label: Steps To Reproduce/Bad Parse Tree
description: Steps to reproduce the behavior. If you have a bad parse tree, please include it here. You can get this by running `tree-sitter parse <path-to-file>` and copying the output.
placeholder: |
1.
2.
3.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior/Parse Tree
description: A concise description of what you expected to happen, or in the case of a bad parse tree, the expected parse tree.
validations:
required: true
- type: textarea
attributes:
label: Repro
description: Minimal code to reproduce this issue. Ideally this should be reproducible with the C library or the tree-sitter cli, do not suggest an editor or external tool.
value: |
/* Example code that fails to parse */
.foo {
color: red;
/* Code that fails to parse, or causes an error */
}
render: CSS
validations:
required: false

@ -0,0 +1,36 @@
name: Feature Request
description: Suggest a new feature
title: "feature: "
labels: [enhancement]
body:
- type: checkboxes
attributes:
label: Did you check the tree-sitter docs?
description: Make sure you read all the docs before submitting a feature request
options:
- label: I have read all the [tree-sitter docs](https://tree-sitter.github.io/tree-sitter/using-parsers) if it relates to using the parser
required: false
- type: textarea
validations:
required: true
attributes:
label: Is your feature request related to a problem? Please describe.
description: A clear and concise description of what the problem is. Ex. I think the grammar models this rule incorrectly and can be improved, or the scanner can be improved by doing [...], or CSS has officially added a new feature that should be added to the grammar.
- type: textarea
validations:
required: true
attributes:
label: Describe the solution you'd like
description: A clear and concise description of what you want to happen.
- type: textarea
validations:
required: true
attributes:
label: Describe alternatives you've considered
description: A clear and concise description of any alternative solutions or features you've considered.
- type: textarea
validations:
required: false
attributes:
label: Additional context
description: Add any other context or screenshots about the feature request here. If your feature request is related to a new CSS feature, please include a link to the relevant **official** CSS documentation.

@ -0,0 +1,32 @@
name: CI
on:
pull_request:
branches:
- "**"
push:
branches:
- "master"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
os: [macos-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: npm install
- run: npm test
test_windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: npm install
- run: npm run-script test-windows

@ -0,0 +1,22 @@
name: Fuzz Parser
on:
push:
paths:
- src/scanner.c
pull_request:
paths:
- src/scanner.c
workflow_dispatch:
jobs:
test:
name: Parser fuzzing
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: vigoux/tree-sitter-fuzz-action@v1
with:
language: css
external-scanner: src/scanner.c
time: 60

@ -0,0 +1,19 @@
name: Lint
on:
push:
branches:
- master
pull_request:
branches:
- "**"
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install modules
run: npm install
- name: Run ESLint
run: npm run lint

@ -0,0 +1,103 @@
name: Release
on:
workflow_run:
workflows: ["CI"]
branches:
- master
types:
- completed
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get previous commit SHA
id: get_previous_commit
run: |
LATEST_TAG=$(git describe --tags --abbrev=0)
if [[ -z "$LATEST_TAG" ]]; then
echo "No tag found. Failing..."
exit 1
fi
echo "latest_tag=${LATEST_TAG#v}" >> "$GITHUB_ENV" # Remove 'v' prefix from the tag
- name: Check if version changed and is greater than the previous
id: version_check
run: |
# Compare the current version with the version from the previous commit
PREVIOUS_NPM_VERSION=${{ env.latest_tag }}
CURRENT_NPM_VERSION=$(jq -r '.version' package.json)
CURRENT_CARGO_VERSION=$(awk -F '"' '/^version/ {print $2}' Cargo.toml)
if [[ "$CURRENT_NPM_VERSION" != "$CURRENT_CARGO_VERSION" ]]; then # Cargo.toml and package.json versions must match
echo "Mismatch: NPM version ($CURRENT_NPM_VERSION) and Cargo.toml version ($CURRENT_CARGO_VERSION)"
echo "version_changed=false" >> "$GITHUB_ENV"
else
if [[ "$PREVIOUS_NPM_VERSION" == "$CURRENT_NPM_VERSION" ]]; then
echo "version_changed=" >> "$GITHUB_ENV"
else
IFS='.' read -ra PREVIOUS_VERSION_PARTS <<< "$PREVIOUS_NPM_VERSION"
IFS='.' read -ra CURRENT_VERSION_PARTS <<< "$CURRENT_NPM_VERSION"
VERSION_CHANGED=false
for i in "${!PREVIOUS_VERSION_PARTS[@]}"; do
if [[ ${CURRENT_VERSION_PARTS[i]} -gt ${PREVIOUS_VERSION_PARTS[i]} ]]; then
VERSION_CHANGED=true
break
elif [[ ${CURRENT_VERSION_PARTS[i]} -lt ${PREVIOUS_VERSION_PARTS[i]} ]]; then
break
fi
done
echo "version_changed=$VERSION_CHANGED" >> "$GITHUB_ENV"
echo "current_version=${CURRENT_NPM_VERSION}" >> "$GITHUB_ENV"
fi
fi
- name: Display result
run: |
echo "Version bump detected: ${{ env.version_changed }}"
- name: Fail if version is lower
if: env.version_changed == 'false'
run: exit 1
- name: Setup Node
if: env.version_changed == 'true'
uses: actions/setup-node@v4
with:
node-version: 18
registry-url: "https://registry.npmjs.org"
- name: Publish to NPM
if: env.version_changed == 'true'
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
run: npm publish
- name: Setup Rust
if: env.version_changed == 'true'
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Publish to Crates.io
if: env.version_changed == 'true'
uses: katyo/publish-crates@v2
with:
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
- name: Tag versions
if: env.version_changed == 'true'
run: |
git checkout master
git config user.name github-actions[bot]
git config user.email github-actions[bot]@users.noreply.github.com
git tag -d "v${{ env.current_version }}" || true
git push origin --delete "v${{ env.current_version }}" || true
git tag -a "v${{ env.current_version }}" -m "Version ${{ env.current_version }}"
git push origin "v${{ env.current_version }}"

@ -1,6 +1,6 @@
Cargo.lock
node_modules
build
target
*.log
package-lock.json
Cargo.lock
/target/
.build/

@ -1,3 +1,5 @@
build
target
Cargo.lock
/test
/examples
/build
/script
/target

@ -1,27 +1,27 @@
[package]
name = "tree-sitter-css"
description = "css grammar for the tree-sitter parsing library"
version = "0.19.0"
description = "CSS grammar for tree-sitter"
version = "0.20.0"
authors = [
"Max Brunsfeld <maxbrunsfeld@gmail.com>",
"Amaan Qureshi <amaanq12@gmail.com>",
]
license = "MIT"
readme = "bindings/rust/README.md"
keywords = ["incremental", "parsing", "css"]
categories = ["parsing", "text-editors"]
repository = "https://github.com/tree-sitter/tree-sitter-javascript"
edition = "2018"
license = "MIT"
authors = ["Max Brunsfeld <maxbrunsfeld@gmail.com>"]
repository = "https://github.com/tree-sitter/tree-sitter-css"
edition = "2021"
autoexamples = false
build = "bindings/rust/build.rs"
include = [
"bindings/rust/*",
"grammar.js",
"queries/*",
"src/*",
]
include = ["bindings/rust/*", "grammar.js", "queries/*", "src/*"]
[lib]
path = "bindings/rust/lib.rs"
[dependencies]
tree-sitter = ">= 0.19"
tree-sitter = "~0.20.10"
[build-dependencies]
cc = "1.0"
cc = "~1.0"

@ -0,0 +1,33 @@
// swift-tools-version:5.3
import PackageDescription
let package = Package(
name: "TreeSitterCSS",
products: [
.library(name: "TreeSitterCSS", targets: ["TreeSitterCSS"]),
],
dependencies: [],
targets: [
.target(name: "TreeSitterCSS",
path: ".",
exclude: [
"binding.gyp",
"bindings",
"Cargo.toml",
"corpus",
"grammar.js",
"LICENSE",
"package.json",
"README.md",
],
sources: [
"src/parser.c",
"src/scanner.c",
],
resources: [
.copy("queries")
],
publicHeadersPath: "bindings/swift",
cSettings: [.headerSearchPath("src")])
]
)

@ -1,11 +1,19 @@
tree-sitter-css
===============
# tree-sitter-css
[![Build Status](https://travis-ci.org/tree-sitter/tree-sitter-css.svg?branch=master)](https://travis-ci.org/tree-sitter/tree-sitter-css)
[![Build status](https://ci.appveyor.com/api/projects/status/smdphgf4ns9jglw5/branch/master?svg=true)](https://ci.appveyor.com/project/maxbrunsfeld/tree-sitter-css/branch/master)
[![CI][ci]](https://github.com/tree-sitter/tree-sitter-css/actions/workflows/ci.yml)
[![discord][discord]](https://discord.gg/w7nTvsVJhm)
[![matrix][matrix]](https://matrix.to/#/#tree-sitter-chat:matrix.org)
[![crates][crates]](https://crates.io/crates/tree-sitter-css)
[![npm][npm]](https://www.npmjs.com/package/tree-sitter-css)
CSS grammar for [tree-sitter](https://github.com/tree-sitter/tree-sitter).
References
* [CSS Syntax Guide](https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax)
- [CSS Syntax Guide](https://developer.mozilla.org/en-US/docs/Web/CSS/Syntax)
[ci]: https://img.shields.io/github/actions/workflow/status/tree-sitter/tree-sitter-css/ci.yml?logo=github&label=CI
[discord]: https://img.shields.io/discord/1063097320771698699?logo=discord&label=discord
[matrix]: https://img.shields.io/matrix/tree-sitter-chat%3Amatrix.org?logo=matrix&label=matrix
[npm]: https://img.shields.io/npm/v/tree-sitter-css?logo=npm
[crates]: https://img.shields.io/crates/v/tree-sitter-css?logo=rust

@ -0,0 +1,36 @@
# tree-sitter-css
This crate provides a CSS grammar for the [tree-sitter][] parsing library.
To use this crate, add it to the `[dependencies]` section of your `Cargo.toml`
file. (Note that you will probably also need to depend on the
[`tree-sitter`][tree-sitter crate] crate to use the parsed result in any useful
way.)
```toml
[dependencies]
tree-sitter = "~0.20.10"
tree-sitter-css = "0.20.0"
```
Typically, you will use the [language][language func] function to add this
grammar to a tree-sitter [Parser][], and then use the parser to parse some code:
```rust
let code = r#"
.foo {
color: red;
}
"#;
let mut parser = Parser::new();
parser.set_language(tree_sitter_css::language()).expect("Error loading CSS grammar");
let parsed = parser.parse(code, None);
```
If you have any questions, please reach out to us in the [tree-sitter
discussions] page.
[language func]: https://docs.rs/tree-sitter-css/*/tree_sitter_css/fn.language.html
[Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html
[tree-sitter]: https://tree-sitter.github.io/
[tree-sitter crate]: https://crates.io/crates/tree-sitter
[tree-sitter discussions]: https://github.com/tree-sitter/tree-sitter/discussions

@ -1,16 +1,19 @@
fn main() {
let src_dir = std::path::Path::new("src");
let mut c_config = cc::Build::new();
c_config.include(&src_dir);
c_config.include(src_dir);
c_config
.flag_if_supported("-Wno-unused-parameter")
.flag_if_supported("-Wno-unused-but-set-variable")
.flag_if_supported("-Wno-trigraphs");
let parser_path = src_dir.join("parser.c");
let scanner_path = src_dir.join("scanner.c");
c_config.file(&parser_path);
let scanner_path = src_dir.join("scanner.c");
c_config.file(&scanner_path);
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
c_config.compile("parser");
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
}

@ -6,7 +6,7 @@
//! ```
//! let code = "";
//! let mut parser = tree_sitter::Parser::new();
//! parser.set_language(tree_sitter_css::language()).expect("Error loading css grammar");
//! parser.set_language(tree_sitter_css::language()).expect("Error loading CSS grammar");
//! let tree = parser.parse(code, None).unwrap();
//! ```
//!
@ -31,9 +31,9 @@ pub fn language() -> Language {
/// The content of the [`node-types.json`][] file for this grammar.
///
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types
pub const NODE_TYPES: &'static str = include_str!("../../src/node-types.json");
pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
pub const HIGHLIGHTS_QUERY: &'static str = include_str!("../../queries/highlights.scm");
pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm");
#[cfg(test)]
mod tests {
@ -42,6 +42,6 @@ mod tests {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(super::language())
.expect("Error loading css language");
.expect("Error loading CSS grammar");
}
}

@ -0,0 +1,16 @@
#ifndef TREE_SITTER_CSS_H_
#define TREE_SITTER_CSS_H_
typedef struct TSLanguage TSLanguage;
#ifdef __cplusplus
extern "C" {
#endif
extern TSLanguage *tree_sitter_css();
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_CSS_H_

@ -1,17 +1,29 @@
/**
* @file CSS grammar for tree-sitter
* @author Max Brunsfeld <maxbrunsfeld@gmail.com>
* @author Amaan Qureshi <amaanq12@gmail.com>
* @license MIT
*/
/* eslint-disable arrow-parens */
/* eslint-disable camelcase */
/* eslint-disable-next-line spaced-comment */
/// <reference types="tree-sitter-cli/dsl" />
// @ts-check
module.exports = grammar({
name: 'css',
extras: $ => [
/\s/,
$.comment,
$.js_comment,
],
externals: $ => [
$._descendant_operator,
],
conflicts: $ => [
[$._selector, $.declaration],
$._pseudo_class_selector_colon,
$.__error_recovery,
],
inline: $ => [
@ -31,7 +43,7 @@ module.exports = grammar({
$.namespace_statement,
$.keyframes_statement,
$.supports_statement,
$.at_rule
$.at_rule,
),
// Statements
@ -40,32 +52,32 @@ module.exports = grammar({
'@import',
$._value,
sep(',', $._query),
';'
';',
),
media_statement: $ => seq(
'@media',
sep1(',', $._query),
$.block
$.block,
),
charset_statement: $ => seq(
'@charset',
$._value,
';'
';',
),
namespace_statement: $ => seq(
'@namespace',
optional(alias($.identifier, $.namespace_name)),
choice($.string_value, $.call_expression),
';'
';',
),
keyframes_statement: $ => seq(
choice(
'@keyframes',
alias(/@[-a-z]+keyframes/, $.at_keyword)
alias(/@[-a-z]+keyframes/, $.at_keyword),
),
alias($.identifier, $.keyframes_name),
$.keyframe_block_list,
@ -74,42 +86,49 @@ module.exports = grammar({
keyframe_block_list: $ => seq(
'{',
repeat($.keyframe_block),
'}'
'}',
),
keyframe_block: $ => seq(
choice($.from, $.to, $.integer_value),
$.block
$.block,
),
from: $ => 'from',
to: $ => 'to',
from: _ => 'from',
to: _ => 'to',
supports_statement: $ => seq(
'@supports',
$._query,
$.block
$.block,
),
postcss_statement: $ => prec(-1, seq(
$.at_keyword,
repeat($._value),
';',
)),
at_rule: $ => seq(
$.at_keyword,
sep(',', $._query),
choice(';', $.block)
choice(';', $.block),
),
// Rule sets
rule_set: $ => seq(
$.selectors,
$.block
$.block,
),
selectors: $ => sep1(',', $._selector),
block: $ => seq('{',
block: $ => seq(
'{',
repeat($._block_item),
optional(alias($.last_declaration, $.declaration)),
'}'
'}',
),
_block_item: $ => choice(
@ -121,7 +140,8 @@ module.exports = grammar({
$.namespace_statement,
$.keyframes_statement,
$.supports_statement,
$.at_rule
$.postcss_statement,
$.at_rule,
),
// Selectors
@ -139,12 +159,13 @@ module.exports = grammar({
$.child_selector,
$.descendant_selector,
$.sibling_selector,
$.adjacent_sibling_selector
$.adjacent_sibling_selector,
$.namespace_selector,
),
nesting_selector: $ => '&',
nesting_selector: _ => '&',
universal_selector: $ => '*',
universal_selector: _ => '*',
class_selector: $ => prec(1, seq(
optional($._selector),
@ -154,33 +175,33 @@ module.exports = grammar({
pseudo_class_selector: $ => seq(
optional($._selector),
':',
alias($._pseudo_class_selector_colon, ':'),
alias($.identifier, $.class_name),
optional(alias($.pseudo_class_arguments, $.arguments))
optional(alias($.pseudo_class_arguments, $.arguments)),
),
pseudo_element_selector: $ => seq(
optional($._selector),
'::',
alias($.identifier, $.tag_name),
optional(alias($.pseudo_element_arguments, $.arguments))
optional(alias($.pseudo_element_arguments, $.arguments)),
),
id_selector: $ => seq(
optional($._selector),
'#',
alias($.identifier, $.id_name)
alias($.identifier, $.id_name),
),
attribute_selector: $ => seq(
optional($._selector),
'[',
alias($.identifier, $.attribute_name),
alias(choice($.identifier, $.namespace_selector), $.attribute_name),
optional(seq(
choice('=', '~=', '^=', '|=', '*=', '$='),
$._value
$._value,
)),
']'
']',
),
child_selector: $ => prec.left(seq($._selector, '>', $._selector)),
@ -191,16 +212,18 @@ module.exports = grammar({
adjacent_sibling_selector: $ => prec.left(seq($._selector, '+', $._selector)),
namespace_selector: $ => prec.left(seq($._selector, '|', $._selector)),
pseudo_class_arguments: $ => seq(
token.immediate('('),
sep(',', choice($._selector, repeat1($._value))),
')'
')',
),
pseudo_element_arguments: $ => seq(
token.immediate('('),
sep(',', choice($._selector, repeat1($._value))),
')'
')',
),
// Declarations
@ -211,10 +234,10 @@ module.exports = grammar({
$._value,
repeat(seq(
optional(','),
$._value
$._value,
)),
optional($.important),
';'
';',
),
last_declaration: $ => prec(1, seq(
@ -223,12 +246,12 @@ module.exports = grammar({
$._value,
repeat(seq(
optional(','),
$._value
$._value,
)),
optional($.important)
optional($.important),
)),
important: $ => '!important',
important: _ => '!important',
// Media queries
@ -238,7 +261,7 @@ module.exports = grammar({
$.binary_query,
$.unary_query,
$.selector_query,
$.parenthesized_query
$.parenthesized_query,
),
feature_query: $ => seq(
@ -246,31 +269,31 @@ module.exports = grammar({
alias($.identifier, $.feature_name),
':',
repeat1($._value),
')'
')',
),
parenthesized_query: $ => seq(
'(',
$._query,
')'
')',
),
binary_query: $ => prec.left(seq(
$._query,
choice('and', 'or'),
$._query
$._query,
)),
unary_query: $ => prec(1, seq(
choice('not', 'only'),
$._query
$._query,
)),
selector_query: $ => seq(
'selector',
'(',
$._selector,
')'
')',
),
// Property Values
@ -282,30 +305,32 @@ module.exports = grammar({
$.integer_value,
$.float_value,
$.string_value,
$.grid_value,
$.binary_expression,
$.parenthesized_value,
$.call_expression
$.call_expression,
$.important,
)),
parenthesized_value: $ => seq(
'(',
$._value,
')'
')',
),
color_value: $ => seq('#', token.immediate(/[0-9a-fA-F]{3,8}/)),
color_value: _ => seq('#', token.immediate(/[0-9a-fA-F]{3,8}/)),
string_value: $ => token(choice(
seq("'", /([^'\n]|\\(.|\n))*/, "'"),
seq('"', /([^"\n]|\\(.|\n))*/, '"')
)),
string_value: _ => choice(
seq('\'', /([^'\n]|\\(.|\n))*/, '\''),
seq('"', /([^"\n]|\\(.|\n))*/, '"'),
),
integer_value: $ => seq(
token(seq(
optional(choice('+', '-')),
/\d+/
/\d+/,
)),
optional($.unit)
optional($.unit),
),
float_value: $ => seq(
@ -315,59 +340,87 @@ module.exports = grammar({
choice(
seq('.', /\d+/),
seq(/[eE]/, optional('-'), /\d+/),
seq('.', /\d+/, /[eE]/, optional('-'), /\d+/)
)
seq('.', /\d+/, /[eE]/, optional('-'), /\d+/),
),
)),
optional($.unit)
optional($.unit),
),
unit: $ => token.immediate(/[a-zA-Z%]+/),
unit: _ => token.immediate(/[a-zA-Z%]+/),
grid_value: $ => seq(
'[',
sep1(',', $._value),
']',
),
call_expression: $ => seq(
alias($.identifier, $.function_name),
$.arguments
$.arguments,
),
binary_expression: $ => prec.left(seq(
$._value,
choice('+', '-', '*', '/'),
$._value
$._value,
)),
arguments: $ => seq(
token.immediate('('),
sep(choice(',', ';'), repeat1($._value)),
')'
')',
),
identifier: $ => /(--|-?[a-zA-Z_])[a-zA-Z0-9-_]*/,
identifier: _ => /(--|-?[a-zA-Z_])[a-zA-Z0-9-_]*/,
at_keyword: _ => /@[a-zA-Z-_]+/,
at_keyword: $ => /@[a-zA-Z-_]+/,
js_comment: _ => token(prec(-1, seq('//', /.*/))),
comment: $ => token(seq(
comment: _ => token(seq(
'/*',
/[^*]*\*+([^/*][^*]*\*+)*/,
'/'
'/',
)),
plain_value: $ => token(seq(
plain_value: _ => token(seq(
repeat(choice(
/[-_]/,
/\/[^\*\s,;!{}()\[\]]/ // Slash not followed by a '*' (which would be a comment)
/\/[^\*\s,;!{}()\[\]]/, // Slash not followed by a '*' (which would be a comment)
)),
/[a-zA-Z]/,
repeat(choice(
/[^/\s,;!{}()\[\]]/, // Not a slash, not a delimiter character
/\/[^\*\s,;!{}()\[\]]/ // Slash not followed by a '*' (which would be a comment)
))
))
}
})
function sep (separator, rule) {
return optional(sep1(separator, rule))
/[^/\s,;!{}()\[\]]/, // Not a slash, not a delimiter character
/\/[^\*\s,;!{}()\[\]]/, // Slash not followed by a '*' (which would be a comment)
)),
)),
},
});
/**
* Creates a rule to optionally match one or more of the rules separated by `separator`
*
* @param {RuleOrLiteral} separator
*
* @param {RuleOrLiteral} rule
*
* @return {ChoiceRule}
*
*/
function sep(separator, rule) {
return optional(sep1(separator, rule));
}
function sep1 (separator, rule) {
return seq(rule, repeat(seq(separator, rule)))
/**
* Creates a rule to match one or more of the rules separated by `separator`
*
* @param {RuleOrLiteral} separator
*
* @param {RuleOrLiteral} rule
*
* @return {SeqRule}
*
*/
function sep1(separator, rule) {
return seq(rule, repeat(seq(separator, rule)));
}

@ -5,7 +5,8 @@
"main": "bindings/node",
"keywords": [
"parser",
"lexer"
"lexer",
"css"
],
"repository": {
"type": "git",
@ -14,12 +15,17 @@
"author": "Max Brunsfeld",
"license": "MIT",
"dependencies": {
"nan": "^2.14.1"
"nan": "^2.18.0"
},
"devDependencies": {
"tree-sitter-cli": "^0.20.0"
"eslint": ">=5.16.0",
"eslint-config-google": "^0.14.0",
"tree-sitter-cli": "^0.20.8"
},
"scripts": {
"build": "tree-sitter generate && node-gyp build",
"lint": "eslint grammar.js",
"parse": "tree-sitter parse",
"test": "tree-sitter test && tree-sitter parse examples/*.css --quiet --time",
"test-windows": "tree-sitter test"
},
@ -29,7 +35,10 @@
"file-types": [
"css"
],
"injection-regex": "^css$"
"injection-regex": "^css$",
"highlights": [
"queries/highlights.scm"
]
}
]
}

@ -304,6 +304,30 @@
}
]
},
"postcss_statement": {
"type": "PREC",
"value": -1,
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "at_keyword"
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_value"
}
},
{
"type": "STRING",
"value": ";"
}
]
}
},
"at_rule": {
"type": "SEQ",
"members": [
@ -469,6 +493,10 @@
"type": "SYMBOL",
"name": "supports_statement"
},
{
"type": "SYMBOL",
"name": "postcss_statement"
},
{
"type": "SYMBOL",
"name": "at_rule"
@ -534,6 +562,10 @@
{
"type": "SYMBOL",
"name": "adjacent_sibling_selector"
},
{
"type": "SYMBOL",
"name": "namespace_selector"
}
]
},
@ -595,7 +627,12 @@
]
},
{
"type": "STRING",
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "_pseudo_class_selector_colon"
},
"named": false,
"value": ":"
},
{
@ -725,8 +762,17 @@
{
"type": "ALIAS",
"content": {
"type": "SYMBOL",
"name": "identifier"
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "identifier"
},
{
"type": "SYMBOL",
"name": "namespace_selector"
}
]
},
"named": true,
"value": "attribute_name"
@ -867,6 +913,27 @@
]
}
},
"namespace_selector": {
"type": "PREC_LEFT",
"value": 0,
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_selector"
},
{
"type": "STRING",
"value": "|"
},
{
"type": "SYMBOL",
"name": "_selector"
}
]
}
},
"pseudo_class_arguments": {
"type": "SEQ",
"members": [
@ -1337,6 +1404,10 @@
"type": "SYMBOL",
"name": "string_value"
},
{
"type": "SYMBOL",
"name": "grid_value"
},
{
"type": "SYMBOL",
"name": "binary_expression"
@ -1348,6 +1419,10 @@
{
"type": "SYMBOL",
"name": "call_expression"
},
{
"type": "SYMBOL",
"name": "important"
}
]
}
@ -1386,46 +1461,43 @@
]
},
"string_value": {
"type": "TOKEN",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "'"
},
{
"type": "PATTERN",
"value": "([^'\\n]|\\\\(.|\\n))*"
},
{
"type": "STRING",
"value": "'"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "\""
},
{
"type": "PATTERN",
"value": "([^\"\\n]|\\\\(.|\\n))*"
},
{
"type": "STRING",
"value": "\""
}
]
}
]
}
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "'"
},
{
"type": "PATTERN",
"value": "([^'\\n]|\\\\(.|\\n))*"
},
{
"type": "STRING",
"value": "'"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "\""
},
{
"type": "PATTERN",
"value": "([^\"\\n]|\\\\(.|\\n))*"
},
{
"type": "STRING",
"value": "\""
}
]
}
]
},
"integer_value": {
"type": "SEQ",
@ -1610,6 +1682,44 @@
"value": "[a-zA-Z%]+"
}
},
"grid_value": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "["
},
{
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "_value"
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": ","
},
{
"type": "SYMBOL",
"name": "_value"
}
]
}
}
]
},
{
"type": "STRING",
"value": "]"
}
]
},
"call_expression": {
"type": "SEQ",
"members": [
@ -1738,6 +1848,26 @@
"type": "PATTERN",
"value": "@[a-zA-Z-_]+"
},
"js_comment": {
"type": "TOKEN",
"content": {
"type": "PREC",
"value": -1,
"content": {
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "//"
},
{
"type": "PATTERN",
"value": ".*"
}
]
}
}
},
"comment": {
"type": "TOKEN",
"content": {
@ -1811,19 +1941,26 @@
{
"type": "SYMBOL",
"name": "comment"
},
{
"type": "SYMBOL",
"name": "js_comment"
}
],
"conflicts": [
[
"_selector",
"declaration"
]
],
"conflicts": [],
"precedences": [],
"externals": [
{
"type": "SYMBOL",
"name": "_descendant_operator"
},
{
"type": "SYMBOL",
"name": "_pseudo_class_selector_colon"
},
{
"type": "SYMBOL",
"name": "__error_recovery"
}
],
"inline": [

@ -31,6 +31,10 @@
"type": "id_selector",
"named": true
},
{
"type": "namespace_selector",
"named": true
},
{
"type": "nesting_selector",
"named": true
@ -106,14 +110,26 @@
"type": "float_value",
"named": true
},
{
"type": "grid_value",
"named": true
},
{
"type": "id_selector",
"named": true
},
{
"type": "important",
"named": true
},
{
"type": "integer_value",
"named": true
},
{
"type": "namespace_selector",
"named": true
},
{
"type": "nesting_selector",
"named": true
@ -196,6 +212,73 @@
]
}
},
{
"type": "attribute_name",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "adjacent_sibling_selector",
"named": true
},
{
"type": "attribute_selector",
"named": true
},
{
"type": "child_selector",
"named": true
},
{
"type": "class_selector",
"named": true
},
{
"type": "descendant_selector",
"named": true
},
{
"type": "id_selector",
"named": true
},
{
"type": "namespace_selector",
"named": true
},
{
"type": "nesting_selector",
"named": true
},
{
"type": "pseudo_class_selector",
"named": true
},
{
"type": "pseudo_element_selector",
"named": true
},
{
"type": "sibling_selector",
"named": true
},
{
"type": "string_value",
"named": true
},
{
"type": "tag_name",
"named": true
},
{
"type": "universal_selector",
"named": true
}
]
}
},
{
"type": "attribute_selector",
"named": true,
@ -244,14 +327,26 @@
"type": "float_value",
"named": true
},
{
"type": "grid_value",
"named": true
},
{
"type": "id_selector",
"named": true
},
{
"type": "important",
"named": true
},
{
"type": "integer_value",
"named": true
},
{
"type": "namespace_selector",
"named": true
},
{
"type": "nesting_selector",
"named": true
@ -315,6 +410,14 @@
"type": "float_value",
"named": true
},
{
"type": "grid_value",
"named": true
},
{
"type": "important",
"named": true
},
{
"type": "integer_value",
"named": true
@ -405,6 +508,10 @@
"type": "namespace_statement",
"named": true
},
{
"type": "postcss_statement",
"named": true
},
{
"type": "rule_set",
"named": true
@ -459,6 +566,14 @@
"type": "float_value",
"named": true
},
{
"type": "grid_value",
"named": true
},
{
"type": "important",
"named": true
},
{
"type": "integer_value",
"named": true
@ -510,6 +625,10 @@
"type": "id_selector",
"named": true
},
{
"type": "namespace_selector",
"named": true
},
{
"type": "nesting_selector",
"named": true
@ -577,6 +696,10 @@
"type": "id_selector",
"named": true
},
{
"type": "namespace_selector",
"named": true
},
{
"type": "nesting_selector",
"named": true
@ -637,6 +760,10 @@
"type": "float_value",
"named": true
},
{
"type": "grid_value",
"named": true
},
{
"type": "important",
"named": true
@ -696,6 +823,10 @@
"type": "id_selector",
"named": true
},
{
"type": "namespace_selector",
"named": true
},
{
"type": "nesting_selector",
"named": true
@ -755,6 +886,14 @@
"type": "float_value",
"named": true
},
{
"type": "grid_value",
"named": true
},
{
"type": "important",
"named": true
},
{
"type": "integer_value",
"named": true
@ -789,6 +928,57 @@
]
}
},
{
"type": "grid_value",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "binary_expression",
"named": true
},
{
"type": "call_expression",
"named": true
},
{
"type": "color_value",
"named": true
},
{
"type": "float_value",
"named": true
},
{
"type": "grid_value",
"named": true
},
{
"type": "important",
"named": true
},
{
"type": "integer_value",
"named": true
},
{
"type": "parenthesized_value",
"named": true
},
{
"type": "plain_value",
"named": true
},
{
"type": "string_value",
"named": true
}
]
}
},
{
"type": "id_selector",
"named": true,
@ -825,6 +1015,10 @@
"type": "id_selector",
"named": true
},
{
"type": "namespace_selector",
"named": true
},
{
"type": "nesting_selector",
"named": true
@ -888,6 +1082,14 @@
"type": "float_value",
"named": true
},
{
"type": "grid_value",
"named": true
},
{
"type": "important",
"named": true
},
{
"type": "integer_value",
"named": true
@ -1042,6 +1244,73 @@
]
}
},
{
"type": "namespace_selector",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "adjacent_sibling_selector",
"named": true
},
{
"type": "attribute_selector",
"named": true
},
{
"type": "child_selector",
"named": true
},
{
"type": "class_selector",
"named": true
},
{
"type": "descendant_selector",
"named": true
},
{
"type": "id_selector",
"named": true
},
{
"type": "namespace_selector",
"named": true
},
{
"type": "nesting_selector",
"named": true
},
{
"type": "pseudo_class_selector",
"named": true
},
{
"type": "pseudo_element_selector",
"named": true
},
{
"type": "sibling_selector",
"named": true
},
{
"type": "string_value",
"named": true
},
{
"type": "tag_name",
"named": true
},
{
"type": "universal_selector",
"named": true
}
]
}
},
{
"type": "namespace_statement",
"named": true,
@ -1124,6 +1393,69 @@
"type": "float_value",
"named": true
},
{
"type": "grid_value",
"named": true
},
{
"type": "important",
"named": true
},
{
"type": "integer_value",
"named": true
},
{
"type": "parenthesized_value",
"named": true
},
{
"type": "plain_value",
"named": true
},
{
"type": "string_value",
"named": true
}
]
}
},
{
"type": "postcss_statement",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": true,
"types": [
{
"type": "at_keyword",
"named": true
},
{
"type": "binary_expression",
"named": true
},
{
"type": "call_expression",
"named": true
},
{
"type": "color_value",
"named": true
},
{
"type": "float_value",
"named": true
},
{
"type": "grid_value",
"named": true
},
{
"type": "important",
"named": true
},
{
"type": "integer_value",
"named": true
@ -1183,6 +1515,10 @@
"type": "id_selector",
"named": true
},
{
"type": "namespace_selector",
"named": true
},
{
"type": "nesting_selector",
"named": true
@ -1250,6 +1586,10 @@
"type": "id_selector",
"named": true
},
{
"type": "namespace_selector",
"named": true
},
{
"type": "nesting_selector",
"named": true
@ -1332,6 +1672,10 @@
"type": "id_selector",
"named": true
},
{
"type": "namespace_selector",
"named": true
},
{
"type": "nesting_selector",
"named": true
@ -1395,6 +1739,10 @@
"type": "id_selector",
"named": true
},
{
"type": "namespace_selector",
"named": true
},
{
"type": "nesting_selector",
"named": true
@ -1458,6 +1806,10 @@
"type": "id_selector",
"named": true
},
{
"type": "namespace_selector",
"named": true
},
{
"type": "nesting_selector",
"named": true
@ -1489,6 +1841,11 @@
]
}
},
{
"type": "string_value",
"named": true,
"fields": {}
},
{
"type": "stylesheet",
"named": true,
@ -1615,6 +1972,10 @@
"named": true,
"fields": {}
},
{
"type": "\"",
"named": false
},
{
"type": "#",
"named": false
@ -1623,6 +1984,10 @@
"type": "$=",
"named": false
},
{
"type": "'",
"named": false
},
{
"type": "(",
"named": false
@ -1723,10 +2088,6 @@
"type": "at_keyword",
"named": true
},
{
"type": "attribute_name",
"named": true
},
{
"type": "class_name",
"named": true
@ -1755,6 +2116,10 @@
"type": "important",
"named": true
},
{
"type": "js_comment",
"named": true
},
{
"type": "keyframes_name",
"named": true
@ -1795,10 +2160,6 @@
"type": "selector",
"named": false
},
{
"type": "string_value",
"named": true
},
{
"type": "tag_name",
"named": true
@ -1815,6 +2176,10 @@
"type": "{",
"named": false
},
{
"type": "|",
"named": false
},
{
"type": "|=",
"named": false

File diff suppressed because it is too large Load Diff

@ -1,53 +1,84 @@
#include <tree_sitter/parser.h>
#include "tree_sitter/parser.h"
#include <wctype.h>
enum TokenType {
DESCENDANT_OP,
DESCENDANT_OP,
PSEUDO_CLASS_SELECTOR_COLON,
ERROR_RECOVERY,
};
static inline void advance(TSLexer *lexer) { lexer->advance(lexer, false); }
static inline void skip(TSLexer *lexer) { lexer->advance(lexer, true); }
void *tree_sitter_css_external_scanner_create() { return NULL; }
void tree_sitter_css_external_scanner_destroy(void *p) {}
void tree_sitter_css_external_scanner_reset(void *p) {}
unsigned tree_sitter_css_external_scanner_serialize(void *p, char *buffer) { return 0; }
void tree_sitter_css_external_scanner_deserialize(void *p, const char *b, unsigned n) {}
bool tree_sitter_css_external_scanner_scan(void *payload, TSLexer *lexer, const bool *valid_symbols) {
if (iswspace(lexer->lookahead) && valid_symbols[DESCENDANT_OP]) {
lexer->result_symbol = DESCENDANT_OP;
void tree_sitter_css_external_scanner_destroy(void *payload) {}
lexer->advance(lexer, true);
while (iswspace(lexer->lookahead)) {
lexer->advance(lexer, true);
void tree_sitter_css_external_scanner_reset(void *payload) {}
unsigned tree_sitter_css_external_scanner_serialize(void *payload, char *buffer) { return 0; }
void tree_sitter_css_external_scanner_deserialize(void *payload, const char *buffer, unsigned length) {}
bool tree_sitter_css_external_scanner_scan(void *payload, TSLexer *lexer, const bool *valid_symbols) {
if (valid_symbols[ERROR_RECOVERY]) {
return false;
}
lexer->mark_end(lexer);
if (
lexer->lookahead == '#' ||
lexer->lookahead == '.' ||
lexer->lookahead == '[' ||
lexer->lookahead == '-' ||
lexer->lookahead == '*' ||
iswalnum(lexer->lookahead)
) {
return true;
if (iswspace(lexer->lookahead) && valid_symbols[DESCENDANT_OP]) {
lexer->result_symbol = DESCENDANT_OP;
lexer->advance(lexer, true);
while (iswspace(lexer->lookahead)) {
lexer->advance(lexer, true);
}
lexer->mark_end(lexer);
if (lexer->lookahead == '#' || lexer->lookahead == '.' || lexer->lookahead == '[' || lexer->lookahead == '-' ||
lexer->lookahead == '*' || iswalnum(lexer->lookahead)) {
return true;
}
if (lexer->lookahead == ':') {
lexer->advance(lexer, false);
if (iswspace(lexer->lookahead)) {
return false;
}
for (;;) {
if (lexer->lookahead == ';' || lexer->lookahead == '}' || lexer->eof(lexer)) {
return false;
}
if (lexer->lookahead == '{') {
return true;
}
lexer->advance(lexer, false);
}
}
}
if (lexer->lookahead == ':') {
lexer->advance(lexer, false);
if (iswspace(lexer->lookahead)) return false;
for (;;) {
if (
lexer->lookahead == ';' ||
lexer->lookahead == '}' ||
lexer->eof(lexer)
) return false;
if (lexer->lookahead == '{') {
return true;
if (valid_symbols[PSEUDO_CLASS_SELECTOR_COLON]) {
while (iswspace(lexer->lookahead)) {
lexer->advance(lexer, true);
}
if (lexer->lookahead == ':') {
advance(lexer);
if (lexer->lookahead == ':') {
return false;
}
lexer->mark_end(lexer);
// We need a { to be a pseudo class selector, a ; indicates a property
while (lexer->lookahead != ';' && lexer->lookahead != '}' && !lexer->eof(lexer)) {
advance(lexer);
if (lexer->lookahead == '{') {
lexer->result_symbol = PSEUDO_CLASS_SELECTOR_COLON;
return true;
}
}
return false;
}
lexer->advance(lexer, false);
}
}
}
return false;
return false;
}

@ -13,9 +13,8 @@ extern "C" {
#define ts_builtin_sym_end 0
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
typedef uint16_t TSStateId;
#ifndef TREE_SITTER_API_H_
typedef uint16_t TSStateId;
typedef uint16_t TSSymbol;
typedef uint16_t TSFieldId;
typedef struct TSLanguage TSLanguage;
@ -140,7 +139,8 @@ struct TSLanguage {
lexer->advance(lexer, skip); \
start: \
skip = false; \
lookahead = lexer->lookahead;
lookahead = lexer->lookahead; \
eof = lexer->eof(lexer);
#define ADVANCE(state_value) \
{ \
@ -166,7 +166,7 @@ struct TSLanguage {
* Parse Table Macros
*/
#define SMALL_STATE(id) id - LARGE_STATE_COUNT
#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
#define STATE(id) id
@ -176,7 +176,7 @@ struct TSLanguage {
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = state_value \
.state = (state_value) \
} \
}}
@ -184,7 +184,7 @@ struct TSLanguage {
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = state_value, \
.state = (state_value), \
.repetition = true \
} \
}}

@ -209,6 +209,7 @@ a {
Comments right after numbers
=======================================
// A comment
a {
shape-outside: circle(20em/*=*/at 50% 50%);
shape-outside: inset(1em, 1em, 1em, 1em);
@ -217,6 +218,7 @@ a {
---
(stylesheet
(js_comment)
(rule_set
(selectors (tag_name))
(block
@ -267,3 +269,65 @@ div {
(declaration
(property_name)
(integer_value)))))
=============================================
No spaces after colons
=============================================
div {
all:unset;
display:flex;
justify-content:center;
}
---
(stylesheet
(rule_set
(selectors (tag_name))
(block
(declaration (property_name) (plain_value))
(declaration (property_name) (plain_value))
(declaration (property_name) (plain_value)))))
=============================================
PostCSS
=============================================
.selector {
@apply variable-a meta-variable-b lots-of-combined-properties-c !important;
}
@layer components {
.btn-blue {
@apply --mixin sm:space-x-0 left-[11%] border-foreground/20 !important;
}
}
---
(stylesheet
(rule_set
(selectors (class_selector (class_name)))
(block
(postcss_statement
(at_keyword)
(plain_value)
(plain_value)
(plain_value)
(important))))
(at_rule
(at_keyword)
(keyword_query)
(block
(rule_set
(selectors (class_selector (class_name)))
(block
(postcss_statement
(at_keyword)
(plain_value)
(plain_value)
(plain_value)
(grid_value (integer_value (unit)))
(plain_value)
(important)))))))

@ -70,6 +70,9 @@ Media statements
@media (min-height: 680px), screen and (orientation: portrait) {}
@media not all and (monochrome) {}
@media only screen {}
@media screen and (min-width: 0 0) {
.tooltipped-multiline: after {}
}
---
@ -88,7 +91,15 @@ Media statements
(media_statement
(binary_query (unary_query (keyword_query)) (parenthesized_query (keyword_query)))
(block))
(media_statement (unary_query (keyword_query)) (block)))
(media_statement (unary_query (keyword_query)) (block))
(media_statement
(binary_query
(keyword_query)
(feature_query (feature_name) (integer_value) (integer_value)))
(block
(rule_set
(selectors (pseudo_class_selector (class_selector (class_name)) (class_name)))
(block)))))
==============================
Supports statements