@solid-stack/mason 1.0.23 → 1.0.24

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.
@@ -51,5 +51,15 @@ export declare function discoverAllFeatures(projectRoot: string): Array<{
51
51
  name: string;
52
52
  dir: string;
53
53
  }>;
54
+ export interface ClassMethodInfo {
55
+ name: string;
56
+ isAsync: boolean;
57
+ isPublic: boolean;
58
+ line: number;
59
+ code: string;
60
+ }
61
+ export declare function extractClassMethods(content: string, className: string): ClassMethodInfo[] | null;
62
+ export declare function checkUseCaseShape(featureName: string, filePath: string, relativePath: string, content: string, addViolation: (v: Violation) => void): void;
63
+ export declare function checkServiceShape(featureName: string, filePath: string, relativePath: string, content: string, addViolation: (v: Violation) => void): void;
54
64
  export declare function lintCodebase(options?: LintOptions): LintSummary;
55
65
  //# sourceMappingURL=lintEngine.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lintEngine.d.ts","sourceRoot":"","sources":["../../src/core/lintEngine.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,sBAAsB,EAEtB,yBAAyB,EACzB,SAAS,EACT,WAAW,EACX,WAAW,EAEX,SAAS,EACV,MAAM,kBAAkB,CAAC;AAG1B,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAgB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxC,SAAgB,UAAU,EAAE,OAAO,CAAC;IAEpC,YAAY,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,UAAU,GAAE,OAAe,EAKhF;CACF;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,WAAW,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IACpC,eAAe,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,kBAAkB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,YAAY,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACtC;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAa1E;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAyBxE;AAED,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;CAAE,CA2BjE;AAED,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,GAClB,UAAU,CAmGZ;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,EAAE,CAsB1E;AAED,qBAAa,sBAAsB;IACjC,OAAO,CAAC,KAAK,CAA6D;IAEnE,OAAO,CAAC,QAAQ,EAAE,yBAAyB,GAAG;QACnD,YAAY,EAAE,OAAO,CAAC;QACtB,aAAa,CAAC,EAAE,yBAAyB,GAAG,SAAS,CAAC;QACtD,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;KACnC,CAwBA;IAEM,QAAQ,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,yBAAyB,CAAA;KAAE,CAAC,CAQ1F;IAEM,gBAAgB,IAAI,sBAAsB,EAAE,CAwBlD;IAED,OAAO,CAAC,aAAa;CAqCtB;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,GAClB,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAYtC;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,WAAgB,GAAG,WAAW,CAgkBnE"}
1
+ {"version":3,"file":"lintEngine.d.ts","sourceRoot":"","sources":["../../src/core/lintEngine.ts"],"names":[],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -4439,6 +4439,350 @@ function discoverAllFeatures(projectRoot) {
4439
4439
  dir: path2__default.default.join(featuresBase, e.name)
4440
4440
  })).sort((a, b) => a.name.localeCompare(b.name));
4441
4441
  }
