eslint-plugin-unslop 0.5.2 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,11 +42,12 @@ export default [
42
42
  ]
43
43
  ```
44
44
 
45
- Architecture rules (`import-control`, `export-control`, `no-false-sharing`, `no-single-use-constants`) require a reachable `tsconfig.json`. Set `compilerOptions.rootDir`, and if you use aliases, configure `compilerOptions.paths`.
45
+ Architecture rules (`import-control`, `no-whitebox-testing`, `export-control`, `no-false-sharing`, `no-single-use-constants`) require a reachable `tsconfig.json`. Set `compilerOptions.rootDir`, and if you use aliases, configure `compilerOptions.paths`.
46
46
 
47
47
  | Rule | What it does |
48
48
  | -------------------------------- | ---------------------------------------------------------------------- |
49
49
  | `unslop/import-control` | Enforces module boundaries and forbids local namespace imports |
50
+ | `unslop/no-whitebox-testing` | Keeps tests on module entrypoints instead of same-folder internals |
50
51
  | `unslop/export-control` | Restricts export patterns and forbids `export *` in module entrypoints |
51
52
  | `unslop/no-false-sharing` | Flags shared entrypoint symbols with fewer than two consumer groups |
52
53
  | `unslop/no-single-use-constants` | Flags module-scope constants used once or never across the project |
@@ -71,6 +72,7 @@ All architecture rules read from `settings.unslop.architecture`. Each key is a m
71
72
  {
72
73
  imports?: string[] // module matchers this module may import from; '*' allows all
73
74
  exports?: string[] // regex patterns symbols exported from index.ts/types.ts must match
75
+ entrypoints?: string[] // public files allowed for external and test imports
74
76
  shared?: boolean // marks module as shared; enables no-false-sharing
75
77
  }
76
78
  ```
@@ -92,6 +94,12 @@ Deny-by-default for cross-module imports, so forgetting to declare a dependency
92
94
 
93
95
  Alias imports are resolved via `compilerOptions.paths` from `tsconfig.json`.
94
96
 
97
+ ### `unslop/no-whitebox-testing`
98
+
99
+ Keeps test files black-boxed. When a recognized test file lives beside a module's implementation, it must import that module through its public entrypoint (`.`, `./index`, or a configured `entrypoints` file) instead of reaching into sibling files like `./model.ts`.
100
+
101
+ This rule only checks recognized test filenames (`*.test.*`, `*.spec.*`, `*.*-test.*`, `*.*-spec.*`). Child submodule imports and cross-module imports are left to `unslop/import-control`.
102
+
95
103
  ### `unslop/export-control`
96
104
 
97
105
  The customs declaration form for the other direction: what are you actually exporting from your module's public entrypoints?
package/dist/index.cjs CHANGED
@@ -37,7 +37,7 @@ module.exports = __toCommonJS(index_exports);
37
37
  // package.json
