first commit

This commit is contained in:
2026-08-06 12:00:39 +10:00
commit 91221f5a8e
3259 changed files with 569069 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
/**
* CSS Language Object
* @implements {Language<{ LangOptions: CSSLanguageOptions; Code: CSSSourceCode; RootNode: StyleSheetPlain; Node: CssNodePlain}>}
*/
export class CSSLanguage implements Language {
/**
* The type of file to read.
* @type {"text"}
*/
fileType: "text";
/**
* The line number at which the parser starts counting.
* @type {0|1}
*/
lineStart: 0 | 1;
/**
* The column number at which the parser starts counting.
* @type {0|1}
*/
columnStart: 0 | 1;
/**
* The name of the key that holds the type of the node.
* @type {string}
*/
nodeTypeKey: string;
/**
* The visitor keys for the CSSTree AST.
* @type {Record<string, string[]>}
*/
visitorKeys: Record<string, string[]>;
/**
* The default language options.
* @type {CSSLanguageOptions}
*/
defaultLanguageOptions: CSSLanguageOptions;
/**
* Validates the language options.
* @param {CSSLanguageOptions} languageOptions The language options to validate.
* @returns {void}
* @throws {TypeError} When the language options are invalid.
*/
validateLanguageOptions(languageOptions: CSSLanguageOptions): void;
/**
* Normalizes the language options so they can be serialized.
* @param {CSSLanguageOptions} languageOptions The language options to normalize.
* @returns {CSSLanguageOptions} The normalized language options.
*/
normalizeLanguageOptions(languageOptions: CSSLanguageOptions): CSSLanguageOptions;
/**
* Parses the given file into an AST.
* @param {File} file The virtual file to parse.
* @param {Object} [context] The parsing context.
* @param {CSSLanguageOptions} [context.languageOptions] The language options to use for parsing.
* @returns {CSSParseResult} The result of parsing.
*/
parse(file: File, { languageOptions }?: {
languageOptions?: CSSLanguageOptions;
}): CSSParseResult;
/**
* Creates a new `CSSSourceCode` object from the given information.
* @param {File} file The virtual file to create a `CSSSourceCode` object from.
* @param {CSSOkParseResult} parseResult The result returned from `parse()`.
* @returns {CSSSourceCode} The new `CSSSourceCode` object.
*/
createSourceCode(file: File, parseResult: CSSOkParseResult): CSSSourceCode;
}
export type CSSOkParseResult = OkParseResult<StyleSheetPlain> & {
comments: Comment[];
lexer: Lexer;
};
export type CSSParseResult = ParseResult<StyleSheetPlain>;
/**
* DefaultSyntaxConfig type representing the structure returned by `@eslint/css-tree/definition-syntax-data`.
* This type is defined inline because it's not exported from the main `@eslint/css-tree` package.
*/
export type DefaultSyntaxConfig = Pick<SyntaxConfig, "atrules" | "types" | "properties">;
export type SyntaxExtensionCallback = (defaultSyntax: DefaultSyntaxConfig) => Partial<SyntaxConfig>;
export type CSSLanguageOptions = {
/**
* Whether to be tolerant of recoverable parsing errors.
*/
tolerant?: boolean;
/**
* Custom syntax to use for parsing.
*/
customSyntax?: Partial<SyntaxConfig> | SyntaxExtensionCallback;
};
import type { Language } from "@eslint/core";
import type { File } from "@eslint/core";
import { CSSSourceCode } from "./css-source-code.js";
import type { StyleSheetPlain } from "@eslint/css-tree";
import type { OkParseResult } from "@eslint/core";
import type { Comment } from "@eslint/css-tree";
import type { Lexer } from "@eslint/css-tree";
import type { ParseResult } from "@eslint/core";
import type { SyntaxConfig } from "@eslint/css-tree";
+270
View File
@@ -0,0 +1,270 @@
/**
* @fileoverview The CSSLanguage class.
* @author Nicholas C. Zakas
*/
//------------------------------------------------------------------------------
// Imports
//------------------------------------------------------------------------------
import { parse as originalParse, lexer as originalLexer, fork, toPlainObject, tokenTypes, } from "@eslint/css-tree";
import defaultSyntax from "@eslint/css-tree/definition-syntax-data";
import { CSSSourceCode } from "./css-source-code.js";
import { visitorKeys } from "./css-visitor-keys.js";
//-----------------------------------------------------------------------------
// Types
//-----------------------------------------------------------------------------
/**
* @import { CssNodePlain, Comment, Lexer, StyleSheetPlain, SyntaxConfig } from "@eslint/css-tree"
* @import { Language, OkParseResult, ParseResult, File, FileError } from "@eslint/core";
*/
/** @typedef {OkParseResult<StyleSheetPlain> & { comments: Comment[], lexer: Lexer }} CSSOkParseResult */
/** @typedef {ParseResult<StyleSheetPlain>} CSSParseResult */
/**
* DefaultSyntaxConfig type representing the structure returned by `@eslint/css-tree/definition-syntax-data`.
* This type is defined inline because it's not exported from the main `@eslint/css-tree` package.
* @typedef {Pick<SyntaxConfig, "atrules" | "types" | "properties">} DefaultSyntaxConfig
*/
/**
* @typedef {(defaultSyntax: DefaultSyntaxConfig) => Partial<SyntaxConfig>} SyntaxExtensionCallback
*/
/**
* @typedef {Object} CSSLanguageOptions
* @property {boolean} [tolerant] Whether to be tolerant of recoverable parsing errors.
* @property {Partial<SyntaxConfig> | SyntaxExtensionCallback} [customSyntax] Custom syntax to use for parsing.
*/
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
const blockOpenerTokenTypes = new Map([
[tokenTypes.Function, ")"],
[tokenTypes.LeftCurlyBracket, "}"],
[tokenTypes.LeftParenthesis, ")"],
[tokenTypes.LeftSquareBracket, "]"],
]);
const blockCloserTokenTypes = new Map([
[tokenTypes.RightCurlyBracket, "{"],
[tokenTypes.RightParenthesis, "("],
[tokenTypes.RightSquareBracket, "["],
]);
/**
* Recursively replaces all function values in an object with boolean true.
* Used to make objects serializable for JSON output.
* @param {Record<string,any>|unknown[]|unknown} object The object to process.
* @returns {Record<string,any>|unknown[]|unknown} A copy of the object with all functions replaced by true.
*/
function replaceFunctions(object) {
if (typeof object !== "object" || object === null) {
return object;
}
if (Array.isArray(object)) {
return object.map(replaceFunctions);
}
const result = {};
for (const key of Object.keys(object)) {
const value = object[key];
if (typeof value === "function") {
result[key] = true;
}
else if (typeof value === "object" && value !== null) {
result[key] = replaceFunctions(value);
}
else {
result[key] = value;
}
}
return result;
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
/**
* CSS Language Object
* @implements {Language<{ LangOptions: CSSLanguageOptions; Code: CSSSourceCode; RootNode: StyleSheetPlain; Node: CssNodePlain}>}
*/
export class CSSLanguage {
/**
* The type of file to read.
* @type {"text"}
*/
fileType = "text";
/**
* The line number at which the parser starts counting.
* @type {0|1}
*/
lineStart = 1;
/**
* The column number at which the parser starts counting.
* @type {0|1}
*/
columnStart = 1;
/**
* The name of the key that holds the type of the node.
* @type {string}
*/
nodeTypeKey = "type";
/**
* The visitor keys for the CSSTree AST.
* @type {Record<string, string[]>}
*/
visitorKeys = visitorKeys;
/**
* The default language options.
* @type {CSSLanguageOptions}
*/
defaultLanguageOptions = {
tolerant: false,
};
/**
* Validates the language options.
* @param {CSSLanguageOptions} languageOptions The language options to validate.
* @returns {void}
* @throws {TypeError} When the language options are invalid.
*/
validateLanguageOptions(languageOptions) {
if ("tolerant" in languageOptions &&
typeof languageOptions.tolerant !== "boolean") {
throw new TypeError("Expected a boolean value for 'tolerant' option.");
}
if ("customSyntax" in languageOptions) {
if (typeof languageOptions.customSyntax !== "object" &&
typeof languageOptions.customSyntax !== "function") {
throw new TypeError("Expected an object or function value for 'customSyntax' option.");
}
if (typeof languageOptions.customSyntax === "object" &&
languageOptions.customSyntax === null) {
throw new TypeError("Expected an object or function value for 'customSyntax' option.");
}
}
}
/**
* Normalizes the language options so they can be serialized.
* @param {CSSLanguageOptions} languageOptions The language options to normalize.
* @returns {CSSLanguageOptions} The normalized language options.
*/
normalizeLanguageOptions(languageOptions) {
// if there's no custom syntax then no changes are necessary
if (!languageOptions?.customSyntax) {
return languageOptions;
}
// Shallow copy
const clone = { ...languageOptions };
// If customSyntax is a function, call it with the default syntax to get the config object
if (typeof languageOptions.customSyntax === "function") {
clone.customSyntax = languageOptions.customSyntax(defaultSyntax);
}
Object.defineProperty(clone, "toJSON", {
value() {
// another shallow copy
const result = { ...this };
result.customSyntax = replaceFunctions(result.customSyntax);
return result;
},
enumerable: false,
configurable: true,
});
return clone;
}
/**
* Parses the given file into an AST.
* @param {File} file The virtual file to parse.
* @param {Object} [context] The parsing context.
* @param {CSSLanguageOptions} [context.languageOptions] The language options to use for parsing.
* @returns {CSSParseResult} The result of parsing.
*/
parse(file, { languageOptions = {} } = {}) {
// Note: BOM already removed
const text = /** @type {string} */ (file.body);
/** @type {Comment[]} */
const comments = [];
/** @type {FileError[]} */
const errors = [];
const { tolerant } = languageOptions;
const { parse, lexer } = languageOptions.customSyntax
? fork(
/** @type {Partial<SyntaxConfig>} */ (languageOptions.customSyntax))
: { parse: originalParse, lexer: originalLexer };
/*
* Check for parsing errors first. If there's a parsing error, nothing
* else can happen. However, a parsing error does not throw an error
* from this method - it's just considered a fatal error message, a
* problem that ESLint identified just like any other.
*/
try {
const root = toPlainObject(parse(text, {
filename: file.path,
positions: true,
onComment(value, loc) {
comments.push({
type: "Comment",
value,
loc,
});
},
onParseError(error) {
if (!tolerant) {
errors.push(error);
}
},
onToken(type, start, end, index) {
if (tolerant) {
return;
}
switch (type) {
// these already generate errors
case tokenTypes.BadString:
case tokenTypes.BadUrl:
break;
default:
/* eslint-disable new-cap -- This is a valid call */
if (this.isBlockOpenerTokenType(type)) {
if (this.getBlockTokenPairIndex(index) ===
-1) {
const loc = this.getRangeLocation(start, end);
errors.push(parse.SyntaxError(`Missing closing ${blockOpenerTokenTypes.get(type)}`, text, start, loc.start.line, loc.start.column));
}
}
else if (this.isBlockCloserTokenType(type)) {
if (this.getBlockTokenPairIndex(index) ===
-1) {
const loc = this.getRangeLocation(start, end);
errors.push(parse.SyntaxError(`Missing opening ${blockCloserTokenTypes.get(type)}`, text, start, loc.start.line, loc.start.column));
}
}
/* eslint-enable new-cap -- This is a valid call */
}
},
}));
if (errors.length) {
return {
ok: false,
errors,
};
}
return {
ok: true,
ast: /** @type {StyleSheetPlain} */ (root),
comments,
lexer,
};
}
catch (ex) {
return {
ok: false,
errors: [ex],
};
}
}
/**
* Creates a new `CSSSourceCode` object from the given information.
* @param {File} file The virtual file to create a `CSSSourceCode` object from.
* @param {CSSOkParseResult} parseResult The result returned from `parse()`.
* @returns {CSSSourceCode} The new `CSSSourceCode` object.
*/
createSourceCode(file, parseResult) {
return new CSSSourceCode({
text: /** @type {string} */ (file.body),
ast: parseResult.ast,
comments: parseResult.comments,
lexer: parseResult.lexer,
});
}
}
@@ -0,0 +1,107 @@
/**
* CSS Source Code Object.
* @extends {TextSourceCodeBase<{LangOptions: CSSLanguageOptions, RootNode: StyleSheetPlain, SyntaxElementWithLoc: CSSSyntaxElement, ConfigNode: Comment}>}
*/
export class CSSSourceCode extends TextSourceCodeBase<{
LangOptions: CSSLanguageOptions;
RootNode: StyleSheetPlain;
SyntaxElementWithLoc: CSSSyntaxElement;
ConfigNode: Comment;
}> {
/**
* Creates a new instance.
* @param {Object} options The options for the instance.
* @param {string} options.text The source code text.
* @param {StyleSheetPlain} options.ast The root AST node.
* @param {Array<Comment>} options.comments The comment nodes in the source code.
* @param {Lexer} options.lexer The lexer used to parse the source code.
*/
constructor({ text, ast, comments, lexer }: {
text: string;
ast: StyleSheetPlain;
comments: Array<Comment>;
lexer: Lexer;
});
/**
* The comment node in the source code.
* @type {Array<Comment>|undefined}
*/
comments: Array<Comment> | undefined;
/**
* The lexer for this instance.
* @type {Lexer}
*/
lexer: Lexer;
/**
* Returns an array of all inline configuration nodes found in the
* source code.
* @returns {Array<Comment>} An array of all inline configuration nodes.
*/
getInlineConfigNodes(): Array<Comment>;
/**
* Returns directives that enable or disable rules along with any problems
* encountered while parsing the directives.
* @returns {{problems:Array<FileProblem>,directives:Array<Directive>}} Information
* that ESLint needs to further process the directives.
*/
getDisableDirectives(): {
problems: Array<FileProblem>;
directives: Array<Directive>;
};
/**
* Returns inline rule configurations along with any problems
* encountered while parsing the configurations.
* @returns {{problems:Array<FileProblem>,configs:Array<{config:{rules:RulesConfig},loc:CssLocationRange}>}} Information
* that ESLint needs to further process the rule configurations.
*/
applyInlineConfig(): {
problems: Array<FileProblem>;
configs: Array<{
config: {
rules: RulesConfig;
};
loc: CssLocationRange;
}>;
};
/**
* Traverse the source code and return the steps that were taken.
* @returns {Iterable<CSSTraversalStep>} The steps that were taken while traversing the source code.
*/
traverse(): Iterable<CSSTraversalStep>;
#private;
}
import type { CSSLanguageOptions } from "./css-language.js";
import type { StyleSheetPlain } from "@eslint/css-tree";
import type { CSSSyntaxElement } from "../types.js";
import type { Comment } from "@eslint/css-tree";
import { TextSourceCodeBase } from "@eslint/plugin-kit";
import type { Lexer } from "@eslint/css-tree";
import type { FileProblem } from "@eslint/core";
import { Directive } from "@eslint/plugin-kit";
import type { RulesConfig } from "@eslint/core";
import type { CssLocationRange } from "@eslint/css-tree";
/**
* A class to represent a step in the traversal process.
*/
declare class CSSTraversalStep extends VisitNodeStep {
/**
* Creates a new instance.
* @param {Object} options The options for the step.
* @param {CssNode} options.target The target of the step.
* @param {1|2} options.phase The phase of the step.
* @param {Array<any>} options.args The arguments of the step.
*/
constructor({ target, phase, args }: {
target: CssNode;
phase: 1 | 2;
args: Array<any>;
});
/**
* The target of the step.
* @type {CssNode}
*/
target: CssNode;
}
import { VisitNodeStep } from "@eslint/plugin-kit";
import type { CssNode } from "@eslint/css-tree";
export {};
+250
View File
@@ -0,0 +1,250 @@
/**
* @fileoverview The CSSSourceCode class.
* @author Nicholas C. Zakas
*/
//-----------------------------------------------------------------------------
// Imports
//-----------------------------------------------------------------------------
import { VisitNodeStep, TextSourceCodeBase, ConfigCommentParser, Directive, } from "@eslint/plugin-kit";
import { visitorKeys } from "./css-visitor-keys.js";
//-----------------------------------------------------------------------------
// Types
//-----------------------------------------------------------------------------
/**
* @import { CssNode, CssNodePlain, CssLocationRange, Comment, Lexer, StyleSheetPlain } from "@eslint/css-tree"
* @import { SourceRange, FileProblem, DirectiveType, RulesConfig } from "@eslint/core"
* @import { CSSSyntaxElement } from "../types.js"
* @import { CSSLanguageOptions } from "./css-language.js"
*/
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
const commentParser = new ConfigCommentParser();
const INLINE_CONFIG = /^\s*eslint(?:-enable|-disable(?:(?:-next)?-line)?)?(?:\s|$)/u;
/**
* A class to represent a step in the traversal process.
*/
class CSSTraversalStep extends VisitNodeStep {
/**
* The target of the step.
* @type {CssNode}
*/
target = undefined;
/**
* Creates a new instance.
* @param {Object} options The options for the step.
* @param {CssNode} options.target The target of the step.
* @param {1|2} options.phase The phase of the step.
* @param {Array<any>} options.args The arguments of the step.
*/
constructor({ target, phase, args }) {
super({ target, phase, args });
this.target = target;
}
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
/**
* CSS Source Code Object.
* @extends {TextSourceCodeBase<{LangOptions: CSSLanguageOptions, RootNode: StyleSheetPlain, SyntaxElementWithLoc: CSSSyntaxElement, ConfigNode: Comment}>}
*/
export class CSSSourceCode extends TextSourceCodeBase {
/**
* Cached traversal steps.
* @type {Array<CSSTraversalStep>|undefined}
*/
#steps;
/**
* Cache of parent nodes.
* @type {WeakMap<CssNodePlain, CssNodePlain>}
*/
#parents = new WeakMap();
/**
* Collection of inline configuration comments.
* @type {Array<Comment>}
*/
#inlineConfigComments;
/**
* The AST of the source code.
* @type {StyleSheetPlain}
*/
ast = undefined;
/**
* The comment node in the source code.
* @type {Array<Comment>|undefined}
*/
comments;
/**
* The lexer for this instance.
* @type {Lexer}
*/
lexer;
/**
* Creates a new instance.
* @param {Object} options The options for the instance.
* @param {string} options.text The source code text.
* @param {StyleSheetPlain} options.ast The root AST node.
* @param {Array<Comment>} options.comments The comment nodes in the source code.
* @param {Lexer} options.lexer The lexer used to parse the source code.
*/
constructor({ text, ast, comments, lexer }) {
super({ text, ast, lineEndingPattern: /\r\n|[\r\n\f]/u });
this.ast = ast;
this.comments = comments;
this.lexer = lexer;
}
/**
* Returns the range of the given node.
* @param {CssNodePlain} node The node to get the range of.
* @returns {SourceRange} The range of the node.
* @override
*/
getRange(node) {
return [node.loc.start.offset, node.loc.end.offset];
}
/**
* Returns an array of all inline configuration nodes found in the
* source code.
* @returns {Array<Comment>} An array of all inline configuration nodes.
*/
getInlineConfigNodes() {
if (!this.#inlineConfigComments) {
this.#inlineConfigComments = this.comments.filter(comment => INLINE_CONFIG.test(comment.value));
}
return this.#inlineConfigComments;
}
/**
* Returns directives that enable or disable rules along with any problems
* encountered while parsing the directives.
* @returns {{problems:Array<FileProblem>,directives:Array<Directive>}} Information
* that ESLint needs to further process the directives.
*/
getDisableDirectives() {
/** @type {Array<FileProblem>} */
const problems = [];
/** @type {Array<Directive>} */
const directives = [];
this.getInlineConfigNodes().forEach(comment => {
const { label, value, justification } = commentParser.parseDirective(comment.value);
// `eslint-disable-line` directives are not allowed to span multiple lines as it would be confusing to which lines they apply
if (label === "eslint-disable-line" &&
comment.loc.start.line !== comment.loc.end.line) {
const message = `${label} comment should not span multiple lines.`;
problems.push({
ruleId: null,
message,
loc: comment.loc,
});
return;
}
switch (label) {
case "eslint-disable":
case "eslint-enable":
case "eslint-disable-next-line":
case "eslint-disable-line": {
const directiveType = label.slice("eslint-".length);
directives.push(new Directive({
type: /** @type {DirectiveType} */ (directiveType),
node: comment,
value,
justification,
}));
}
// no default
}
});
return { problems, directives };
}
/**
* Returns inline rule configurations along with any problems
* encountered while parsing the configurations.
* @returns {{problems:Array<FileProblem>,configs:Array<{config:{rules:RulesConfig},loc:CssLocationRange}>}} Information
* that ESLint needs to further process the rule configurations.
*/
applyInlineConfig() {
/** @type {Array<FileProblem>} */
const problems = [];
/** @type {Array<{config:{rules:RulesConfig},loc:CssLocationRange}>} */
const configs = [];
this.getInlineConfigNodes().forEach(comment => {
const { label, value } = commentParser.parseDirective(comment.value);
if (label === "eslint") {
const parseResult = commentParser.parseJSONLikeConfig(value);
if (parseResult.ok) {
configs.push({
config: {
rules: parseResult.config,
},
loc: comment.loc,
});
}
else {
problems.push({
ruleId: null,
message:
/** @type {{ok: false, error: { message: string }}} */ (parseResult).error.message,
loc: comment.loc,
});
}
}
});
return {
configs,
problems,
};
}
/**
* Returns the parent of the given node.
* @param {CssNodePlain} node The node to get the parent of.
* @returns {CssNodePlain|undefined} The parent of the node.
*/
getParent(node) {
return this.#parents.get(node);
}
/**
* Traverse the source code and return the steps that were taken.
* @returns {Iterable<CSSTraversalStep>} The steps that were taken while traversing the source code.
*/
traverse() {
// Because the AST doesn't mutate, we can cache the steps
if (this.#steps) {
return this.#steps.values();
}
/** @type {Array<CSSTraversalStep>} */
const steps = (this.#steps = []);
// Note: We can't use `walk` from `css-tree` because it uses `CssNode` instead of `CssNodePlain`
const visit = (node, parent) => {
// first set the parent
this.#parents.set(node, parent);
// then add the step
steps.push(new CSSTraversalStep({
target: node,
phase: 1,
args: [node, parent],
}));
// then visit the children
for (const key of visitorKeys[node.type] || []) {
const child = node[key];
if (child) {
if (Array.isArray(child)) {
child.forEach(grandchild => {
visit(grandchild, node);
});
}
else {
visit(child, node);
}
}
}
// then add the exit step
steps.push(new CSSTraversalStep({
target: node,
phase: 2,
args: [node, parent],
}));
};
visit(this.ast);
return steps;
}
}
@@ -0,0 +1,51 @@
export namespace visitorKeys {
let AnPlusB: any[];
let Atrule: string[];
let AtrulePrelude: string[];
let AttributeSelector: string[];
let Block: string[];
let Brackets: string[];
let CDC: any[];
let CDO: any[];
let ClassSelector: any[];
let Combinator: any[];
let Comment: any[];
let Condition: string[];
let Declaration: string[];
let DeclarationList: string[];
let Dimension: any[];
let Feature: string[];
let FeatureFunction: string[];
let FeatureRange: string[];
let Function: string[];
let GeneralEnclosed: string[];
let Hash: any[];
let IdSelector: any[];
let Identifier: any[];
let Layer: any[];
let LayerList: string[];
let MediaQuery: string[];
let MediaQueryList: string[];
let NestingSelector: any[];
let Nth: string[];
let Number: any[];
let Operator: any[];
let Parentheses: string[];
let Percentage: any[];
let PseudoClassSelector: string[];
let PseudoElementSelector: string[];
let Ratio: string[];
let Raw: any[];
let Rule: string[];
let Scope: string[];
let Selector: string[];
let SelectorList: string[];
let String: any[];
let StyleSheet: string[];
let SupportsDeclaration: string[];
let TypeSelector: any[];
let UnicodeRange: any[];
let Url: any[];
let Value: string[];
let WhiteSpace: any[];
}
@@ -0,0 +1,55 @@
/**
* @fileoverview Visitor keys for the CSS Tree AST.
* @author Nicholas C. Zakas
*/
export const visitorKeys = {
AnPlusB: [],
Atrule: ["prelude", "block"],
AtrulePrelude: ["children"],
AttributeSelector: ["name", "value"],
Block: ["children"],
Brackets: ["children"],
CDC: [],
CDO: [],
ClassSelector: [],
Combinator: [],
Comment: [],
Condition: ["children"],
Declaration: ["value"],
DeclarationList: ["children"],
Dimension: [],
Feature: ["value"],
FeatureFunction: ["value"],
FeatureRange: ["left", "middle", "right"],
Function: ["children"],
GeneralEnclosed: ["children"],
Hash: [],
IdSelector: [],
Identifier: [],
Layer: [],
LayerList: ["children"],
MediaQuery: ["condition"],
MediaQueryList: ["children"],
NestingSelector: [],
Nth: ["nth", "selector"],
Number: [],
Operator: [],
Parentheses: ["children"],
Percentage: [],
PseudoClassSelector: ["children"],
PseudoElementSelector: ["children"],
Ratio: ["left", "right"],
Raw: [],
Rule: ["prelude", "block"],
Scope: ["root", "limit"],
Selector: ["children"],
SelectorList: ["children"],
String: [],
StyleSheet: ["children"],
SupportsDeclaration: ["declaration"],
TypeSelector: [],
UnicodeRange: [],
Url: [],
Value: ["children"],
WhiteSpace: [],
};