4442
+ function extractClassMethods(content, className) {
4443
+ let clean = "";
4444
+ let inLineComment = false;
4445
+ let inBlockComment = false;
4446
+ let inString = null;
4447
+ let escaped = false;
4448
+ for (let i = 0; i < content.length; i++) {
4449
+ const c = content[i];
4450
+ const next = content[i + 1];
4451
+ if (inLineComment) {
4452
+ if (c === "\n") {
4453
+ inLineComment = false;
4454
+ clean += "\n";
4455
+ } else {
4456
+ clean += " ";
4457
+ }
4458
+ continue;
4459
+ }
4460
+ if (inBlockComment) {
4461
+ if (c === "*" && next === "/") {
4462
+ inBlockComment = false;
4463
+ clean += " ";
4464
+ i++;
4465
+ } else {
4466
+ clean += c === "\n" ? "\n" : " ";
4467
+ }
4468
+ continue;
4469
+ }
4470
+ if (inString) {
4471
+ clean += c === "\n" ? "\n" : " ";
4472
+ if (escaped) {
4473
+ escaped = false;
4474
+ } else if (c === "\\") {
4475
+ escaped = true;
4476
+ } else if (c === inString) {
4477
+ inString = null;
4478
+ }
4479
+ continue;
4480
+ }
4481
+ if (c === "/" && next === "/") {
4482
+ inLineComment = true;
4483
+ clean += " ";
4484
+ i++;
4485
+ continue;
4486
+ }
4487
+ if (c === "/" && next === "*") {
4488
+ inBlockComment = true;
4489
+ clean += " ";
4490
+ i++;
4491
+ continue;
4492
+ }
4493
+ if (c === '"' || c === "'" || c === "`") {
4494
+ inString = c;
4495
+ clean += " ";
4496
+ continue;
4497
+ }
4498
+ clean += c;
4499
+ }
4500
+ const classDeclRegex = new RegExp(`(?:export\\s+(?:default\\s+)?)?class\\s+${className}\\b[^{]*\\{`);
4501
+ const match = classDeclRegex.exec(clean);
4502
+ if (!match) return null;
4503
+ const classBodyStart = match.index + match[0].length - 1;
4504
+ const methods = [];
4505
+ let depth = 1;
4506
+ let parenDepth = 0;
4507
+ let atMemberStart = true;
4508
+ const methodRegex = /^\s*(?:@[a-zA-Z0-9_$]+(?:\([^)]*\))?\s+)*(?:(public|private|protected|static)\s+)*(?:(async)\s+)?(?:(get|set)\s+)?(#?[a-zA-Z0-9_$]+)\s*(?:<[^>]+>)?\s*\(/;
4509
+ const arrowRegex = /^\s*(?:(public|private|protected|static)\s+)?(#?[a-zA-Z0-9_$]+)\s*=\s*(?:async\s*)?\(/;
4510
+ for (let i = classBodyStart + 1; i < clean.length && depth > 0; i++) {
4511
+ const c = clean[i];
4512
+ if (c === "{") {
4513
+ depth++;
4514
+ atMemberStart = false;
4515
+ continue;
4516
+ }
4517
+ if (c === "}") {
4518
+ depth--;
4519
+ if (depth === 1) {
4520
+ atMemberStart = true;
4521
+ }
4522
+ continue;
4523
+ }
4524
+ if (depth === 1) {
4525
+ if (c === ";") {
4526
+ atMemberStart = true;
4527
+ parenDepth = 0;
4528
+ continue;
4529
+ }
4530
+ if (c === "(") {
4531
+ parenDepth++;
4532
+ } else if (c === ")") {
4533
+ parenDepth = Math.max(0, parenDepth - 1);
4534
+ }
4535
+ if (atMemberStart && parenDepth === 0) {
4536
+ if (/\s/.test(c)) continue;
4537
+ const slice = clean.slice(i);
4538
+ const methodMatch = slice.match(methodRegex);
4539
+ if (methodMatch) {
4540
+ const fullMatch = methodMatch[0];
4541
+ const beforeParen = fullMatch.slice(0, fullMatch.lastIndexOf("(")).trim();
4542
+ const tokens = beforeParen.split(/\s+/).filter(Boolean);
4543
+ const rawName = tokens[tokens.length - 1] || "";
4544
+ const methodName = rawName.replace(/<.*$/, "");
4545
+ const controlKeywords = ["if", "for", "while", "switch", "catch", "with"];
4546
+ if (!controlKeywords.includes(methodName)) {
4547
+ const isPrivate = tokens.includes("private") || tokens.includes("protected") || methodName.startsWith("#");
4548
+ const isAsync = tokens.includes("async");
4549
+ const isPublic = !isPrivate;
4550
+ const lineNum = content.slice(0, i).split("\n").length;
4551
+ const lineCode = content.split("\n")[lineNum - 1]?.trim() || "";
4552
+ methods.push({
4553
+ name: methodName,
4554
+ isAsync,
4555
+ isPublic,
4556
+ line: lineNum,
4557
+ code: lineCode
4558
+ });
4559
+ i += fullMatch.length - 1;
4560
+ parenDepth = 1;
4561
+ atMemberStart = false;
4562
+ continue;
4563
+ }
4564
+ }
4565
+ const arrowMatch = slice.match(arrowRegex);
4566
+ if (arrowMatch) {
4567
+ const fullMatch = arrowMatch[0];
4568
+ const beforeEquals = fullMatch.split("=")[0].trim();
4569
+ const tokens = beforeEquals.split(/\s+/).filter(Boolean);
4570
+ const rawName = tokens[tokens.length - 1] || "";
4571
+ const isPrivate = tokens.includes("private") || tokens.includes("protected") || rawName.startsWith("#");
4572
+ const isAsync = fullMatch.includes("async");
4573
+ const isPublic = !isPrivate;
4574
+ const lineNum = content.slice(0, i).split("\n").length;
4575
+ const lineCode = content.split("\n")[lineNum - 1]?.trim() || "";
4576
+ methods.push({
4577
+ name: rawName,
4578
+ isAsync,
4579
+ isPublic,
4580
+ line: lineNum,
4581
+ code: lineCode
4582
+ });
4583
+ i += fullMatch.length - 1;
4584
+ parenDepth = 1;
4585
+ atMemberStart = false;
4586
+ continue;
4587
+ }
4588
+ atMemberStart = false;
4589
+ }
4590
+ }
4591
+ }
4592
+ return methods;
4593
+ }
4594
+ function checkUseCaseShape(featureName, filePath, relativePath, content, addViolation) {
4595
+ const fileName = path2__default.default.basename(filePath);
4596
+ const lines = content.split(/\r?\n/);
4597
+ const classMatch = content.match(/(?:export\s+(?:default\s+)?)?class\s+([A-Za-z0-9_]+)/);
4598
+ if (!classMatch) {
4599
+ addViolation({
4600
+ feature: featureName,
4601
+ layer: "useCases",
4602
+ file: filePath,
4603
+ relativePath,
4604
+ line: 1,
4605
+ category: "Use Case Shape Violation",
4606
+ code: lines[0]?.trim() || "",
4607
+ ruleId: "U_SHAPE_CLASS",
4608
+ severity: "error",
4609
+ advice: `Use case file '${fileName}' must define and export a class.`,
4610
+ fixExample: {
4611
+ bad: "// Missing class definition",
4612
+ good: `export class ${fileName.replace(/\.(ts|js)$/, "")} { ... }`
4613
+ }
4614
+ });
4615
+ return;
4616
+ }
4617
+ const className = classMatch[1];
4618
+ let classLine = 1;
4619
+ for (let i = 0; i < lines.length; i++) {
4620
+ if (new RegExp(`(?:export\\s+(?:default\\s+)?)?class\\s+${className}\\b`).test(lines[i])) {
4621
+ classLine = i + 1;
4622
+ break;
4623
+ }
4624
+ }
4625
+ if (!/@MakeInjectable\b/.test(content)) {
4626
+ addViolation({
4627
+ feature: featureName,
4628
+ layer: "useCases",
4629
+ file: filePath,
4630
+ relativePath,
4631
+ line: classLine,
4632
+ category: "Use Case Shape Violation",
4633
+ code: lines[classLine - 1]?.trim() || `class ${className}`,
4634
+ ruleId: "U_SHAPE_DECORATOR",
4635
+ severity: "error",
4636
+ advice: `Use case class '${className}' in '${fileName}' must be decorated with @MakeInjectable.`,
4637
+ fixExample: {
4638
+ bad: lines[classLine - 1]?.trim() || `class ${className}`,
4639
+ good: `@MakeInjectable
4640
+ export class ${className} { ... }`
4641
+ }
4642
+ });
4643
+ }
4644
+ const hasAsyncExecute = /(?:public\s+)?async\s+execute\s*(?:<[^>]+>)?\s*\(/.test(content);
4645
+ if (!hasAsyncExecute) {
4646
+ const hasSyncExecute = /(?:public\s+)?execute\s*(?:<[^>]+>)?\s*\(/.test(content);
4647
+ addViolation({
4648
+ feature: featureName,
4649
+ layer: "useCases",
4650
+ file: filePath,
4651
+ relativePath,
4652
+ line: classLine,
4653
+ category: "Use Case Shape Violation",
4654
+ code: lines[classLine - 1]?.trim() || `class ${className}`,
4655
+ ruleId: "U_SHAPE_EXECUTE",
4656
+ severity: "error",
4657
+ advice: hasSyncExecute ? `Use case class '${className}' in '${fileName}' must have an 'async execute' method (found synchronous 'execute').` : `Use case class '${className}' in '${fileName}' must implement an 'async execute(...)' method.`,
4658
+ fixExample: {
4659
+ bad: hasSyncExecute ? "execute(props: ...) { ... }" : `class ${className} { ... }`,
4660
+ good: "async execute(props: ...): Promise<...> { ... }"
4661
+ }
4662
+ });
4663
+ }
4664
+ const classMethods = extractClassMethods(content, className);
4665
+ if (classMethods) {
4666
+ const extraPublicMethods = classMethods.filter(
4667
+ (m) => m.isPublic && m.name !== "constructor" && m.name !== "execute"
4668
+ );
4669
+ for (const m of extraPublicMethods) {
4670
+ addViolation({
4671
+ feature: featureName,
4672
+ layer: "useCases",
4673
+ file: filePath,
4674
+ relativePath,
4675
+ line: m.line,
4676
+ category: "Use Case Shape Violation",
4677
+ code: m.code || `public ${m.name}(...)`,
4678
+ ruleId: "U_SHAPE_ONLY_EXECUTE",
4679
+ severity: "error",
4680
+ advice: `Use case class '${className}' in '${fileName}' has disallowed public method '${m.name}'. The only allowed public methods are constructor and async execute. Extract extra methods into their own use cases, or make them private.`,
4681
+ fixExample: {
4682
+ bad: m.code || `${m.name}(...) { ... }`,
4683
+ good: `// Extract '${m.name}' into its own dedicated Use Case, or make it private if it is internal helper logic:
4684
+ private ${m.name}(...) { ... }`
4685
+ }
4686
+ });
4687
+ }
4688
+ }
4689
+ }
4690
+ function checkServiceShape(featureName, filePath, relativePath, content, addViolation) {
4691
+ const fileName = path2__default.default.basename(filePath);
4692
+ const lines = content.split(/\r?\n/);
4693
+ const classMatch = content.match(/(?:export\s+(?:default\s+)?)?class\s+([A-Za-z0-9_]+)/);
4694
+ if (!classMatch) {
4695
+ addViolation({
4696
+ feature: featureName,
4697
+ layer: "services",
4698
+ file: filePath,
4699
+ relativePath,
4700
+ line: 1,
4701
+ category: "Service Shape Violation",
4702
+ code: lines[0]?.trim() || "",
4703
+ ruleId: "S_SHAPE_CLASS",
4704
+ severity: "error",
4705
+ advice: `Service file '${fileName}' must define and export a class.`,
4706
+ fixExample: {
4707
+ bad: "// Missing class definition",
4708
+ good: `export class ${fileName.replace(/\.(ts|js)$/, "")} { ... }`
4709
+ }
4710
+ });
4711
+ return;
4712
+ }
4713
+ const className = classMatch[1];
4714
+ let classLine = 1;
4715
+ for (let i = 0; i < lines.length; i++) {
4716
+ if (new RegExp(`(?:export\\s+(?:default\\s+)?)?class\\s+${className}\\b`).test(lines[i])) {
4717
+ classLine = i + 1;
4718
+ break;
4719
+ }
4720
+ }
4721
+ if (!/@MakeInjectable\b/.test(content)) {
4722
+ addViolation({
4723
+ feature: featureName,
4724
+ layer: "services",
4725
+ file: filePath,
4726
+ relativePath,
4727
+ line: classLine,
4728
+ category: "Service Shape Violation",
4729
+ code: lines[classLine - 1]?.trim() || `class ${className}`,
4730
+ ruleId: "S_SHAPE_DECORATOR",
4731
+ severity: "error",
4732
+ advice: `Service class '${className}' in '${fileName}' must be decorated with @MakeInjectable.`,
4733
+ fixExample: {
4734
+ bad: lines[classLine - 1]?.trim() || `class ${className}`,
4735
+ good: `@MakeInjectable
4736
+ export class ${className} { ... }`
4737
+ }
4738
+ });
4739
+ }
4740
+ const hasAsyncExecute = /(?:public\s+)?async\s+execute\s*(?:<[^>]+>)?\s*\(/.test(content);
4741
+ if (!hasAsyncExecute) {
4742
+ const hasSyncExecute = /(?:public\s+)?execute\s*(?:<[^>]+>)?\s*\(/.test(content);
4743
+ addViolation({
4744
+ feature: featureName,
4745
+ layer: "services",
4746
+ file: filePath,
4747
+ relativePath,
4748
+ line: classLine,
4749
+ category: "Service Shape Violation",
4750
+ code: lines[classLine - 1]?.trim() || `class ${className}`,
4751
+ ruleId: "S_SHAPE_EXECUTE",
4752
+ severity: "error",
4753
+ advice: hasSyncExecute ? `Service class '${className}' in '${fileName}' must have an 'async execute' method (found synchronous 'execute').` : `Service class '${className}' in '${fileName}' must implement an 'async execute(...)' method.`,
4754
+ fixExample: {
4755
+ bad: hasSyncExecute ? "execute(props: ...) { ... }" : `class ${className} { ... }`,
4756
+ good: "async execute(props: ...): Promise<...> { ... }"
4757
+ }
4758
+ });
4759
+ }
4760
+ const classMethods = extractClassMethods(content, className);
4761
+ if (classMethods) {
4762
+ const extraPublicMethods = classMethods.filter(
4763
+ (m) => m.isPublic && m.name !== "constructor" && m.name !== "execute"
4764
+ );
4765
+ for (const m of extraPublicMethods) {
4766
+ addViolation({
4767
+ feature: featureName,
4768
+ layer: "services",
4769
+ file: filePath,
4770
+ relativePath,
4771
+ line: m.line,
4772
+ category: "Service Shape Violation",
4773
+ code: m.code || `public ${m.name}(...)`,
4774
+ ruleId: "S_SHAPE_ONLY_EXECUTE",
4775
+ severity: "error",
4776
+ advice: `Service class '${className}' in '${fileName}' has disallowed public method '${m.name}'. The only allowed public methods are constructor and async execute. If a service has multiple public methods, those should be on their own services instead of being cramped in a single service.`,
4777
+ fixExample: {
4778
+ bad: m.code || `${m.name}(...) { ... }`,
4779
+ good: `// Extract '${m.name}' into its own dedicated Service, or make it private if it is internal helper logic:
4780
+ private ${m.name}(...) { ... }`
4781
+ }
4782
+ });
4783
+ }
4784
+ }
4785
+ }
4442
4786
  function lintCodebase(options = {}) {
4443
4787
  const startTime = Date.now();
4444
4788
  const projectRoot = options.projectRoot || findProjectRoot(options.targetDir || process.cwd());
@@ -4482,6 +4826,14 @@ Code: ${v.code}`;
4482
4826
  if (!layer) continue;
4483
4827
  const content = fs4__default.default.readFileSync(file, "utf-8");
4484
4828
  const statements = extractImportStatements(content);
4829
+ const baseName = path2__default.default.basename(file);
4830
+ const isBarrel = baseName === "index.ts" || baseName === "index.js";
4831
+ if (layer === "useCases" && !isBarrel) {
4832
+ checkUseCaseShape(feat.name, file, relPath, content, addViolation);
4833
+ }
4834
+ if (layer === "services" && !isBarrel) {
4835
+ checkServiceShape(feat.name, file, relPath, content, addViolation);
4836
+ }
4485
4837
  if (layer === "domain") {
4486
4838
  const lines = content.split(/\r?\n/);
4487
4839
  for (let i = 0; i < lines.length; i++) {
@@ -4892,6 +5244,24 @@ Code: ${v.code}`;
4892
5244
  }
4893
5245
  });
4894
5246
  }
5247
+ if (target.targetLayer === "services") {
5248
+ addViolation({
5249
+ feature: feat.name,
5250
+ layer,
5251
+ file,
5252
+ relativePath: relPath,
5253
+ line: stmt.line,
5254
+ category: "Presentation Layer Violation",
5255
+ code: stmt.rawCode,
5256
+ ruleId: "P4_DIRECT_SERVICE",
5257
+ severity: "error",
5258
+ advice: "Presentation handlers must not depend on services directly. They must use use cases, and use cases will then depend on the service.",
5259
+ fixExample: {
5260
+ bad: stmt.rawCode,
5261
+ good: "// Inject a Use Case via static deps instead of a Service."
5262
+ }
5263
+ });
5264
+ }
4895
5265
  }
4896
5266
  }
4897
5267
  }