mcp-software-design 0.1.5 → 0.1.6
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/CHANGELOG.md +6 -0
- package/build/index.js +5 -1
- package/build/smells.js +42 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 25dab4b: `check_smells` no longer reports declarations as duplicated logic: members of `interface`, `type` and `enum` bodies, typed member and parameter lines, fields initialized with a literal or declared with a modifier such as Angular signal inputs, and callback openers whose only argument is the callback (`useEffect(() => {`). Repeated statements, and calls that pass other arguments before a callback, are still reported.
|
|
8
|
+
|
|
3
9
|
## 0.1.5
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/build/index.js
CHANGED
|
@@ -35,7 +35,11 @@ All thresholds are overridable per call on the \`check_smells\` tool.
|
|
|
35
35
|
Duplication skips members of multi-line object and array literals (rows of a
|
|
36
36
|
lookup table are data) and calls or throws whose arguments were all string
|
|
37
37
|
literals (\`lines.push('...')\`, \`throw new Error('...')\`), so message and
|
|
38
|
-
state-name lines do not register.
|
|
38
|
+
state-name lines do not register. It also skips declarations rather than
|
|
39
|
+
logic: members of \`interface\` / \`type\` / \`enum\` bodies, typed member and
|
|
40
|
+
parameter lines (\`depthTest?: boolean;\`), fields initialized with a literal or
|
|
41
|
+
declared with a modifier (\`readonly name = input(undefined);\`), and callback
|
|
42
|
+
openers whose only argument is the callback (\`useEffect(() => {\`).
|
|
39
43
|
`;
|
|
40
44
|
/** Render smells as a compact text report. */
|
|
41
45
|
function renderSmells(smells) {
|
package/build/smells.js
CHANGED
|
@@ -492,25 +492,60 @@ function opensBlock(prefix) {
|
|
|
492
492
|
* Such lines are messages and state names, not repeated logic.
|
|
493
493
|
*/
|
|
494
494
|
const TRIVIAL_CALL = /^(?:return\s+|await\s+|throw\s+new\s+)?[\w$.]+\s*\(\s*[,\s]*\)\s*;?$/;
|
|
495
|
+
/**
|
|
496
|
+
* What a `{` opens: a `type` / `interface` / `enum` body (members are
|
|
497
|
+
* declarations, never duplicated logic), an object literal, or a block.
|
|
498
|
+
*/
|
|
499
|
+
function braceKind(prefix) {
|
|
500
|
+
if (/^(?:export\s+)?(?:declare\s+)?(?:interface|enum|type)\b/.test(prefix.trim()))
|
|
501
|
+
return "declaration";
|
|
502
|
+
return opensBlock(prefix) ? "block" : "literal";
|
|
503
|
+
}
|
|
504
|
+
const MEMBER_MODIFIERS = "(?:(?:readonly|private|protected|public|override|static|declare|abstract)\\s+)";
|
|
505
|
+
/**
|
|
506
|
+
* A member or parameter declaration: `depthTest?: boolean;`, `worldWind: T,`,
|
|
507
|
+
* `[key: string]: any;`. The type part may be a function type but never a
|
|
508
|
+
* block, so a labelled statement or a `default: return x;` is left alone.
|
|
509
|
+
*/
|
|
510
|
+
const TYPED_DECLARATION = new RegExp(`^${MEMBER_MODIFIERS}*(?:\\[[\\w$]+\\s*:\\s*[^\\]]+\\]|[\\w$#]+)\\??\\s*:\\s*([^={}]+?)\\s*[;,]?$`);
|
|
511
|
+
/** A field with an initializer: `enabled = true;`, `readonly name = input<T>(undefined);`. */
|
|
512
|
+
const INITIALIZED_MEMBER = new RegExp(`^${MEMBER_MODIFIERS}+[\\w$#]+\\??\\s*(?::\\s*[^=]+?)?\\s*=\\s*[^;]*;?$`);
|
|
513
|
+
/** A bare field whose initializer is a literal (strings are already blanked): `highlighted = false;`, `label = ;`. */
|
|
514
|
+
const LITERAL_ASSIGNMENT = /^[\w$#]+\s*(?::\s*[^=]+?)?\s*=\s*(?:true|false|null|undefined|-?\d[\d._]*|\[\s*\]|\{\s*\}|new\s+[\w$.]+\(\s*\)|)\s*;?$/;
|
|
515
|
+
/**
|
|
516
|
+
* A callback opener whose only argument is the callback: `useEffect(() => {`,
|
|
517
|
+
* `effect((onCleanup) => {`, `.then(async (layer) => {`. The logic is on the
|
|
518
|
+
* lines that follow; a call with other arguments before the callback still counts.
|
|
519
|
+
*/
|
|
520
|
+
const CALLBACK_OPENER = /^(?:(?:return|await)\s+|(?:const|let|var)\s+[\w$]+\s*=\s*)?[\w$.]+\s*\(\s*(?:async\s+)?(?:\(\s*[\w$,\s]*\)|[\w$]+)\s*=>\s*\{\s*$/;
|
|
521
|
+
/** True for lines that declare rather than do: members, parameters, literal-initialized fields, callback openers. */
|
|
522
|
+
function isDeclarationLine(trimmed) {
|
|
523
|
+
if (/^(?:case|default)\b/.test(trimmed))
|
|
524
|
+
return false;
|
|
525
|
+
const typed = trimmed.match(TYPED_DECLARATION);
|
|
526
|
+
if (typed && (!typed[1].includes("(") || typed[1].includes("=>")))
|
|
527
|
+
return true;
|
|
528
|
+
return INITIALIZED_MEMBER.test(trimmed) || LITERAL_ASSIGNMENT.test(trimmed) || CALLBACK_OPENER.test(trimmed);
|
|
529
|
+
}
|
|
495
530
|
/** Detect duplicated non-trivial lines (a DRY proxy). */
|
|
496
531
|
function detectDuplication(sanitized, options) {
|
|
497
532
|
const lineCounts = new Map();
|
|
498
|
-
// Innermost open `{`/`[` per line
|
|
499
|
-
//
|
|
533
|
+
// Innermost open `{`/`[` per line: rows of a lookup table and members of
|
|
534
|
+
// a type declaration are data and shape, not duplicated logic.
|
|
500
535
|
const bracketStack = [];
|
|
501
536
|
for (let lineIndex = 0; lineIndex < sanitized.length; lineIndex++) {
|
|
502
537
|
const line = sanitized[lineIndex];
|
|
503
|
-
const
|
|
538
|
+
const enclosing = bracketStack[bracketStack.length - 1] ?? "block";
|
|
504
539
|
for (let charIndex = 0; charIndex < line.length; charIndex++) {
|
|
505
540
|
const char = line[charIndex];
|
|
506
541
|
if (char === "{")
|
|
507
|
-
bracketStack.push(
|
|
542
|
+
bracketStack.push(braceKind(line.slice(0, charIndex)));
|
|
508
543
|
else if (char === "[")
|
|
509
|
-
bracketStack.push(
|
|
544
|
+
bracketStack.push("literal");
|
|
510
545
|
else if (char === "}" || char === "]")
|
|
511
546
|
bracketStack.pop();
|
|
512
547
|
}
|
|
513
|
-
if (
|
|
548
|
+
if (enclosing !== "block")
|
|
514
549
|
continue;
|
|
515
550
|
const trimmed = line.trim();
|
|
516
551
|
if (trimmed.length < 15)
|
|
@@ -519,7 +554,7 @@ function detectDuplication(sanitized, options) {
|
|
|
519
554
|
continue; // pure punctuation
|
|
520
555
|
if (/^(import|from|export|package|using|#include|@)/.test(trimmed))
|
|
521
556
|
continue;
|
|
522
|
-
if (TRIVIAL_CALL.test(trimmed))
|
|
557
|
+
if (TRIVIAL_CALL.test(trimmed) || isDeclarationLine(trimmed))
|
|
523
558
|
continue;
|
|
524
559
|
const record = lineCounts.get(trimmed) ?? { count: 0, firstLine: lineIndex + 1 };
|
|
525
560
|
record.count++;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-software-design",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "MCP server that teaches and applies software-design guidance: SOLID/OOP/DRY principles, the 23 GoF design patterns, pattern scaffolding, and heuristic code-smell detection.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|