@jesscss/scss-parser 2.0.0-alpha.7 → 2.0.0-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -12
- package/lib/ast/grammar.cjs +36610 -0
- package/lib/ast/grammar.d.ts +1 -0
- package/lib/ast/grammar.js +36609 -0
- package/lib/ast/lower-user-function-calls.d.ts +6 -0
- package/lib/cst.cjs +1 -1
- package/lib/cst.js +1 -1
- package/lib/grammar.cjs +2 -55568
- package/lib/grammar.js +1 -55567
- package/lib/grammar2.cjs +88858 -0
- package/lib/grammar2.js +88853 -0
- package/lib/index.cjs +108 -11
- package/lib/index.d.ts +10 -7
- package/lib/index.js +107 -5
- package/package.json +10 -20
- package/lib/builders.d.ts +0 -154
- package/lib/functional-parser.cjs +0 -2872
- package/lib/functional-parser.d.ts +0 -46
- package/lib/functional-parser.js +0 -2855
- package/lib/interp.d.ts +0 -26
- package/lib/jess.cjs +0 -6
- package/lib/jess.d.ts +0 -2
- package/lib/jess.js +0 -2
- package/lib/scss-atroot-helpers.d.ts +0 -4
- package/lib/scss-atrule-helpers.d.ts +0 -36
- package/lib/scss-value-helpers.d.ts +0 -11
- package/src/builders.ts +0 -1408
- package/src/cst.ts +0 -25
- package/src/functional-parser.ts +0 -135
- package/src/grammar.ts +0 -621
- package/src/index.ts +0 -14
- package/src/interp.ts +0 -158
- package/src/jess.ts +0 -11
- package/src/scss-atroot-helpers.ts +0 -105
- package/src/scss-atrule-helpers.ts +0 -191
- package/src/scss-value-helpers.ts +0 -105
package/src/interp.ts
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SCSS `#{…}` interpolation helpers for the functional grammar builders.
|
|
3
|
-
* Mirrors productions/helpers.ts (Chevrotain) without the nested parser bootstrap.
|
|
4
|
-
*/
|
|
5
|
-
import {
|
|
6
|
-
Any,
|
|
7
|
-
Expression,
|
|
8
|
-
Interpolated,
|
|
9
|
-
INTERPOLATION_PLACEHOLDER,
|
|
10
|
-
Reference,
|
|
11
|
-
isNode,
|
|
12
|
-
N,
|
|
13
|
-
Node,
|
|
14
|
-
type LocationInfo
|
|
15
|
-
} from '@jesscss/core';
|
|
16
|
-
|
|
17
|
-
export type InterpRole = 'ident' | 'property' | 'any' | 'name';
|
|
18
|
-
|
|
19
|
-
let parseScssFnLazy: ((input: string, rule?: string) => import('./grammar.js').ScssFnParseResult) | undefined;
|
|
20
|
-
|
|
21
|
-
/** Wired from grammar.ts after `parseScssFn` is defined (breaks circular import). */
|
|
22
|
-
export function setParseScssFnForInterp(
|
|
23
|
-
fn: (input: string, rule?: string) => import('./grammar.js').ScssFnParseResult
|
|
24
|
-
): void {
|
|
25
|
-
parseScssFnLazy = fn;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function findScssInterpolationSpans(value: string): Array<{ start: number; end: number; content: string }> {
|
|
29
|
-
const matches: Array<{ start: number; end: number; content: string }> = [];
|
|
30
|
-
let i = 0;
|
|
31
|
-
while (i < value.length) {
|
|
32
|
-
if (value[i] === '#' && value[i + 1] === '{') {
|
|
33
|
-
const start = i;
|
|
34
|
-
i += 2;
|
|
35
|
-
let depth = 1;
|
|
36
|
-
const contentStart = i;
|
|
37
|
-
while (i < value.length && depth > 0) {
|
|
38
|
-
const ch = value[i]!;
|
|
39
|
-
if (ch === '{') {
|
|
40
|
-
depth++;
|
|
41
|
-
} else if (ch === '}') {
|
|
42
|
-
depth--;
|
|
43
|
-
}
|
|
44
|
-
i++;
|
|
45
|
-
}
|
|
46
|
-
if (depth === 0) {
|
|
47
|
-
matches.push({ start, end: i, content: value.slice(contentStart, i - 1) });
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
50
|
-
i++;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return matches;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function unwrapSingleReference(n: Node): Reference | undefined {
|
|
57
|
-
if (
|
|
58
|
-
isNode(n, N.Reference)
|
|
59
|
-
&& n.options?.type === 'variable'
|
|
60
|
-
&& !n.target
|
|
61
|
-
&& typeof n.key === 'string'
|
|
62
|
-
) {
|
|
63
|
-
return n;
|
|
64
|
-
}
|
|
65
|
-
return undefined;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function valueFromParseResult(
|
|
69
|
-
r: import('./grammar.js').ScssFnParseResult,
|
|
70
|
-
fallback: string,
|
|
71
|
-
loc: LocationInfo
|
|
72
|
-
): Node {
|
|
73
|
-
const root = r.tree;
|
|
74
|
-
if (isNode(root, N.Rules) && root.rules.length > 0) {
|
|
75
|
-
return root.rules[0]!;
|
|
76
|
-
}
|
|
77
|
-
if (root instanceof Node) {
|
|
78
|
-
return root;
|
|
79
|
-
}
|
|
80
|
-
return new Any(fallback, { role: 'any' }, loc);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Parse a `#{…}` inner expression via the functional value grammar. */
|
|
84
|
-
export function parseScssInterpExpr(expr: string, loc: LocationInfo): Node {
|
|
85
|
-
const trimmed = expr.trim();
|
|
86
|
-
if (!trimmed) {
|
|
87
|
-
return new Any('', { role: 'any' }, loc);
|
|
88
|
-
}
|
|
89
|
-
if (!parseScssFnLazy) {
|
|
90
|
-
throw new Error('parseScssFn not wired for interpolation (setParseScssFnForInterp)');
|
|
91
|
-
}
|
|
92
|
-
const r = parseScssFnLazy(trimmed, 'valueList');
|
|
93
|
-
if (r.errors.length) {
|
|
94
|
-
return new Any(trimmed, { role: 'any' }, loc);
|
|
95
|
-
}
|
|
96
|
-
const tree = valueFromParseResult(r, trimmed, loc);
|
|
97
|
-
const ref = unwrapSingleReference(tree);
|
|
98
|
-
if (ref && typeof ref.key === 'string') {
|
|
99
|
-
return new Reference({ key: ref.key }, { type: 'variable', role: 'ident' }, loc);
|
|
100
|
-
}
|
|
101
|
-
if (isNode(tree, N.Reference)) {
|
|
102
|
-
return new Expression(tree, undefined, loc);
|
|
103
|
-
}
|
|
104
|
-
return tree;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Validate a `selector.parse("…")` argument through the functional selector
|
|
109
|
-
* grammar. Returns `true` when the text is a well-formed selector list. Used to
|
|
110
|
-
* gate lifting a `selector.*` call into a `SelectorCapture`; the capture keeps the
|
|
111
|
-
* lean string payload (`SelectorCapture` supports a bare-string `SelectorLike`).
|
|
112
|
-
*/
|
|
113
|
-
export function isValidScssSelectorList(selectorText: string): boolean {
|
|
114
|
-
const trimmed = selectorText.trim();
|
|
115
|
-
if (!trimmed) {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
118
|
-
if (!parseScssFnLazy) {
|
|
119
|
-
throw new Error('parseScssFn not wired for interpolation (setParseScssFnForInterp)');
|
|
120
|
-
}
|
|
121
|
-
const r = parseScssFnLazy(trimmed, 'SelectorList');
|
|
122
|
-
return r.errors.length === 0;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** Turn a parsed expression into an interpolation replacement (name/ident slots). */
|
|
126
|
-
export function toInterpReplacement(expr: Node, loc: LocationInfo): Node {
|
|
127
|
-
const ref = unwrapSingleReference(expr);
|
|
128
|
-
if (ref && typeof ref.key === 'string') {
|
|
129
|
-
return new Reference({ key: ref.key }, { type: 'variable', role: 'ident' }, loc);
|
|
130
|
-
}
|
|
131
|
-
if (isNode(expr, N.Reference)) {
|
|
132
|
-
return new Expression(expr, undefined, loc);
|
|
133
|
-
}
|
|
134
|
-
return expr;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/** Build an `Interpolated` node from a string containing `#{…}` runs. */
|
|
138
|
-
export function buildScssInterpolatedFromString(
|
|
139
|
-
value: string,
|
|
140
|
-
loc: LocationInfo,
|
|
141
|
-
role: InterpRole
|
|
142
|
-
): Any | Interpolated {
|
|
143
|
-
const matches = findScssInterpolationSpans(value);
|
|
144
|
-
if (matches.length === 0) {
|
|
145
|
-
return new Any(value, { role }, loc);
|
|
146
|
-
}
|
|
147
|
-
const replacements: Node[] = [];
|
|
148
|
-
let source = value;
|
|
149
|
-
let offset = 0;
|
|
150
|
-
for (const match of matches) {
|
|
151
|
-
const adjustedStart = match.start - offset;
|
|
152
|
-
const adjustedEnd = match.end - offset;
|
|
153
|
-
source = source.slice(0, adjustedStart) + INTERPOLATION_PLACEHOLDER + source.slice(adjustedEnd);
|
|
154
|
-
offset += (match.end - match.start) - INTERPOLATION_PLACEHOLDER.length;
|
|
155
|
-
replacements.push(toInterpReplacement(parseScssInterpExpr(match.content, loc), loc));
|
|
156
|
-
}
|
|
157
|
-
return new Interpolated({ source, replacements }, { role }, loc);
|
|
158
|
-
}
|
package/src/jess.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export { ScssGrammar } from './builders.js';
|
|
2
|
-
export {
|
|
3
|
-
ScssParser,
|
|
4
|
-
ScssParser as Parser,
|
|
5
|
-
parseScssFn,
|
|
6
|
-
type ScssFnParseResult,
|
|
7
|
-
type ScssFnParseOptions,
|
|
8
|
-
type ScssParserConfig,
|
|
9
|
-
type ScssRules,
|
|
10
|
-
type SyntacticContentAssistSuggestion
|
|
11
|
-
} from './functional-parser.js';
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Ampersand,
|
|
3
|
-
ComplexSelector,
|
|
4
|
-
isNode,
|
|
5
|
-
N,
|
|
6
|
-
Nil,
|
|
7
|
-
Ruleset,
|
|
8
|
-
SelectorList,
|
|
9
|
-
sourceSpanOf,
|
|
10
|
-
type LocationInfo,
|
|
11
|
-
type Node,
|
|
12
|
-
type Rules,
|
|
13
|
-
type Selector,
|
|
14
|
-
type TreeContext
|
|
15
|
-
} from '@jesscss/core';
|
|
16
|
-
|
|
17
|
-
export function createNullParentAmpersand(context?: TreeContext, selector?: Selector): Ampersand {
|
|
18
|
-
const location = selector ? sourceSpanOf(selector) : undefined;
|
|
19
|
-
const nil = new Nil(undefined, undefined, location, context);
|
|
20
|
-
const amp = new Ampersand(
|
|
21
|
-
{ selectorContainer: { selector: nil } },
|
|
22
|
-
undefined,
|
|
23
|
-
location,
|
|
24
|
-
context
|
|
25
|
-
);
|
|
26
|
-
amp.adopt(nil);
|
|
27
|
-
return amp;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function getNodeLocation(node: Node): LocationInfo | undefined {
|
|
31
|
-
return sourceSpanOf(node);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function prefixAtRootSelector(selector: Selector, context?: TreeContext): Selector {
|
|
35
|
-
if (isNode(selector, N.SelectorList)) {
|
|
36
|
-
const list = selector;
|
|
37
|
-
return new SelectorList(
|
|
38
|
-
list.value.map(item => prefixAtRootSelector(item, context)),
|
|
39
|
-
undefined,
|
|
40
|
-
getNodeLocation(selector),
|
|
41
|
-
context
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const amp = createNullParentAmpersand(context, selector);
|
|
46
|
-
if (isNode(selector, N.ComplexSelector)) {
|
|
47
|
-
const complex = selector;
|
|
48
|
-
return new ComplexSelector(
|
|
49
|
-
[amp, ...complex.value],
|
|
50
|
-
undefined,
|
|
51
|
-
getNodeLocation(selector),
|
|
52
|
-
context
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return new ComplexSelector([amp, selector], undefined, getNodeLocation(selector), context);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function lowerPlainAtRootRules(rules: Rules, context?: TreeContext): void {
|
|
60
|
-
const transformRule = (node: Node): Node => {
|
|
61
|
-
if (isNode(node, N.Ruleset)) {
|
|
62
|
-
const rs = node;
|
|
63
|
-
if (!isNode(rs.selector, N.Nil)) {
|
|
64
|
-
return new Ruleset({
|
|
65
|
-
selector: prefixAtRootSelector(rs.selector, context),
|
|
66
|
-
rules: rs.rules,
|
|
67
|
-
...(rs.guard !== undefined && { guard: rs.guard }),
|
|
68
|
-
...(rs.selectorBeforeExtend !== undefined && {
|
|
69
|
-
selectorBeforeExtend: rs.selectorBeforeExtend
|
|
70
|
-
})
|
|
71
|
-
}, rs.options, sourceSpanOf(rs), context);
|
|
72
|
-
}
|
|
73
|
-
return node;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (isNode(node, N.AtRule) && node.rules) {
|
|
77
|
-
lowerPlainAtRootRules(node.rules, context);
|
|
78
|
-
return node;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (isNode(node, N.If)) {
|
|
82
|
-
lowerPlainAtRootRules(node, context);
|
|
83
|
-
if (node.else) {
|
|
84
|
-
lowerPlainAtRootRules(node.else, context);
|
|
85
|
-
}
|
|
86
|
-
return node;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (isNode(node, N.For)) {
|
|
90
|
-
lowerPlainAtRootRules(node, context);
|
|
91
|
-
return node;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (isNode(node, N.While)) {
|
|
95
|
-
lowerPlainAtRootRules(node, context);
|
|
96
|
-
return node;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return node;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
for (let i = 0; i < rules.rules.length; i++) {
|
|
103
|
-
rules.rules[i] = transformRule(rules.rules[i]!);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared helpers for SCSS module-system at-rules in the functional parser.
|
|
3
|
-
*/
|
|
4
|
-
/* eslint-disable @typescript-eslint/no-unsafe-type-assertion */
|
|
5
|
-
import {
|
|
6
|
-
Any,
|
|
7
|
-
Quoted,
|
|
8
|
-
isNode,
|
|
9
|
-
N,
|
|
10
|
-
sourceSpanOf,
|
|
11
|
-
type Node,
|
|
12
|
-
type LocationInfo,
|
|
13
|
-
type ExtendSelectorKind,
|
|
14
|
-
Url
|
|
15
|
-
} from '@jesscss/core';
|
|
16
|
-
|
|
17
|
-
export function isScriptUsePath(path: string): boolean {
|
|
18
|
-
return path.endsWith('.js') || path.endsWith('.ts') || path.endsWith('.json');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function defaultNamespaceFromPath(path: string): string | undefined {
|
|
22
|
-
if (path.startsWith('sass:')) {
|
|
23
|
-
const name = path.slice('sass:'.length);
|
|
24
|
-
return name.split('/').filter(Boolean).pop();
|
|
25
|
-
}
|
|
26
|
-
const base = path.split('/').filter(Boolean).pop();
|
|
27
|
-
if (!base) {
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
const noExt = base.replace(/\.(scss|sass|css|jess|js|ts|json)$/i, '');
|
|
31
|
-
return noExt || undefined;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function quotedLike(original: Quoted, nextValue: string, loc?: LocationInfo): Quoted {
|
|
35
|
-
const quote = original.options?.quote ?? '"';
|
|
36
|
-
const escaped = original.options?.escaped;
|
|
37
|
-
const nodeLoc: LocationInfo | undefined = loc ?? sourceSpanOf(original);
|
|
38
|
-
return new Quoted(new Any(nextValue, { role: 'any' }), { quote, escaped }, nodeLoc);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Detect the CSS `@import` ordering violations Sass parse-rejects (`error/wrong_order/*`).
|
|
43
|
-
* The full media-query-list / `supports()` grammar is out of scope for the
|
|
44
|
-
* scanned prelude, so this catches the clearly-invalid, low-false-positive
|
|
45
|
-
* shapes on the raw prelude text (everything after `@import`, minus the path):
|
|
46
|
-
*
|
|
47
|
-
* 1. A bare media feature `(x: y)` (NOT a `fn(...)` call — hence the
|
|
48
|
-
* no-ident-before-`(` guard) followed by anything other than `and` / `or` /
|
|
49
|
-
* `,` / `;` / end. Catches `"a" (b: c) supports(d: e)`, `"a" (b: c) d`,
|
|
50
|
-
* `"a" (b: c) d(e)`.
|
|
51
|
-
* 2. A comma directly followed by a function call `ident(` — a new import item
|
|
52
|
-
* can never be `supports(...)` or an unknown function. Catches
|
|
53
|
-
* `"a" b, supports(c: d)`, `"a" b, c(d)`, and `"a", url(b)`.
|
|
54
|
-
*
|
|
55
|
-
* Not caught (documented as remaining): a string after a comma in media context
|
|
56
|
-
* (`"a" b, "c"` — indistinguishable from a valid plain-import continuation without
|
|
57
|
-
* modelling media-vs-plain state) and `supports()` value-syntax errors
|
|
58
|
-
* (`supports(--a:)`).
|
|
59
|
-
*/
|
|
60
|
-
export function checkImportPreludeOrder(
|
|
61
|
-
preludeText: string,
|
|
62
|
-
recordError: (message: string) => void
|
|
63
|
-
): void {
|
|
64
|
-
const text = preludeText;
|
|
65
|
-
// A bare media feature may be followed by `and`/`or` (query chain), `,`/`;`
|
|
66
|
-
// (list / end), `{`/`}` (block bounds), or `)` (closes an enclosing
|
|
67
|
-
// `supports(...)` / group). Anything else — `supports(`, an ident, a function —
|
|
68
|
-
// is the wrong-order violation.
|
|
69
|
-
const badFeatureOrder = /(?<![-\w])\([^()]*:[^()]*\)\s*(?!and(?![-\w])|or(?![-\w])|[,;{})])\S/i;
|
|
70
|
-
const badCommaFunction = /,\s*[a-zA-Z][-\w]*\s*\(/;
|
|
71
|
-
if (badFeatureOrder.test(text) || badCommaFunction.test(text)) {
|
|
72
|
-
recordError('Invalid @import: a media-query list must not follow a media feature without `and`/`or`, and a comma-separated @import item must be a URL or string (not `supports(…)` or another function).');
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function isPlainCssImportPath(rawPath: string): boolean {
|
|
77
|
-
return /\.css(?:$|[?#])/i.test(rawPath)
|
|
78
|
-
|| /^[a-z]+:\/\//i.test(rawPath)
|
|
79
|
-
|| rawPath.startsWith('//');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function isPlainCssImportPrelude(prelude: Node, extraText: string | undefined): boolean {
|
|
83
|
-
if (prelude instanceof Url) {
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
if (extraText && extraText.trim()) {
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
if (isNode(prelude, N.Quoted)) {
|
|
90
|
-
return isPlainCssImportPath(prelude.valueOf());
|
|
91
|
-
}
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function findDisallowedExtendSelector(
|
|
96
|
-
selector: Node,
|
|
97
|
-
allowed: readonly ExtendSelectorKind[]
|
|
98
|
-
): { kind: ExtendSelectorKind; selector: Node } | undefined {
|
|
99
|
-
if (isNode(selector, N.SelectorList)) {
|
|
100
|
-
for (const item of selector.value) {
|
|
101
|
-
const disallowed = findDisallowedExtendSelector(item as Node, allowed);
|
|
102
|
-
if (disallowed) {
|
|
103
|
-
return disallowed;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return undefined;
|
|
107
|
-
}
|
|
108
|
-
const kinds: ExtendSelectorKind[] = isNode(selector, N.BasicSelector)
|
|
109
|
-
? ['simple', 'basic']
|
|
110
|
-
: isNode(selector, N.PseudoSelector)
|
|
111
|
-
? ['simple', 'pseudo']
|
|
112
|
-
: isNode(selector, N.CompoundSelector)
|
|
113
|
-
? ['compound']
|
|
114
|
-
: isNode(selector, N.ComplexSelector)
|
|
115
|
-
? ['complex']
|
|
116
|
-
: ['simple'];
|
|
117
|
-
if (isNode(selector, N.CompoundSelector) && selector.value.length === 1) {
|
|
118
|
-
return findDisallowedExtendSelector(selector.value[0] as Node, allowed);
|
|
119
|
-
}
|
|
120
|
-
if (isNode(selector, N.ComplexSelector) && selector.value.length === 1) {
|
|
121
|
-
return findDisallowedExtendSelector(selector.value[0] as Node, allowed);
|
|
122
|
-
}
|
|
123
|
-
if (kinds.some(k => allowed.includes(k))) {
|
|
124
|
-
return undefined;
|
|
125
|
-
}
|
|
126
|
-
return { kind: kinds[0]!, selector };
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function validateExtendTarget(
|
|
130
|
-
target: Node,
|
|
131
|
-
allowed: readonly ExtendSelectorKind[] | undefined,
|
|
132
|
-
recordError: (message: string) => void
|
|
133
|
-
): void {
|
|
134
|
-
if (!allowed) {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
const disallowed = findDisallowedExtendSelector(target, allowed);
|
|
138
|
-
if (!disallowed) {
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
const kindList = allowed.length === 1 ? `${allowed[0]} value` : allowed.join(', ');
|
|
142
|
-
recordError(
|
|
143
|
-
`@extend only allows ${kindList}, but found ${disallowed.kind} selector "${disallowed.selector.valueOf()}".`
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function checkForwardPreludeErrors(
|
|
148
|
-
preludeExtra: string | undefined,
|
|
149
|
-
recordError: (message: string) => void
|
|
150
|
-
): void {
|
|
151
|
-
if (!preludeExtra?.trim()) {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
// Normalize away loud/silent comments and collapse whitespace so a prefix form
|
|
155
|
-
// interrupted by a comment or newline (`as /**/ a-*`, `as //\n a-*`) still
|
|
156
|
-
// matches the rejection patterns below.
|
|
157
|
-
const text = preludeExtra
|
|
158
|
-
.replace(/\/\*[\s\S]*?\*\//g, ' ')
|
|
159
|
-
.replace(/\/\/[^\n\r]*/g, ' ')
|
|
160
|
-
.replace(/\s+/g, ' ')
|
|
161
|
-
.trim();
|
|
162
|
-
if (/\bas\s+\S+-\*/.test(text)) {
|
|
163
|
-
recordError(
|
|
164
|
-
'@forward with "as <prefix>-*" prefixing is not supported in Jess and will never be. Use explicit namespacing instead.'
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
if (/\b(show|hide)\b/.test(text)) {
|
|
168
|
-
recordError(
|
|
169
|
-
'@forward with "show"/"hide" lists is not supported in Jess and will never be. Visibility control belongs to the module itself.'
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export function isPlaceholderExtendTarget(target: Node | string): boolean {
|
|
175
|
-
if (typeof target === 'string') {
|
|
176
|
-
return target.startsWith('\\');
|
|
177
|
-
}
|
|
178
|
-
if (isNode(target, N.BasicSelector)) {
|
|
179
|
-
return target.value.startsWith('\\');
|
|
180
|
-
}
|
|
181
|
-
if (isNode(target, N.SelectorList) && target.value.length === 1) {
|
|
182
|
-
return isPlaceholderExtendTarget(target.value[0] as Node);
|
|
183
|
-
}
|
|
184
|
-
if (isNode(target, N.CompoundSelector) && target.value.length === 1) {
|
|
185
|
-
return isPlaceholderExtendTarget(target.value[0] as Node);
|
|
186
|
-
}
|
|
187
|
-
if (isNode(target, N.ComplexSelector) && target.value.length === 1) {
|
|
188
|
-
return isPlaceholderExtendTarget(target.value[0] as Node);
|
|
189
|
-
}
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SCSS value desugaring helpers for the functional grammar builders.
|
|
3
|
-
* Ports productions/helpers.ts without the Chevrotain parser bootstrap.
|
|
4
|
-
*/
|
|
5
|
-
import {
|
|
6
|
-
Call,
|
|
7
|
-
Reference,
|
|
8
|
-
isNode,
|
|
9
|
-
N,
|
|
10
|
-
type LocationInfo,
|
|
11
|
-
type Node
|
|
12
|
-
} from '@jesscss/core';
|
|
13
|
-
|
|
14
|
-
export function unwrapSingleSequence(n: Node): Node {
|
|
15
|
-
if (isNode(n, N.Sequence) && n.value.length === 1) {
|
|
16
|
-
return n.value[0]!;
|
|
17
|
-
}
|
|
18
|
-
return n;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function toDeclKey(node: Node): string {
|
|
22
|
-
return String(node.valueOf());
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function isValidIdentifierKey(key: string): boolean {
|
|
26
|
-
return /^[a-zA-Z_-][a-zA-Z0-9_-]*$/.test(key);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function makeNamespacedReference(
|
|
30
|
-
parts: string[],
|
|
31
|
-
finalType: 'variable' | 'function' | 'mixin' | 'mixin-ruleset',
|
|
32
|
-
loc: LocationInfo
|
|
33
|
-
): Reference {
|
|
34
|
-
let current: Reference = new Reference(parts[0]!, { type: 'variable' }, loc);
|
|
35
|
-
for (let i = 1; i < parts.length; i++) {
|
|
36
|
-
const isFinal = i === parts.length - 1;
|
|
37
|
-
current = new Reference(
|
|
38
|
-
{ target: current, key: parts[i]! },
|
|
39
|
-
{ type: isFinal ? finalType : 'index' },
|
|
40
|
-
loc
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
return current;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function desugarNamespacedCall(call: Call, loc: LocationInfo): Call {
|
|
47
|
-
const { name, args } = call;
|
|
48
|
-
if (typeof name !== 'string') {
|
|
49
|
-
return call;
|
|
50
|
-
}
|
|
51
|
-
if (!name.includes('.')) {
|
|
52
|
-
return call;
|
|
53
|
-
}
|
|
54
|
-
if (name === 'map.get') {
|
|
55
|
-
return call;
|
|
56
|
-
}
|
|
57
|
-
const parts = name.split('.').filter(Boolean);
|
|
58
|
-
if (parts.length < 2) {
|
|
59
|
-
return call;
|
|
60
|
-
}
|
|
61
|
-
const ref = makeNamespacedReference(parts, 'function', loc);
|
|
62
|
-
return new Call({ name: ref, args }, call.options, loc);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function desugarMapLookup(call: Call, loc: LocationInfo): Node {
|
|
66
|
-
const { name, args: argsList } = call;
|
|
67
|
-
if (typeof name !== 'string') {
|
|
68
|
-
return call;
|
|
69
|
-
}
|
|
70
|
-
if (name !== 'map-get' && name !== 'map.get') {
|
|
71
|
-
return call;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const args = isNode(argsList, N.List) ? argsList.value : [];
|
|
75
|
-
if (args.length < 2) {
|
|
76
|
-
return call;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const mapExpr = unwrapSingleSequence(args[0]!);
|
|
80
|
-
const keyArgs = args.slice(1).map(a => unwrapSingleSequence(a));
|
|
81
|
-
|
|
82
|
-
const initialTarget =
|
|
83
|
-
isNode(mapExpr, N.Reference)
|
|
84
|
-
? mapExpr
|
|
85
|
-
: isNode(mapExpr, N.Call)
|
|
86
|
-
? mapExpr
|
|
87
|
-
: undefined;
|
|
88
|
-
|
|
89
|
-
if (!initialTarget) {
|
|
90
|
-
return call;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
let currentTarget: Reference | Call = initialTarget;
|
|
94
|
-
for (const keyNode of keyArgs) {
|
|
95
|
-
const keyStr = toDeclKey(keyNode);
|
|
96
|
-
const useDeclaration = isValidIdentifierKey(keyStr);
|
|
97
|
-
currentTarget = new Reference(
|
|
98
|
-
{ target: currentTarget, key: useDeclaration ? keyStr : keyNode },
|
|
99
|
-
{ type: useDeclaration ? 'declaration' : 'index' },
|
|
100
|
-
loc
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return currentTarget;
|
|
105
|
-
}
|