@fuzdev/fuz_code 0.41.1 → 0.43.0
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 +1 -1
- package/dist/Code.svelte +1 -1
- package/dist/Code.svelte.d.ts +1 -1
- package/dist/{svelte_preprocess_code_static.d.ts → svelte_preprocess_fuz_code.d.ts} +3 -3
- package/dist/svelte_preprocess_fuz_code.d.ts.map +1 -0
- package/dist/{svelte_preprocess_code_static.js → svelte_preprocess_fuz_code.js} +11 -106
- package/package.json +7 -2
- package/src/lib/{svelte_preprocess_code_static.ts → svelte_preprocess_fuz_code.ts} +20 -114
- package/dist/svelte_preprocess_code_static.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -175,7 +175,7 @@ Experimental modules:
|
|
|
175
175
|
|
|
176
176
|
## License [🐦](https://wikipedia.org/wiki/Free_and_open-source_software)
|
|
177
177
|
|
|
178
|
-
based on [Prism](https://github.com/PrismJS/prism)
|
|
178
|
+
based on [Prism](https://github.com/PrismJS/prism) ([prismjs.com](https://prismjs.com/))
|
|
179
179
|
by [Lea Verou](https://lea.verou.me/)
|
|
180
180
|
|
|
181
181
|
the [Svelte grammar](src/lib/grammar_svelte.ts)
|
package/dist/Code.svelte
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
| {
|
|
28
28
|
content?: undefined;
|
|
29
29
|
/**
|
|
30
|
-
* Pre-highlighted HTML from the `
|
|
30
|
+
* Pre-highlighted HTML from the `svelte_preprocess_fuz_code` preprocessor.
|
|
31
31
|
* When provided, skips runtime syntax highlighting entirely.
|
|
32
32
|
*
|
|
33
33
|
* Named `dangerous_raw_html` to signal that it bypasses sanitization,
|
package/dist/Code.svelte.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ type $$ComponentProps = SvelteHTMLElements['code'] & ({
|
|
|
8
8
|
} | {
|
|
9
9
|
content?: undefined;
|
|
10
10
|
/**
|
|
11
|
-
* Pre-highlighted HTML from the `
|
|
11
|
+
* Pre-highlighted HTML from the `svelte_preprocess_fuz_code` preprocessor.
|
|
12
12
|
* When provided, skips runtime syntax highlighting entirely.
|
|
13
13
|
*
|
|
14
14
|
* Named `dangerous_raw_html` to signal that it bypasses sanitization,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type PreprocessorGroup } from 'svelte/compiler';
|
|
2
2
|
import type { SyntaxStyler } from './syntax_styler.js';
|
|
3
|
-
export interface
|
|
3
|
+
export interface PreprocessFuzCodeOptions {
|
|
4
4
|
/** File patterns to exclude. */
|
|
5
5
|
exclude?: Array<string | RegExp>;
|
|
6
6
|
/** Custom syntax styler. @default syntax_styler_global */
|
|
@@ -20,5 +20,5 @@ export interface PreprocessCodeStaticOptions {
|
|
|
20
20
|
*/
|
|
21
21
|
on_error?: 'log' | 'throw';
|
|
22
22
|
}
|
|
23
|
-
export declare const
|
|
24
|
-
//# sourceMappingURL=
|
|
23
|
+
export declare const svelte_preprocess_fuz_code: (options?: PreprocessFuzCodeOptions) => PreprocessorGroup;
|
|
24
|
+
//# sourceMappingURL=svelte_preprocess_fuz_code.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svelte_preprocess_fuz_code.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/svelte_preprocess_fuz_code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,iBAAiB,EAAW,MAAM,iBAAiB,CAAC;AAcxE,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,wBAAwB;IACxC,gCAAgC;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAEjC,0DAA0D;IAC1D,aAAa,CAAC,EAAE,YAAY,CAAC;IAE7B,8CAA8C;IAC9C,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAElC;;;OAGG;IACH,QAAQ,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;CAC3B;AAED,eAAO,MAAM,0BAA0B,GACtC,UAAS,wBAA6B,KACpC,iBA0DF,CAAC"}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { parse } from 'svelte/compiler';
|
|
2
2
|
import MagicString from 'magic-string';
|
|
3
3
|
import { walk } from 'zimmerframe';
|
|
4
|
+
import { should_exclude_path } from '@fuzdev/fuz_util/path.js';
|
|
5
|
+
import { escape_js_string } from '@fuzdev/fuz_util/string.js';
|
|
6
|
+
import { find_attribute, evaluate_static_expr, extract_static_string, resolve_component_names, } from '@fuzdev/fuz_util/svelte_preprocess_helpers.js';
|
|
4
7
|
import { syntax_styler_global } from './syntax_styler_global.js';
|
|
5
|
-
export const
|
|
8
|
+
export const svelte_preprocess_fuz_code = (options = {}) => {
|
|
6
9
|
const { exclude = [], syntax_styler = syntax_styler_global, cache = true, component_imports = ['@fuzdev/fuz_code/Code.svelte'], on_error = process.env.CI ? 'throw' : 'log', } = options;
|
|
7
10
|
// In-memory cache: content+lang hash → highlighted HTML
|
|
8
11
|
const highlight_cache = new Map();
|
|
9
12
|
return {
|
|
10
|
-
name: 'code
|
|
13
|
+
name: 'fuz-code',
|
|
11
14
|
markup: ({ content, filename }) => {
|
|
12
15
|
// Skip excluded files
|
|
13
|
-
if (
|
|
16
|
+
if (should_exclude_path(filename, exclude)) {
|
|
14
17
|
return { code: content };
|
|
15
18
|
}
|
|
16
19
|
// Quick check: does file import from a known Code component source?
|
|
@@ -20,7 +23,7 @@ export const svelte_preprocess_code_static = (options = {}) => {
|
|
|
20
23
|
const s = new MagicString(content);
|
|
21
24
|
const ast = parse(content, { filename, modern: true });
|
|
22
25
|
// Resolve which local names map to the Code component
|
|
23
|
-
const code_names =
|
|
26
|
+
const code_names = resolve_component_names(ast, component_imports);
|
|
24
27
|
if (code_names.size === 0) {
|
|
25
28
|
return { code: content };
|
|
26
29
|
}
|
|
@@ -45,39 +48,6 @@ export const svelte_preprocess_code_static = (options = {}) => {
|
|
|
45
48
|
},
|
|
46
49
|
};
|
|
47
50
|
};
|
|
48
|
-
/**
|
|
49
|
-
* Check if a filename matches any exclusion pattern.
|
|
50
|
-
*/
|
|
51
|
-
const should_exclude = (filename, exclude) => {
|
|
52
|
-
if (!filename || exclude.length === 0)
|
|
53
|
-
return false;
|
|
54
|
-
return exclude.some((pattern) => typeof pattern === 'string' ? filename.includes(pattern) : pattern.test(filename));
|
|
55
|
-
};
|
|
56
|
-
/**
|
|
57
|
-
* Scans import declarations to find local names that import from known Code component sources.
|
|
58
|
-
* Handles default imports, named imports, and aliased imports.
|
|
59
|
-
* Checks both instance (`<script>`) and module (`<script module>`) scripts.
|
|
60
|
-
*/
|
|
61
|
-
const resolve_code_names = (ast, component_imports) => {
|
|
62
|
-
const names = new Set();
|
|
63
|
-
for (const script of [ast.instance, ast.module]) {
|
|
64
|
-
if (!script)
|
|
65
|
-
continue;
|
|
66
|
-
for (const node of script.content.body) {
|
|
67
|
-
if (node.type !== 'ImportDeclaration')
|
|
68
|
-
continue;
|
|
69
|
-
if (!component_imports.includes(node.source.value))
|
|
70
|
-
continue;
|
|
71
|
-
for (const specifier of node.specifiers) {
|
|
72
|
-
// default import: `import Code from '...'`
|
|
73
|
-
// aliased: `import Highlighter from '...'`
|
|
74
|
-
// named: `import { default as Code } from '...'`
|
|
75
|
-
names.add(specifier.local.name);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return names;
|
|
80
|
-
};
|
|
81
51
|
/**
|
|
82
52
|
* Attempt to highlight content, using cache if available.
|
|
83
53
|
* Returns the highlighted HTML, or `null` on error.
|
|
@@ -111,6 +81,9 @@ const find_code_usages = (ast, syntax_styler, code_names, options) => {
|
|
|
111
81
|
context.next();
|
|
112
82
|
if (!code_names.has(node.name))
|
|
113
83
|
return;
|
|
84
|
+
// Skip if spread attributes present — can't determine content statically
|
|
85
|
+
if (node.attributes.some((attr) => attr.type === 'SpreadAttribute'))
|
|
86
|
+
return;
|
|
114
87
|
const content_attr = find_attribute(node, 'content');
|
|
115
88
|
if (!content_attr)
|
|
116
89
|
return;
|
|
@@ -159,62 +132,6 @@ const find_code_usages = (ast, syntax_styler, code_names, options) => {
|
|
|
159
132
|
});
|
|
160
133
|
return transformations;
|
|
161
134
|
};
|
|
162
|
-
/**
|
|
163
|
-
* Find an attribute by name on a component node.
|
|
164
|
-
*/
|
|
165
|
-
const find_attribute = (node, name) => {
|
|
166
|
-
for (const attr of node.attributes) {
|
|
167
|
-
if (attr.type === 'Attribute' && attr.name === name) {
|
|
168
|
-
return attr;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return undefined;
|
|
172
|
-
};
|
|
173
|
-
/**
|
|
174
|
-
* Recursively evaluate an expression AST node to a static string value.
|
|
175
|
-
* Handles string literals, template literals (no interpolation), and string concatenation.
|
|
176
|
-
* Returns `null` for dynamic or non-string expressions.
|
|
177
|
-
*/
|
|
178
|
-
const evaluate_static_expr = (expr) => {
|
|
179
|
-
if (expr.type === 'Literal' && typeof expr.value === 'string')
|
|
180
|
-
return expr.value;
|
|
181
|
-
if (expr.type === 'TemplateLiteral' && expr.expressions.length === 0) {
|
|
182
|
-
return expr.quasis.map((q) => q.value.cooked ?? q.value.raw).join('');
|
|
183
|
-
}
|
|
184
|
-
if (expr.type === 'BinaryExpression' && expr.operator === '+') {
|
|
185
|
-
const left = evaluate_static_expr(expr.left);
|
|
186
|
-
if (left === null)
|
|
187
|
-
return null;
|
|
188
|
-
const right = evaluate_static_expr(expr.right);
|
|
189
|
-
if (right === null)
|
|
190
|
-
return null;
|
|
191
|
-
return left + right;
|
|
192
|
-
}
|
|
193
|
-
return null;
|
|
194
|
-
};
|
|
195
|
-
/**
|
|
196
|
-
* Extract the string value from a static attribute value.
|
|
197
|
-
* Returns `null` for dynamic, non-string, or null literal values.
|
|
198
|
-
*/
|
|
199
|
-
const extract_static_string = (value) => {
|
|
200
|
-
// Boolean attribute
|
|
201
|
-
if (value === true)
|
|
202
|
-
return null;
|
|
203
|
-
// Plain attribute: content="text"
|
|
204
|
-
if (Array.isArray(value)) {
|
|
205
|
-
const first = value[0];
|
|
206
|
-
if (value.length === 1 && first?.type === 'Text') {
|
|
207
|
-
return first.data;
|
|
208
|
-
}
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
// ExpressionTag
|
|
212
|
-
const expr = value.expression;
|
|
213
|
-
// Null literal
|
|
214
|
-
if (expr.type === 'Literal' && expr.value === null)
|
|
215
|
-
return null;
|
|
216
|
-
return evaluate_static_expr(expr);
|
|
217
|
-
};
|
|
218
135
|
/**
|
|
219
136
|
* Try to extract a conditional expression where both branches are static strings.
|
|
220
137
|
* Returns the condition source text and both branch values, or `null` if not applicable.
|
|
@@ -235,23 +152,11 @@ const try_extract_conditional = (value, source) => {
|
|
|
235
152
|
const test_source = source.slice(test.start, test.end);
|
|
236
153
|
return { test_source, consequent, alternate };
|
|
237
154
|
};
|
|
238
|
-
/**
|
|
239
|
-
* Escapes a string for use inside a single-quoted JS string literal.
|
|
240
|
-
* Single quotes are used because `stylize()` output contains double quotes
|
|
241
|
-
* on every token span, so wrapping with single quotes avoids escaping those.
|
|
242
|
-
*/
|
|
243
|
-
const escape_js_string = (html) => {
|
|
244
|
-
return html
|
|
245
|
-
.replace(/\\/g, '\\\\') // backslashes first
|
|
246
|
-
.replace(/'/g, "\\'") // single quotes
|
|
247
|
-
.replace(/\n/g, '\\n') // newlines
|
|
248
|
-
.replace(/\r/g, '\\r'); // carriage returns
|
|
249
|
-
};
|
|
250
155
|
/**
|
|
251
156
|
* Handle errors during highlighting.
|
|
252
157
|
*/
|
|
253
158
|
const handle_error = (error, options) => {
|
|
254
|
-
const message = `[code
|
|
159
|
+
const message = `[fuz-code] Highlighting failed${options.filename ? ` in ${options.filename}` : ''}: ${error instanceof Error ? error.message : String(error)}`;
|
|
255
160
|
if (options.on_error === 'throw') {
|
|
256
161
|
throw new Error(message);
|
|
257
162
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fuzdev/fuz_code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "syntax styling utilities and components for TypeScript, Svelte, and Markdown",
|
|
5
5
|
"glyph": "🎨",
|
|
6
6
|
"logo": "logo.svg",
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"@fuzdev/fuz_css": ">=0.44.1",
|
|
36
|
+
"@fuzdev/fuz_util": ">=0.49.0",
|
|
37
|
+
"esm-env": "^1",
|
|
36
38
|
"magic-string": "^0.30",
|
|
37
39
|
"svelte": "^5",
|
|
38
40
|
"zimmerframe": "^1"
|
|
@@ -41,6 +43,9 @@
|
|
|
41
43
|
"@fuzdev/fuz_css": {
|
|
42
44
|
"optional": true
|
|
43
45
|
},
|
|
46
|
+
"@fuzdev/fuz_util": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
44
49
|
"magic-string": {
|
|
45
50
|
"optional": true
|
|
46
51
|
},
|
|
@@ -55,7 +60,7 @@
|
|
|
55
60
|
"@changesets/changelog-git": "^0.2.1",
|
|
56
61
|
"@fuzdev/fuz_css": "^0.47.0",
|
|
57
62
|
"@fuzdev/fuz_ui": "^0.181.1",
|
|
58
|
-
"@fuzdev/fuz_util": "^0.
|
|
63
|
+
"@fuzdev/fuz_util": "^0.49.0",
|
|
59
64
|
"@ryanatkn/eslint-config": "^0.9.0",
|
|
60
65
|
"@ryanatkn/gro": "^0.190.0",
|
|
61
66
|
"@sveltejs/adapter-static": "^3.0.10",
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import {parse, type PreprocessorGroup, type AST} from 'svelte/compiler';
|
|
2
2
|
import MagicString from 'magic-string';
|
|
3
3
|
import {walk} from 'zimmerframe';
|
|
4
|
+
import {should_exclude_path} from '@fuzdev/fuz_util/path.js';
|
|
5
|
+
import {escape_js_string} from '@fuzdev/fuz_util/string.js';
|
|
6
|
+
import {
|
|
7
|
+
find_attribute,
|
|
8
|
+
evaluate_static_expr,
|
|
9
|
+
extract_static_string,
|
|
10
|
+
resolve_component_names,
|
|
11
|
+
type ResolvedComponentImport,
|
|
12
|
+
} from '@fuzdev/fuz_util/svelte_preprocess_helpers.js';
|
|
4
13
|
|
|
5
14
|
import {syntax_styler_global} from './syntax_styler_global.js';
|
|
6
15
|
import type {SyntaxStyler} from './syntax_styler.js';
|
|
7
16
|
|
|
8
|
-
export interface
|
|
17
|
+
export interface PreprocessFuzCodeOptions {
|
|
9
18
|
/** File patterns to exclude. */
|
|
10
19
|
exclude?: Array<string | RegExp>;
|
|
11
20
|
|
|
@@ -30,8 +39,8 @@ export interface PreprocessCodeStaticOptions {
|
|
|
30
39
|
on_error?: 'log' | 'throw';
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
export const
|
|
34
|
-
options:
|
|
42
|
+
export const svelte_preprocess_fuz_code = (
|
|
43
|
+
options: PreprocessFuzCodeOptions = {},
|
|
35
44
|
): PreprocessorGroup => {
|
|
36
45
|
const {
|
|
37
46
|
exclude = [],
|
|
@@ -45,11 +54,11 @@ export const svelte_preprocess_code_static = (
|
|
|
45
54
|
const highlight_cache: Map<string, string> = new Map();
|
|
46
55
|
|
|
47
56
|
return {
|
|
48
|
-
name: 'code
|
|
57
|
+
name: 'fuz-code',
|
|
49
58
|
|
|
50
59
|
markup: ({content, filename}) => {
|
|
51
60
|
// Skip excluded files
|
|
52
|
-
if (
|
|
61
|
+
if (should_exclude_path(filename, exclude)) {
|
|
53
62
|
return {code: content};
|
|
54
63
|
}
|
|
55
64
|
|
|
@@ -62,7 +71,7 @@ export const svelte_preprocess_code_static = (
|
|
|
62
71
|
const ast = parse(content, {filename, modern: true});
|
|
63
72
|
|
|
64
73
|
// Resolve which local names map to the Code component
|
|
65
|
-
const code_names =
|
|
74
|
+
const code_names = resolve_component_names(ast, component_imports);
|
|
66
75
|
if (code_names.size === 0) {
|
|
67
76
|
return {code: content};
|
|
68
77
|
}
|
|
@@ -92,43 +101,6 @@ export const svelte_preprocess_code_static = (
|
|
|
92
101
|
};
|
|
93
102
|
};
|
|
94
103
|
|
|
95
|
-
/**
|
|
96
|
-
* Check if a filename matches any exclusion pattern.
|
|
97
|
-
*/
|
|
98
|
-
const should_exclude = (filename: string | undefined, exclude: Array<string | RegExp>): boolean => {
|
|
99
|
-
if (!filename || exclude.length === 0) return false;
|
|
100
|
-
return exclude.some((pattern) =>
|
|
101
|
-
typeof pattern === 'string' ? filename.includes(pattern) : pattern.test(filename),
|
|
102
|
-
);
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Scans import declarations to find local names that import from known Code component sources.
|
|
107
|
-
* Handles default imports, named imports, and aliased imports.
|
|
108
|
-
* Checks both instance (`<script>`) and module (`<script module>`) scripts.
|
|
109
|
-
*/
|
|
110
|
-
const resolve_code_names = (ast: AST.Root, component_imports: Array<string>): Set<string> => {
|
|
111
|
-
const names: Set<string> = new Set();
|
|
112
|
-
|
|
113
|
-
for (const script of [ast.instance, ast.module]) {
|
|
114
|
-
if (!script) continue;
|
|
115
|
-
|
|
116
|
-
for (const node of script.content.body) {
|
|
117
|
-
if (node.type !== 'ImportDeclaration') continue;
|
|
118
|
-
if (!component_imports.includes(node.source.value as string)) continue;
|
|
119
|
-
|
|
120
|
-
for (const specifier of node.specifiers) {
|
|
121
|
-
// default import: `import Code from '...'`
|
|
122
|
-
// aliased: `import Highlighter from '...'`
|
|
123
|
-
// named: `import { default as Code } from '...'`
|
|
124
|
-
names.add(specifier.local.name);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return names;
|
|
130
|
-
};
|
|
131
|
-
|
|
132
104
|
interface Transformation {
|
|
133
105
|
start: number;
|
|
134
106
|
end: number;
|
|
@@ -173,7 +145,7 @@ const try_highlight = (
|
|
|
173
145
|
const find_code_usages = (
|
|
174
146
|
ast: AST.Root,
|
|
175
147
|
syntax_styler: SyntaxStyler,
|
|
176
|
-
code_names:
|
|
148
|
+
code_names: Map<string, ResolvedComponentImport>,
|
|
177
149
|
options: FindCodeUsagesOptions,
|
|
178
150
|
): Array<Transformation> => {
|
|
179
151
|
const transformations: Array<Transformation> = [];
|
|
@@ -187,6 +159,9 @@ const find_code_usages = (
|
|
|
187
159
|
|
|
188
160
|
if (!code_names.has(node.name)) return;
|
|
189
161
|
|
|
162
|
+
// Skip if spread attributes present — can't determine content statically
|
|
163
|
+
if (node.attributes.some((attr: any) => attr.type === 'SpreadAttribute')) return;
|
|
164
|
+
|
|
190
165
|
const content_attr = find_attribute(node, 'content');
|
|
191
166
|
if (!content_attr) return;
|
|
192
167
|
|
|
@@ -237,64 +212,8 @@ const find_code_usages = (
|
|
|
237
212
|
return transformations;
|
|
238
213
|
};
|
|
239
214
|
|
|
240
|
-
/**
|
|
241
|
-
* Find an attribute by name on a component node.
|
|
242
|
-
*/
|
|
243
|
-
const find_attribute = (node: AST.Component, name: string): AST.Attribute | undefined => {
|
|
244
|
-
for (const attr of node.attributes) {
|
|
245
|
-
if (attr.type === 'Attribute' && attr.name === name) {
|
|
246
|
-
return attr;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return undefined;
|
|
250
|
-
};
|
|
251
|
-
|
|
252
215
|
type Attribute_Value = AST.Attribute['value'];
|
|
253
216
|
|
|
254
|
-
/**
|
|
255
|
-
* Recursively evaluate an expression AST node to a static string value.
|
|
256
|
-
* Handles string literals, template literals (no interpolation), and string concatenation.
|
|
257
|
-
* Returns `null` for dynamic or non-string expressions.
|
|
258
|
-
*/
|
|
259
|
-
const evaluate_static_expr = (expr: any): string | null => {
|
|
260
|
-
if (expr.type === 'Literal' && typeof expr.value === 'string') return expr.value;
|
|
261
|
-
if (expr.type === 'TemplateLiteral' && expr.expressions.length === 0) {
|
|
262
|
-
return expr.quasis.map((q: any) => q.value.cooked ?? q.value.raw).join('');
|
|
263
|
-
}
|
|
264
|
-
if (expr.type === 'BinaryExpression' && expr.operator === '+') {
|
|
265
|
-
const left = evaluate_static_expr(expr.left);
|
|
266
|
-
if (left === null) return null;
|
|
267
|
-
const right = evaluate_static_expr(expr.right);
|
|
268
|
-
if (right === null) return null;
|
|
269
|
-
return left + right;
|
|
270
|
-
}
|
|
271
|
-
return null;
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Extract the string value from a static attribute value.
|
|
276
|
-
* Returns `null` for dynamic, non-string, or null literal values.
|
|
277
|
-
*/
|
|
278
|
-
const extract_static_string = (value: Attribute_Value): string | null => {
|
|
279
|
-
// Boolean attribute
|
|
280
|
-
if (value === true) return null;
|
|
281
|
-
|
|
282
|
-
// Plain attribute: content="text"
|
|
283
|
-
if (Array.isArray(value)) {
|
|
284
|
-
const first = value[0];
|
|
285
|
-
if (value.length === 1 && first?.type === 'Text') {
|
|
286
|
-
return first.data;
|
|
287
|
-
}
|
|
288
|
-
return null;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// ExpressionTag
|
|
292
|
-
const expr = value.expression;
|
|
293
|
-
// Null literal
|
|
294
|
-
if (expr.type === 'Literal' && expr.value === null) return null;
|
|
295
|
-
return evaluate_static_expr(expr);
|
|
296
|
-
};
|
|
297
|
-
|
|
298
217
|
interface ConditionalStaticStrings {
|
|
299
218
|
test_source: string;
|
|
300
219
|
consequent: string;
|
|
@@ -323,24 +242,11 @@ const try_extract_conditional = (
|
|
|
323
242
|
return {test_source, consequent, alternate};
|
|
324
243
|
};
|
|
325
244
|
|
|
326
|
-
/**
|
|
327
|
-
* Escapes a string for use inside a single-quoted JS string literal.
|
|
328
|
-
* Single quotes are used because `stylize()` output contains double quotes
|
|
329
|
-
* on every token span, so wrapping with single quotes avoids escaping those.
|
|
330
|
-
*/
|
|
331
|
-
const escape_js_string = (html: string): string => {
|
|
332
|
-
return html
|
|
333
|
-
.replace(/\\/g, '\\\\') // backslashes first
|
|
334
|
-
.replace(/'/g, "\\'") // single quotes
|
|
335
|
-
.replace(/\n/g, '\\n') // newlines
|
|
336
|
-
.replace(/\r/g, '\\r'); // carriage returns
|
|
337
|
-
};
|
|
338
|
-
|
|
339
245
|
/**
|
|
340
246
|
* Handle errors during highlighting.
|
|
341
247
|
*/
|
|
342
248
|
const handle_error = (error: unknown, options: FindCodeUsagesOptions): void => {
|
|
343
|
-
const message = `[code
|
|
249
|
+
const message = `[fuz-code] Highlighting failed${options.filename ? ` in ${options.filename}` : ''}: ${error instanceof Error ? error.message : String(error)}`;
|
|
344
250
|
|
|
345
251
|
if (options.on_error === 'throw') {
|
|
346
252
|
throw new Error(message);
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"svelte_preprocess_code_static.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/svelte_preprocess_code_static.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,iBAAiB,EAAW,MAAM,iBAAiB,CAAC;AAKxE,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AAErD,MAAM,WAAW,2BAA2B;IAC3C,gCAAgC;IAChC,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;IAEjC,0DAA0D;IAC1D,aAAa,CAAC,EAAE,YAAY,CAAC;IAE7B,8CAA8C;IAC9C,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAElC;;;OAGG;IACH,QAAQ,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;CAC3B;AAED,eAAO,MAAM,6BAA6B,GACzC,UAAS,2BAAgC,KACvC,iBA0DF,CAAC"}
|