@tsfpp/eslint-config 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @tsfpp/eslint-config/layered
3
+ *
4
+ * TSF++ ESLint profile enforcing Rule 11.5 (layer boundaries — functional core,
5
+ * imperative shell) and Rule 11.6 (import purity).
6
+ *
7
+ * Rule references in this file are to CODING_STANDARD.md.
8
+ *
9
+ * Why a separate profile: the base config constrains how a *file* is written.
10
+ * These rules constrain how files may *depend on each other*, which is what
11
+ * actually keeps the axioms true at program scale — a single outward import
12
+ * makes every function in a module effectful no matter how each was written.
13
+ *
14
+ * Usage:
15
+ * // eslint.config.js
16
+ * import tsfppLayered from '@tsfpp/eslint-config/layered'
17
+ * export default tsfppLayered
18
+ *
19
+ * The path globs below assume the conventional layout:
20
+ *
21
+ * src/core/ pure domain — types, ADTs, decisions
22
+ * src/use-case/ orchestration + ports
23
+ * src/boundary/ parsing and error mapping
24
+ * src/adapters/ effects: db, http clients, clock, entropy
25
+ * src/handlers/ transport entry points
26
+ * src/main.ts composition root
27
+ *
28
+ * Layer *names* are conventional; the inward-only *direction* is normative
29
+ * (Rule 11.5). Projects using a different layout should copy this file and
30
+ * adjust the globs — the shape of the restriction is the part worth keeping.
31
+ */
32
+ import type { Linter } from 'eslint';
33
+ declare const config: Linter.Config[];
34
+ export default config;
35
+ //# sourceMappingURL=layered.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layered.d.ts","sourceRoot":"","sources":["../layered.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AA6BpC,QAAA,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,EA4D1B,CAAA;AAED,eAAe,MAAM,CAAA"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @tsfpp/eslint-config/layered
3
+ *
4
+ * TSF++ ESLint profile enforcing Rule 11.5 (layer boundaries — functional core,
5
+ * imperative shell) and Rule 11.6 (import purity).
6
+ *
7
+ * Rule references in this file are to CODING_STANDARD.md.
8
+ *
9
+ * Why a separate profile: the base config constrains how a *file* is written.
10
+ * These rules constrain how files may *depend on each other*, which is what
11
+ * actually keeps the axioms true at program scale — a single outward import
12
+ * makes every function in a module effectful no matter how each was written.
13
+ *
14
+ * Usage:
15
+ * // eslint.config.js
16
+ * import tsfppLayered from '@tsfpp/eslint-config/layered'
17
+ * export default tsfppLayered
18
+ *
19
+ * The path globs below assume the conventional layout:
20
+ *
21
+ * src/core/ pure domain — types, ADTs, decisions
22
+ * src/use-case/ orchestration + ports
23
+ * src/boundary/ parsing and error mapping
24
+ * src/adapters/ effects: db, http clients, clock, entropy
25
+ * src/handlers/ transport entry points
26
+ * src/main.ts composition root
27
+ *
28
+ * Layer *names* are conventional; the inward-only *direction* is normative
29
+ * (Rule 11.5). Projects using a different layout should copy this file and
30
+ * adjust the globs — the shape of the restriction is the part worth keeping.
31
+ */
32
+ /** Modules a pure core may never reach for. */
33
+ const OUTWARD_FROM_CORE = [
34
+ '**/adapters/**',
35
+ '**/infra/**',
36
+ '**/handlers/**',
37
+ '**/boundary/**',
38
+ '**/use-case/**',
39
+ 'node:*',
40
+ 'express',
41
+ 'fastify',
42
+ 'koa',
43
+ 'next/*',
44
+ 'react',
45
+ 'react-dom',
46
+ ];
47
+ /** Modules a use case may never reach for (it may use the core). */
48
+ const OUTWARD_FROM_USE_CASE = [
49
+ '**/adapters/**',
50
+ '**/infra/**',
51
+ '**/handlers/**',
52
+ 'node:*',
53
+ 'express',
54
+ 'fastify',
55
+ 'koa',
56
+ ];
57
+ const config = [
58
+ // ── Rule 11.5 — the core is pure: nothing peripheral, nothing transport-shaped
59
+ {
60
+ files: ['**/core/**/*.ts', '**/domain/**/*.ts'],
61
+ rules: {
62
+ 'no-restricted-imports': ['error', {
63
+ patterns: [{
64
+ group: OUTWARD_FROM_CORE,
65
+ message: 'TSF++ Rule 11.5: the core must not import adapters, transports, or node builtins. ' +
66
+ 'Define a port (a function type) in the core and inject an implementation at the composition root (Rule 6.5).',
67
+ }],
68
+ }],
69
+ },
70
+ },
71
+ // ── Rule 11.5 — use cases orchestrate the core; they do not perform effects
72
+ {
73
+ files: ['**/use-case/**/*.ts', '**/use-cases/**/*.ts', '**/application/**/*.ts'],
74
+ rules: {
75
+ 'no-restricted-imports': ['error', {
76
+ patterns: [{
77
+ group: OUTWARD_FROM_USE_CASE,
78
+ message: 'TSF++ Rule 11.5: a use case may depend on the core and on ports, never on a concrete adapter. ' +
79
+ 'Accept the port through Deps (Rule 6.5).',
80
+ }],
81
+ }],
82
+ },
83
+ },
84
+ // ── Rule 11.6 — import purity: a module may only define bindings
85
+ //
86
+ // Top-level ambient reads are worse than the in-function reads Rule 4.6
87
+ // forbids: they run before any caller exists, in module-graph order, and can
88
+ // be neither injected nor stubbed nor deferred.
89
+ {
90
+ files: ['**/*.ts'],
91
+ ignores: ['**/main.ts', '**/index.ts', '**/*.config.ts', '**/*.test.ts'],
92
+ rules: {
93
+ 'no-restricted-syntax': ['error',
94
+ {
95
+ selector: ":matches(Program > VariableDeclaration, Program > ExportNamedDeclaration > VariableDeclaration) > VariableDeclarator > NewExpression[callee.name='Date']",
96
+ message: 'TSF++ Rule 11.6: reading the clock at import time. Expose a function and inject the clock (Rule 4.6).',
97
+ },
98
+ {
99
+ selector: ":matches(Program > VariableDeclaration, Program > ExportNamedDeclaration > VariableDeclaration) > VariableDeclarator > MemberExpression[object.object.name='process'][object.property.name='env']",
100
+ message: 'TSF++ Rule 11.6: reading process.env at import time. Load config at the boundary and inject it.',
101
+ },
102
+ {
103
+ selector: ":matches(Program > VariableDeclaration, Program > ExportNamedDeclaration > VariableDeclaration) > VariableDeclarator > AwaitExpression",
104
+ message: 'TSF++ Rule 11.6: top-level await performs I/O at import time and blocks the module graph.',
105
+ },
106
+ {
107
+ selector: 'Program > ExpressionStatement > CallExpression',
108
+ message: 'TSF++ Rule 11.6: a bare call at module scope is a side effect at import time. Importing a module must only define bindings.',
109
+ },
110
+ ],
111
+ },
112
+ },
113
+ ];
114
+ export default config;
115
+ //# sourceMappingURL=layered.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layered.js","sourceRoot":"","sources":["../layered.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH,+CAA+C;AAC/C,MAAM,iBAAiB,GAAG;IACxB,gBAAgB;IAChB,aAAa;IACb,gBAAgB;IAChB,gBAAgB;IAChB,gBAAgB;IAChB,QAAQ;IACR,SAAS;IACT,SAAS;IACT,KAAK;IACL,QAAQ;IACR,OAAO;IACP,WAAW;CACZ,CAAA;AAED,oEAAoE;AACpE,MAAM,qBAAqB,GAAG;IAC5B,gBAAgB;IAChB,aAAa;IACb,gBAAgB;IAChB,QAAQ;IACR,SAAS;IACT,SAAS;IACT,KAAK;CACN,CAAA;AAED,MAAM,MAAM,GAAoB;IAC9B,gFAAgF;IAChF;QACE,KAAK,EAAE,CAAC,iBAAiB,EAAE,mBAAmB,CAAC;QAC/C,KAAK,EAAE;YACL,uBAAuB,EAAE,CAAC,OAAO,EAAE;oBACjC,QAAQ,EAAE,CAAC;4BACT,KAAK,EAAE,iBAAiB;4BACxB,OAAO,EACL,oFAAoF;gCACpF,8GAA8G;yBACjH,CAAC;iBACH,CAAC;SACH;KACF;IAED,6EAA6E;IAC7E;QACE,KAAK,EAAE,CAAC,qBAAqB,EAAE,sBAAsB,EAAE,wBAAwB,CAAC;QAChF,KAAK,EAAE;YACL,uBAAuB,EAAE,CAAC,OAAO,EAAE;oBACjC,QAAQ,EAAE,CAAC;4BACT,KAAK,EAAE,qBAAqB;4BAC5B,OAAO,EACL,gGAAgG;gCAChG,0CAA0C;yBAC7C,CAAC;iBACH,CAAC;SACH;KACF;IAED,kEAAkE;IAClE,EAAE;IACF,wEAAwE;IACxE,6EAA6E;IAC7E,gDAAgD;IAChD;QACE,KAAK,EAAE,CAAC,SAAS,CAAC;QAClB,OAAO,EAAE,CAAC,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,cAAc,CAAC;QACxE,KAAK,EAAE;YACL,sBAAsB,EAAE,CAAC,OAAO;gBAC9B;oBACE,QAAQ,EAAE,0JAA0J;oBACpK,OAAO,EAAE,uGAAuG;iBACjH;gBACD;oBACE,QAAQ,EAAE,mMAAmM;oBAC7M,OAAO,EAAE,iGAAiG;iBAC3G;gBACD;oBACE,QAAQ,EAAE,wIAAwI;oBAClJ,OAAO,EAAE,2FAA2F;iBACrG;gBACD;oBACE,QAAQ,EAAE,gDAAgD;oBAC1D,OAAO,EAAE,6HAA6H;iBACvI;aACF;SACF;KACF;CACF,CAAA;AAED,eAAe,MAAM,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-compat.d.ts","sourceRoot":"","sources":["../plugin-compat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEpC;;;;;;GAMG;AAEH,eAAO,MAAM,YAAY,GAAI,QAAQ,OAAO,KAAG,MAAM,CAAC,MAC7B,CAAA"}
1
+ {"version":3,"file":"plugin-compat.d.ts","sourceRoot":"","sources":["../plugin-compat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEpC;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,QAAQ,OAAO,KAAG,MAAM,CAAC,MAE7B,CAAA"}
@@ -25,6 +25,7 @@
25
25
  * Use exclusively at plugin registration sites. Never use to silence unrelated
26
26
  * type errors.
27
27
  */
28
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
29
- export const coercePlugin = (plugin) => plugin; // DEVIATION(1.6): see module JSDoc above
28
+ export const coercePlugin = (plugin) =>
29
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- DEVIATION(1.6): see module JSDoc above
30
+ plugin;
30
31
  //# sourceMappingURL=plugin-compat.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-compat.js","sourceRoot":"","sources":["../plugin-compat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH;;;;;;GAMG;AACH,yEAAyE;AACzE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAe,EAAiB,EAAE,CAC7D,MAAuB,CAAA,CAAE,yCAAyC"}
1
+ {"version":3,"file":"plugin-compat.js","sourceRoot":"","sources":["../plugin-compat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAe,EAAiB,EAAE;AAC7D,mHAAmH;AACnH,MAAuB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsfpp/eslint-config",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "Shared ESLint flat configuration for TSF++ projects",
5
5
  "keywords": [
6
6
  "eslint",
@@ -19,20 +19,21 @@
19
19
  "license": "MIT",
20
20
  "type": "module",
21
21
  "exports": {
22
- ".": "./dist/base.js",
23
- "./react": "./dist/react.js",
24
- "./api": "./dist/api.js",
25
- "./package.json": "./package.json"
22
+ ".": "./dist/base.js",
23
+ "./react": "./dist/react.js",
24
+ "./api": "./dist/api.js",
25
+ "./package.json": "./package.json",
26
+ "./layered": "./dist/layered.js"
26
27
  },
27
28
  "files": [
28
29
  "dist/",
29
30
  "README.md"
30
31
  ],
31
32
  "scripts": {
32
- "build": "tsc --project tsconfig.build.json",
33
+ "build": "tsc --project tsconfig.build.json",
33
34
  "prepublishOnly": "pnpm run build",
34
- "lint": "eslint dist/",
35
- "test": "node --input-type=module --eval \"import './dist/base.js'\""
35
+ "lint": "eslint .",
36
+ "test": "node --input-type=module --eval \"import './dist/base.js'\""
36
37
  },
37
38
  "engines": {
38
39
  "node": ">=18.18.0"
@@ -49,20 +50,28 @@
49
50
  "typescript": ">=5.3.0"
50
51
  },
51
52
  "peerDependenciesMeta": {
52
- "@tanstack/eslint-plugin-query": { "optional": true },
53
- "eslint-plugin-jsx-a11y": { "optional": true },
54
- "eslint-plugin-react": { "optional": true },
55
- "eslint-plugin-react-hooks": { "optional": true }
53
+ "@tanstack/eslint-plugin-query": {
54
+ "optional": true
55
+ },
56
+ "eslint-plugin-jsx-a11y": {
57
+ "optional": true
58
+ },
59
+ "eslint-plugin-react": {
60
+ "optional": true
61
+ },
62
+ "eslint-plugin-react-hooks": {
63
+ "optional": true
64
+ }
56
65
  },
57
66
  "devDependencies": {
58
- "@tanstack/eslint-plugin-query": "^5.100.10",
67
+ "@tanstack/eslint-plugin-query": "^5.100.10",
59
68
  "@typescript-eslint/eslint-plugin": "^8.59.3",
60
- "@typescript-eslint/parser": "^8.59.3",
61
- "eslint": "^9.39.4",
62
- "eslint-plugin-functional": "^9.0.0",
63
- "eslint-plugin-jsx-a11y": "^6.10.2",
64
- "eslint-plugin-react": "^7.37.5",
65
- "eslint-plugin-react-hooks": "^7.1.1",
66
- "typescript": "^5.9.3"
69
+ "@typescript-eslint/parser": "^8.59.3",
70
+ "eslint": "^9.39.4",
71
+ "eslint-plugin-functional": "^9.0.0",
72
+ "eslint-plugin-jsx-a11y": "^6.10.2",
73
+ "eslint-plugin-react": "^7.37.5",
74
+ "eslint-plugin-react-hooks": "^7.1.1",
75
+ "typescript": "^5.9.3"
67
76
  }
68
- }
77
+ }