Skip to content

Commit ffdace4

Browse files
authored
add sourceIndices helper function (#1022)
* add sourceIndices helper function * wording * cleanup * document magic index values
1 parent 5d2c5be commit ffdace4

File tree

9 files changed

+128
-2
lines changed

9 files changed

+128
-2
lines changed

packages/css-parser-algorithms/CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changes to CSS Parser Algorithms
22

3+
### Unreleased (minor)
4+
5+
- Add `sourceIndices` helper function.
6+
7+
This makes it easier to get the start and end indices of a node in the source string.
8+
This function accepts any node that can be converted into an array of tokens.
9+
310
### 2.2.0
411

512
_June 1, 2023_

packages/css-parser-algorithms/dist/index.cjs

+1-1
Large diffs are not rendered by default.

packages/css-parser-algorithms/dist/index.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export { replaceComponentValues } from './util/replace-component-values';
77
export { stringify } from './util/stringify';
88
export { ComponentValueType } from './util/component-value-type';
99
export { isCommentNode, isFunctionNode, isSimpleBlockNode, isTokenNode, isWhitespaceNode, } from './util/type-predicates';
10+
export { sourceIndices } from './util/source-indices';

packages/css-parser-algorithms/dist/index.mjs

+1-1
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { CSSToken } from '@csstools/css-tokenizer';
2+
interface TokenConvertible {
3+
tokens(): Array<CSSToken>;
4+
}
5+
/**
6+
* Returns the start and end index of a node in the CSS source string.
7+
*/
8+
export declare function sourceIndices(x: TokenConvertible | Array<TokenConvertible>): [number, number];
9+
export {};

packages/css-parser-algorithms/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ export {
1313
isTokenNode,
1414
isWhitespaceNode,
1515
} from './util/type-predicates';
16+
export { sourceIndices } from './util/source-indices';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { CSSToken } from '@csstools/css-tokenizer';
2+
3+
interface TokenConvertible {
4+
tokens(): Array<CSSToken>
5+
}
6+
7+
/**
8+
* Returns the start and end index of a node in the CSS source string.
9+
*/
10+
export function sourceIndices(x: TokenConvertible | Array<TokenConvertible>): [number, number] {
11+
if (Array.isArray(x)) {
12+
const firstNode = x[0];
13+
14+
if (!firstNode) {
15+
return [0, 0];
16+
}
17+
18+
const lastNode = x[x.length - 1] || firstNode;
19+
20+
return [
21+
// start index of the first node
22+
sourceIndices(firstNode)[0],
23+
// end index of the last node
24+
sourceIndices(lastNode)[1],
25+
];
26+
}
27+
28+
const tokens = x.tokens();
29+
30+
const firstToken = tokens[0];
31+
const lastToken = tokens[tokens.length - 1];
32+
33+
if (!firstToken || !lastToken) {
34+
return [0, 0];
35+
}
36+
37+
return [
38+
// CSSTokens have this layout : [type, raw, start, end]
39+
// start index of the first token
40+
firstToken[2],
41+
// end index of the last token
42+
lastToken[3],
43+
];
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { tokenize } from '@csstools/css-tokenizer';
2+
import assert from 'assert';
3+
import { parseCommaSeparatedListOfComponentValues, sourceIndices } from '@csstools/css-parser-algorithms';
4+
5+
const onParseError = (err) => {
6+
throw err;
7+
};
8+
9+
{
10+
const indicesAndParts = [
11+
[[0, 49], '(image-set( url("image1.jpg") calc(2 * 1x)), 50px)'],
12+
[[0, 49], '(image-set( url("image1.jpg") calc(2 * 1x)), 50px)'],
13+
[[1, 42], 'image-set( url("image1.jpg") calc(2 * 1x))'],
14+
[[11, 11], ' '],
15+
[[12, 28], 'url("image1.jpg")'],
16+
[[16, 27], '"image1.jpg"'],
17+
[[29, 29], ' '],
18+
[[30, 41], 'calc(2 * 1x)'],
19+
[[35, 35], '2'],
20+
[[36, 36], ' '],
21+
[[37, 37], '*'],
22+
[[38, 38], ' '],
23+
[[39, 40], '1x'],
24+
[[43, 43], ','],
25+
[[44, 44], ' '],
26+
[[45, 48], '50px'],
27+
[[51, 65], ' something-else'],
28+
[[51, 51], ' '],
29+
[[52, 65], 'something-else'],
30+
].reverse();
31+
32+
const source = '(image-set( url("image1.jpg") calc(2 * 1x)), 50px), something-else';
33+
34+
const tokens = tokenize({ css: source }, { onParseError: onParseError });
35+
const list = parseCommaSeparatedListOfComponentValues(tokens, { onParseError: onParseError });
36+
list.forEach((listItem) => {
37+
{
38+
const [y, part] = indicesAndParts.pop();
39+
const x = sourceIndices(listItem);
40+
assert.deepStrictEqual(x, y);
41+
assert.deepStrictEqual(source.slice(x[0], x[1] + 1), part);
42+
}
43+
44+
listItem.forEach((componentValue) => {
45+
{
46+
const [y, part] = indicesAndParts.pop();
47+
const x = sourceIndices(componentValue);
48+
assert.deepStrictEqual(x, y);
49+
assert.deepStrictEqual(source.slice(x[0], x[1] + 1), part);
50+
}
51+
52+
if ('walk' in componentValue) {
53+
componentValue.walk((entry) => {
54+
const [y, part] = indicesAndParts.pop();
55+
const x = sourceIndices(entry.node);
56+
assert.deepStrictEqual(x, y);
57+
assert.deepStrictEqual(source.slice(x[0], x[1] + 1), part);
58+
});
59+
}
60+
});
61+
});
62+
}

packages/css-parser-algorithms/test/test.mjs

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import './cases/query-with-type/0003.mjs';
1818

1919
import './cases/replacer/0001.mjs';
2020

21+
import './cases/source-indices/0001.mjs';
22+
2123
import './cases/various/0001.mjs';
2224
import './cases/various/0002.mjs';
2325
import './cases/various/0003.mjs';

0 commit comments

Comments
 (0)