mirror of
https://github.com/openharmony/third_party_css-what.git
synced 2026-07-01 03:23:11 -04:00
update css-what 7.0.0
Signed-off-by: Bojiang <jiangbo91@huawei.com> Change-Id: I94ace9042cbcb23f1bac2cf46d3d3540f85d781b
This commit is contained in:
+32
-11
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"extends": ["eslint:recommended", "plugin:node/recommended", "prettier"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"prettier",
|
||||
"plugin:n/recommended",
|
||||
"plugin:unicorn/recommended"
|
||||
],
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
@@ -23,10 +28,14 @@
|
||||
"curly": [2, "multi-line"],
|
||||
"no-else-return": 2,
|
||||
|
||||
"node/no-unsupported-features/es-syntax": [
|
||||
2,
|
||||
{ "ignores": ["modules"] }
|
||||
]
|
||||
"unicorn/prefer-module": 0,
|
||||
"unicorn/filename-case": 0,
|
||||
"unicorn/no-null": 0,
|
||||
"unicorn/prefer-code-point": 0,
|
||||
"unicorn/prefer-string-slice": 0,
|
||||
"unicorn/prefer-add-event-listener": 0,
|
||||
"unicorn/prefer-at": 0,
|
||||
"unicorn/prefer-string-replace-all": 0
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
@@ -40,12 +49,9 @@
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.eslint.json"
|
||||
},
|
||||
"settings": {
|
||||
"node": {
|
||||
"tryExtensions": [".js", ".json", ".node", ".ts"]
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"curly": [2, "multi-line"],
|
||||
|
||||
"@typescript-eslint/prefer-for-of": 0,
|
||||
"@typescript-eslint/member-ordering": 0,
|
||||
"@typescript-eslint/explicit-function-return-type": 0,
|
||||
@@ -65,7 +71,22 @@
|
||||
"@typescript-eslint/prefer-includes": 2,
|
||||
"@typescript-eslint/no-unnecessary-condition": 2,
|
||||
"@typescript-eslint/switch-exhaustiveness-check": 2,
|
||||
"@typescript-eslint/prefer-nullish-coalescing": 2
|
||||
"@typescript-eslint/prefer-nullish-coalescing": 2,
|
||||
"@typescript-eslint/consistent-type-imports": [
|
||||
2,
|
||||
{ "fixStyle": "inline-type-imports" }
|
||||
],
|
||||
"@typescript-eslint/consistent-type-exports": 2,
|
||||
|
||||
"n/no-missing-import": 0,
|
||||
"n/no-unsupported-features/es-syntax": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.spec.ts",
|
||||
"rules": {
|
||||
"n/no-unsupported-features/node-builtins": 0,
|
||||
"n/no-unpublished-import": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
"Name": "css-what",
|
||||
"License": "BSD 2-Clause License",
|
||||
"License File": "LICENSE",
|
||||
"Version Number": "v6.1.0",
|
||||
"Version Number": "v7.0.0",
|
||||
"Owner": "lixingchi1@huawei.com",
|
||||
"Upstream URL": "https://github.com/fb55/css-what",
|
||||
"Description": "a CSS selector parser."
|
||||
|
||||
Generated
+5517
-5917
File diff suppressed because it is too large
Load Diff
+58
-36
@@ -1,59 +1,81 @@
|
||||
{
|
||||
"author": "Felix Böhm <me@feedic.com> (http://feedic.com)",
|
||||
"name": "css-what",
|
||||
"version": "7.0.0",
|
||||
"description": "a CSS selector parser",
|
||||
"version": "6.1.0",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/fb55/css-what"
|
||||
},
|
||||
"main": "lib/commonjs/index.js",
|
||||
"module": "lib/es/index.js",
|
||||
"types": "lib/es/index.d.ts",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
},
|
||||
"license": "BSD-2-Clause",
|
||||
"author": "Felix Böhm <me@feedic.com> (http://feedic.com)",
|
||||
"sideEffects": false,
|
||||
"type": "commonjs",
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/esm/index.d.ts",
|
||||
"default": "./dist/esm/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"default": "./dist/commonjs/index.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"main": "./dist/commonjs/index.js",
|
||||
"module": "./dist/esm/index.js",
|
||||
"types": "./dist/commonjs/index.d.ts",
|
||||
"files": [
|
||||
"lib/**/*"
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "npm run test:jest && npm run lint",
|
||||
"test:jest": "jest",
|
||||
"lint": "npm run lint:es && npm run lint:prettier",
|
||||
"lint:es": "eslint src",
|
||||
"lint:prettier": "npm run prettier -- --check",
|
||||
"format": "npm run format:es && npm run format:prettier",
|
||||
"format:es": "npm run lint:es -- --fix",
|
||||
"format:prettier": "npm run prettier -- --write",
|
||||
"lint": "npm run lint:tsc && npm run lint:es && npm run lint:prettier",
|
||||
"lint:es": "eslint src",
|
||||
"lint:prettier": "npm run prettier -- --check",
|
||||
"lint:tsc": "tsc --noEmit",
|
||||
"prepublishOnly": "tshy",
|
||||
"prettier": "prettier '**/*.{ts,md,json,yml}'",
|
||||
"build": "tsc && tsc -p tsconfig.es.json",
|
||||
"prepare": "npm run build"
|
||||
"test": "npm run test:vi && npm run lint",
|
||||
"test:vi": "vitest run"
|
||||
},
|
||||
"prettier": {
|
||||
"tabWidth": 4
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/node": "^17.0.23",
|
||||
"@typescript-eslint/eslint-plugin": "^5.17.0",
|
||||
"@typescript-eslint/parser": "^5.17.0",
|
||||
"eslint": "^8.12.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"jest": "^27.5.1",
|
||||
"prettier": "^2.6.1",
|
||||
"ts-jest": "^27.1.4",
|
||||
"typescript": "^4.6.3"
|
||||
"@types/node": "^22.15.30",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-n": "^17.20.0",
|
||||
"eslint-plugin-unicorn": "^55.0.0",
|
||||
"prettier": "^3.6.2",
|
||||
"tshy": "^3.0.2",
|
||||
"typescript": "^5.8.3",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"license": "BSD-2-Clause",
|
||||
"jest": {
|
||||
"preset": "ts-jest",
|
||||
"roots": [
|
||||
"src"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"tabWidth": 4
|
||||
"tshy": {
|
||||
"exclude": [
|
||||
"**/*.spec.ts",
|
||||
"**/__fixtures__/*",
|
||||
"**/__tests__/*",
|
||||
"**/__snapshots__/*"
|
||||
],
|
||||
"exports": {
|
||||
"./package.json": "./package.json",
|
||||
".": "./src/index.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# css-what
|
||||
|
||||
[](https://github.com/fb55/css-what/actions/workflows/nodejs-test.yml)
|
||||
[](https://github.com/fb55/css-what/actions/workflows/nodejs-test.yml)
|
||||
[](https://coveralls.io/github/fb55/css-what?branch=master)
|
||||
|
||||
A CSS selector parser.
|
||||
|
||||
@@ -17279,5 +17279,18 @@
|
||||
"namespace": null
|
||||
}
|
||||
]
|
||||
],
|
||||
".before\\:h-\\[calc\\(100\\%-28px\\)\\]::before": [
|
||||
[
|
||||
{
|
||||
"type": "attribute",
|
||||
"name": "class",
|
||||
"action": "element",
|
||||
"value": "before:h-[calc(100%-28px)]",
|
||||
"namespace": null,
|
||||
"ignoreCase": "quirks"
|
||||
},
|
||||
{ "type": "pseudo-element", "name": "before", "data": null }
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
+30
-25
@@ -1,9 +1,14 @@
|
||||
import { Selector, SelectorType, AttributeAction, IgnoreCaseMode } from "..";
|
||||
import {
|
||||
Selector,
|
||||
SelectorType,
|
||||
AttributeAction,
|
||||
IgnoreCaseMode,
|
||||
} from "../types";
|
||||
|
||||
export const tests: [
|
||||
selector: string,
|
||||
expected: Selector[][],
|
||||
message: string
|
||||
message: string,
|
||||
][] = [
|
||||
// Tag names
|
||||
[
|
||||
@@ -141,7 +146,7 @@ export const tests: [
|
||||
|
||||
// Escaped whitespace
|
||||
[
|
||||
"#\\ > a ",
|
||||
String.raw`#\ > a `,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -165,7 +170,7 @@ export const tests: [
|
||||
"Space between escaped space and combinator",
|
||||
],
|
||||
[
|
||||
".\\ ",
|
||||
String.raw`.\ `,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -197,7 +202,7 @@ export const tests: [
|
||||
"Special charecters in selector",
|
||||
],
|
||||
[
|
||||
"\\61 ",
|
||||
String.raw`\61 `,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -210,7 +215,7 @@ export const tests: [
|
||||
"Numeric escape with space (BMP)",
|
||||
],
|
||||
[
|
||||
"\\1d306\\01d306",
|
||||
String.raw`\1d306\01d306`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -223,7 +228,7 @@ export const tests: [
|
||||
"Numeric escape (outside BMP)",
|
||||
],
|
||||
[
|
||||
"#\\26 B",
|
||||
String.raw`#\26 B`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -321,7 +326,7 @@ export const tests: [
|
||||
"quoted attribute with internal newline",
|
||||
],
|
||||
[
|
||||
"[name=foo\\.baz]",
|
||||
String.raw`[name=foo\.baz]`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -337,7 +342,7 @@ export const tests: [
|
||||
"attribute with escaped dot",
|
||||
],
|
||||
[
|
||||
"[name=foo\\[bar\\]]",
|
||||
String.raw`[name=foo\[bar\]]`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -353,7 +358,7 @@ export const tests: [
|
||||
"attribute with escaped square brackets",
|
||||
],
|
||||
[
|
||||
"[xml\\:test]",
|
||||
String.raw`[xml\:test]`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -506,7 +511,7 @@ export const tests: [
|
||||
"pseudo selector with data",
|
||||
],
|
||||
[
|
||||
':contains("(a((foo\\\\\\))))")',
|
||||
String.raw`:contains("(a((foo\\\))))")`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -617,7 +622,7 @@ export const tests: [
|
||||
"Underscores don't need escaping",
|
||||
],
|
||||
[
|
||||
"[name=foo\\ bar]",
|
||||
String.raw`[name=foo\ bar]`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -633,7 +638,7 @@ export const tests: [
|
||||
"Escaped space",
|
||||
],
|
||||
[
|
||||
"[name=foo\\.baz]",
|
||||
String.raw`[name=foo\.baz]`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -649,7 +654,7 @@ export const tests: [
|
||||
"Escaped dot",
|
||||
],
|
||||
[
|
||||
"[name=foo\\[baz\\]]",
|
||||
String.raw`[name=foo\[baz\]]`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -665,7 +670,7 @@ export const tests: [
|
||||
"Escaped brackets",
|
||||
],
|
||||
[
|
||||
"[data-attr='foo_baz\\']']",
|
||||
String.raw`[data-attr='foo_baz\']']`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -681,7 +686,7 @@ export const tests: [
|
||||
"Escaped quote + right bracket",
|
||||
],
|
||||
[
|
||||
"[data-attr='\\'']",
|
||||
String.raw`[data-attr='\'']`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -697,7 +702,7 @@ export const tests: [
|
||||
"Quoted quote",
|
||||
],
|
||||
[
|
||||
"[data-attr='\\\\']",
|
||||
String.raw`[data-attr='\\']`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -713,7 +718,7 @@ export const tests: [
|
||||
"Quoted backslash",
|
||||
],
|
||||
[
|
||||
"[data-attr='\\\\\\'']",
|
||||
String.raw`[data-attr='\\\'']`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -721,7 +726,7 @@ export const tests: [
|
||||
namespace: null,
|
||||
action: AttributeAction.Equals,
|
||||
name: "data-attr",
|
||||
value: "\\'",
|
||||
value: String.raw`\'`,
|
||||
ignoreCase: IgnoreCaseMode.Unknown,
|
||||
},
|
||||
],
|
||||
@@ -729,7 +734,7 @@ export const tests: [
|
||||
"Quoted backslash quote",
|
||||
],
|
||||
[
|
||||
"[data-attr='\\\\\\\\']",
|
||||
String.raw`[data-attr='\\\\']`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -745,7 +750,7 @@ export const tests: [
|
||||
"Quoted backslash backslash",
|
||||
],
|
||||
[
|
||||
"[data-attr='\\5C\\\\']",
|
||||
String.raw`[data-attr='\5C\\']`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -761,7 +766,7 @@ export const tests: [
|
||||
"Quoted backslash backslash (numeric escape)",
|
||||
],
|
||||
[
|
||||
"[data-attr='\\5C \\\\']",
|
||||
String.raw`[data-attr='\5C \\']`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -793,7 +798,7 @@ export const tests: [
|
||||
"Quoted backslash backslash (numeric escape with trailing tab)",
|
||||
],
|
||||
[
|
||||
"[data-attr='\\04e00']",
|
||||
String.raw`[data-attr='\04e00']`,
|
||||
[
|
||||
[
|
||||
{
|
||||
@@ -801,7 +806,7 @@ export const tests: [
|
||||
namespace: null,
|
||||
action: AttributeAction.Equals,
|
||||
name: "data-attr",
|
||||
value: "\u4e00",
|
||||
value: "\u4E00",
|
||||
ignoreCase: IgnoreCaseMode.Unknown,
|
||||
},
|
||||
],
|
||||
@@ -809,7 +814,7 @@ export const tests: [
|
||||
"Long numeric escape (BMP)",
|
||||
],
|
||||
[
|
||||
"[data-attr='\\01D306A']",
|
||||
String.raw`[data-attr='\01D306A']`,
|
||||
[
|
||||
[
|
||||
{
|
||||
|
||||
+12
-7
@@ -1,5 +1,6 @@
|
||||
import { readFileSync } from "fs";
|
||||
import { parse } from ".";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { parse } from "./parse";
|
||||
import { tests } from "./__fixtures__/tests";
|
||||
|
||||
const broken = [
|
||||
@@ -29,18 +30,16 @@ const broken = [
|
||||
describe("Parse", () => {
|
||||
describe("Own tests", () => {
|
||||
for (const [selector, expected, message] of tests) {
|
||||
test(message, () =>
|
||||
expect(parse(selector)).toStrictEqual(expected)
|
||||
);
|
||||
it(message, () => expect(parse(selector)).toStrictEqual(expected));
|
||||
}
|
||||
});
|
||||
|
||||
describe("Collected selectors (qwery, sizzle, nwmatcher)", () => {
|
||||
const out = JSON.parse(
|
||||
readFileSync(`${__dirname}/__fixtures__/out.json`, "utf8")
|
||||
readFileSync(`${__dirname}/__fixtures__/out.json`, "utf8"),
|
||||
);
|
||||
for (const s of Object.keys(out)) {
|
||||
test(s, () => {
|
||||
it(s, () => {
|
||||
expect(parse(s)).toStrictEqual(out[s]);
|
||||
});
|
||||
}
|
||||
@@ -61,4 +60,10 @@ describe("Parse", () => {
|
||||
|
||||
expect(() => parse("/*/")).toThrowError("Comment was not terminated");
|
||||
});
|
||||
|
||||
it("should support legacy pseudo-elements with single colon", () => {
|
||||
expect(parse(":before")).toEqual([
|
||||
[{ name: "before", data: null, type: "pseudo-element" }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
+107
-76
@@ -8,7 +8,7 @@ import {
|
||||
DataType,
|
||||
} from "./types";
|
||||
|
||||
const reName = /^[^\\#]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\-\u00b0-\uFFFF])+/;
|
||||
const reName = /^[^#\\]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\u00B0-\uFFFF-])+/;
|
||||
const reEscape = /\\([\da-f]{1,6}\s?|(\s)|.)/gi;
|
||||
|
||||
const enum CharCode {
|
||||
@@ -26,7 +26,6 @@ const enum CharCode {
|
||||
QuestionMark = 63,
|
||||
ExclamationMark = 33,
|
||||
Slash = 47,
|
||||
Star = 42,
|
||||
Equal = 61,
|
||||
Dollar = 36,
|
||||
Pipe = 124,
|
||||
@@ -67,6 +66,19 @@ const unpackPseudos = new Set([
|
||||
"host-context",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Pseudo elements defined in CSS Level 1 and CSS Level 2 can be written with
|
||||
* a single colon; eg. :before will turn into ::before.
|
||||
*
|
||||
* @see {@link https://www.w3.org/TR/2018/WD-selectors-4-20181121/#pseudo-element-syntax}
|
||||
*/
|
||||
const pseudosToPseudoElements = new Set([
|
||||
"before",
|
||||
"after",
|
||||
"first-line",
|
||||
"first-letter",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Checks whether a specific selector is a traversal.
|
||||
* This is useful eg. in swapping the order of elements that
|
||||
@@ -81,10 +93,12 @@ export function isTraversal(selector: Selector): selector is Traversal {
|
||||
case SelectorType.Descendant:
|
||||
case SelectorType.Parent:
|
||||
case SelectorType.Sibling:
|
||||
case SelectorType.ColumnCombinator:
|
||||
case SelectorType.ColumnCombinator: {
|
||||
return true;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,20 +106,23 @@ const stripQuotesFromPseudos = new Set(["contains", "icontains"]);
|
||||
|
||||
// Unescape function taken from https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L152
|
||||
function funescape(_: string, escaped: string, escapedWhitespace?: string) {
|
||||
const high = parseInt(escaped, 16) - 0x10000;
|
||||
const high = Number.parseInt(escaped, 16) - 0x1_00_00;
|
||||
|
||||
// NaN means non-codepoint
|
||||
return high !== high || escapedWhitespace
|
||||
? escaped
|
||||
: high < 0
|
||||
? // BMP codepoint
|
||||
String.fromCharCode(high + 0x10000)
|
||||
: // Supplemental Plane codepoint (surrogate pair)
|
||||
String.fromCharCode((high >> 10) | 0xd800, (high & 0x3ff) | 0xdc00);
|
||||
? // BMP codepoint
|
||||
String.fromCharCode(high + 0x1_00_00)
|
||||
: // Supplemental Plane codepoint (surrogate pair)
|
||||
String.fromCharCode(
|
||||
(high >> 10) | 0xd8_00,
|
||||
(high & 0x3_ff) | 0xdc_00,
|
||||
);
|
||||
}
|
||||
|
||||
function unescapeCSS(str: string) {
|
||||
return str.replace(reEscape, funescape);
|
||||
function unescapeCSS(cssString: string) {
|
||||
return cssString.replace(reEscape, funescape);
|
||||
}
|
||||
|
||||
function isQuote(c: number): boolean {
|
||||
@@ -123,10 +140,9 @@ function isWhitespace(c: number): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses `selector`, optionally with the passed `options`.
|
||||
* Parses `selector`.
|
||||
*
|
||||
* @param selector Selector to parse.
|
||||
* @param options Options for parsing.
|
||||
* @returns Returns a two-dimensional array.
|
||||
* The first dimension represents selectors separated by commas (eg. `sub1, sub2`),
|
||||
* the second contains the relevant tokens for that selector.
|
||||
@@ -146,7 +162,7 @@ export function parse(selector: string): Selector[][] {
|
||||
function parseSelector(
|
||||
subselects: Selector[][],
|
||||
selector: string,
|
||||
selectorIndex: number
|
||||
selectorIndex: number,
|
||||
): number {
|
||||
let tokens: Selector[] = [];
|
||||
|
||||
@@ -155,7 +171,7 @@ function parseSelector(
|
||||
|
||||
if (!match) {
|
||||
throw new Error(
|
||||
`Expected name, found ${selector.slice(selectorIndex)}`
|
||||
`Expected name, found ${selector.slice(selectorIndex)}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -178,40 +194,37 @@ function parseSelector(
|
||||
function readValueWithParenthesis(): string {
|
||||
selectorIndex += 1;
|
||||
const start = selectorIndex;
|
||||
let counter = 1;
|
||||
|
||||
for (
|
||||
;
|
||||
counter > 0 && selectorIndex < selector.length;
|
||||
let counter = 1;
|
||||
selectorIndex < selector.length;
|
||||
selectorIndex++
|
||||
) {
|
||||
if (
|
||||
selector.charCodeAt(selectorIndex) ===
|
||||
CharCode.LeftParenthesis &&
|
||||
!isEscaped(selectorIndex)
|
||||
) {
|
||||
counter++;
|
||||
} else if (
|
||||
selector.charCodeAt(selectorIndex) ===
|
||||
CharCode.RightParenthesis &&
|
||||
!isEscaped(selectorIndex)
|
||||
) {
|
||||
counter--;
|
||||
switch (selector.charCodeAt(selectorIndex)) {
|
||||
case CharCode.BackSlash: {
|
||||
// Skip next character
|
||||
selectorIndex += 1;
|
||||
break;
|
||||
}
|
||||
case CharCode.LeftParenthesis: {
|
||||
counter += 1;
|
||||
break;
|
||||
}
|
||||
case CharCode.RightParenthesis: {
|
||||
counter -= 1;
|
||||
|
||||
if (counter === 0) {
|
||||
return unescapeCSS(
|
||||
selector.slice(start, selectorIndex++),
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (counter) {
|
||||
throw new Error("Parenthesis not matched");
|
||||
}
|
||||
|
||||
return unescapeCSS(selector.slice(start, selectorIndex - 1));
|
||||
}
|
||||
|
||||
function isEscaped(pos: number): boolean {
|
||||
let slashCount = 0;
|
||||
|
||||
while (selector.charCodeAt(--pos) === CharCode.BackSlash) slashCount++;
|
||||
return (slashCount & 1) === 1;
|
||||
throw new Error("Parenthesis not matched");
|
||||
}
|
||||
|
||||
function ensureNotTraversal() {
|
||||
@@ -254,7 +267,7 @@ function parseSelector(
|
||||
*/
|
||||
function finalizeSubselector() {
|
||||
if (
|
||||
tokens.length &&
|
||||
tokens.length > 0 &&
|
||||
tokens[tokens.length - 1].type === SelectorType.Descendant
|
||||
) {
|
||||
tokens.pop();
|
||||
@@ -357,7 +370,7 @@ function parseSelector(
|
||||
|
||||
let action: AttributeAction = AttributeAction.Exists;
|
||||
const possibleAction = actionTypes.get(
|
||||
selector.charCodeAt(selectorIndex)
|
||||
selector.charCodeAt(selectorIndex),
|
||||
);
|
||||
|
||||
if (possibleAction) {
|
||||
@@ -386,57 +399,65 @@ function parseSelector(
|
||||
if (action !== "exists") {
|
||||
if (isQuote(selector.charCodeAt(selectorIndex))) {
|
||||
const quote = selector.charCodeAt(selectorIndex);
|
||||
let sectionEnd = selectorIndex + 1;
|
||||
selectorIndex += 1;
|
||||
const sectionStart = selectorIndex;
|
||||
while (
|
||||
sectionEnd < selector.length &&
|
||||
(selector.charCodeAt(sectionEnd) !== quote ||
|
||||
isEscaped(sectionEnd))
|
||||
selectorIndex < selector.length &&
|
||||
selector.charCodeAt(selectorIndex) !== quote
|
||||
) {
|
||||
sectionEnd += 1;
|
||||
selectorIndex +=
|
||||
// Skip next character if it is escaped
|
||||
selector.charCodeAt(selectorIndex) ===
|
||||
CharCode.BackSlash
|
||||
? 2
|
||||
: 1;
|
||||
}
|
||||
|
||||
if (selector.charCodeAt(sectionEnd) !== quote) {
|
||||
if (selector.charCodeAt(selectorIndex) !== quote) {
|
||||
throw new Error("Attribute value didn't end");
|
||||
}
|
||||
|
||||
value = unescapeCSS(
|
||||
selector.slice(selectorIndex + 1, sectionEnd)
|
||||
selector.slice(sectionStart, selectorIndex),
|
||||
);
|
||||
selectorIndex = sectionEnd + 1;
|
||||
selectorIndex += 1;
|
||||
} else {
|
||||
const valueStart = selectorIndex;
|
||||
|
||||
while (
|
||||
selectorIndex < selector.length &&
|
||||
((!isWhitespace(
|
||||
selector.charCodeAt(selectorIndex)
|
||||
) &&
|
||||
selector.charCodeAt(selectorIndex) !==
|
||||
CharCode.RightSquareBracket) ||
|
||||
isEscaped(selectorIndex))
|
||||
!isWhitespace(selector.charCodeAt(selectorIndex)) &&
|
||||
selector.charCodeAt(selectorIndex) !==
|
||||
CharCode.RightSquareBracket
|
||||
) {
|
||||
selectorIndex += 1;
|
||||
selectorIndex +=
|
||||
// Skip next character if it is escaped
|
||||
selector.charCodeAt(selectorIndex) ===
|
||||
CharCode.BackSlash
|
||||
? 2
|
||||
: 1;
|
||||
}
|
||||
|
||||
value = unescapeCSS(
|
||||
selector.slice(valueStart, selectorIndex)
|
||||
selector.slice(valueStart, selectorIndex),
|
||||
);
|
||||
}
|
||||
|
||||
stripWhitespace(0);
|
||||
|
||||
// See if we have a force ignore flag
|
||||
|
||||
const forceIgnore =
|
||||
selector.charCodeAt(selectorIndex) | 0x20;
|
||||
|
||||
// If the forceIgnore flag is set (either `i` or `s`), use that value
|
||||
if (forceIgnore === CharCode.LowerS) {
|
||||
ignoreCase = false;
|
||||
stripWhitespace(1);
|
||||
} else if (forceIgnore === CharCode.LowerI) {
|
||||
ignoreCase = true;
|
||||
stripWhitespace(1);
|
||||
switch (selector.charCodeAt(selectorIndex) | 0x20) {
|
||||
// If the forceIgnore flag is set (either `i` or `s`), use that value
|
||||
case CharCode.LowerI: {
|
||||
ignoreCase = true;
|
||||
stripWhitespace(1);
|
||||
break;
|
||||
}
|
||||
case CharCode.LowerS: {
|
||||
ignoreCase = false;
|
||||
stripWhitespace(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,10 +493,20 @@ function parseSelector(
|
||||
? readValueWithParenthesis()
|
||||
: null,
|
||||
});
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
const name = getName(1).toLowerCase();
|
||||
|
||||
if (pseudosToPseudoElements.has(name)) {
|
||||
tokens.push({
|
||||
type: SelectorType.PseudoElement,
|
||||
name,
|
||||
data: null,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
let data: DataType = null;
|
||||
|
||||
if (
|
||||
@@ -485,7 +516,7 @@ function parseSelector(
|
||||
if (unpackPseudos.has(name)) {
|
||||
if (isQuote(selector.charCodeAt(selectorIndex + 1))) {
|
||||
throw new Error(
|
||||
`Pseudo-selector ${name} cannot be quoted`
|
||||
`Pseudo-selector ${name} cannot be quoted`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -493,7 +524,7 @@ function parseSelector(
|
||||
selectorIndex = parseSelector(
|
||||
data,
|
||||
selector,
|
||||
selectorIndex + 1
|
||||
selectorIndex + 1,
|
||||
);
|
||||
|
||||
if (
|
||||
@@ -501,7 +532,7 @@ function parseSelector(
|
||||
CharCode.RightParenthesis
|
||||
) {
|
||||
throw new Error(
|
||||
`Missing closing parenthesis in :${name} (${selector})`
|
||||
`Missing closing parenthesis in :${name} (${selector})`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -592,7 +623,7 @@ function parseSelector(
|
||||
tokens.push(
|
||||
name === "*"
|
||||
? { type: SelectorType.Universal, namespace }
|
||||
: { type: SelectorType.Tag, name, namespace }
|
||||
: { type: SelectorType.Tag, name, namespace },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { readFileSync } from "fs";
|
||||
import { parse, stringify } from ".";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { parse, stringify } from "./index";
|
||||
import { tests } from "./__fixtures__/tests";
|
||||
|
||||
describe("Stringify & re-parse", () => {
|
||||
describe("Own tests", () => {
|
||||
for (const [selector, expected, message] of tests) {
|
||||
test(`${message} (${selector})`, () => {
|
||||
it(`${message} (${selector})`, () => {
|
||||
expect(parse(stringify(expected))).toStrictEqual(expected);
|
||||
});
|
||||
}
|
||||
@@ -13,7 +14,7 @@ describe("Stringify & re-parse", () => {
|
||||
|
||||
it("Collected Selectors (qwery, sizzle, nwmatcher)", () => {
|
||||
const out = JSON.parse(
|
||||
readFileSync(`${__dirname}/__fixtures__/out.json`, "utf8")
|
||||
readFileSync(`${__dirname}/__fixtures__/out.json`, "utf8"),
|
||||
);
|
||||
for (const s of Object.keys(out)) {
|
||||
expect(parse(stringify(out[s]))).toStrictEqual(out[s]);
|
||||
|
||||
+68
-43
@@ -1,17 +1,17 @@
|
||||
import { Selector, SelectorType, AttributeAction } from "./types";
|
||||
|
||||
const attribValChars = ["\\", '"'];
|
||||
const pseudoValChars = [...attribValChars, "(", ")"];
|
||||
const attribValueChars = ["\\", '"'];
|
||||
const pseudoValueChars = [...attribValueChars, "(", ")"];
|
||||
|
||||
const charsToEscapeInAttributeValue = new Set(
|
||||
attribValChars.map((c) => c.charCodeAt(0))
|
||||
attribValueChars.map((c) => c.charCodeAt(0)),
|
||||
);
|
||||
const charsToEscapeInPseudoValue = new Set(
|
||||
pseudoValChars.map((c) => c.charCodeAt(0))
|
||||
pseudoValueChars.map((c) => c.charCodeAt(0)),
|
||||
);
|
||||
const charsToEscapeInName = new Set(
|
||||
[
|
||||
...pseudoValChars,
|
||||
...pseudoValueChars,
|
||||
"~",
|
||||
"^",
|
||||
"$",
|
||||
@@ -24,7 +24,8 @@ const charsToEscapeInName = new Set(
|
||||
"]",
|
||||
" ",
|
||||
".",
|
||||
].map((c) => c.charCodeAt(0))
|
||||
"%",
|
||||
].map((c) => c.charCodeAt(0)),
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -34,48 +35,63 @@ const charsToEscapeInName = new Set(
|
||||
*/
|
||||
export function stringify(selector: Selector[][]): string {
|
||||
return selector
|
||||
.map((token) => token.map(stringifyToken).join(""))
|
||||
.map((token) =>
|
||||
token
|
||||
.map((token, index, array) =>
|
||||
stringifyToken(token, index, array),
|
||||
)
|
||||
.join(""),
|
||||
)
|
||||
.join(", ");
|
||||
}
|
||||
|
||||
function stringifyToken(
|
||||
token: Selector,
|
||||
index: number,
|
||||
arr: Selector[]
|
||||
array: Selector[],
|
||||
): string {
|
||||
switch (token.type) {
|
||||
// Simple types
|
||||
case SelectorType.Child:
|
||||
case SelectorType.Child: {
|
||||
return index === 0 ? "> " : " > ";
|
||||
case SelectorType.Parent:
|
||||
}
|
||||
case SelectorType.Parent: {
|
||||
return index === 0 ? "< " : " < ";
|
||||
case SelectorType.Sibling:
|
||||
}
|
||||
case SelectorType.Sibling: {
|
||||
return index === 0 ? "~ " : " ~ ";
|
||||
case SelectorType.Adjacent:
|
||||
}
|
||||
case SelectorType.Adjacent: {
|
||||
return index === 0 ? "+ " : " + ";
|
||||
case SelectorType.Descendant:
|
||||
}
|
||||
case SelectorType.Descendant: {
|
||||
return " ";
|
||||
case SelectorType.ColumnCombinator:
|
||||
}
|
||||
case SelectorType.ColumnCombinator: {
|
||||
return index === 0 ? "|| " : " || ";
|
||||
case SelectorType.Universal:
|
||||
}
|
||||
case SelectorType.Universal: {
|
||||
// Return an empty string if the selector isn't needed.
|
||||
return token.namespace === "*" &&
|
||||
index + 1 < arr.length &&
|
||||
"name" in arr[index + 1]
|
||||
index + 1 < array.length &&
|
||||
"name" in array[index + 1]
|
||||
? ""
|
||||
: `${getNamespace(token.namespace)}*`;
|
||||
}
|
||||
|
||||
case SelectorType.Tag:
|
||||
case SelectorType.Tag: {
|
||||
return getNamespacedName(token);
|
||||
}
|
||||
|
||||
case SelectorType.PseudoElement:
|
||||
case SelectorType.PseudoElement: {
|
||||
return `::${escapeName(token.name, charsToEscapeInName)}${
|
||||
token.data === null
|
||||
? ""
|
||||
: `(${escapeName(token.data, charsToEscapeInPseudoValue)})`
|
||||
}`;
|
||||
}
|
||||
|
||||
case SelectorType.Pseudo:
|
||||
case SelectorType.Pseudo: {
|
||||
return `:${escapeName(token.name, charsToEscapeInName)}${
|
||||
token.data === null
|
||||
? ""
|
||||
@@ -83,11 +99,12 @@ function stringifyToken(
|
||||
typeof token.data === "string"
|
||||
? escapeName(
|
||||
token.data,
|
||||
charsToEscapeInPseudoValue
|
||||
charsToEscapeInPseudoValue,
|
||||
)
|
||||
: stringify(token.data)
|
||||
})`
|
||||
}`;
|
||||
}
|
||||
|
||||
case SelectorType.Attribute: {
|
||||
if (
|
||||
@@ -115,7 +132,7 @@ function stringifyToken(
|
||||
|
||||
return `[${name}${getActionValue(token.action)}="${escapeName(
|
||||
token.value,
|
||||
charsToEscapeInAttributeValue
|
||||
charsToEscapeInAttributeValue,
|
||||
)}"${
|
||||
token.ignoreCase === null ? "" : token.ignoreCase ? " i" : " s"
|
||||
}]`;
|
||||
@@ -125,22 +142,30 @@ function stringifyToken(
|
||||
|
||||
function getActionValue(action: AttributeAction): string {
|
||||
switch (action) {
|
||||
case AttributeAction.Equals:
|
||||
case AttributeAction.Equals: {
|
||||
return "";
|
||||
case AttributeAction.Element:
|
||||
}
|
||||
case AttributeAction.Element: {
|
||||
return "~";
|
||||
case AttributeAction.Start:
|
||||
}
|
||||
case AttributeAction.Start: {
|
||||
return "^";
|
||||
case AttributeAction.End:
|
||||
}
|
||||
case AttributeAction.End: {
|
||||
return "$";
|
||||
case AttributeAction.Any:
|
||||
}
|
||||
case AttributeAction.Any: {
|
||||
return "*";
|
||||
case AttributeAction.Not:
|
||||
}
|
||||
case AttributeAction.Not: {
|
||||
return "!";
|
||||
case AttributeAction.Hyphen:
|
||||
}
|
||||
case AttributeAction.Hyphen: {
|
||||
return "|";
|
||||
case AttributeAction.Exists:
|
||||
}
|
||||
default: {
|
||||
throw new Error("Shouldn't be here");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,30 +175,30 @@ function getNamespacedName(token: {
|
||||
}): string {
|
||||
return `${getNamespace(token.namespace)}${escapeName(
|
||||
token.name,
|
||||
charsToEscapeInName
|
||||
charsToEscapeInName,
|
||||
)}`;
|
||||
}
|
||||
|
||||
function getNamespace(namespace: string | null): string {
|
||||
return namespace !== null
|
||||
? `${
|
||||
return namespace === null
|
||||
? ""
|
||||
: `${
|
||||
namespace === "*"
|
||||
? "*"
|
||||
: escapeName(namespace, charsToEscapeInName)
|
||||
}|`
|
||||
: "";
|
||||
}|`;
|
||||
}
|
||||
|
||||
function escapeName(str: string, charsToEscape: Set<number>): string {
|
||||
let lastIdx = 0;
|
||||
let ret = "";
|
||||
function escapeName(name: string, charsToEscape: Set<number>): string {
|
||||
let lastIndex = 0;
|
||||
let escapedName = "";
|
||||
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
if (charsToEscape.has(str.charCodeAt(i))) {
|
||||
ret += `${str.slice(lastIdx, i)}\\${str.charAt(i)}`;
|
||||
lastIdx = i + 1;
|
||||
for (let index = 0; index < name.length; index++) {
|
||||
if (charsToEscape.has(name.charCodeAt(index))) {
|
||||
escapedName += `${name.slice(lastIndex, index)}\\${name.charAt(index)}`;
|
||||
lastIndex = index + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ret.length > 0 ? ret + str.slice(lastIdx) : str;
|
||||
return escapedName.length > 0 ? escapedName + name.slice(lastIndex) : name;
|
||||
}
|
||||
|
||||
+7
-6
@@ -4,11 +4,12 @@
|
||||
* @license BSD-3-Clause (https://github.com/web-platform-tests/wpt/blob/master/LICENSE.md)
|
||||
*/
|
||||
|
||||
import { parse, stringify } from ".";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { parse, stringify } from "./index";
|
||||
|
||||
function test_valid_selector(
|
||||
selector: string,
|
||||
serialized: string | string[] = selector
|
||||
serialized: string | string[] = selector,
|
||||
) {
|
||||
const result = stringify(parse(selector));
|
||||
if (Array.isArray(serialized)) {
|
||||
@@ -110,7 +111,7 @@ describe("Web Platform Tests", () => {
|
||||
it("The Matches-Any Pseudo-class: ':is()'", () => {
|
||||
test_valid_selector(
|
||||
":is(ul,ol,.list) > [hidden]",
|
||||
":is(ul, ol, .list) > [hidden]"
|
||||
":is(ul, ol, .list) > [hidden]",
|
||||
);
|
||||
test_valid_selector(":is(:hover,:focus)", ":is(:hover, :focus)");
|
||||
test_valid_selector("a:is(:not(:hover))");
|
||||
@@ -140,11 +141,11 @@ describe("Web Platform Tests", () => {
|
||||
test_valid_selector(":not(:host(:not(.a)))");
|
||||
test_valid_selector(
|
||||
":not([disabled][selected])",
|
||||
":not([disabled][selected])"
|
||||
":not([disabled][selected])",
|
||||
);
|
||||
test_valid_selector(
|
||||
":not([disabled],[selected])",
|
||||
":not([disabled], [selected])"
|
||||
":not([disabled], [selected])",
|
||||
);
|
||||
|
||||
test_invalid_selector(":not()");
|
||||
@@ -172,7 +173,7 @@ describe("Web Platform Tests", () => {
|
||||
it("The Specificity-adjustment Pseudo-class: ':where()'", () => {
|
||||
test_valid_selector(
|
||||
":where(ul,ol,.list) > [hidden]",
|
||||
":where(ul, ol, .list) > [hidden]"
|
||||
":where(ul, ol, .list) > [hidden]",
|
||||
);
|
||||
test_valid_selector(":where(:hover,:focus)", ":where(:hover, :focus)");
|
||||
test_valid_selector("a:where(:not(:hover))");
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2019",
|
||||
"module": "es2015",
|
||||
"outDir": "lib/es",
|
||||
"module": "CommonJS",
|
||||
"outDir": "dist/esm",
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
||||
|
||||
+7
-13
@@ -1,19 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
|
||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
||||
"target": "ES2019" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
|
||||
"module": "CommonJS" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
"declaration": true /* Generates corresponding '.d.ts' file. */,
|
||||
"declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */,
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
"outDir": "lib/commonjs" /* Redirect output structure to the directory. */,
|
||||
"outDir": "dist/esm" /* Redirect output structure to the directory. */,
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
|
||||
/* Additional Checks */
|
||||
"isolatedDeclarations": true,
|
||||
"isolatedModules": true,
|
||||
"noUnusedLocals": true /* Report errors on unused locals. */,
|
||||
"noUnusedParameters": true /* Report errors on unused parameters. */,
|
||||
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
|
||||
@@ -21,14 +23,6 @@
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"**/*.spec.ts",
|
||||
"**/__fixtures__/*",
|
||||
"**/__tests__/*",
|
||||
"**/__snapshots__/*"
|
||||
]
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user