38
38
  var package_default = {
39
39
  name: "eslint-plugin-unslop",
40
- version: "0.5.2",
40
+ version: "0.5.3",
41
41
  description: "ESLint plugin with rules for reducing AI-generated code smells",
42
42
  repository: {
43
43
  type: "git",
@@ -721,7 +721,7 @@ function reportUnsharedSymbols(context, node, options) {
721
721
  for (const symbol of exportedSymbols) {
722
722
  const consumerGroups = consumerGroupsBySymbol.get(symbol.exportedName);
723
723
  if (consumerGroups === void 0) continue;
724
- if (consumerGroups.size >= MIN_CONSUMER_GROUPS) continue;
724
+ if (consumerGroups.size >= 2) continue;
725
725
  context.report({
726
726
  node,
727
727
  messageId: "notTrulyShared",
@@ -929,7 +929,6 @@ function getSingleConsumerGroup(groups) {
929
929
  const [single] = [...groups];
930
930
  return ` (group: ${single})`;
931
931
  }
932
- var MIN_CONSUMER_GROUPS = 2;
933
932
 
934
933
  // src/rules/read-friendly-order/ast-utils.ts
935
934
  function getDeclName(node) {
@@ -1753,6 +1752,70 @@ function importPatternMatches(pattern, target) {
1753
1752
  return patternSegs.every((seg, i) => seg === "*" || seg === targetSegs[i]);
1754
1753
  }
1755
1754
 
1755
+ // src/rules/no-whitebox-testing/index.ts
1756
+ var import_node_path6 = __toESM(require("path"), 1);
1757
+ var no_whitebox_testing_default = {
1758
+ meta: {
1759
+ type: "problem",
1760
+ docs: {
1761
+ description: "Require tests to import a module through its public entrypoint",
1762
+ recommended: false
1763
+ },
1764
+ schema: [],
1765
+ messages: {
1766
+ usePublicEntrypoint: "White-box test import denied: tests must import this module through its public entrypoint (offending import: {{specifier}})."
1767
+ }
1768
+ },
1769
+ create(context) {
1770
+ if (!isRecognizedTestFile(context.filename)) return {};
1771
+ const state = getArchitectureRuleState(context);
1772
+ if (state === void 0) return {};
1773
+ return {
1774
+ ImportDeclaration(node) {
1775
+ checkImportDeclaration(context, node, state);
1776
+ }
1777
+ };
1778
+ }
1779
+ };
1780
+ function checkImportDeclaration(context, node, state) {
1781
+ const resolvedImport = resolveImport(node, state);
1782
+ if (resolvedImport === void 0) return;
1783
+ if (resolvedImport.targetModule.instance !== state.moduleMatch.instance) return;
1784
+ if (!isSameDirectoryImport(state.filename, resolvedImport.targetFile)) return;
1785
+ if (isAllowedModuleEntrypoint2(resolvedImport.targetFile, state.moduleMatch.policy.entrypoints))
1786
+ return;
1787
+ context.report({
1788
+ node,
1789
+ messageId: "usePublicEntrypoint",
1790
+ data: { specifier: resolvedImport.specifier }
1791
+ });
1792
+ }
1793
+ function resolveImport(node, state) {
1794
+ const specifier = getSpecifier2(node);
1795
+ if (specifier === void 0) return void 0;
1796
+ const resolvedTarget = resolveImportTarget(state.filename, state.policy.projectContext, specifier);
1797
+ if (resolvedTarget === void 0) return void 0;
1798
+ const targetFile = normalizeResolvedPath2(resolvedTarget);
1799
+ const targetModule = matchFileToArchitectureModule(targetFile, state.policy);
1800
+ if (targetModule === void 0) return void 0;
1801
+ return { specifier, targetFile, targetModule };
1802
+ }
1803
+ function getSpecifier2(node) {
1804
+ const value = node.source.value;
1805
+ return typeof value === "string" ? value : void 0;
1806
+ }
1807
+ function isRecognizedTestFile(filename) {
1808
+ if (filename.length === 0) return false;
1809
+ const basename = import_node_path6.default.basename(filename);
1810
+ return /^.+\.test\..+$/.test(basename) || /^.+\.spec\..+$/.test(basename) || /^.+\.[^.]+-test\..+$/.test(basename) || /^.+\.[^.]+-spec\..+$/.test(basename);
1811
+ }
1812
+ function isSameDirectoryImport(importerFile, targetFile) {
1813
+ return isSamePath(import_node_path6.default.dirname(importerFile), import_node_path6.default.dirname(targetFile));
1814
+ }
1815
+ function isAllowedModuleEntrypoint2(targetFile, entrypoints) {
1816
+ return entrypoints.includes(import_node_path6.default.basename(targetFile));
1817
+ }
1818
+
1756
1819
  // src/rules/export-control/index.ts
1757
1820
  var export_control_default = {
1758
1821
  meta: {
@@ -2043,6 +2106,7 @@ var rules_default = {
2043
2106
  "no-false-sharing": no_false_sharing_default,
2044
2107
  "read-friendly-order": read_friendly_order_default,
2045
2108
  "import-control": import_control_default,
2109
+ "no-whitebox-testing": no_whitebox_testing_default,
2046
2110
  "export-control": export_control_default,
2047
2111
  "no-single-use-constants": no_single_use_constants_default
2048
2112
  };
@@ -2071,6 +2135,7 @@ var full = {
2071
2135
  "unslop/no-special-unicode": "error",
2072
2136
  "unslop/no-unicode-escape": "error",
2073
2137
  "unslop/import-control": "error",
2138
+ "unslop/no-whitebox-testing": "error",
2074
2139
  "unslop/export-control": "error",
2075
2140
  "unslop/no-false-sharing": "error",
2076
2141
  "unslop/no-single-use-constants": "error",