omp-plugin-duplicate-detector 0.1.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 +117 -0
- package/dist/detector-worker.js +13897 -0
- package/package.json +105 -0
- package/src/config-loader.ts +336 -0
- package/src/coordinator.ts +536 -0
- package/src/detector-worker.ts +828 -0
- package/src/disk-cache.ts +703 -0
- package/src/duplicate-ledger.ts +144 -0
- package/src/index.ts +807 -0
- package/src/jscpd-engine.ts +797 -0
- package/src/project-state.ts +129 -0
- package/src/source-aware-index.ts +919 -0
- package/src/test-detector.ts +337 -0
- package/src/tui-notification.ts +464 -0
- package/src/worker-protocol.ts +330 -0
- package/types/global.d.ts +19 -0
- package/types/jscpd.d.ts +139 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker RPC Protocol definitions for Main <-> Worker communication.
|
|
3
|
+
* Provides strictly-typed discriminated union messages, type guards, and helper constructors.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { IClone } from "@jscpd/core";
|
|
7
|
+
import type { BaselineStatus } from "./jscpd-engine";
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Payload Interfaces
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
export interface WorkspaceOptions {
|
|
14
|
+
minTokens?: number;
|
|
15
|
+
minLines?: number;
|
|
16
|
+
maxLines?: number;
|
|
17
|
+
ignorePatterns?: string[];
|
|
18
|
+
ignoreTests?: boolean;
|
|
19
|
+
customTestPatterns?: string[];
|
|
20
|
+
excludeTestPatterns?: string[];
|
|
21
|
+
formatsExts?: Record<string, string[]>;
|
|
22
|
+
crossFormats?: boolean;
|
|
23
|
+
cacheDir?: string;
|
|
24
|
+
maxCacheBytes?: number;
|
|
25
|
+
maxIndexedFiles?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface OpenWorkspacePayload {
|
|
29
|
+
rootDir: string;
|
|
30
|
+
options?: WorkspaceOptions;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface CheckSnippetPayload {
|
|
34
|
+
filePath: string;
|
|
35
|
+
content: string;
|
|
36
|
+
format?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CheckAndUpdatePayload {
|
|
40
|
+
filePath: string;
|
|
41
|
+
content: string;
|
|
42
|
+
revision?: number;
|
|
43
|
+
format?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface UpdateFilePayload {
|
|
47
|
+
filePath: string;
|
|
48
|
+
content: string;
|
|
49
|
+
format?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface RemoveFilePayload {
|
|
53
|
+
filePath: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ReconcileFileEntry {
|
|
57
|
+
filePath: string;
|
|
58
|
+
content?: string;
|
|
59
|
+
mtime?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ReconcilePayload {
|
|
63
|
+
files: ReconcileFileEntry[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ScanPayload {
|
|
67
|
+
targetPath?: string;
|
|
68
|
+
options?: WorkspaceOptions;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Main to Worker Request Messages
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
interface OpenWorkspaceRequest {
|
|
76
|
+
id: string;
|
|
77
|
+
type: "openWorkspace";
|
|
78
|
+
payload: OpenWorkspacePayload;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface CheckSnippetRequest {
|
|
82
|
+
id: string;
|
|
83
|
+
type: "checkSnippet";
|
|
84
|
+
payload: CheckSnippetPayload;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface CheckAndUpdateRequest {
|
|
88
|
+
id: string;
|
|
89
|
+
type: "checkAndUpdate";
|
|
90
|
+
payload: CheckAndUpdatePayload;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface UpdateFileRequest {
|
|
94
|
+
id: string;
|
|
95
|
+
type: "updateFile";
|
|
96
|
+
payload: UpdateFilePayload;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
interface RemoveFileRequest {
|
|
100
|
+
id: string;
|
|
101
|
+
type: "removeFile";
|
|
102
|
+
payload: RemoveFilePayload;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface ReconcileRequest {
|
|
106
|
+
id: string;
|
|
107
|
+
type: "reconcile";
|
|
108
|
+
payload: ReconcilePayload;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface ScanRequest {
|
|
112
|
+
id: string;
|
|
113
|
+
type: "scan";
|
|
114
|
+
payload?: ScanPayload;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
interface CloseRequest {
|
|
118
|
+
id: string;
|
|
119
|
+
type: "close";
|
|
120
|
+
payload?: Record<string, never>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export type WorkerRequestMessage =
|
|
124
|
+
| OpenWorkspaceRequest
|
|
125
|
+
| CheckSnippetRequest
|
|
126
|
+
| CheckAndUpdateRequest
|
|
127
|
+
| UpdateFileRequest
|
|
128
|
+
| RemoveFileRequest
|
|
129
|
+
| ReconcileRequest
|
|
130
|
+
| ScanRequest
|
|
131
|
+
| CloseRequest;
|
|
132
|
+
|
|
133
|
+
// ============================================================================
|
|
134
|
+
// Worker to Main Response Messages
|
|
135
|
+
// ============================================================================
|
|
136
|
+
|
|
137
|
+
export interface WorkerSuccessResponse<T = unknown> {
|
|
138
|
+
id: string;
|
|
139
|
+
success: true;
|
|
140
|
+
data: T;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface WorkerErrorResponse {
|
|
144
|
+
id: string;
|
|
145
|
+
success: false;
|
|
146
|
+
error: string;
|
|
147
|
+
stack?: string;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
type WorkerResponse<T = unknown> =
|
|
151
|
+
| WorkerSuccessResponse<T>
|
|
152
|
+
| WorkerErrorResponse;
|
|
153
|
+
|
|
154
|
+
export type WorkerResponseMessage = WorkerResponse<unknown>;
|
|
155
|
+
|
|
156
|
+
// ============================================================================
|
|
157
|
+
// Worker to Main Event Notifications
|
|
158
|
+
// ============================================================================
|
|
159
|
+
|
|
160
|
+
type WorkerProgressPhase = "scanning" | "indexing" | "reconciling";
|
|
161
|
+
|
|
162
|
+
export interface WorkerProgressPayload {
|
|
163
|
+
phase: WorkerProgressPhase;
|
|
164
|
+
indexedCount: number;
|
|
165
|
+
totalFiles?: number;
|
|
166
|
+
currentFile?: string;
|
|
167
|
+
percentage?: number;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface WorkerProgressEvent {
|
|
171
|
+
type: "progress";
|
|
172
|
+
payload: WorkerProgressPayload;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export interface WorkerCompletePayload {
|
|
176
|
+
indexedCount: number;
|
|
177
|
+
cachedCount?: number;
|
|
178
|
+
totalSourceBytes: number;
|
|
179
|
+
cloneCount: number;
|
|
180
|
+
durationMs: number;
|
|
181
|
+
status?: BaselineStatus;
|
|
182
|
+
error?: string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface WorkerCompleteEvent {
|
|
186
|
+
type: "complete";
|
|
187
|
+
payload: WorkerCompletePayload;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface WorkerLateFindingPayload {
|
|
191
|
+
clone: IClone;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface WorkerLateFindingEvent {
|
|
195
|
+
type: "lateFinding";
|
|
196
|
+
payload: WorkerLateFindingPayload;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export type WorkerStatusCode =
|
|
200
|
+
| "idle"
|
|
201
|
+
| "indexing"
|
|
202
|
+
| "ready"
|
|
203
|
+
| "error"
|
|
204
|
+
| "closed";
|
|
205
|
+
|
|
206
|
+
export interface WorkerStatusPayload {
|
|
207
|
+
status: WorkerStatusCode;
|
|
208
|
+
message?: string;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface WorkerStatusEvent {
|
|
212
|
+
type: "status";
|
|
213
|
+
payload: WorkerStatusPayload;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export type WorkerEventMessage =
|
|
217
|
+
| WorkerProgressEvent
|
|
218
|
+
| WorkerCompleteEvent
|
|
219
|
+
| WorkerLateFindingEvent
|
|
220
|
+
| WorkerStatusEvent;
|
|
221
|
+
|
|
222
|
+
// ============================================================================
|
|
223
|
+
|
|
224
|
+
// ============================================================================
|
|
225
|
+
// Type Guards
|
|
226
|
+
// ============================================================================
|
|
227
|
+
|
|
228
|
+
const REQUEST_TYPES: Record<string, true> = {
|
|
229
|
+
openWorkspace: true,
|
|
230
|
+
checkSnippet: true,
|
|
231
|
+
checkAndUpdate: true,
|
|
232
|
+
updateFile: true,
|
|
233
|
+
removeFile: true,
|
|
234
|
+
reconcile: true,
|
|
235
|
+
scan: true,
|
|
236
|
+
close: true,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const EVENT_TYPES: Record<string, true> = {
|
|
240
|
+
progress: true,
|
|
241
|
+
complete: true,
|
|
242
|
+
lateFinding: true,
|
|
243
|
+
status: true,
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
export function isWorkerRequest(msg: unknown): msg is WorkerRequestMessage {
|
|
247
|
+
if (!msg || typeof msg !== "object") return false;
|
|
248
|
+
const m = msg as Record<string, unknown>;
|
|
249
|
+
return (
|
|
250
|
+
typeof m.id === "string" &&
|
|
251
|
+
typeof m.type === "string" &&
|
|
252
|
+
Boolean(REQUEST_TYPES[m.type])
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function isWorkerResponse(msg: unknown): msg is WorkerResponseMessage {
|
|
257
|
+
if (!msg || typeof msg !== "object") return false;
|
|
258
|
+
const m = msg as Record<string, unknown>;
|
|
259
|
+
return typeof m.id === "string" && typeof m.success === "boolean";
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function isWorkerEvent(msg: unknown): msg is WorkerEventMessage {
|
|
263
|
+
if (!msg || typeof msg !== "object") return false;
|
|
264
|
+
const m = msg as Record<string, unknown>;
|
|
265
|
+
return (
|
|
266
|
+
!("id" in m) && typeof m.type === "string" && Boolean(EVENT_TYPES[m.type])
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ============================================================================
|
|
271
|
+
// Helper Constructors
|
|
272
|
+
// ============================================================================
|
|
273
|
+
|
|
274
|
+
export function createSuccessResponse<T>(
|
|
275
|
+
id: string,
|
|
276
|
+
data: T,
|
|
277
|
+
): WorkerSuccessResponse<T> {
|
|
278
|
+
return {
|
|
279
|
+
id,
|
|
280
|
+
success: true,
|
|
281
|
+
data,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function createErrorResponse(
|
|
286
|
+
id: string,
|
|
287
|
+
error: string | Error,
|
|
288
|
+
): WorkerErrorResponse {
|
|
289
|
+
return {
|
|
290
|
+
id,
|
|
291
|
+
success: false,
|
|
292
|
+
error: error instanceof Error ? error.message : String(error),
|
|
293
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function createProgressEvent(
|
|
298
|
+
payload: WorkerProgressPayload,
|
|
299
|
+
): WorkerProgressEvent {
|
|
300
|
+
return {
|
|
301
|
+
type: "progress",
|
|
302
|
+
payload,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function createCompleteEvent(
|
|
307
|
+
payload: WorkerCompletePayload,
|
|
308
|
+
): WorkerCompleteEvent {
|
|
309
|
+
return {
|
|
310
|
+
type: "complete",
|
|
311
|
+
payload,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function createLateFindingEvent(clone: IClone): WorkerLateFindingEvent {
|
|
316
|
+
return {
|
|
317
|
+
type: "lateFinding",
|
|
318
|
+
payload: { clone },
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function createStatusEvent(
|
|
323
|
+
status: WorkerStatusCode,
|
|
324
|
+
message?: string,
|
|
325
|
+
): WorkerStatusEvent {
|
|
326
|
+
return {
|
|
327
|
+
type: "status",
|
|
328
|
+
payload: { status, message },
|
|
329
|
+
};
|
|
330
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
declare module "*.md" {
|
|
2
|
+
const content: string;
|
|
3
|
+
export default content;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare module "*.applescript" {
|
|
7
|
+
const content: string;
|
|
8
|
+
export default content;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
declare module "*.sh" {
|
|
12
|
+
const content: string;
|
|
13
|
+
export default content;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
declare module "*.css" {
|
|
17
|
+
const content: string;
|
|
18
|
+
export default content;
|
|
19
|
+
}
|
package/types/jscpd.d.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
declare module "@jscpd/core" {
|
|
2
|
+
import type EventEmitter from "eventemitter3";
|
|
3
|
+
|
|
4
|
+
export interface IClonePosition {
|
|
5
|
+
line: number;
|
|
6
|
+
column: number;
|
|
7
|
+
position?: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ITokenLocation {
|
|
11
|
+
line: number;
|
|
12
|
+
column?: number;
|
|
13
|
+
position?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface IToken {
|
|
17
|
+
type?: string;
|
|
18
|
+
value?: string;
|
|
19
|
+
length?: number;
|
|
20
|
+
format?: string;
|
|
21
|
+
range: [number, number];
|
|
22
|
+
loc?: {
|
|
23
|
+
start: ITokenLocation;
|
|
24
|
+
end: ITokenLocation;
|
|
25
|
+
};
|
|
26
|
+
line?: number;
|
|
27
|
+
column?: number;
|
|
28
|
+
position?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface IDuplication {
|
|
32
|
+
format?: string;
|
|
33
|
+
sourceId: string;
|
|
34
|
+
start: IClonePosition;
|
|
35
|
+
end: IClonePosition;
|
|
36
|
+
range?: [number, number];
|
|
37
|
+
fragment?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface IClone {
|
|
41
|
+
format: string;
|
|
42
|
+
duplicationA: IDuplication;
|
|
43
|
+
duplicationB: IDuplication;
|
|
44
|
+
isNew?: boolean;
|
|
45
|
+
foundDate?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface IMapFrame {
|
|
49
|
+
id: string;
|
|
50
|
+
sourceId: string;
|
|
51
|
+
start: IToken;
|
|
52
|
+
end: IToken;
|
|
53
|
+
localStart?: IClonePosition;
|
|
54
|
+
localEnd?: IClonePosition;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface IOptions {
|
|
58
|
+
minLines?: number;
|
|
59
|
+
maxLines?: number;
|
|
60
|
+
minTokens?: number;
|
|
61
|
+
maxSize?: string;
|
|
62
|
+
mode?: unknown;
|
|
63
|
+
formatsExts?: Record<string, string[]>;
|
|
64
|
+
[key: string]: unknown;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface IStore<T = IMapFrame> {
|
|
68
|
+
namespace(ns: string): void;
|
|
69
|
+
get(key: string): Promise<T>;
|
|
70
|
+
set(key: string, value: T): Promise<T>;
|
|
71
|
+
close(): void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class MemoryStore<T = IMapFrame> implements IStore<T> {
|
|
75
|
+
protected _namespace: string;
|
|
76
|
+
values: Record<string, Record<string, T>>;
|
|
77
|
+
namespace(namespace: string): void;
|
|
78
|
+
get(key: string): Promise<T>;
|
|
79
|
+
set(key: string, value: T): Promise<T>;
|
|
80
|
+
close(): void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ITokenMap {
|
|
84
|
+
getFormat(): string;
|
|
85
|
+
getId(): string;
|
|
86
|
+
getLinesCount(): number;
|
|
87
|
+
getTokensCount(): number;
|
|
88
|
+
next(): IteratorResult<IMapFrame | boolean>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ITokenizer {
|
|
92
|
+
generateMaps(
|
|
93
|
+
id: string,
|
|
94
|
+
text: string,
|
|
95
|
+
format: string,
|
|
96
|
+
options: IOptions,
|
|
97
|
+
): ITokenMap[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class Detector extends EventEmitter {
|
|
101
|
+
constructor(
|
|
102
|
+
tokenizer: ITokenizer,
|
|
103
|
+
store: IStore<IMapFrame>,
|
|
104
|
+
cloneValidators?: unknown[],
|
|
105
|
+
options?: IOptions,
|
|
106
|
+
);
|
|
107
|
+
detect(id: string, text: string, format: string): Promise<IClone[]>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export class Statistic {
|
|
111
|
+
getStatistic(): unknown;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function getDefaultOptions(): IOptions;
|
|
115
|
+
export function getModeByName(name: string): unknown;
|
|
116
|
+
export function getModeHandler(mode: unknown): unknown;
|
|
117
|
+
export function getOption(name: string, options?: IOptions): unknown;
|
|
118
|
+
export function mild(token: unknown): boolean;
|
|
119
|
+
export function strict(token: unknown): boolean;
|
|
120
|
+
export function weak(token: unknown): boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
declare module "@jscpd/tokenizer" {
|
|
124
|
+
import type { IOptions, ITokenizer, ITokenMap } from "@jscpd/core";
|
|
125
|
+
|
|
126
|
+
export class Tokenizer implements ITokenizer {
|
|
127
|
+
generateMaps(
|
|
128
|
+
id: string,
|
|
129
|
+
text: string,
|
|
130
|
+
format: string,
|
|
131
|
+
options: IOptions,
|
|
132
|
+
): ITokenMap[];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function getFormatByFile(
|
|
136
|
+
filePath: string,
|
|
137
|
+
formatsExts?: Record<string, string[]>,
|
|
138
|
+
): string | undefined;
|
|
139
|
+
}
|