@omfalos/mokosh 0.5.0 → 0.5.1
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/cli.js +55 -50
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +57 -52
- package/dist/cli.mjs.map +1 -1
- package/dist/duplication-worker.d.mts +1 -1
- package/dist/duplication-worker.d.ts +1 -1
- package/dist/duplication-worker.js +6 -4
- package/dist/duplication-worker.js.map +1 -1
- package/dist/duplication-worker.mjs +6 -4
- package/dist/duplication-worker.mjs.map +1 -1
- package/dist/index.d.mts +186 -18
- package/dist/index.d.ts +186 -18
- package/dist/index.js +37 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +37 -34
- package/dist/index.mjs.map +1 -1
- package/dist/mcp.js +38 -34
- package/dist/mcp.js.map +1 -1
- package/dist/mcp.mjs +40 -36
- package/dist/mcp.mjs.map +1 -1
- package/dist/parse-worker.d.mts +1 -1
- package/dist/parse-worker.d.ts +1 -1
- package/dist/parse-worker.js +6 -6
- package/dist/parse-worker.js.map +1 -1
- package/dist/parse-worker.mjs +6 -6
- package/dist/parse-worker.mjs.map +1 -1
- package/dist/{tokenizer-DIbb0GLe.d.mts → tokenizer-D9P-y7lH.d.mts} +1 -1
- package/dist/{tokenizer-DIbb0GLe.d.ts → tokenizer-D9P-y7lH.d.ts} +1 -1
- package/dist/{types-C153Tz5u.d.mts → types-Ba-BjVQG.d.mts} +4 -0
- package/dist/{types-y90pqeDR.d.ts → types-DWARi8gF.d.ts} +4 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { N as NormalizedToken } from './tokenizer-
|
|
1
|
+
import { N as NormalizedToken } from './tokenizer-D9P-y7lH.mjs';
|
|
2
2
|
import { F as FileType } from './parse-yamNoaoL.mjs';
|
|
3
3
|
|
|
4
4
|
/** Piscina task handler: tokenizes a single file's content in a worker thread, for `findDuplicates`. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { N as NormalizedToken } from './tokenizer-
|
|
1
|
+
import { N as NormalizedToken } from './tokenizer-D9P-y7lH.js';
|
|
2
2
|
import { F as FileType } from './parse-yamNoaoL.js';
|
|
3
3
|
|
|
4
4
|
/** Piscina task handler: tokenizes a single file's content in a worker thread, for `findDuplicates`. */
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`,c),l
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
"use strict";var f=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var x=Object.prototype.hasOwnProperty;var h=(e,t)=>{for(var n in t)f(e,n,{get:t[n],enumerable:!0})},y=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of b(t))!x.call(e,r)&&r!==n&&f(e,r,{get:()=>t[r],enumerable:!(i=T(t,r))||i.enumerable});return e};var N=e=>y(f({},"__esModule",{value:!0}),e);var O={};h(O,{default:()=>g});module.exports=N(O);var E={typescript:{line:["//"],block:[["/*","*/"]]},javascript:{line:["//"],block:[["/*","*/"]]},go:{line:["//"],block:[["/*","*/"]]},java:{line:["//"],block:[["/*","*/"]]},kotlin:{line:["//"],block:[["/*","*/"]]},scala:{line:["//"],block:[["/*","*/"]]},groovy:{line:["//"],block:[["/*","*/"]]},css:{block:[["/*","*/"]]},scss:{line:["//"],block:[["/*","*/"]]},less:{line:["//"],block:[["/*","*/"]]},stylus:{line:["//"],block:[["/*","*/"]]},python:{line:["#"]},gherkin:{line:["#"]},coffeescript:{line:["#"],block:[["###","###"]]},livescript:{line:["#"],block:[["###","###"]]},lua:{line:["--"],block:[["--[[","]]"]]},markdown:{block:[["<!--","-->"]]}};function k(e,t,n){let i=e.slice(t,n).replace(/[^\n]/g," ");return e.slice(0,t)+i+e.slice(n)}function z(e,t){if(!t)return e;let n=e;for(let[i,r]of t.block??[]){let c=0;for(;;){let l=n.indexOf(i,c);if(l===-1)break;let o=n.indexOf(r,l+i.length),s=o===-1?n.length:o+r.length;n=k(n,l,s),c=s}}for(let i of t.line??[]){let r=0;for(;;){let c=n.indexOf(i,r);if(c===-1)break;let l=n.indexOf(`
|
|
2
|
+
`,c),o=l===-1?n.length:l;n=k(n,c,o),r=o}}return n}var A={typescript:[/^\s*import[\s{("*]/,/^\s*export\s.*\sfrom\s/],javascript:[/^\s*import[\s{("*]/,/^\s*export\s.*\sfrom\s/],python:[/^\s*import\s+\S/,/^\s*from\s+\S+\s+import[\s(]/],go:[/^\s*import\s+["(]/],java:[/^\s*import\s+/],kotlin:[/^\s*import\s+/],scala:[/^\s*import\s+/],groovy:[/^\s*import\s+/]};function S(e,t){let n=A[t];if(!n)return e;let i=e.split(`
|
|
3
|
+
`),r=t==="go",c=!1,l=!1;for(let o=0;o<i.length;o++){let s=i[o],p=()=>{i[o]=s.replace(/[^\r]/g," ")};if(c){p(),s.includes(")")&&(c=!1);continue}if(l){p(),(/}\s*from\s/.test(s)||s.includes("}"))&&(l=!1);continue}n.some(a=>a.test(s))&&(p(),r&&/^\s*import\s+\(/.test(s)&&!s.includes(")")?c=!0:(t==="typescript"||t==="javascript")&&/^\s*import\b/.test(s)&&s.includes("{")&&!s.includes("}")&&(l=!0))}return i.join(`
|
|
4
|
+
`)}var _=["===","!==","...","=>","==","!=","<=",">=","&&","||","::","->","..","+=","-=","*=","/="],m=new RegExp(`${_.map(e=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")).join("|")}|[A-Za-z_$][A-Za-z0-9_$]*|\\d+(?:\\.\\d+)?|"(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`|\\S`,"g"),I=/^[A-Za-z_$][A-Za-z0-9_$]*$/,d=/^\d+(?:\.\d+)?$/,F=/^(".*"|'.*'|`.*`)$/s,w=new Set(["if","else","elif","for","while","do","switch","case","default","break","continue","return","function","def","class","struct","interface","const","let","var","local","import","export","from","package","try","catch","except","finally","throw","raise","new","this","self","end","then","yield","async","await","nil","null","None","true","false","True","False","and","or","not","in","of","is"]);function u(e,t,n=!0){let i=S(z(e,E[t]),t),r=[],c=1,l=0;m.lastIndex=0;let o=m.exec(i);for(;o!==null;){for(let a=l;a<o.index;a++)i[a]===`
|
|
5
|
+
`&&c++;l=o.index;let s=o[0],p=s;I.test(s)&&!w.has(s)?p="ID":n&&(d.test(s)||F.test(s))&&(p=d.test(s)?"NUM":"STR"),r.push({text:p,line:c});for(let a=l;a<o.index+s.length;a++)i[a]===`
|
|
6
|
+
`&&c++;l=o.index+s.length,o=m.exec(i)}return r}function g(e){return u(e.source,e.fileType,e.ignoreLiterals)}
|
|
5
7
|
//# sourceMappingURL=duplication-worker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/duplication-worker.ts","../src/graph/duplication/tokenizer.ts"],"sourcesContent":["/** Piscina task handler: tokenizes a single file's content in a worker thread, for `findDuplicates`. */\n\nimport type { NormalizedToken } from \"./graph/duplication/tokenizer.js\";\nimport { tokenize } from \"./graph/duplication/tokenizer.js\";\nimport type { FileType } from \"./types/parse\";\n\nexport default function tokenizeInWorker(payload: {\n source: string;\n fileType: FileType;\n ignoreLiterals: boolean;\n}): NormalizedToken[] {\n return tokenize(payload.source, payload.fileType, payload.ignoreLiterals);\n}\n","/**\n * Language-agnostic source tokenizer for duplicate-code detection. Strips per-language\n * comment syntax, then splits what remains into a normalized token stream shared by every\n * language `DEFAULT_EXTENSIONS` covers — one generic tokenizer rather than a per-language\n * lexer, so `findDuplicates` works uniformly across TS/JS, Python, Go, Java, Kotlin, Scala,\n * Groovy, CoffeeScript, LiveScript, Lua, Gherkin, style files, and Markdown.\n */\nimport type { FileType } from \"../../types/parse\";\n\n/** One normalized token plus the 1-based source line it came from. */\nexport interface NormalizedToken {\n text: string;\n line: number;\n}\n\ninterface CommentSyntax {\n line?: string[];\n block?: Array<[string, string]>;\n}\n\n/**\n * Per-`FileType` comment markers used to mask out comment text before tokenizing, so comment\n * wording never contributes to a duplicate match. Deliberately not string-literal-aware — a\n * `//` inside a string is rare and the cost of occasionally over-stripping is low for a\n * heuristic duplicate finder, same trade-off the complexity scorers make elsewhere.\n */\nconst COMMENT_SYNTAX: Partial<Record<FileType, CommentSyntax>> = {\n typescript: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n javascript: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n go: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n java: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n kotlin: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n scala: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n groovy: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n css: { block: [[\"/*\", \"*/\"]] },\n scss: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n less: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n stylus: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n python: { line: [\"#\"] },\n gherkin: { line: [\"#\"] },\n coffeescript: { line: [\"#\"], block: [[\"###\", \"###\"]] },\n livescript: { line: [\"#\"], block: [[\"###\", \"###\"]] },\n lua: { line: [\"--\"], block: [[\"--[[\", \"]]\"]] },\n markdown: { block: [[\"<!--\", \"-->\"]] },\n};\n\n/**\n * @description Replaces every non-newline character in `source[start, end)` with a space,\n * so downstream line-number tracking stays correct while the masked text can no longer\n * match anything.\n * @param source - Full file source.\n * @param start - Start offset (inclusive) of the range to mask.\n * @param end - End offset (exclusive) of the range to mask.\n * @returns `source` with the range masked.\n */\nfunction maskRange(source: string, start: number, end: number): string {\n const masked = source.slice(start, end).replace(/[^\\n]/g, \" \");\n return source.slice(0, start) + masked + source.slice(end);\n}\n\n/**\n * @description Masks out every line and block comment matching `syntax`, preserving line\n * breaks and overall string length so line numbers computed later stay accurate.\n * @param source - Full file source.\n * @param syntax - Comment markers for the file's language; absent for languages with no\n * configured comment syntax, in which case the source passes through unchanged.\n * @returns The source with comment text masked to spaces.\n */\nfunction stripComments(source: string, syntax: CommentSyntax | undefined): string {\n if (!syntax) return source;\n let result = source;\n\n for (const [open, close] of syntax.block ?? []) {\n let searchFrom = 0;\n for (;;) {\n const start = result.indexOf(open, searchFrom);\n if (start === -1) break;\n const end = result.indexOf(close, start + open.length);\n const rangeEnd = end === -1 ? result.length : end + close.length;\n result = maskRange(result, start, rangeEnd);\n searchFrom = rangeEnd;\n }\n }\n\n for (const marker of syntax.line ?? []) {\n let searchFrom = 0;\n for (;;) {\n const start = result.indexOf(marker, searchFrom);\n if (start === -1) break;\n const lineEnd = result.indexOf(\"\\n\", start);\n const rangeEnd = lineEnd === -1 ? result.length : lineEnd;\n result = maskRange(result, start, rangeEnd);\n searchFrom = rangeEnd;\n }\n }\n\n return result;\n}\n\nconst MULTI_CHAR_OPERATORS = [\n \"===\",\n \"!==\",\n \"...\",\n \"=>\",\n \"==\",\n \"!=\",\n \"<=\",\n \">=\",\n \"&&\",\n \"||\",\n \"::\",\n \"->\",\n \"..\",\n \"+=\",\n \"-=\",\n \"*=\",\n \"/=\",\n];\n\n/** Matches, in priority order: multi-char operators, identifiers, numbers, and quoted strings. */\nconst TOKEN_PATTERN = new RegExp(\n `${MULTI_CHAR_OPERATORS.map((op) => op.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")).join(\"|\")}|[A-Za-z_$][A-Za-z0-9_$]*|\\\\d+(?:\\\\.\\\\d+)?|\"(?:\\\\\\\\.|[^\"\\\\\\\\])*\"|'(?:\\\\\\\\.|[^'\\\\\\\\])*'|\\`(?:\\\\\\\\.|[^\\`\\\\\\\\])*\\`|\\\\S`,\n \"g\",\n);\n\nconst IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\nconst NUMBER_PATTERN = /^\\d+(?:\\.\\d+)?$/;\nconst STRING_PATTERN = /^(\".*\"|'.*'|`.*`)$/s;\n\n/**\n * Keywords kept verbatim rather than collapsed to the `ID` placeholder, shared across every\n * language rather than a per-language keyword table — deliberately coarse. Without this,\n * structurally different code (an `if` vs. a `for`, a `class` vs. a `function`) would tokenize\n * identically once every identifier-shaped word became `ID`, which would make the shingle\n * matcher far too eager. This list only needs to cover the keywords common enough across\n * TS/JS/Python/Go/CoffeeScript/LiveScript/Lua/Gherkin to matter for shape-preservation — it\n * doesn't need to be exhaustive or language-precise, since keeping a non-keyword here just\n * costs a little precision, not correctness.\n */\nconst KEYWORDS = new Set([\n \"if\",\n \"else\",\n \"elif\",\n \"for\",\n \"while\",\n \"do\",\n \"switch\",\n \"case\",\n \"default\",\n \"break\",\n \"continue\",\n \"return\",\n \"function\",\n \"def\",\n \"class\",\n \"struct\",\n \"interface\",\n \"const\",\n \"let\",\n \"var\",\n \"local\",\n \"import\",\n \"export\",\n \"from\",\n \"package\",\n \"try\",\n \"catch\",\n \"except\",\n \"finally\",\n \"throw\",\n \"raise\",\n \"new\",\n \"this\",\n \"self\",\n \"end\",\n \"then\",\n \"yield\",\n \"async\",\n \"await\",\n \"nil\",\n \"null\",\n \"None\",\n \"true\",\n \"false\",\n \"True\",\n \"False\",\n \"and\",\n \"or\",\n \"not\",\n \"in\",\n \"of\",\n \"is\",\n]);\n\n/**\n * @description Tokenizes source text into a normalized stream for shingle-based duplicate\n * matching: identifiers always collapse to a single placeholder (so renamed-variable clones\n * still hash identically), and literals optionally collapse too (`ignoreLiterals`, default\n * on) so only structural shape — not the specific values used — drives the match.\n * @param source - Raw file content.\n * @param fileType - Selects the comment-stripping rule; unrecognised types skip stripping.\n * @param ignoreLiterals - When true (default), string/number literal tokens are also\n * normalized to a placeholder rather than compared verbatim.\n * @returns Normalized tokens in source order, each carrying its 1-based source line.\n */\nexport function tokenize(\n source: string,\n fileType: FileType,\n ignoreLiterals = true,\n): NormalizedToken[] {\n const stripped = stripComments(source, COMMENT_SYNTAX[fileType]);\n const tokens: NormalizedToken[] = [];\n\n let line = 1;\n let lastIndex = 0;\n TOKEN_PATTERN.lastIndex = 0;\n let match = TOKEN_PATTERN.exec(stripped);\n\n while (match !== null) {\n for (let i = lastIndex; i < match.index; i++) {\n if (stripped[i] === \"\\n\") line++;\n }\n lastIndex = match.index;\n\n const raw = match[0];\n let text = raw;\n if (IDENTIFIER_PATTERN.test(raw) && !KEYWORDS.has(raw)) {\n text = \"ID\";\n } else if (ignoreLiterals && (NUMBER_PATTERN.test(raw) || STRING_PATTERN.test(raw))) {\n text = NUMBER_PATTERN.test(raw) ? \"NUM\" : \"STR\";\n }\n tokens.push({ text, line });\n\n for (let i = lastIndex; i < match.index + raw.length; i++) {\n if (stripped[i] === \"\\n\") line++;\n }\n lastIndex = match.index + raw.length;\n match = TOKEN_PATTERN.exec(stripped);\n }\n\n return tokens;\n}\n"],"mappings":"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","names":["duplication_worker_exports","__export","tokenizeInWorker","__toCommonJS","COMMENT_SYNTAX","maskRange","source","start","end","masked","stripComments","syntax","result","open","close","searchFrom","rangeEnd","marker","lineEnd","MULTI_CHAR_OPERATORS","TOKEN_PATTERN","op","IDENTIFIER_PATTERN","NUMBER_PATTERN","STRING_PATTERN","KEYWORDS","tokenize","fileType","ignoreLiterals","stripped","tokens","line","lastIndex","match","i","raw","text","tokenizeInWorker","payload","tokenize"]}
|
|
1
|
+
{"version":3,"sources":["../src/duplication-worker.ts","../src/graph/duplication/tokenizer.ts"],"sourcesContent":["/** Piscina task handler: tokenizes a single file's content in a worker thread, for `findDuplicates`. */\n\nimport type { NormalizedToken } from \"./graph/duplication/tokenizer.js\";\nimport { tokenize } from \"./graph/duplication/tokenizer.js\";\nimport type { FileType } from \"./types/parse\";\n\nexport default function tokenizeInWorker(payload: {\n source: string;\n fileType: FileType;\n ignoreLiterals: boolean;\n}): NormalizedToken[] {\n return tokenize(payload.source, payload.fileType, payload.ignoreLiterals);\n}\n","/**\n * Language-agnostic source tokenizer for duplicate-code detection. Strips per-language\n * comment syntax and import/using statements, then splits what remains into a normalized token stream shared by every\n * language `DEFAULT_EXTENSIONS` covers — one generic tokenizer rather than a per-language\n * lexer, so `findDuplicates` works uniformly across TS/JS, Python, Go, Java, Kotlin, Scala,\n * Groovy, CoffeeScript, LiveScript, Lua, Gherkin, style files, and Markdown.\n */\nimport type { FileType } from \"../../types/parse\";\n\n/** One normalized token plus the 1-based source line it came from. */\nexport interface NormalizedToken {\n text: string;\n line: number;\n}\n\ninterface CommentSyntax {\n line?: string[];\n block?: Array<[string, string]>;\n}\n\n/**\n * Per-`FileType` comment markers used to mask out comment text before tokenizing, so comment\n * wording never contributes to a duplicate match. Deliberately not string-literal-aware — a\n * `//` inside a string is rare and the cost of occasionally over-stripping is low for a\n * heuristic duplicate finder, same trade-off the complexity scorers make elsewhere.\n */\nconst COMMENT_SYNTAX: Partial<Record<FileType, CommentSyntax>> = {\n typescript: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n javascript: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n go: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n java: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n kotlin: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n scala: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n groovy: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n css: { block: [[\"/*\", \"*/\"]] },\n scss: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n less: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n stylus: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n python: { line: [\"#\"] },\n gherkin: { line: [\"#\"] },\n coffeescript: { line: [\"#\"], block: [[\"###\", \"###\"]] },\n livescript: { line: [\"#\"], block: [[\"###\", \"###\"]] },\n lua: { line: [\"--\"], block: [[\"--[[\", \"]]\"]] },\n markdown: { block: [[\"<!--\", \"-->\"]] },\n};\n\n/**\n * @description Replaces every non-newline character in `source[start, end)` with a space,\n * so downstream line-number tracking stays correct while the masked text can no longer\n * match anything.\n * @param source - Full file source.\n * @param start - Start offset (inclusive) of the range to mask.\n * @param end - End offset (exclusive) of the range to mask.\n * @returns `source` with the range masked.\n */\nfunction maskRange(source: string, start: number, end: number): string {\n const masked = source.slice(start, end).replace(/[^\\n]/g, \" \");\n return source.slice(0, start) + masked + source.slice(end);\n}\n\n/**\n * @description Masks out every line and block comment matching `syntax`, preserving line\n * breaks and overall string length so line numbers computed later stay accurate.\n * @param source - Full file source.\n * @param syntax - Comment markers for the file's language; absent for languages with no\n * configured comment syntax, in which case the source passes through unchanged.\n * @returns The source with comment text masked to spaces.\n */\nfunction stripComments(source: string, syntax: CommentSyntax | undefined): string {\n if (!syntax) return source;\n let result = source;\n\n for (const [open, close] of syntax.block ?? []) {\n let searchFrom = 0;\n for (;;) {\n const start = result.indexOf(open, searchFrom);\n if (start === -1) break;\n const end = result.indexOf(close, start + open.length);\n const rangeEnd = end === -1 ? result.length : end + close.length;\n result = maskRange(result, start, rangeEnd);\n searchFrom = rangeEnd;\n }\n }\n\n for (const marker of syntax.line ?? []) {\n let searchFrom = 0;\n for (;;) {\n const start = result.indexOf(marker, searchFrom);\n if (start === -1) break;\n const lineEnd = result.indexOf(\"\\n\", start);\n const rangeEnd = lineEnd === -1 ? result.length : lineEnd;\n result = maskRange(result, start, rangeEnd);\n searchFrom = rangeEnd;\n }\n }\n\n return result;\n}\n\n/**\n * Per-`FileType` patterns matching a *single* import/using line. Long shared import lists\n * tokenize identically across unrelated files and register as \"duplicates\" that carry no shared\n * logic — same rationale as stripping comments. Masked (space-filled, line breaks kept) before\n * tokenizing. Deliberately coarse and not string-literal-aware, like `stripComments`.\n */\nconst IMPORT_LINE_PATTERNS: Partial<Record<FileType, RegExp[]>> = {\n typescript: [/^\\s*import[\\s{(\"*]/, /^\\s*export\\s.*\\sfrom\\s/],\n javascript: [/^\\s*import[\\s{(\"*]/, /^\\s*export\\s.*\\sfrom\\s/],\n python: [/^\\s*import\\s+\\S/, /^\\s*from\\s+\\S+\\s+import[\\s(]/],\n go: [/^\\s*import\\s+[\"(]/],\n java: [/^\\s*import\\s+/],\n kotlin: [/^\\s*import\\s+/],\n scala: [/^\\s*import\\s+/],\n groovy: [/^\\s*import\\s+/],\n};\n\n/**\n * @description Masks import / `from … import` / `using` statements to spaces before tokenizing,\n * preserving line breaks so later line-number tracking stays accurate. Handles the two\n * multi-line shapes that matter in practice: a JS/TS `import { … } from \"x\"` whose braces span\n * lines, and a Go `import ( … )` block. Everything else is matched line-by-line.\n * @param source - File source, already comment-stripped.\n * @param fileType - Selects the import-line patterns; unrecognised types pass through unchanged.\n * @returns The source with import statements masked to spaces.\n */\nfunction stripImports(source: string, fileType: FileType): string {\n const patterns = IMPORT_LINE_PATTERNS[fileType];\n if (!patterns) return source;\n\n const lines = source.split(\"\\n\");\n const isGo = fileType === \"go\";\n let inGoBlock = false;\n let inJsBraces = false;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] as string;\n const mask = () => {\n lines[i] = line.replace(/[^\\r]/g, \" \");\n };\n\n if (inGoBlock) {\n mask();\n if (line.includes(\")\")) inGoBlock = false;\n continue;\n }\n if (inJsBraces) {\n mask();\n if (/}\\s*from\\s/.test(line) || line.includes(\"}\")) inJsBraces = false;\n continue;\n }\n if (!patterns.some((pattern) => pattern.test(line))) continue;\n\n mask();\n if (isGo && /^\\s*import\\s+\\(/.test(line) && !line.includes(\")\")) inGoBlock = true;\n else if (\n (fileType === \"typescript\" || fileType === \"javascript\") &&\n /^\\s*import\\b/.test(line) &&\n line.includes(\"{\") &&\n !line.includes(\"}\")\n ) {\n inJsBraces = true;\n }\n }\n\n return lines.join(\"\\n\");\n}\n\nconst MULTI_CHAR_OPERATORS = [\n \"===\",\n \"!==\",\n \"...\",\n \"=>\",\n \"==\",\n \"!=\",\n \"<=\",\n \">=\",\n \"&&\",\n \"||\",\n \"::\",\n \"->\",\n \"..\",\n \"+=\",\n \"-=\",\n \"*=\",\n \"/=\",\n];\n\n/** Matches, in priority order: multi-char operators, identifiers, numbers, and quoted strings. */\nconst TOKEN_PATTERN = new RegExp(\n `${MULTI_CHAR_OPERATORS.map((op) => op.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")).join(\"|\")}|[A-Za-z_$][A-Za-z0-9_$]*|\\\\d+(?:\\\\.\\\\d+)?|\"(?:\\\\\\\\.|[^\"\\\\\\\\])*\"|'(?:\\\\\\\\.|[^'\\\\\\\\])*'|\\`(?:\\\\\\\\.|[^\\`\\\\\\\\])*\\`|\\\\S`,\n \"g\",\n);\n\nconst IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\nconst NUMBER_PATTERN = /^\\d+(?:\\.\\d+)?$/;\nconst STRING_PATTERN = /^(\".*\"|'.*'|`.*`)$/s;\n\n/**\n * Keywords kept verbatim rather than collapsed to the `ID` placeholder, shared across every\n * language rather than a per-language keyword table — deliberately coarse. Without this,\n * structurally different code (an `if` vs. a `for`, a `class` vs. a `function`) would tokenize\n * identically once every identifier-shaped word became `ID`, which would make the shingle\n * matcher far too eager. This list only needs to cover the keywords common enough across\n * TS/JS/Python/Go/CoffeeScript/LiveScript/Lua/Gherkin to matter for shape-preservation — it\n * doesn't need to be exhaustive or language-precise, since keeping a non-keyword here just\n * costs a little precision, not correctness.\n */\nconst KEYWORDS = new Set([\n \"if\",\n \"else\",\n \"elif\",\n \"for\",\n \"while\",\n \"do\",\n \"switch\",\n \"case\",\n \"default\",\n \"break\",\n \"continue\",\n \"return\",\n \"function\",\n \"def\",\n \"class\",\n \"struct\",\n \"interface\",\n \"const\",\n \"let\",\n \"var\",\n \"local\",\n \"import\",\n \"export\",\n \"from\",\n \"package\",\n \"try\",\n \"catch\",\n \"except\",\n \"finally\",\n \"throw\",\n \"raise\",\n \"new\",\n \"this\",\n \"self\",\n \"end\",\n \"then\",\n \"yield\",\n \"async\",\n \"await\",\n \"nil\",\n \"null\",\n \"None\",\n \"true\",\n \"false\",\n \"True\",\n \"False\",\n \"and\",\n \"or\",\n \"not\",\n \"in\",\n \"of\",\n \"is\",\n]);\n\n/**\n * @description Tokenizes source text into a normalized stream for shingle-based duplicate\n * matching: comments and import/using statements are masked out first (shared import lists are\n * not shared logic), then identifiers always collapse to a single placeholder (so\n * renamed-variable clones still hash identically), and literals optionally collapse too\n * (`ignoreLiterals`, default on) so only structural shape — not the specific values used —\n * drives the match.\n * @param source - Raw file content.\n * @param fileType - Selects the comment- and import-stripping rules; unrecognised types skip both.\n * @param ignoreLiterals - When true (default), string/number literal tokens are also\n * normalized to a placeholder rather than compared verbatim.\n * @returns Normalized tokens in source order, each carrying its 1-based source line.\n */\nexport function tokenize(\n source: string,\n fileType: FileType,\n ignoreLiterals = true,\n): NormalizedToken[] {\n const stripped = stripImports(stripComments(source, COMMENT_SYNTAX[fileType]), fileType);\n const tokens: NormalizedToken[] = [];\n\n let line = 1;\n let lastIndex = 0;\n TOKEN_PATTERN.lastIndex = 0;\n let match = TOKEN_PATTERN.exec(stripped);\n\n while (match !== null) {\n for (let i = lastIndex; i < match.index; i++) {\n if (stripped[i] === \"\\n\") line++;\n }\n lastIndex = match.index;\n\n const raw = match[0];\n let text = raw;\n if (IDENTIFIER_PATTERN.test(raw) && !KEYWORDS.has(raw)) {\n text = \"ID\";\n } else if (ignoreLiterals && (NUMBER_PATTERN.test(raw) || STRING_PATTERN.test(raw))) {\n text = NUMBER_PATTERN.test(raw) ? \"NUM\" : \"STR\";\n }\n tokens.push({ text, line });\n\n for (let i = lastIndex; i < match.index + raw.length; i++) {\n if (stripped[i] === \"\\n\") line++;\n }\n lastIndex = match.index + raw.length;\n match = TOKEN_PATTERN.exec(stripped);\n }\n\n return tokens;\n}\n"],"mappings":"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","names":["duplication_worker_exports","__export","tokenizeInWorker","__toCommonJS","COMMENT_SYNTAX","maskRange","source","start","end","masked","stripComments","syntax","result","open","close","searchFrom","rangeEnd","marker","lineEnd","IMPORT_LINE_PATTERNS","stripImports","fileType","patterns","lines","isGo","inGoBlock","inJsBraces","i","line","mask","pattern","MULTI_CHAR_OPERATORS","TOKEN_PATTERN","op","IDENTIFIER_PATTERN","NUMBER_PATTERN","STRING_PATTERN","KEYWORDS","tokenize","ignoreLiterals","stripped","tokens","lastIndex","match","raw","text","tokenizeInWorker","payload","tokenize"]}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
var
|
|
2
|
-
`,
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
var T={typescript:{line:["//"],block:[["/*","*/"]]},javascript:{line:["//"],block:[["/*","*/"]]},go:{line:["//"],block:[["/*","*/"]]},java:{line:["//"],block:[["/*","*/"]]},kotlin:{line:["//"],block:[["/*","*/"]]},scala:{line:["//"],block:[["/*","*/"]]},groovy:{line:["//"],block:[["/*","*/"]]},css:{block:[["/*","*/"]]},scss:{line:["//"],block:[["/*","*/"]]},less:{line:["//"],block:[["/*","*/"]]},stylus:{line:["//"],block:[["/*","*/"]]},python:{line:["#"]},gherkin:{line:["#"]},coffeescript:{line:["#"],block:[["###","###"]]},livescript:{line:["#"],block:[["###","###"]]},lua:{line:["--"],block:[["--[[","]]"]]},markdown:{block:[["<!--","-->"]]}};function m(s,r,t){let i=s.slice(r,t).replace(/[^\n]/g," ");return s.slice(0,r)+i+s.slice(t)}function b(s,r){if(!r)return s;let t=s;for(let[i,c]of r.block??[]){let l=0;for(;;){let o=t.indexOf(i,l);if(o===-1)break;let n=t.indexOf(c,o+i.length),e=n===-1?t.length:n+c.length;t=m(t,o,e),l=e}}for(let i of r.line??[]){let c=0;for(;;){let l=t.indexOf(i,c);if(l===-1)break;let o=t.indexOf(`
|
|
2
|
+
`,l),n=o===-1?t.length:o;t=m(t,l,n),c=n}}return t}var x={typescript:[/^\s*import[\s{("*]/,/^\s*export\s.*\sfrom\s/],javascript:[/^\s*import[\s{("*]/,/^\s*export\s.*\sfrom\s/],python:[/^\s*import\s+\S/,/^\s*from\s+\S+\s+import[\s(]/],go:[/^\s*import\s+["(]/],java:[/^\s*import\s+/],kotlin:[/^\s*import\s+/],scala:[/^\s*import\s+/],groovy:[/^\s*import\s+/]};function h(s,r){let t=x[r];if(!t)return s;let i=s.split(`
|
|
3
|
+
`),c=r==="go",l=!1,o=!1;for(let n=0;n<i.length;n++){let e=i[n],p=()=>{i[n]=e.replace(/[^\r]/g," ")};if(l){p(),e.includes(")")&&(l=!1);continue}if(o){p(),(/}\s*from\s/.test(e)||e.includes("}"))&&(o=!1);continue}t.some(a=>a.test(e))&&(p(),c&&/^\s*import\s+\(/.test(e)&&!e.includes(")")?l=!0:(r==="typescript"||r==="javascript")&&/^\s*import\b/.test(e)&&e.includes("{")&&!e.includes("}")&&(o=!0))}return i.join(`
|
|
4
|
+
`)}var y=["===","!==","...","=>","==","!=","<=",">=","&&","||","::","->","..","+=","-=","*=","/="],f=new RegExp(`${y.map(s=>s.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")).join("|")}|[A-Za-z_$][A-Za-z0-9_$]*|\\d+(?:\\.\\d+)?|"(?:\\\\.|[^"\\\\])*"|'(?:\\\\.|[^'\\\\])*'|\`(?:\\\\.|[^\`\\\\])*\`|\\S`,"g"),N=/^[A-Za-z_$][A-Za-z0-9_$]*$/,k=/^\d+(?:\.\d+)?$/,R=/^(".*"|'.*'|`.*`)$/s,E=new Set(["if","else","elif","for","while","do","switch","case","default","break","continue","return","function","def","class","struct","interface","const","let","var","local","import","export","from","package","try","catch","except","finally","throw","raise","new","this","self","end","then","yield","async","await","nil","null","None","true","false","True","False","and","or","not","in","of","is"]);function d(s,r,t=!0){let i=h(b(s,T[r]),r),c=[],l=1,o=0;f.lastIndex=0;let n=f.exec(i);for(;n!==null;){for(let a=o;a<n.index;a++)i[a]===`
|
|
5
|
+
`&&l++;o=n.index;let e=n[0],p=e;N.test(e)&&!E.has(e)?p="ID":t&&(k.test(e)||R.test(e))&&(p=k.test(e)?"NUM":"STR"),c.push({text:p,line:l});for(let a=o;a<n.index+e.length;a++)i[a]===`
|
|
6
|
+
`&&l++;o=n.index+e.length,n=f.exec(i)}return c}function z(s){return d(s.source,s.fileType,s.ignoreLiterals)}export{z as default};
|
|
5
7
|
//# sourceMappingURL=duplication-worker.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/graph/duplication/tokenizer.ts","../src/duplication-worker.ts"],"sourcesContent":["/**\n * Language-agnostic source tokenizer for duplicate-code detection. Strips per-language\n * comment syntax, then splits what remains into a normalized token stream shared by every\n * language `DEFAULT_EXTENSIONS` covers — one generic tokenizer rather than a per-language\n * lexer, so `findDuplicates` works uniformly across TS/JS, Python, Go, Java, Kotlin, Scala,\n * Groovy, CoffeeScript, LiveScript, Lua, Gherkin, style files, and Markdown.\n */\nimport type { FileType } from \"../../types/parse\";\n\n/** One normalized token plus the 1-based source line it came from. */\nexport interface NormalizedToken {\n text: string;\n line: number;\n}\n\ninterface CommentSyntax {\n line?: string[];\n block?: Array<[string, string]>;\n}\n\n/**\n * Per-`FileType` comment markers used to mask out comment text before tokenizing, so comment\n * wording never contributes to a duplicate match. Deliberately not string-literal-aware — a\n * `//` inside a string is rare and the cost of occasionally over-stripping is low for a\n * heuristic duplicate finder, same trade-off the complexity scorers make elsewhere.\n */\nconst COMMENT_SYNTAX: Partial<Record<FileType, CommentSyntax>> = {\n typescript: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n javascript: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n go: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n java: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n kotlin: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n scala: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n groovy: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n css: { block: [[\"/*\", \"*/\"]] },\n scss: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n less: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n stylus: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n python: { line: [\"#\"] },\n gherkin: { line: [\"#\"] },\n coffeescript: { line: [\"#\"], block: [[\"###\", \"###\"]] },\n livescript: { line: [\"#\"], block: [[\"###\", \"###\"]] },\n lua: { line: [\"--\"], block: [[\"--[[\", \"]]\"]] },\n markdown: { block: [[\"<!--\", \"-->\"]] },\n};\n\n/**\n * @description Replaces every non-newline character in `source[start, end)` with a space,\n * so downstream line-number tracking stays correct while the masked text can no longer\n * match anything.\n * @param source - Full file source.\n * @param start - Start offset (inclusive) of the range to mask.\n * @param end - End offset (exclusive) of the range to mask.\n * @returns `source` with the range masked.\n */\nfunction maskRange(source: string, start: number, end: number): string {\n const masked = source.slice(start, end).replace(/[^\\n]/g, \" \");\n return source.slice(0, start) + masked + source.slice(end);\n}\n\n/**\n * @description Masks out every line and block comment matching `syntax`, preserving line\n * breaks and overall string length so line numbers computed later stay accurate.\n * @param source - Full file source.\n * @param syntax - Comment markers for the file's language; absent for languages with no\n * configured comment syntax, in which case the source passes through unchanged.\n * @returns The source with comment text masked to spaces.\n */\nfunction stripComments(source: string, syntax: CommentSyntax | undefined): string {\n if (!syntax) return source;\n let result = source;\n\n for (const [open, close] of syntax.block ?? []) {\n let searchFrom = 0;\n for (;;) {\n const start = result.indexOf(open, searchFrom);\n if (start === -1) break;\n const end = result.indexOf(close, start + open.length);\n const rangeEnd = end === -1 ? result.length : end + close.length;\n result = maskRange(result, start, rangeEnd);\n searchFrom = rangeEnd;\n }\n }\n\n for (const marker of syntax.line ?? []) {\n let searchFrom = 0;\n for (;;) {\n const start = result.indexOf(marker, searchFrom);\n if (start === -1) break;\n const lineEnd = result.indexOf(\"\\n\", start);\n const rangeEnd = lineEnd === -1 ? result.length : lineEnd;\n result = maskRange(result, start, rangeEnd);\n searchFrom = rangeEnd;\n }\n }\n\n return result;\n}\n\nconst MULTI_CHAR_OPERATORS = [\n \"===\",\n \"!==\",\n \"...\",\n \"=>\",\n \"==\",\n \"!=\",\n \"<=\",\n \">=\",\n \"&&\",\n \"||\",\n \"::\",\n \"->\",\n \"..\",\n \"+=\",\n \"-=\",\n \"*=\",\n \"/=\",\n];\n\n/** Matches, in priority order: multi-char operators, identifiers, numbers, and quoted strings. */\nconst TOKEN_PATTERN = new RegExp(\n `${MULTI_CHAR_OPERATORS.map((op) => op.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")).join(\"|\")}|[A-Za-z_$][A-Za-z0-9_$]*|\\\\d+(?:\\\\.\\\\d+)?|\"(?:\\\\\\\\.|[^\"\\\\\\\\])*\"|'(?:\\\\\\\\.|[^'\\\\\\\\])*'|\\`(?:\\\\\\\\.|[^\\`\\\\\\\\])*\\`|\\\\S`,\n \"g\",\n);\n\nconst IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\nconst NUMBER_PATTERN = /^\\d+(?:\\.\\d+)?$/;\nconst STRING_PATTERN = /^(\".*\"|'.*'|`.*`)$/s;\n\n/**\n * Keywords kept verbatim rather than collapsed to the `ID` placeholder, shared across every\n * language rather than a per-language keyword table — deliberately coarse. Without this,\n * structurally different code (an `if` vs. a `for`, a `class` vs. a `function`) would tokenize\n * identically once every identifier-shaped word became `ID`, which would make the shingle\n * matcher far too eager. This list only needs to cover the keywords common enough across\n * TS/JS/Python/Go/CoffeeScript/LiveScript/Lua/Gherkin to matter for shape-preservation — it\n * doesn't need to be exhaustive or language-precise, since keeping a non-keyword here just\n * costs a little precision, not correctness.\n */\nconst KEYWORDS = new Set([\n \"if\",\n \"else\",\n \"elif\",\n \"for\",\n \"while\",\n \"do\",\n \"switch\",\n \"case\",\n \"default\",\n \"break\",\n \"continue\",\n \"return\",\n \"function\",\n \"def\",\n \"class\",\n \"struct\",\n \"interface\",\n \"const\",\n \"let\",\n \"var\",\n \"local\",\n \"import\",\n \"export\",\n \"from\",\n \"package\",\n \"try\",\n \"catch\",\n \"except\",\n \"finally\",\n \"throw\",\n \"raise\",\n \"new\",\n \"this\",\n \"self\",\n \"end\",\n \"then\",\n \"yield\",\n \"async\",\n \"await\",\n \"nil\",\n \"null\",\n \"None\",\n \"true\",\n \"false\",\n \"True\",\n \"False\",\n \"and\",\n \"or\",\n \"not\",\n \"in\",\n \"of\",\n \"is\",\n]);\n\n/**\n * @description Tokenizes source text into a normalized stream for shingle-based duplicate\n * matching: identifiers always collapse to a single placeholder (so renamed-variable clones\n * still hash identically), and literals optionally collapse too (`ignoreLiterals`, default\n * on) so only structural shape — not the specific values used — drives the match.\n * @param source - Raw file content.\n * @param fileType - Selects the comment-stripping rule; unrecognised types skip stripping.\n * @param ignoreLiterals - When true (default), string/number literal tokens are also\n * normalized to a placeholder rather than compared verbatim.\n * @returns Normalized tokens in source order, each carrying its 1-based source line.\n */\nexport function tokenize(\n source: string,\n fileType: FileType,\n ignoreLiterals = true,\n): NormalizedToken[] {\n const stripped = stripComments(source, COMMENT_SYNTAX[fileType]);\n const tokens: NormalizedToken[] = [];\n\n let line = 1;\n let lastIndex = 0;\n TOKEN_PATTERN.lastIndex = 0;\n let match = TOKEN_PATTERN.exec(stripped);\n\n while (match !== null) {\n for (let i = lastIndex; i < match.index; i++) {\n if (stripped[i] === \"\\n\") line++;\n }\n lastIndex = match.index;\n\n const raw = match[0];\n let text = raw;\n if (IDENTIFIER_PATTERN.test(raw) && !KEYWORDS.has(raw)) {\n text = \"ID\";\n } else if (ignoreLiterals && (NUMBER_PATTERN.test(raw) || STRING_PATTERN.test(raw))) {\n text = NUMBER_PATTERN.test(raw) ? \"NUM\" : \"STR\";\n }\n tokens.push({ text, line });\n\n for (let i = lastIndex; i < match.index + raw.length; i++) {\n if (stripped[i] === \"\\n\") line++;\n }\n lastIndex = match.index + raw.length;\n match = TOKEN_PATTERN.exec(stripped);\n }\n\n return tokens;\n}\n","/** Piscina task handler: tokenizes a single file's content in a worker thread, for `findDuplicates`. */\n\nimport type { NormalizedToken } from \"./graph/duplication/tokenizer.js\";\nimport { tokenize } from \"./graph/duplication/tokenizer.js\";\nimport type { FileType } from \"./types/parse\";\n\nexport default function tokenizeInWorker(payload: {\n source: string;\n fileType: FileType;\n ignoreLiterals: boolean;\n}): NormalizedToken[] {\n return tokenize(payload.source, payload.fileType, payload.ignoreLiterals);\n}\n"],"mappings":"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","names":["COMMENT_SYNTAX","maskRange","source","start","end","masked","stripComments","syntax","result","open","close","searchFrom","rangeEnd","marker","lineEnd","MULTI_CHAR_OPERATORS","TOKEN_PATTERN","op","IDENTIFIER_PATTERN","NUMBER_PATTERN","STRING_PATTERN","KEYWORDS","tokenize","fileType","ignoreLiterals","stripped","tokens","line","lastIndex","match","i","raw","text","tokenizeInWorker","payload","tokenize"]}
|
|
1
|
+
{"version":3,"sources":["../src/graph/duplication/tokenizer.ts","../src/duplication-worker.ts"],"sourcesContent":["/**\n * Language-agnostic source tokenizer for duplicate-code detection. Strips per-language\n * comment syntax and import/using statements, then splits what remains into a normalized token stream shared by every\n * language `DEFAULT_EXTENSIONS` covers — one generic tokenizer rather than a per-language\n * lexer, so `findDuplicates` works uniformly across TS/JS, Python, Go, Java, Kotlin, Scala,\n * Groovy, CoffeeScript, LiveScript, Lua, Gherkin, style files, and Markdown.\n */\nimport type { FileType } from \"../../types/parse\";\n\n/** One normalized token plus the 1-based source line it came from. */\nexport interface NormalizedToken {\n text: string;\n line: number;\n}\n\ninterface CommentSyntax {\n line?: string[];\n block?: Array<[string, string]>;\n}\n\n/**\n * Per-`FileType` comment markers used to mask out comment text before tokenizing, so comment\n * wording never contributes to a duplicate match. Deliberately not string-literal-aware — a\n * `//` inside a string is rare and the cost of occasionally over-stripping is low for a\n * heuristic duplicate finder, same trade-off the complexity scorers make elsewhere.\n */\nconst COMMENT_SYNTAX: Partial<Record<FileType, CommentSyntax>> = {\n typescript: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n javascript: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n go: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n java: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n kotlin: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n scala: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n groovy: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n css: { block: [[\"/*\", \"*/\"]] },\n scss: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n less: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n stylus: { line: [\"//\"], block: [[\"/*\", \"*/\"]] },\n python: { line: [\"#\"] },\n gherkin: { line: [\"#\"] },\n coffeescript: { line: [\"#\"], block: [[\"###\", \"###\"]] },\n livescript: { line: [\"#\"], block: [[\"###\", \"###\"]] },\n lua: { line: [\"--\"], block: [[\"--[[\", \"]]\"]] },\n markdown: { block: [[\"<!--\", \"-->\"]] },\n};\n\n/**\n * @description Replaces every non-newline character in `source[start, end)` with a space,\n * so downstream line-number tracking stays correct while the masked text can no longer\n * match anything.\n * @param source - Full file source.\n * @param start - Start offset (inclusive) of the range to mask.\n * @param end - End offset (exclusive) of the range to mask.\n * @returns `source` with the range masked.\n */\nfunction maskRange(source: string, start: number, end: number): string {\n const masked = source.slice(start, end).replace(/[^\\n]/g, \" \");\n return source.slice(0, start) + masked + source.slice(end);\n}\n\n/**\n * @description Masks out every line and block comment matching `syntax`, preserving line\n * breaks and overall string length so line numbers computed later stay accurate.\n * @param source - Full file source.\n * @param syntax - Comment markers for the file's language; absent for languages with no\n * configured comment syntax, in which case the source passes through unchanged.\n * @returns The source with comment text masked to spaces.\n */\nfunction stripComments(source: string, syntax: CommentSyntax | undefined): string {\n if (!syntax) return source;\n let result = source;\n\n for (const [open, close] of syntax.block ?? []) {\n let searchFrom = 0;\n for (;;) {\n const start = result.indexOf(open, searchFrom);\n if (start === -1) break;\n const end = result.indexOf(close, start + open.length);\n const rangeEnd = end === -1 ? result.length : end + close.length;\n result = maskRange(result, start, rangeEnd);\n searchFrom = rangeEnd;\n }\n }\n\n for (const marker of syntax.line ?? []) {\n let searchFrom = 0;\n for (;;) {\n const start = result.indexOf(marker, searchFrom);\n if (start === -1) break;\n const lineEnd = result.indexOf(\"\\n\", start);\n const rangeEnd = lineEnd === -1 ? result.length : lineEnd;\n result = maskRange(result, start, rangeEnd);\n searchFrom = rangeEnd;\n }\n }\n\n return result;\n}\n\n/**\n * Per-`FileType` patterns matching a *single* import/using line. Long shared import lists\n * tokenize identically across unrelated files and register as \"duplicates\" that carry no shared\n * logic — same rationale as stripping comments. Masked (space-filled, line breaks kept) before\n * tokenizing. Deliberately coarse and not string-literal-aware, like `stripComments`.\n */\nconst IMPORT_LINE_PATTERNS: Partial<Record<FileType, RegExp[]>> = {\n typescript: [/^\\s*import[\\s{(\"*]/, /^\\s*export\\s.*\\sfrom\\s/],\n javascript: [/^\\s*import[\\s{(\"*]/, /^\\s*export\\s.*\\sfrom\\s/],\n python: [/^\\s*import\\s+\\S/, /^\\s*from\\s+\\S+\\s+import[\\s(]/],\n go: [/^\\s*import\\s+[\"(]/],\n java: [/^\\s*import\\s+/],\n kotlin: [/^\\s*import\\s+/],\n scala: [/^\\s*import\\s+/],\n groovy: [/^\\s*import\\s+/],\n};\n\n/**\n * @description Masks import / `from … import` / `using` statements to spaces before tokenizing,\n * preserving line breaks so later line-number tracking stays accurate. Handles the two\n * multi-line shapes that matter in practice: a JS/TS `import { … } from \"x\"` whose braces span\n * lines, and a Go `import ( … )` block. Everything else is matched line-by-line.\n * @param source - File source, already comment-stripped.\n * @param fileType - Selects the import-line patterns; unrecognised types pass through unchanged.\n * @returns The source with import statements masked to spaces.\n */\nfunction stripImports(source: string, fileType: FileType): string {\n const patterns = IMPORT_LINE_PATTERNS[fileType];\n if (!patterns) return source;\n\n const lines = source.split(\"\\n\");\n const isGo = fileType === \"go\";\n let inGoBlock = false;\n let inJsBraces = false;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] as string;\n const mask = () => {\n lines[i] = line.replace(/[^\\r]/g, \" \");\n };\n\n if (inGoBlock) {\n mask();\n if (line.includes(\")\")) inGoBlock = false;\n continue;\n }\n if (inJsBraces) {\n mask();\n if (/}\\s*from\\s/.test(line) || line.includes(\"}\")) inJsBraces = false;\n continue;\n }\n if (!patterns.some((pattern) => pattern.test(line))) continue;\n\n mask();\n if (isGo && /^\\s*import\\s+\\(/.test(line) && !line.includes(\")\")) inGoBlock = true;\n else if (\n (fileType === \"typescript\" || fileType === \"javascript\") &&\n /^\\s*import\\b/.test(line) &&\n line.includes(\"{\") &&\n !line.includes(\"}\")\n ) {\n inJsBraces = true;\n }\n }\n\n return lines.join(\"\\n\");\n}\n\nconst MULTI_CHAR_OPERATORS = [\n \"===\",\n \"!==\",\n \"...\",\n \"=>\",\n \"==\",\n \"!=\",\n \"<=\",\n \">=\",\n \"&&\",\n \"||\",\n \"::\",\n \"->\",\n \"..\",\n \"+=\",\n \"-=\",\n \"*=\",\n \"/=\",\n];\n\n/** Matches, in priority order: multi-char operators, identifiers, numbers, and quoted strings. */\nconst TOKEN_PATTERN = new RegExp(\n `${MULTI_CHAR_OPERATORS.map((op) => op.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")).join(\"|\")}|[A-Za-z_$][A-Za-z0-9_$]*|\\\\d+(?:\\\\.\\\\d+)?|\"(?:\\\\\\\\.|[^\"\\\\\\\\])*\"|'(?:\\\\\\\\.|[^'\\\\\\\\])*'|\\`(?:\\\\\\\\.|[^\\`\\\\\\\\])*\\`|\\\\S`,\n \"g\",\n);\n\nconst IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\nconst NUMBER_PATTERN = /^\\d+(?:\\.\\d+)?$/;\nconst STRING_PATTERN = /^(\".*\"|'.*'|`.*`)$/s;\n\n/**\n * Keywords kept verbatim rather than collapsed to the `ID` placeholder, shared across every\n * language rather than a per-language keyword table — deliberately coarse. Without this,\n * structurally different code (an `if` vs. a `for`, a `class` vs. a `function`) would tokenize\n * identically once every identifier-shaped word became `ID`, which would make the shingle\n * matcher far too eager. This list only needs to cover the keywords common enough across\n * TS/JS/Python/Go/CoffeeScript/LiveScript/Lua/Gherkin to matter for shape-preservation — it\n * doesn't need to be exhaustive or language-precise, since keeping a non-keyword here just\n * costs a little precision, not correctness.\n */\nconst KEYWORDS = new Set([\n \"if\",\n \"else\",\n \"elif\",\n \"for\",\n \"while\",\n \"do\",\n \"switch\",\n \"case\",\n \"default\",\n \"break\",\n \"continue\",\n \"return\",\n \"function\",\n \"def\",\n \"class\",\n \"struct\",\n \"interface\",\n \"const\",\n \"let\",\n \"var\",\n \"local\",\n \"import\",\n \"export\",\n \"from\",\n \"package\",\n \"try\",\n \"catch\",\n \"except\",\n \"finally\",\n \"throw\",\n \"raise\",\n \"new\",\n \"this\",\n \"self\",\n \"end\",\n \"then\",\n \"yield\",\n \"async\",\n \"await\",\n \"nil\",\n \"null\",\n \"None\",\n \"true\",\n \"false\",\n \"True\",\n \"False\",\n \"and\",\n \"or\",\n \"not\",\n \"in\",\n \"of\",\n \"is\",\n]);\n\n/**\n * @description Tokenizes source text into a normalized stream for shingle-based duplicate\n * matching: comments and import/using statements are masked out first (shared import lists are\n * not shared logic), then identifiers always collapse to a single placeholder (so\n * renamed-variable clones still hash identically), and literals optionally collapse too\n * (`ignoreLiterals`, default on) so only structural shape — not the specific values used —\n * drives the match.\n * @param source - Raw file content.\n * @param fileType - Selects the comment- and import-stripping rules; unrecognised types skip both.\n * @param ignoreLiterals - When true (default), string/number literal tokens are also\n * normalized to a placeholder rather than compared verbatim.\n * @returns Normalized tokens in source order, each carrying its 1-based source line.\n */\nexport function tokenize(\n source: string,\n fileType: FileType,\n ignoreLiterals = true,\n): NormalizedToken[] {\n const stripped = stripImports(stripComments(source, COMMENT_SYNTAX[fileType]), fileType);\n const tokens: NormalizedToken[] = [];\n\n let line = 1;\n let lastIndex = 0;\n TOKEN_PATTERN.lastIndex = 0;\n let match = TOKEN_PATTERN.exec(stripped);\n\n while (match !== null) {\n for (let i = lastIndex; i < match.index; i++) {\n if (stripped[i] === \"\\n\") line++;\n }\n lastIndex = match.index;\n\n const raw = match[0];\n let text = raw;\n if (IDENTIFIER_PATTERN.test(raw) && !KEYWORDS.has(raw)) {\n text = \"ID\";\n } else if (ignoreLiterals && (NUMBER_PATTERN.test(raw) || STRING_PATTERN.test(raw))) {\n text = NUMBER_PATTERN.test(raw) ? \"NUM\" : \"STR\";\n }\n tokens.push({ text, line });\n\n for (let i = lastIndex; i < match.index + raw.length; i++) {\n if (stripped[i] === \"\\n\") line++;\n }\n lastIndex = match.index + raw.length;\n match = TOKEN_PATTERN.exec(stripped);\n }\n\n return tokens;\n}\n","/** Piscina task handler: tokenizes a single file's content in a worker thread, for `findDuplicates`. */\n\nimport type { NormalizedToken } from \"./graph/duplication/tokenizer.js\";\nimport { tokenize } from \"./graph/duplication/tokenizer.js\";\nimport type { FileType } from \"./types/parse\";\n\nexport default function tokenizeInWorker(payload: {\n source: string;\n fileType: FileType;\n ignoreLiterals: boolean;\n}): NormalizedToken[] {\n return tokenize(payload.source, payload.fileType, payload.ignoreLiterals);\n}\n"],"mappings":"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","names":["COMMENT_SYNTAX","maskRange","source","start","end","masked","stripComments","syntax","result","open","close","searchFrom","rangeEnd","marker","lineEnd","IMPORT_LINE_PATTERNS","stripImports","fileType","patterns","lines","isGo","inGoBlock","inJsBraces","i","line","mask","pattern","MULTI_CHAR_OPERATORS","TOKEN_PATTERN","op","IDENTIFIER_PATTERN","NUMBER_PATTERN","STRING_PATTERN","KEYWORDS","tokenize","ignoreLiterals","stripped","tokens","lastIndex","match","raw","text","tokenizeInWorker","payload","tokenize"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { F as FileNode, C as CallEdge, I as ImportEdge, P as ParseResult, S as StructuredTag } from './types-
|
|
2
|
-
export { E as ExportedSymbol } from './types-
|
|
3
|
-
import { N as NormalizedToken } from './tokenizer-
|
|
1
|
+
import { F as FileNode, C as CallEdge, I as ImportEdge, P as ParseResult, S as StructuredTag } from './types-Ba-BjVQG.mjs';
|
|
2
|
+
export { E as ExportedSymbol } from './types-Ba-BjVQG.mjs';
|
|
3
|
+
import { N as NormalizedToken } from './tokenizer-D9P-y7lH.mjs';
|
|
4
4
|
import { F as FileType, N as NodeCategory } from './parse-yamNoaoL.mjs';
|
|
5
5
|
export { I as ImportType, T as TagKind } from './parse-yamNoaoL.mjs';
|
|
6
6
|
|
|
@@ -17,6 +17,61 @@ interface TraversalOptions {
|
|
|
17
17
|
direction?: "outgoing" | "incoming";
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** Analyzes a dependency graph node map for unused files, export-usage hotspots, and circular import chains. */
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Edge kinds that {@link GraphAnalyzer.findCycles} treats as *not* real import cycles and skips
|
|
24
|
+
* by default: `"samePackage"` for the synthetic JVM same-package sibling clique, `"docReference"`
|
|
25
|
+
* for Markdown link / code-span file references (ADR-009). Pass one via `includeKinds` to walk it
|
|
26
|
+
* anyway (e.g. a caller that genuinely wants to see doc cross-link loops).
|
|
27
|
+
*/
|
|
28
|
+
type CycleEdgeKind = "docReference" | "samePackage";
|
|
29
|
+
/** Options for {@link GraphAnalyzer.findCycles}. */
|
|
30
|
+
interface FindCyclesOptions {
|
|
31
|
+
/** Edge kinds to include in the walk that are otherwise skipped (see {@link CycleEdgeKind}). */
|
|
32
|
+
includeKinds?: CycleEdgeKind[] | undefined;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* @description Utility for analyzing the dependency graph for cycles and unused files.
|
|
36
|
+
* Operates on the raw node map rather than a `Graph` instance so it can be used
|
|
37
|
+
* without the full traversal infrastructure.
|
|
38
|
+
*/
|
|
39
|
+
declare class GraphAnalyzer {
|
|
40
|
+
private nodes;
|
|
41
|
+
/**
|
|
42
|
+
* @param {Map<string, FileNode>} nodes - The full node map of the graph to analyze, keyed by project-relative file path.
|
|
43
|
+
*/
|
|
44
|
+
constructor(nodes: Map<string, FileNode>);
|
|
45
|
+
/**
|
|
46
|
+
* @description Returns files from `allFiles` that are absent from the graph — meaning nothing
|
|
47
|
+
* imports them directly or transitively from any entry point, making them deletion candidates.
|
|
48
|
+
* @param {string[]} allFiles - Complete list of project-relative file paths to test against the graph.
|
|
49
|
+
* @returns {string[]} Subset of `allFiles` whose paths do not appear as graph nodes.
|
|
50
|
+
*/
|
|
51
|
+
findUnusedFiles(allFiles: string[]): string[];
|
|
52
|
+
/**
|
|
53
|
+
* @description Returns files whose highest single-edge export usage ratio meets or exceeds
|
|
54
|
+
* `threshold`, sorted descending by `maxExportUsage`. Useful for identifying files
|
|
55
|
+
* that consume a large fraction of one dependency's API surface.
|
|
56
|
+
* @param {number} threshold - Minimum `maxExportUsage` value (0–1) for a file to be included.
|
|
57
|
+
* @returns {Array<{ path: string; maxExportUsage: number; tightestDep: string }>} Entries sorted descending by `maxExportUsage`.
|
|
58
|
+
*/
|
|
59
|
+
findHighExportUsage(threshold: number): Array<{
|
|
60
|
+
path: string;
|
|
61
|
+
maxExportUsage: number;
|
|
62
|
+
tightestDep: string;
|
|
63
|
+
}>;
|
|
64
|
+
/**
|
|
65
|
+
* @description Detects all circular import chains using DFS with a recursion-stack back-edge check.
|
|
66
|
+
* Each returned array is one cycle as an ordered list of file paths ending at the entry that closes the loop.
|
|
67
|
+
* Non-import edge kinds — the synthetic JVM same-package clique and Markdown doc references
|
|
68
|
+
* (ADR-009) — are skipped by default; pass `includeKinds` to walk them anyway.
|
|
69
|
+
* @param {FindCyclesOptions} [opts] - `includeKinds` opts specific edge kinds back into the walk.
|
|
70
|
+
* @returns {string[][]} Array of cycles; each cycle is an ordered list of file paths forming a loop.
|
|
71
|
+
*/
|
|
72
|
+
findCycles(opts?: FindCyclesOptions): string[][];
|
|
73
|
+
}
|
|
74
|
+
|
|
20
75
|
/** Graph class wrapping the raw node map with DFS traversal, cycle detection, serialization, and reverse-edge helpers. */
|
|
21
76
|
|
|
22
77
|
/**
|
|
@@ -112,9 +167,12 @@ declare class Graph {
|
|
|
112
167
|
/**
|
|
113
168
|
* @description Detects all circular import chains in the graph using DFS
|
|
114
169
|
* with a back-edge check. Each returned array is one cycle as an ordered path.
|
|
170
|
+
* Synthetic JVM same-package edges and Markdown doc references are skipped by default —
|
|
171
|
+
* pass `opts.includeKinds` to walk them.
|
|
172
|
+
* @param {FindCyclesOptions} [opts] - Forwarded to {@link GraphAnalyzer.findCycles}.
|
|
115
173
|
* @returns Array of cycles; each cycle is a list of file paths forming a loop.
|
|
116
174
|
*/
|
|
117
|
-
findCycles(): string[][];
|
|
175
|
+
findCycles(opts?: FindCyclesOptions): string[][];
|
|
118
176
|
}
|
|
119
177
|
|
|
120
178
|
/** Configures whether/how `parseFile` calls are offloaded to a `piscina` worker pool. `false` always parses in-process. */
|
|
@@ -197,6 +255,19 @@ interface MokoshConfig {
|
|
|
197
255
|
* in-process, or pass `{ minFiles, maxThreads }` to raise the threshold instead.
|
|
198
256
|
*/
|
|
199
257
|
parallelParsing?: ParallelParsingOption;
|
|
258
|
+
/** Duplicate-detection (`find_duplicates` / `--find-duplicates`) tuning. */
|
|
259
|
+
duplication?: {
|
|
260
|
+
/**
|
|
261
|
+
* Extra generated / vendored file patterns to exclude from duplicate scanning, merged with
|
|
262
|
+
* the built-in list (protobuf output, `*.generated.*`, `generated/` dirs, `@generated`
|
|
263
|
+
* markers). Two shapes only — `** /name/**` (any path segment equals `name`) and `*.suffix`
|
|
264
|
+
* (basename ends with `.suffix`) — not full glob syntax.
|
|
265
|
+
*/
|
|
266
|
+
ignoreGlobs?: string[];
|
|
267
|
+
/** When true, scan generated / vendored files too (default false). Matches involving one are
|
|
268
|
+
* tagged `signals: ["generated"]`. */
|
|
269
|
+
includeGenerated?: boolean;
|
|
270
|
+
};
|
|
200
271
|
}
|
|
201
272
|
/**
|
|
202
273
|
* @description Loads a mokosh config file, probing standard filenames in `rootDirOrPath` or reading an explicit path when `isExplicitPath` is true.
|
|
@@ -247,6 +318,11 @@ declare const DEFAULT_DUPLICATION_TOKEN_CACHE_FILE = "duplication-tokens.json";
|
|
|
247
318
|
* (`src/cli/graph-loader.ts`) after every build; read by the MCP server (`src/mcp/cache.ts`) to
|
|
248
319
|
* seed a session's first `analyze` call so it reuses unchanged nodes instead of parsing cold. */
|
|
249
320
|
declare const DEFAULT_GRAPH_CACHE_FILE = "graph.json";
|
|
321
|
+
/** Filename for the disk-persisted *workspace* (monorepo) graph cache within `DEFAULT_CACHE_DIR`.
|
|
322
|
+
* Written by the MCP server (`src/mcp/cache.ts`) after a `createWorkspaceGraph` build and hydrated
|
|
323
|
+
* on the next session's first workspace query when the source-file digest still matches — so
|
|
324
|
+
* repeat sessions against an unchanged monorepo skip the full per-package rebuild. */
|
|
325
|
+
declare const DEFAULT_WORKSPACE_GRAPH_CACHE_FILE = "workspace-graph.json";
|
|
250
326
|
/** Subdirectory of `DEFAULT_CACHE_DIR` holding one JSON file per commit sha — graphs built for
|
|
251
327
|
* the "other" ref in a `compareBranches` call (`src/graph/branch-graph-cache.ts`). Keyed by sha
|
|
252
328
|
* rather than branch name so entries are immutable and never need invalidation. */
|
|
@@ -497,6 +573,13 @@ interface DuplicateOccurrence {
|
|
|
497
573
|
startLine: number;
|
|
498
574
|
endLine: number;
|
|
499
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* Advisory tags on a {@link DuplicateGroup} that help a caller (and issue 6's query layer) judge
|
|
578
|
+
* whether a match is worth acting on: `"same-file"` — every occurrence is in one file (an
|
|
579
|
+
* internally repeated block); `"generated"` — at least one occurrence is in a file only scanned
|
|
580
|
+
* because `includeGenerated: true` was passed. Designed to grow (issue 7 adds more).
|
|
581
|
+
*/
|
|
582
|
+
type DuplicateSignal = "same-file" | "generated";
|
|
500
583
|
interface DuplicateGroup {
|
|
501
584
|
/** Every location sharing this duplicated block (two or more) — locations that pairwise
|
|
502
585
|
* chain-match are clustered into one group instead of being reported once per pair, so a
|
|
@@ -512,6 +595,9 @@ interface DuplicateGroup {
|
|
|
512
595
|
* matches across families — see docs/adr-013-duplicate-detection-noise-reduction.md).
|
|
513
596
|
* Absent when called directly with a token stream that isn't family-scoped, e.g. in tests. */
|
|
514
597
|
family?: DuplicateFamily | undefined;
|
|
598
|
+
/** Advisory tags for filtering — see {@link DuplicateSignal}. Absent (not `[]`) when no
|
|
599
|
+
* signal applies. */
|
|
600
|
+
signals?: DuplicateSignal[] | undefined;
|
|
515
601
|
}
|
|
516
602
|
|
|
517
603
|
/** One file's cached tokenize result, fingerprinted by `mtime`/`size`/`ignoreLiterals` — any
|
|
@@ -522,6 +608,10 @@ interface CachedFileTokens {
|
|
|
522
608
|
mtime: number;
|
|
523
609
|
size: number;
|
|
524
610
|
ignoreLiterals: boolean;
|
|
611
|
+
/** Whether this file is generated / vendored (path heuristic or first-bytes marker) — cached
|
|
612
|
+
* so a `includeGenerated: false` scan that gets a cache hit can still exclude it, and a
|
|
613
|
+
* `includeGenerated: true` scan can still tag matches, without re-reading the file. */
|
|
614
|
+
generated: boolean;
|
|
525
615
|
tokens: NormalizedToken[];
|
|
526
616
|
}
|
|
527
617
|
/** Caller-owned cache, keyed by project-relative path, reused across repeated `findDuplicates`
|
|
@@ -550,6 +640,24 @@ declare function loadTokenCacheFromDisk(cachePath: string): DuplicationTokenCach
|
|
|
550
640
|
*/
|
|
551
641
|
declare function saveTokenCacheToDisk(cache: DuplicationTokenCache, cachePath: string): void;
|
|
552
642
|
|
|
643
|
+
/**
|
|
644
|
+
* @description Whether a file's path alone marks it as generated — a known codegen basename
|
|
645
|
+
* suffix, a generated-output directory segment, or a caller-supplied `ignoreGlobs` match.
|
|
646
|
+
* Cheap and synchronous, so it runs before the file is read.
|
|
647
|
+
* @param relPath - Project-relative file path.
|
|
648
|
+
* @param ignoreGlobs - Extra patterns from `MokoshConfig.duplication.ignoreGlobs`.
|
|
649
|
+
* @returns Whether `relPath` should be treated as generated.
|
|
650
|
+
*/
|
|
651
|
+
declare function isGeneratedPath(relPath: string, ignoreGlobs?: readonly string[]): boolean;
|
|
652
|
+
/**
|
|
653
|
+
* @description Whether the start of a file's source carries a generated-by marker
|
|
654
|
+
* (`@generated`, `DO NOT EDIT`, `Code generated by …`, etc.). Only the first
|
|
655
|
+
* {@link MARKER_SCAN_BYTES} characters are checked — codegen tools put the banner at the top.
|
|
656
|
+
* @param source - Full file source.
|
|
657
|
+
* @returns Whether a generated marker is present near the top of `source`.
|
|
658
|
+
*/
|
|
659
|
+
declare function hasGeneratedMarker(source: string): boolean;
|
|
660
|
+
|
|
553
661
|
/** Configures whether/how tokenizing is offloaded to a `piscina` worker pool. `false` always
|
|
554
662
|
* tokenizes in-process. */
|
|
555
663
|
type ParallelTokenizingOption = boolean | {
|
|
@@ -577,6 +685,16 @@ interface FindDuplicatesOptions {
|
|
|
577
685
|
/** Directory names to exclude, matched against any path segment (default `DEFAULT_IGNORE_DIRS`
|
|
578
686
|
* — `node_modules`, `dist`, `.git`, `mokosh-cache`, `coverage`, etc.). Pass `[]` to disable. */
|
|
579
687
|
ignoreDirs?: readonly string[] | undefined;
|
|
688
|
+
/** When false (default), skip generated / vendored files — protobuf output, `*.generated.*`,
|
|
689
|
+
* codegen basenames, files under a `generated/` segment, and files whose first ~500 bytes
|
|
690
|
+
* carry a `@generated` / `DO NOT EDIT` / `Code generated by` marker. Their repetition is not
|
|
691
|
+
* actionable copy-paste. Set true to scan them anyway; matches involving one are tagged
|
|
692
|
+
* `signals: ["generated"]`. See docs/adr-013-duplicate-detection-noise-reduction.md. */
|
|
693
|
+
includeGenerated?: boolean | undefined;
|
|
694
|
+
/** Extra generated-file patterns merged with the built-in list (from
|
|
695
|
+
* `MokoshConfig.duplication.ignoreGlobs`). Two shapes only — `**/name/**` (path segment) and
|
|
696
|
+
* `*.suffix` (basename) — not full glob syntax. */
|
|
697
|
+
ignoreGlobs?: readonly string[] | undefined;
|
|
580
698
|
/** Controls worker-pool offloading of per-file tokenizing (default `true`): offloads once the
|
|
581
699
|
* candidate file count reaches `minFiles` (default 20, matching `GraphBuilder`'s parse pool);
|
|
582
700
|
* `false` always tokenizes in-process; an object overrides `minFiles`/`maxThreads`. See
|
|
@@ -920,17 +1038,6 @@ interface MonorepoDetector {
|
|
|
920
1038
|
*/
|
|
921
1039
|
declare function registerMonorepoDetector(detector: MonorepoDetector): void;
|
|
922
1040
|
|
|
923
|
-
/**
|
|
924
|
-
* @description Runs all registered monorepo detectors against `rootDir` and merges
|
|
925
|
-
* their results into a single `MonorepoLayout`. All matching detectors contribute
|
|
926
|
-
* their `type` string and packages — so a Turborepo + pnpm repo will have
|
|
927
|
-
* `types: ["turborepo", "pnpm"]` and packages from the pnpm detector.
|
|
928
|
-
*
|
|
929
|
-
* Packages are deduplicated by name: the first detector to emit a package name wins.
|
|
930
|
-
* Returns `type: "none"` when no detector fires.
|
|
931
|
-
*/
|
|
932
|
-
declare function detectMonorepo(rootDir: string, detectors?: readonly MonorepoDetector[]): MonorepoLayout;
|
|
933
|
-
|
|
934
1041
|
/** WorkspaceGraph holds one per-package Graph for a monorepo and exposes cross-package blast-radius queries. */
|
|
935
1042
|
|
|
936
1043
|
/** @description JSON-safe snapshot of a `WorkspaceGraph`, suitable for writing to disk and restoring via `WorkspaceGraph.deserialize`. */
|
|
@@ -1016,6 +1123,47 @@ declare class WorkspaceGraph {
|
|
|
1016
1123
|
static deserialize(data: SerializedWorkspaceGraph): WorkspaceGraph;
|
|
1017
1124
|
}
|
|
1018
1125
|
|
|
1126
|
+
interface WorkspaceLayoutPackageSummary {
|
|
1127
|
+
name: string;
|
|
1128
|
+
relativeRoot: string;
|
|
1129
|
+
/** Sibling workspace packages this one depends on. Exact when `dependsOnResolved`, best-effort otherwise. */
|
|
1130
|
+
dependsOn: string[];
|
|
1131
|
+
/** Present only when a built workspace graph was available. */
|
|
1132
|
+
nodeCount?: number;
|
|
1133
|
+
}
|
|
1134
|
+
interface WorkspaceLayoutSummary {
|
|
1135
|
+
monorepoType: string;
|
|
1136
|
+
monorepoTypes: string[];
|
|
1137
|
+
packageCount: number;
|
|
1138
|
+
packages: WorkspaceLayoutPackageSummary[];
|
|
1139
|
+
/** `true` when `dependsOn` came from a built graph or every package exposed a manifest. */
|
|
1140
|
+
dependsOnResolved: boolean;
|
|
1141
|
+
/** `true` when per-package `nodeCount` is populated (a built graph was available). */
|
|
1142
|
+
nodeCountsResolved: boolean;
|
|
1143
|
+
note?: string;
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* @description Summarizes a monorepo from its detected layout alone — package names, relative
|
|
1147
|
+
* roots, and best-effort `dependsOn` from `package.json` manifests — without building any
|
|
1148
|
+
* dependency graph. When a built `WorkspaceGraph` is supplied, its exact per-package node
|
|
1149
|
+
* counts and cross-package edges are used instead.
|
|
1150
|
+
* @param layout - The result of `detectMonorepo`. Must not be `type: "none"`.
|
|
1151
|
+
* @param builtGraph - An already-built workspace graph for `layout.root`, if one is cached.
|
|
1152
|
+
* @returns A layout summary suitable for the `get_workspace_packages` response.
|
|
1153
|
+
*/
|
|
1154
|
+
declare function summarizeWorkspaceLayout(layout: MonorepoLayout, builtGraph?: WorkspaceGraph): WorkspaceLayoutSummary;
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* @description Runs all registered monorepo detectors against `rootDir` and merges
|
|
1158
|
+
* their results into a single `MonorepoLayout`. All matching detectors contribute
|
|
1159
|
+
* their `type` string and packages — so a Turborepo + pnpm repo will have
|
|
1160
|
+
* `types: ["turborepo", "pnpm"]` and packages from the pnpm detector.
|
|
1161
|
+
*
|
|
1162
|
+
* Packages are deduplicated by name: the first detector to emit a package name wins.
|
|
1163
|
+
* Returns `type: "none"` when no detector fires.
|
|
1164
|
+
*/
|
|
1165
|
+
declare function detectMonorepo(rootDir: string, detectors?: readonly MonorepoDetector[]): MonorepoLayout;
|
|
1166
|
+
|
|
1019
1167
|
/**
|
|
1020
1168
|
* @description Contract for graph serializers. Implement this to add a new export format
|
|
1021
1169
|
* (e.g. Graphviz DOT, JSON, SVG) without touching the core graph model.
|
|
@@ -1705,20 +1853,40 @@ declare function createImportMap(rootDir: string, entryPoints: string[], previou
|
|
|
1705
1853
|
parallelParsing?: ParallelParsingOption | undefined;
|
|
1706
1854
|
pathAliases?: Record<string, string[]> | undefined;
|
|
1707
1855
|
additionalIgnoreDirs?: string[] | undefined;
|
|
1856
|
+
docFiles?: string[] | null | undefined;
|
|
1708
1857
|
}): Promise<Graph>;
|
|
1858
|
+
/**
|
|
1859
|
+
* @description Computes a digest of every source file under `rootDir` (path + mtime + size),
|
|
1860
|
+
* used to decide whether a disk-persisted workspace graph is still valid. Changing, adding, or
|
|
1861
|
+
* removing any file changes the digest.
|
|
1862
|
+
* @param rootDir - Absolute monorepo root.
|
|
1863
|
+
* @returns A hex sha-256 digest, and the relative file list it was computed from (reused by the
|
|
1864
|
+
* caller so the directory tree is walked only once).
|
|
1865
|
+
*/
|
|
1866
|
+
declare function computeWorkspaceSourceDigest(rootDir: string): {
|
|
1867
|
+
digest: string;
|
|
1868
|
+
files: string[];
|
|
1869
|
+
};
|
|
1709
1870
|
/**
|
|
1710
1871
|
* @description Auto-detects the monorepo layout under `rootDir` and builds a per-package
|
|
1711
1872
|
* dependency graph, stitching them together into a single WorkspaceGraph.
|
|
1712
1873
|
* @param rootDir - Absolute path to the monorepo root.
|
|
1713
|
-
* @param options - `packages` filters to a named subset of packages; `silent` suppresses progress; `gitStats` attaches git churn data per file; `parallelParsing` controls worker-pool offloading of file parsing per package (see {@link ParallelParsingOption}); `pathAliases` overrides/extends tsconfig path-alias resolution for every package (see `MokoshConfig.pathAliases`).
|
|
1874
|
+
* @param options - `packages` filters to a named subset of packages; `silent` suppresses progress; `gitStats` attaches git churn data per file; `parallelParsing` controls worker-pool offloading of file parsing per package (see {@link ParallelParsingOption}); `pathAliases` overrides/extends tsconfig path-alias resolution for every package (see `MokoshConfig.pathAliases`); `layout` supplies a pre-computed `detectMonorepo` result so detection is not repeated by callers that already ran it.
|
|
1714
1875
|
* @returns A WorkspaceGraph where each package has its own Graph and cross-package edges are resolved.
|
|
1715
1876
|
*/
|
|
1716
1877
|
declare function createWorkspaceGraph(rootDir: string, options?: {
|
|
1717
|
-
packages?: string[];
|
|
1878
|
+
packages?: string[] | undefined;
|
|
1718
1879
|
silent?: boolean;
|
|
1719
1880
|
gitStats?: boolean;
|
|
1720
1881
|
parallelParsing?: ParallelParsingOption | undefined;
|
|
1721
1882
|
pathAliases?: Record<string, string[]> | undefined;
|
|
1883
|
+
additionalIgnoreDirs?: string[] | undefined;
|
|
1884
|
+
layout?: MonorepoLayout | undefined;
|
|
1885
|
+
/** Pre-computed `getAllProjectFiles(rootDir)` result, so the caller's digest walk isn't repeated. */
|
|
1886
|
+
projectFiles?: string[] | undefined;
|
|
1887
|
+
/** A previously built workspace graph; each package reuses its prior graph as an incremental
|
|
1888
|
+
* base so unchanged files are not re-parsed (mtime+size match). */
|
|
1889
|
+
previousWorkspace?: WorkspaceGraph | undefined;
|
|
1722
1890
|
}): Promise<WorkspaceGraph>;
|
|
1723
1891
|
/**
|
|
1724
1892
|
* @description Recursively walks `rootDir` and returns paths of every file whose extension
|
|
@@ -1729,4 +1897,4 @@ declare function createWorkspaceGraph(rootDir: string, options?: {
|
|
|
1729
1897
|
*/
|
|
1730
1898
|
declare function getAllProjectFiles(rootDir: string, options?: ScanOptions): string[];
|
|
1731
1899
|
|
|
1732
|
-
export { type ApiSurface, type ApplyTagsFileResult, type ApplyTagsResult, type BranchComparison, type BranchComparisonSummary, type BuildGraphAtRefOptions, CALL_EDGE_TYPES, type CachedFileTokens, CallEdge, type CalleeEntry, type CallerEntry$1 as CallerEntry, type ChangeImpactCache, type CompareBranchesOptions, type ComplexFunctionEntry, type ComplexityDelta, type CoverageDelta, DEFAULT_BRANCH_GRAPH_CACHE_DIR, DEFAULT_CACHE_DIR, DEFAULT_DUPLICATION_TOKEN_CACHE_FILE, DEFAULT_EXTENSIONS, DEFAULT_GRAPH_CACHE_FILE, DEFAULT_IGNORE_DIRS, type DependencyGraph, type DocDriftDelta, type DuplicateFamily, type DuplicateGroup, type DuplicateOccurrence, type DuplicationDelta, type DuplicationTokenCache, EXPORT_TRACKING_TYPES, type ExportKind, type FeatureDetectionOptions, type FeatureDomain, type FeatureGraph, type FeatureGraphOptions, type FeatureInfo, type FileDiff, FileNode, FileType, type FindComplexFunctionsOptions, type FindDuplicatesOptions, type FindDuplicatesResult, type FindRiskHotspotsOptions, type FunctionCallInfo, type GetAffectedOptions, type GetCallersOptions, Graph, type CallerEntry as GraphCallerEntry, type GraphExporter, IMPORT_SYMBOL_TYPES, ImportEdge, type LanguageCoverage, MermaidExporter, type ModuleResponsibility, type ModuleRole, type MokoshConfig, type MonorepoDetector, type MonorepoLayout, NodeCategory, type NodeMeta, type NodeQuery, type ParallelParsingOption, type PathWithSymbols, type ProposeTagsOptions, type PublicExport, type ResponsibilityGraph, type RiskHotspotEntry, type RiskHotspotsResult, type ScanOptions, type SerializedGraph, type SerializedWorkspaceGraph, type SlimNode, type SlimSerializedGraph, type StaleReference, StructuredTag, type SummarizeOptions, type SymbolCaller, type SymbolImporter, type SymbolMatch, type SymbolPrecision, SymbolTraversalContext, type TestNodeIdentifier, type TraversalOptions, type TraversalVisitor, type TypeEdge, type TypeGraph, type TypeKind, type TypeNode, type TypeQueryResult, WorkspaceGraph, type WorkspacePackage, type WorkspacePackageSummary, type WorkspacePackagesSummary, applyConfig, applyTags, buildApiSurface, buildChangeImpactCache, buildFeatureGraph, buildGraphAtRef, buildResponsibilityGraph, buildTypeGraph, compareBranches, computeGraphHash, configToGraphOptions, createImportMap, createWorkspaceGraph, detectAllEntryPoints, detectEntryPoint, detectFeatures, detectMonorepo, filterGraph, findComplexFunctions, findDuplicates, findRiskHotspots, findSymbol, getAffected, getAllProjectFiles, getCallers, getDependencies, getDependents, getLanguageCoverage, getNodeMeta, hasChurnData, hasCoverageData, isChangeImpactCacheValid, loadChangeImpactCache, loadCoverageMap, loadMokoshConfig, loadTokenCacheFromDisk, parseQuery, proposeAffectedTests, proposeTags, queryCallGraph, queryChangeImpact, queryTypeGraph, registerConfigMatcher, registerMonorepoDetector, registerParser, registerTestLibrary, registerTestPattern, resetClassifyRegistries, saveChangeImpactCache, saveTokenCacheToDisk, slimSerialize, summarizeBranchComparison, summarizeWorkspacePackages, toMermaid };
|
|
1900
|
+
export { type ApiSurface, type ApplyTagsFileResult, type ApplyTagsResult, type BranchComparison, type BranchComparisonSummary, type BuildGraphAtRefOptions, CALL_EDGE_TYPES, type CachedFileTokens, CallEdge, type CalleeEntry, type CallerEntry$1 as CallerEntry, type ChangeImpactCache, type CompareBranchesOptions, type ComplexFunctionEntry, type ComplexityDelta, type CoverageDelta, type CycleEdgeKind, DEFAULT_BRANCH_GRAPH_CACHE_DIR, DEFAULT_CACHE_DIR, DEFAULT_DUPLICATION_TOKEN_CACHE_FILE, DEFAULT_EXTENSIONS, DEFAULT_GRAPH_CACHE_FILE, DEFAULT_IGNORE_DIRS, DEFAULT_WORKSPACE_GRAPH_CACHE_FILE, type DependencyGraph, type DocDriftDelta, type DuplicateFamily, type DuplicateGroup, type DuplicateOccurrence, type DuplicateSignal, type DuplicationDelta, type DuplicationTokenCache, EXPORT_TRACKING_TYPES, type ExportKind, type FeatureDetectionOptions, type FeatureDomain, type FeatureGraph, type FeatureGraphOptions, type FeatureInfo, type FileDiff, FileNode, FileType, type FindComplexFunctionsOptions, type FindCyclesOptions, type FindDuplicatesOptions, type FindDuplicatesResult, type FindRiskHotspotsOptions, type FunctionCallInfo, type GetAffectedOptions, type GetCallersOptions, Graph, GraphAnalyzer, type CallerEntry as GraphCallerEntry, type GraphExporter, IMPORT_SYMBOL_TYPES, ImportEdge, type LanguageCoverage, MermaidExporter, type ModuleResponsibility, type ModuleRole, type MokoshConfig, type MonorepoDetector, type MonorepoLayout, NodeCategory, type NodeMeta, type NodeQuery, type ParallelParsingOption, type PathWithSymbols, type ProposeTagsOptions, type PublicExport, type ResponsibilityGraph, type RiskHotspotEntry, type RiskHotspotsResult, type ScanOptions, type SerializedGraph, type SerializedWorkspaceGraph, type SlimNode, type SlimSerializedGraph, type StaleReference, StructuredTag, type SummarizeOptions, type SymbolCaller, type SymbolImporter, type SymbolMatch, type SymbolPrecision, SymbolTraversalContext, type TestNodeIdentifier, type TraversalOptions, type TraversalVisitor, type TypeEdge, type TypeGraph, type TypeKind, type TypeNode, type TypeQueryResult, WorkspaceGraph, type WorkspaceLayoutPackageSummary, type WorkspaceLayoutSummary, type WorkspacePackage, type WorkspacePackageSummary, type WorkspacePackagesSummary, applyConfig, applyTags, buildApiSurface, buildChangeImpactCache, buildFeatureGraph, buildGraphAtRef, buildResponsibilityGraph, buildTypeGraph, compareBranches, computeGraphHash, computeWorkspaceSourceDigest, configToGraphOptions, createImportMap, createWorkspaceGraph, detectAllEntryPoints, detectEntryPoint, detectFeatures, detectMonorepo, filterGraph, findComplexFunctions, findDuplicates, findRiskHotspots, findSymbol, getAffected, getAllProjectFiles, getCallers, getDependencies, getDependents, getLanguageCoverage, getNodeMeta, hasChurnData, hasCoverageData, hasGeneratedMarker, isChangeImpactCacheValid, isGeneratedPath, loadChangeImpactCache, loadCoverageMap, loadMokoshConfig, loadTokenCacheFromDisk, parseQuery, proposeAffectedTests, proposeTags, queryCallGraph, queryChangeImpact, queryTypeGraph, registerConfigMatcher, registerMonorepoDetector, registerParser, registerTestLibrary, registerTestPattern, resetClassifyRegistries, saveChangeImpactCache, saveTokenCacheToDisk, slimSerialize, summarizeBranchComparison, summarizeWorkspaceLayout, summarizeWorkspacePackages, toMermaid };
|