clone-alert 0.3.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/dist/svelte.js ADDED
@@ -0,0 +1,287 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.tokenizeSvelte = tokenizeSvelte;
37
+ /**
38
+ * `.svelte` tokenizer: `<script>`/`<script module>` + markup (ast.fragment).
39
+ * Built on top of the shared layer in tokenizers.ts (optional/moduleResolveDirs/
40
+ * remap/tokenizeTypeScript) and the sentinel `S` from core.ts. The core knows
41
+ * nothing about Svelte.
42
+ *
43
+ * Two token layers:
44
+ * 1. markup structure (tags, blocks, directives, static text) -> images with
45
+ * the SV prefix (via the sentinel S), so they do NOT match script tokens;
46
+ * 2. expressions inside `{...}`/bindings -> the same TypeScript in the same
47
+ * component scope, so we slice the source and run it through the shared
48
+ * tokenizeTypeScript WITHOUT a prefix, so a duplicated expression across
49
+ * template<->script is caught.
50
+ *
51
+ * @packageDocumentation
52
+ */
53
+ const ts = __importStar(require("typescript"));
54
+ const core_1 = require("./core");
55
+ const tokenizers_1 = require("./tokenizers");
56
+ const SV = `${core_1.S}SV:`; // structural markup marker
57
+ const SV_TEXT = `${core_1.S}SVTEXT`; // non-empty static text
58
+ let warnedSvelte = false;
59
+ /** Build an offset -> {line, col} mapper (both 1-based) from a precomputed line-start table. */
60
+ function makeOffsetMapper(source) {
61
+ const lineStarts = [0];
62
+ for (let i = 0; i < source.length; i++) {
63
+ if (source.charCodeAt(i) === 10 /* \n */)
64
+ lineStarts.push(i + 1);
65
+ }
66
+ return (offset) => {
67
+ let lo = 0;
68
+ let hi = lineStarts.length - 1;
69
+ while (lo < hi) {
70
+ const mid = (lo + hi + 1) >> 1;
71
+ if (lineStarts[mid] <= offset)
72
+ lo = mid;
73
+ else
74
+ hi = mid - 1;
75
+ }
76
+ return { line: lo + 1, col: offset - lineStarts[lo] + 1 };
77
+ };
78
+ }
79
+ /** Tokenize a `.svelte` component (`<script>` blocks + markup). */
80
+ function tokenizeSvelte(filePath, source, options = {}) {
81
+ const svelte = (0, tokenizers_1.optional)('svelte/compiler', (0, tokenizers_1.moduleResolveDirs)(filePath));
82
+ if (!svelte || typeof svelte.parse !== 'function') {
83
+ if (!warnedSvelte) {
84
+ console.warn('[cpd] .svelte skipped: install svelte');
85
+ warnedSvelte = true;
86
+ }
87
+ return [];
88
+ }
89
+ let ast;
90
+ try {
91
+ // modern: true -> markup lives in ast.fragment (becomes the default in Svelte 6).
92
+ ast = svelte.parse(source, { filename: filePath, modern: true });
93
+ }
94
+ catch {
95
+ return [];
96
+ }
97
+ const out = [];
98
+ const at = makeOffsetMapper(source);
99
+ // --- Structural markup token (coordinates from the node offset) ---
100
+ const emitStruct = (image, node) => {
101
+ const off = typeof node?.start === 'number' ? node.start : 0;
102
+ const { line, col } = at(off);
103
+ out.push({ image, line, column: col });
104
+ };
105
+ // --- Expression: slice the source -> shared TS tokenizer -> remap ---
106
+ const emitExpr = (node) => {
107
+ if (!node || typeof node.start !== 'number' || typeof node.end !== 'number')
108
+ return;
109
+ const code = source.slice(node.start, node.end);
110
+ if (!code)
111
+ return;
112
+ const { line, col } = at(node.start);
113
+ for (const t of (0, tokenizers_1.tokenizeTypeScript)(filePath, code, options, ts.ScriptKind.TS)) {
114
+ out.push((0, tokenizers_1.remap)(t, line, col));
115
+ }
116
+ };
117
+ // --- <script> and <script module>: plain TS, like a regular .ts ---
118
+ for (const part of [ast.instance, ast.module]) {
119
+ if (!part?.content)
120
+ continue;
121
+ const start = part.content.start;
122
+ const end = part.content.end;
123
+ const { line, col } = at(start);
124
+ for (const t of (0, tokenizers_1.tokenizeTypeScript)(filePath, source.slice(start, end), options, ts.ScriptKind.TS)) {
125
+ out.push((0, tokenizers_1.remap)(t, line, col));
126
+ }
127
+ out.push({ image: '', line, column: col, barrier: true });
128
+ }
129
+ // --- Markup ---
130
+ const walkAttr = (attr) => {
131
+ if (!attr || typeof attr !== 'object')
132
+ return;
133
+ switch (attr.type) {
134
+ case 'Attribute': {
135
+ emitStruct(`${SV}@${attr.name}`, attr);
136
+ const v = attr.value;
137
+ if (v === true)
138
+ return; // boolean attribute with no value
139
+ if (Array.isArray(v))
140
+ v.forEach(walkNode); // (Text | ExpressionTag)[]
141
+ else
142
+ walkNode(v); // a single ExpressionTag
143
+ return;
144
+ }
145
+ case 'SpreadAttribute':
146
+ emitStruct(`${SV}{...}`, attr);
147
+ emitExpr(attr.expression);
148
+ return;
149
+ case 'BindDirective':
150
+ emitStruct(`${SV}bind:${attr.name}`, attr);
151
+ emitExpr(attr.expression);
152
+ return;
153
+ case 'OnDirective':
154
+ emitStruct(`${SV}on:${attr.name}`, attr);
155
+ emitExpr(attr.expression);
156
+ return;
157
+ case 'ClassDirective':
158
+ emitStruct(`${SV}class:${attr.name}`, attr);
159
+ emitExpr(attr.expression);
160
+ return;
161
+ case 'StyleDirective': {
162
+ emitStruct(`${SV}style:${attr.name}`, attr);
163
+ const v = attr.value;
164
+ if (v === true)
165
+ return;
166
+ if (Array.isArray(v))
167
+ v.forEach(walkNode);
168
+ else
169
+ walkNode(v);
170
+ return;
171
+ }
172
+ case 'UseDirective':
173
+ emitStruct(`${SV}use:${attr.name}`, attr);
174
+ emitExpr(attr.expression);
175
+ return;
176
+ case 'TransitionDirective': {
177
+ const kw = attr.intro && attr.outro ? 'transition' : attr.outro ? 'out' : 'in';
178
+ emitStruct(`${SV}${kw}:${attr.name}`, attr);
179
+ emitExpr(attr.expression);
180
+ return;
181
+ }
182
+ case 'AnimateDirective':
183
+ emitStruct(`${SV}animate:${attr.name}`, attr);
184
+ emitExpr(attr.expression);
185
+ return;
186
+ case 'LetDirective':
187
+ emitStruct(`${SV}let:${attr.name}`, attr);
188
+ return;
189
+ case 'AttachTag':
190
+ emitStruct(`${SV}@attach`, attr);
191
+ emitExpr(attr.expression);
192
+ return;
193
+ default:
194
+ if (attr.expression)
195
+ emitExpr(attr.expression);
196
+ return;
197
+ }
198
+ };
199
+ function walkNode(node) {
200
+ if (!node || typeof node !== 'object')
201
+ return;
202
+ switch (node.type) {
203
+ case 'Fragment':
204
+ (node.nodes ?? []).forEach(walkNode);
205
+ return;
206
+ case 'Text':
207
+ if (typeof node.data === 'string' && node.data.trim())
208
+ emitStruct(SV_TEXT, node);
209
+ return;
210
+ case 'Comment':
211
+ return;
212
+ case 'ExpressionTag':
213
+ case 'HtmlTag':
214
+ case 'RenderTag':
215
+ case 'AttachTag':
216
+ emitExpr(node.expression);
217
+ return;
218
+ case 'ConstTag':
219
+ emitStruct(`${SV}@const`, node);
220
+ emitExpr(node.declaration);
221
+ return;
222
+ case 'DebugTag':
223
+ (node.identifiers ?? []).forEach(emitExpr);
224
+ return;
225
+ case 'IfBlock':
226
+ emitStruct(`${SV}#if`, node);
227
+ emitExpr(node.test);
228
+ walkNode(node.consequent);
229
+ if (node.alternate)
230
+ walkNode(node.alternate);
231
+ return;
232
+ case 'EachBlock':
233
+ emitStruct(`${SV}#each`, node);
234
+ emitExpr(node.expression);
235
+ if (node.key)
236
+ emitExpr(node.key);
237
+ walkNode(node.body);
238
+ if (node.fallback)
239
+ walkNode(node.fallback);
240
+ return;
241
+ case 'AwaitBlock':
242
+ emitStruct(`${SV}#await`, node);
243
+ emitExpr(node.expression);
244
+ if (node.pending)
245
+ walkNode(node.pending);
246
+ if (node.then)
247
+ walkNode(node.then);
248
+ if (node.catch)
249
+ walkNode(node.catch);
250
+ return;
251
+ case 'KeyBlock':
252
+ emitStruct(`${SV}#key`, node);
253
+ emitExpr(node.expression);
254
+ walkNode(node.fragment);
255
+ return;
256
+ case 'SnippetBlock':
257
+ emitStruct(`${SV}#snippet`, node);
258
+ walkNode(node.body);
259
+ return;
260
+ default: {
261
+ // Element-like: RegularElement / Component / SvelteElement /
262
+ // SvelteComponent / SvelteSelf / SvelteFragment / SlotElement /
263
+ // TitleElement / SvelteWindow|Body|Head|Document|Boundary.
264
+ if (Array.isArray(node.attributes) || node.fragment) {
265
+ emitStruct(`${SV}<${node.name ?? node.type}`, node);
266
+ (node.attributes ?? []).forEach(walkAttr);
267
+ if (node.fragment)
268
+ walkNode(node.fragment);
269
+ return;
270
+ }
271
+ // Unknown node — try the common fields so we don't lose expressions.
272
+ if (node.expression)
273
+ emitExpr(node.expression);
274
+ if (node.fragment)
275
+ walkNode(node.fragment);
276
+ if (Array.isArray(node.nodes))
277
+ node.nodes.forEach(walkNode);
278
+ return;
279
+ }
280
+ }
281
+ }
282
+ // Markup is tokenized only when the toggle is on (default yes). Markup and
283
+ // script are usually run at different --minimum-tokens thresholds.
284
+ if ((options.svelteTemplates ?? true) && ast.fragment)
285
+ walkNode(ast.fragment);
286
+ return out;
287
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The TypeScript/JavaScript tokenizer plus the shared helpers used by every
3
+ * framework tokenizer extension (`optional`, `moduleResolveDirs`, `remap`).
4
+ *
5
+ * @packageDocumentation
6
+ */
7
+ import * as ts from 'typescript';
8
+ import { type RawToken } from './core';
9
+ export interface TokenizeOptions {
10
+ ignoreIdentifiers?: boolean;
11
+ ignoreLiterals?: boolean;
12
+ /** Split TS-file tokens to match PMD typescript (templates, regexp). `.ts/.tsx` only. */
13
+ pmdTypescriptCompatibility?: boolean;
14
+ /**
15
+ * Tokenize `.svelte` markup (ast.fragment), not just `<script>`. Markup and
16
+ * script usually want different `--minimum-tokens` thresholds, so this is put
17
+ * behind a toggle: turn it off to scan scripts on their own.
18
+ */
19
+ svelteTemplates?: boolean;
20
+ /**
21
+ * Tokenize `.vue` markup (descriptor.template.ast), not just `<script>`. Like
22
+ * svelteTemplates, this layer sits behind a toggle so markup and code can run
23
+ * at separate `--minimum-tokens` thresholds.
24
+ */
25
+ vueTemplates?: boolean;
26
+ }
27
+ export declare const DEFAULTS: Required<TokenizeOptions>;
28
+ /**
29
+ * Load an optional compiler (`@angular/compiler`, `@vue/compiler-sfc`, `svelte`)
30
+ * — a peerDependency. Resolution starts FROM the analyzed file and walks up the
31
+ * tree (so we use the compiler version installed in the scanned project), and
32
+ * only then falls back to clone-alert's own node_modules. `fromPaths` is usually
33
+ * `[dirname(filePath)]`; Node climbs node_modules from there.
34
+ */
35
+ export declare function optional<T = any>(name: string, fromPaths?: string[]): T | null;
36
+ /**
37
+ * Starting point for resolving a peer compiler: the directory of the analyzed
38
+ * file. Node walks up node_modules from there to the project root.
39
+ */
40
+ export declare function moduleResolveDirs(filePath: string): string[];
41
+ /**
42
+ * Remap an embedded block's positions into file coordinates. Block tokens are
43
+ * counted from (0,0) within the block; baseLine/baseCol (1-based) give the
44
+ * absolute start of the block. The column shift applies only to the block's
45
+ * first line.
46
+ */
47
+ export declare function remap(tok: RawToken, baseLine: number, baseCol: number): RawToken;
48
+ /**
49
+ * Tokenize TS / TSX / JSX via the TypeScript scanner. CPD counts the stream of
50
+ * lexical tokens, including keywords and punctuation.
51
+ */
52
+ export declare function tokenizeTypeScript(filePath: string, source: string, opts?: TokenizeOptions, scriptKind?: ts.ScriptKind): RawToken[];
53
+ export declare function scriptKindFor(ext: string): ts.ScriptKind;