@pjts/eslint-style-rules 0.0.0-stage → 0.0.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.
Files changed (113) hide show
  1. package/README.md +138 -2
  2. package/dist/index.d.ts +38 -0
  3. package/dist/index.js +232 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/lib/ast.d.ts +27 -0
  6. package/dist/lib/ast.js +60 -0
  7. package/dist/lib/ast.js.map +1 -0
  8. package/dist/lib/db.d.ts +13 -0
  9. package/dist/lib/db.js +50 -0
  10. package/dist/lib/db.js.map +1 -0
  11. package/dist/lib/messages.d.ts +13 -0
  12. package/dist/lib/messages.js +76 -0
  13. package/dist/lib/messages.js.map +1 -0
  14. package/dist/lib/rule.d.ts +9 -0
  15. package/dist/lib/rule.js +10 -0
  16. package/dist/lib/rule.js.map +1 -0
  17. package/dist/rules/app-class-suffix.d.ts +3 -0
  18. package/dist/rules/app-class-suffix.js +28 -0
  19. package/dist/rules/app-class-suffix.js.map +1 -0
  20. package/dist/rules/app-service-endpoints.d.ts +12 -0
  21. package/dist/rules/app-service-endpoints.js +79 -0
  22. package/dist/rules/app-service-endpoints.js.map +1 -0
  23. package/dist/rules/auth-access.d.ts +14 -0
  24. package/dist/rules/auth-access.js +122 -0
  25. package/dist/rules/auth-access.js.map +1 -0
  26. package/dist/rules/db-client-dependency.d.ts +11 -0
  27. package/dist/rules/db-client-dependency.js +74 -0
  28. package/dist/rules/db-client-dependency.js.map +1 -0
  29. package/dist/rules/db-writes-in-repositories.d.ts +12 -0
  30. package/dist/rules/db-writes-in-repositories.js +105 -0
  31. package/dist/rules/db-writes-in-repositories.js.map +1 -0
  32. package/dist/rules/decorated-class-contract.d.ts +6 -0
  33. package/dist/rules/decorated-class-contract.js +37 -0
  34. package/dist/rules/decorated-class-contract.js.map +1 -0
  35. package/dist/rules/entity-constructor-data.d.ts +6 -0
  36. package/dist/rules/entity-constructor-data.js +35 -0
  37. package/dist/rules/entity-constructor-data.js.map +1 -0
  38. package/dist/rules/event-sourced-aggregate.d.ts +15 -0
  39. package/dist/rules/event-sourced-aggregate.js +134 -0
  40. package/dist/rules/event-sourced-aggregate.js.map +1 -0
  41. package/dist/rules/find-one-naming.d.ts +7 -0
  42. package/dist/rules/find-one-naming.js +38 -0
  43. package/dist/rules/find-one-naming.js.map +1 -0
  44. package/dist/rules/import-only-in.d.ts +7 -0
  45. package/dist/rules/import-only-in.js +52 -0
  46. package/dist/rules/import-only-in.js.map +1 -0
  47. package/dist/rules/knex-first.d.ts +3 -0
  48. package/dist/rules/knex-first.js +28 -0
  49. package/dist/rules/knex-first.js.map +1 -0
  50. package/dist/rules/message-file.d.ts +18 -0
  51. package/dist/rules/message-file.js +118 -0
  52. package/dist/rules/message-file.js.map +1 -0
  53. package/dist/rules/message-handler.d.ts +19 -0
  54. package/dist/rules/message-handler.js +114 -0
  55. package/dist/rules/message-handler.js.map +1 -0
  56. package/dist/rules/message-name-reference.d.ts +7 -0
  57. package/dist/rules/message-name-reference.js +52 -0
  58. package/dist/rules/message-name-reference.js.map +1 -0
  59. package/dist/rules/message-names.d.ts +7 -0
  60. package/dist/rules/message-names.js +80 -0
  61. package/dist/rules/message-names.js.map +1 -0
  62. package/dist/rules/module-layout.d.ts +9 -0
  63. package/dist/rules/module-layout.js +71 -0
  64. package/dist/rules/module-layout.js.map +1 -0
  65. package/dist/rules/module-service-getters.d.ts +19 -0
  66. package/dist/rules/module-service-getters.js +132 -0
  67. package/dist/rules/module-service-getters.js.map +1 -0
  68. package/dist/rules/no-async-in-domain.d.ts +2 -0
  69. package/dist/rules/no-async-in-domain.js +116 -0
  70. package/dist/rules/no-async-in-domain.js.map +1 -0
  71. package/dist/rules/no-duplicate-sdk-types.d.ts +6 -0
  72. package/dist/rules/no-duplicate-sdk-types.js +40 -0
  73. package/dist/rules/no-duplicate-sdk-types.js.map +1 -0
  74. package/dist/rules/no-entity-assembly-in-app.d.ts +7 -0
  75. package/dist/rules/no-entity-assembly-in-app.js +37 -0
  76. package/dist/rules/no-entity-assembly-in-app.js.map +1 -0
  77. package/dist/rules/no-entity-mutation-outside-domain.d.ts +11 -0
  78. package/dist/rules/no-entity-mutation-outside-domain.js +68 -0
  79. package/dist/rules/no-entity-mutation-outside-domain.js.map +1 -0
  80. package/dist/rules/no-message-construction-outside-domain.d.ts +15 -0
  81. package/dist/rules/no-message-construction-outside-domain.js +45 -0
  82. package/dist/rules/no-message-construction-outside-domain.js.map +1 -0
  83. package/dist/rules/no-reconstitute.d.ts +3 -0
  84. package/dist/rules/no-reconstitute.js +22 -0
  85. package/dist/rules/no-reconstitute.js.map +1 -0
  86. package/dist/rules/port-file.d.ts +9 -0
  87. package/dist/rules/port-file.js +94 -0
  88. package/dist/rules/port-file.js.map +1 -0
  89. package/dist/rules/port-has-adapters.d.ts +9 -0
  90. package/dist/rules/port-has-adapters.js +62 -0
  91. package/dist/rules/port-has-adapters.js.map +1 -0
  92. package/dist/rules/repository-naming.d.ts +19 -0
  93. package/dist/rules/repository-naming.js +122 -0
  94. package/dist/rules/repository-naming.js.map +1 -0
  95. package/dist/rules/repository-serialization.d.ts +6 -0
  96. package/dist/rules/repository-serialization.js +50 -0
  97. package/dist/rules/repository-serialization.js.map +1 -0
  98. package/dist/rules/return-entities.d.ts +6 -0
  99. package/dist/rules/return-entities.js +38 -0
  100. package/dist/rules/return-entities.js.map +1 -0
  101. package/dist/rules/save-contract.d.ts +7 -0
  102. package/dist/rules/save-contract.js +41 -0
  103. package/dist/rules/save-contract.js.map +1 -0
  104. package/dist/rules/test-location.d.ts +14 -0
  105. package/dist/rules/test-location.js +94 -0
  106. package/dist/rules/test-location.js.map +1 -0
  107. package/dist/rules/throw-app-error.d.ts +12 -0
  108. package/dist/rules/throw-app-error.js +68 -0
  109. package/dist/rules/throw-app-error.js.map +1 -0
  110. package/dist/rules/trackable-repository.d.ts +10 -0
  111. package/dist/rules/trackable-repository.js +97 -0
  112. package/dist/rules/trackable-repository.js.map +1 -0
  113. package/package.json +46 -4
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.walkOwnBody = exports.switchCaseNames = exports.nameProperty = exports.messageNameOf = exports.screamingSnake = void 0;
4
+ /** `CatalogProfilePublished` → `CATALOG_PROFILE_PUBLISHED`. */
5
+ const screamingSnake = (name) => name.replace(/(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/g, '_').toUpperCase();
6
+ exports.screamingSnake = screamingSnake;
7
+ /** `MessageNames.X` (expression or type position) → 'X'; anything else → null. */
8
+ const messageNameOf = (node, enumName = 'MessageNames') => {
9
+ if (!node)
10
+ return null;
11
+ if (node.type === 'MemberExpression' && !node.computed && node.object.type === 'Identifier' && node.object.name === enumName)
12
+ return node.property.name;
13
+ if (node.type === 'TSTypeReference')
14
+ return (0, exports.messageNameOf)(node.typeName, enumName);
15
+ if (node.type === 'TSQualifiedName' && node.left.type === 'Identifier' && node.left.name === enumName)
16
+ return node.right.name;
17
+ return null;
18
+ };
19
+ exports.messageNameOf = messageNameOf;
20
+ /** Names set in `name: MessageNames.X` of an object literal (spreads ignored). */
21
+ const nameProperty = (objectExpression, enumName = 'MessageNames') => {
22
+ if (objectExpression?.type !== 'ObjectExpression')
23
+ return null;
24
+ const property = objectExpression.properties.find((p) => p.type === 'Property' && !p.computed && (p.key.name ?? p.key.value) === 'name');
25
+ return property ? { name: (0, exports.messageNameOf)(property.value, enumName), node: property } : null;
26
+ };
27
+ exports.nameProperty = nameProperty;
28
+ /** Every `case MessageNames.X:` inside `node` (any switch). */
29
+ const switchCaseNames = (node, enumName = 'MessageNames') => {
30
+ const names = new Set();
31
+ const visit = (current) => {
32
+ if (!current || typeof current.type !== 'string')
33
+ return;
34
+ if (current.type === 'SwitchCase') {
35
+ const name = (0, exports.messageNameOf)(current.test, enumName);
36
+ if (name)
37
+ names.add(name);
38
+ }
39
+ for (const key of Object.keys(current)) {
40
+ if (key === 'parent')
41
+ continue;
42
+ const value = current[key];
43
+ if (Array.isArray(value))
44
+ value.forEach(visit);
45
+ else if (value && typeof value === 'object')
46
+ visit(value);
47
+ }
48
+ };
49
+ visit(node);
50
+ return names;
51
+ };
52
+ exports.switchCaseNames = switchCaseNames;
53
+ /** Depth-first walk that does not enter nested functions or classes. */
54
+ const walkOwnBody = (node, visit) => {
55
+ if (!node || typeof node.type !== 'string')
56
+ return;
57
+ visit(node);
58
+ for (const key of Object.keys(node)) {
59
+ if (key === 'parent')
60
+ continue;
61
+ const value = node[key];
62
+ const step = (child) => {
63
+ if (!child || typeof child.type !== 'string')
64
+ return;
65
+ if (/Function|Class/.test(child.type))
66
+ return;
67
+ (0, exports.walkOwnBody)(child, visit);
68
+ };
69
+ if (Array.isArray(value))
70
+ value.forEach(step);
71
+ else if (value && typeof value === 'object')
72
+ step(value);
73
+ }
74
+ };
75
+ exports.walkOwnBody = walkOwnBody;
76
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/lib/messages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { Rule } from 'eslint';
2
+ /**
3
+ * Types a rule for ESLint while keeping its visitors loosely typed: the rules read TypeScript AST
4
+ * fields (decorators, type annotations, accessibility) that ESLint's ESTree types don't describe.
5
+ */
6
+ export declare const defineRule: (rule: {
7
+ meta: Record<string, any>;
8
+ create: (context: Rule.RuleContext) => Record<string, any>;
9
+ }) => Rule.RuleModule;
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defineRule = void 0;
4
+ /**
5
+ * Types a rule for ESLint while keeping its visitors loosely typed: the rules read TypeScript AST
6
+ * fields (decorators, type annotations, accessibility) that ESLint's ESTree types don't describe.
7
+ */
8
+ const defineRule = (rule) => rule;
9
+ exports.defineRule = defineRule;
10
+ //# sourceMappingURL=rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule.js","sourceRoot":"","sources":["../../src/lib/rule.ts"],"names":[],"mappings":";;;AAEA;;;GAGG;AACI,MAAM,UAAU,GAAG,CAAC,IAA+F,EAAmB,EAAE,CAC7I,IAAkC,CAAC;AADxB,QAAA,UAAU,cACc"}
@@ -0,0 +1,3 @@
1
+ /** Application-layer classes are use-case entry points: `…Service`, `…Handler` or `…View`. */
2
+ declare const _default: import("eslint").Rule.RuleModule;
3
+ export default _default;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const rule_1 = require("../lib/rule");
4
+ /** Application-layer classes are use-case entry points: `…Service`, `…Handler` or `…View`. */
5
+ exports.default = (0, rule_1.defineRule)({
6
+ meta: {
7
+ type: 'suggestion',
8
+ docs: { description: 'Require app-layer class names to end with an allowed suffix' },
9
+ messages: { suffix: 'App-layer class "{{name}}" must end with {{allowed}}.' },
10
+ schema: [{
11
+ type: 'object',
12
+ properties: { allowed: { type: 'array', items: { type: 'string' }, minItems: 1 } },
13
+ additionalProperties: false,
14
+ }],
15
+ },
16
+ create(context) {
17
+ const allowed = context.options[0]?.allowed ?? ['Service', 'Handler', 'View'];
18
+ return {
19
+ 'ClassDeclaration[id]'(node) {
20
+ const name = node.id.name;
21
+ if (!allowed.some((suffix) => name.endsWith(suffix))) {
22
+ context.report({ node: node.id, messageId: 'suffix', data: { name, allowed: allowed.map((s) => `"${s}"`).join(' | ') } });
23
+ }
24
+ },
25
+ };
26
+ },
27
+ });
28
+ //# sourceMappingURL=app-class-suffix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-class-suffix.js","sourceRoot":"","sources":["../../src/rules/app-class-suffix.ts"],"names":[],"mappings":";;AAAA,sCAAyC;AACzC,8FAA8F;AAC9F,kBAAe,IAAA,iBAAU,EAAC;IACxB,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE,EAAE,WAAW,EAAE,6DAA6D,EAAE;QACpF,QAAQ,EAAE,EAAE,MAAM,EAAE,uDAAuD,EAAE;QAC7E,MAAM,EAAE,CAAC;gBACP,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE;gBAClF,oBAAoB,EAAE,KAAK;aAC5B,CAAC;KACH;IACD,MAAM,CAAC,OAAO;QACZ,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QAC9E,OAAO;YACL,sBAAsB,CAAC,IAAI;gBACzB,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC;gBAC1B,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;oBACrD,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC5H,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Every public method of an `@AppService` class is an HTTP endpoint: it carries exactly one HTTP
3
+ * method decorator, and every parameter carries a request-binding decorator.
4
+ *
5
+ * @Post
6
+ * public async login(@Body dto: LoginDto, @Ip ip: ClientIp): Promise<…>
7
+ *
8
+ * Non-public methods are helpers and must not carry an HTTP method decorator. Other method
9
+ * decorators (`@Pipe(...)` guards) are allowed alongside.
10
+ */
11
+ declare const _default: import("eslint").Rule.RuleModule;
12
+ export default _default;
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ast_1 = require("../lib/ast");
4
+ const rule_1 = require("../lib/rule");
5
+ /** `@Foo` and `@Foo(...)` → 'Foo'. */
6
+ const decoratorName = (decorator) => decorator.expression.type === 'CallExpression' ? decorator.expression.callee.name : decorator.expression.name;
7
+ /**
8
+ * Every public method of an `@AppService` class is an HTTP endpoint: it carries exactly one HTTP
9
+ * method decorator, and every parameter carries a request-binding decorator.
10
+ *
11
+ * @Post
12
+ * public async login(@Body dto: LoginDto, @Ip ip: ClientIp): Promise<…>
13
+ *
14
+ * Non-public methods are helpers and must not carry an HTTP method decorator. Other method
15
+ * decorators (`@Pipe(...)` guards) are allowed alongside.
16
+ */
17
+ exports.default = (0, rule_1.defineRule)({
18
+ meta: {
19
+ type: 'problem',
20
+ docs: { description: 'Require HTTP and parameter decorators on @AppService public methods' },
21
+ messages: {
22
+ missingHttp: 'Public method `{{method}}` of @{{service}} class must be decorated with one of {{http}}.',
23
+ multipleHttp: 'Method `{{method}}` has several HTTP method decorators ({{found}}) — keep exactly one.',
24
+ missingParam: 'Parameter `{{param}}` of `{{method}}` must be decorated with one of {{params}}.',
25
+ helperEndpoint: '`{{method}}` is not public, so it must not carry @{{found}} — make it public or drop the decorator.',
26
+ },
27
+ schema: [{
28
+ type: 'object',
29
+ properties: {
30
+ serviceDecorators: { type: 'array', items: { type: 'string' } },
31
+ httpDecorators: { type: 'array', items: { type: 'string' } },
32
+ paramDecorators: { type: 'array', items: { type: 'string' } },
33
+ },
34
+ additionalProperties: false,
35
+ }],
36
+ },
37
+ create(context) {
38
+ const { serviceDecorators = ['AppService'], httpDecorators = ['Get', 'Post', 'Put', 'Patch', 'Delete'], paramDecorators = ['Auth', 'Body', 'UrlParam', 'QueryParam', 'QueryParams', 'Header', 'Ip', 'File'], } = context.options[0] ?? {};
39
+ const list = (names) => names.map((name) => `@${name}`).join(' | ');
40
+ const paramNode = (param) => (param.type === 'TSParameterProperty' ? param.parameter : param);
41
+ const paramLabel = (param) => {
42
+ const node = paramNode(param);
43
+ const target = node.type === 'AssignmentPattern' ? node.left : node.type === 'RestElement' ? node.argument : node;
44
+ return target.type === 'Identifier' ? target.name : context.sourceCode.getText(target);
45
+ };
46
+ return {
47
+ ClassDeclaration(classNode) {
48
+ const service = (0, ast_1.decoratorNames)(classNode).find((name) => serviceDecorators.includes(name));
49
+ if (!service)
50
+ return;
51
+ for (const member of classNode.body.body) {
52
+ if (member.type !== 'MethodDefinition' || member.kind !== 'method' || member.static || member.computed)
53
+ continue;
54
+ const method = member.key.name ?? member.key.value;
55
+ const http = (member.decorators ?? []).map(decoratorName).filter((name) => httpDecorators.includes(name));
56
+ const isPublic = member.accessibility !== 'private' && member.accessibility !== 'protected' && member.key.type !== 'PrivateIdentifier';
57
+ if (!isPublic) {
58
+ if (http.length)
59
+ context.report({ node: member.key, messageId: 'helperEndpoint', data: { method, found: http.join(', @') } });
60
+ continue;
61
+ }
62
+ if (http.length === 0) {
63
+ context.report({ node: member.key, messageId: 'missingHttp', data: { method, service, http: list(httpDecorators) } });
64
+ }
65
+ else if (http.length > 1) {
66
+ context.report({ node: member.key, messageId: 'multipleHttp', data: { method, found: http.map((n) => `@${n}`).join(', ') } });
67
+ }
68
+ for (const param of member.value.params) {
69
+ const decorators = [...(param.decorators ?? []), ...(paramNode(param).decorators ?? [])].map(decoratorName);
70
+ if (!decorators.some((name) => paramDecorators.includes(name))) {
71
+ context.report({ node: param, messageId: 'missingParam', data: { param: paramLabel(param), method, params: list(paramDecorators) } });
72
+ }
73
+ }
74
+ }
75
+ },
76
+ };
77
+ },
78
+ });
79
+ //# sourceMappingURL=app-service-endpoints.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-service-endpoints.js","sourceRoot":"","sources":["../../src/rules/app-service-endpoints.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Every authenticated endpoint declares the right it needs (altbra `common/core/AuthAccess.ts`):
3
+ *
4
+ * @Get
5
+ * public async findBySku(@AuthAccess('distribution:read', 'offer:read') _authToken: AuthToken, …)
6
+ *
7
+ * In `@AppService` classes, a parameter typed `AuthToken` must use `@AuthAccess(...)` with at least one
8
+ * right matching `rightPattern` (several rights = any of them). Plain `@Auth` is allowed only for the
9
+ * `Service.method` (or `File.method`) entries in `allow` — endpoints whose right is not decided yet; an entry that already
10
+ * uses @AuthAccess is reported so the list shrinks. With `checkParamName` (default) the parameter is
11
+ * named `authToken`, or `_authToken` when the method body doesn't read it.
12
+ */
13
+ declare const _default: import("eslint").Rule.RuleModule;
14
+ export default _default;
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const rule_1 = require("../lib/rule");
4
+ const ast_1 = require("../lib/ast");
5
+ const db_1 = require("../lib/db");
6
+ /** `@Foo` / `@Foo(...)` → { name, args } */
7
+ const decoratorCall = (decorator) => decorator.expression.type === 'CallExpression'
8
+ ? { name: decorator.expression.callee.name, args: decorator.expression.arguments }
9
+ : { name: decorator.expression.name, args: [] };
10
+ /**
11
+ * Every authenticated endpoint declares the right it needs (altbra `common/core/AuthAccess.ts`):
12
+ *
13
+ * @Get
14
+ * public async findBySku(@AuthAccess('distribution:read', 'offer:read') _authToken: AuthToken, …)
15
+ *
16
+ * In `@AppService` classes, a parameter typed `AuthToken` must use `@AuthAccess(...)` with at least one
17
+ * right matching `rightPattern` (several rights = any of them). Plain `@Auth` is allowed only for the
18
+ * `Service.method` (or `File.method`) entries in `allow` — endpoints whose right is not decided yet; an entry that already
19
+ * uses @AuthAccess is reported so the list shrinks. With `checkParamName` (default) the parameter is
20
+ * named `authToken`, or `_authToken` when the method body doesn't read it.
21
+ */
22
+ exports.default = (0, rule_1.defineRule)({
23
+ meta: {
24
+ type: 'problem',
25
+ docs: { description: 'Require an access right on every authenticated endpoint' },
26
+ messages: {
27
+ plainAuth: '`{{method}}` must declare its right: `@{{decorator}}(\'scope:read\') authToken: {{tokenType}}` instead of @{{found}}.',
28
+ noRights: '@{{decorator}} needs at least one right, e.g. @{{decorator}}(\'scope:read\').',
29
+ badRight: 'Access right {{right}} must match {{pattern}}.',
30
+ dynamicRight: 'Access rights are string literals so they can be audited — not `{{text}}`.',
31
+ paramName: 'Name the {{tokenType}} parameter `{{expected}}`.',
32
+ stale: '"{{entry}}" is in the plain-@Auth allowlist but already uses @{{decorator}} — remove it from the list.',
33
+ },
34
+ schema: [{
35
+ type: 'object',
36
+ properties: {
37
+ decorator: { type: 'string' },
38
+ plainDecorators: { type: 'array', items: { type: 'string' } },
39
+ tokenType: { type: 'string' },
40
+ serviceDecorators: { type: 'array', items: { type: 'string' } },
41
+ rightPattern: { type: 'string' },
42
+ allow: { type: 'array', items: { type: 'string' } },
43
+ checkParamName: { type: 'boolean' },
44
+ },
45
+ additionalProperties: false,
46
+ }],
47
+ },
48
+ create(context) {
49
+ const { decorator = 'AuthAccess', plainDecorators = ['Auth'], tokenType = 'AuthToken', serviceDecorators = ['AppService'], rightPattern = '^[a-z][a-z0-9-]*:(read|write)$', allow = [], checkParamName = true, } = context.options[0] ?? {};
50
+ const pattern = new RegExp(rightPattern);
51
+ const readsParam = (body, name) => {
52
+ let found = false;
53
+ const visit = (node) => {
54
+ if (found || !node || typeof node.type !== 'string')
55
+ return;
56
+ const parent = node.parent;
57
+ const isKey = (parent?.type === 'Property' && parent.key === node && !parent.shorthand) || (parent?.type === 'MemberExpression' && parent.property === node && !parent.computed);
58
+ if (node.type === 'Identifier' && node.name === name && !isKey)
59
+ found = true;
60
+ for (const key of Object.keys(node)) {
61
+ if (key === 'parent')
62
+ continue;
63
+ const value = node[key];
64
+ if (Array.isArray(value))
65
+ value.forEach(visit);
66
+ else if (value && typeof value === 'object')
67
+ visit(value);
68
+ }
69
+ };
70
+ visit(body);
71
+ return found;
72
+ };
73
+ return {
74
+ ClassDeclaration(classNode) {
75
+ if (!(0, ast_1.decoratorNames)(classNode).some((name) => serviceDecorators.includes(name)))
76
+ return;
77
+ const service = classNode.id?.name ?? '';
78
+ const fileBase = context.filename.split(/[\\/]/).pop().replace(/\.ts$/, '');
79
+ for (const member of classNode.body.body) {
80
+ if (member.type !== 'MethodDefinition' || member.kind !== 'method')
81
+ continue;
82
+ const method = member.key.name ?? member.key.value;
83
+ const entry = `${service}.${method}`;
84
+ // Allowlist entries may name the class or the file (altbra's list keys by file name).
85
+ const allowed = allow.includes(entry) || allow.includes(`${fileBase}.${method}`);
86
+ for (const param of member.value.params) {
87
+ const binding = (0, db_1.paramBinding)(param);
88
+ const type = binding.typeAnnotation?.typeAnnotation;
89
+ if (type?.type !== 'TSTypeReference' || type.typeName.name !== tokenType)
90
+ continue;
91
+ const calls = [...(param.decorators ?? []), ...(binding.decorators ?? [])].map(decoratorCall);
92
+ const access = calls.find((call) => call.name === decorator);
93
+ const plain = calls.find((call) => plainDecorators.includes(call.name));
94
+ if (access) {
95
+ if (allowed)
96
+ context.report({ node: param, messageId: 'stale', data: { entry, decorator } });
97
+ if (access.args.length === 0)
98
+ context.report({ node: param, messageId: 'noRights', data: { decorator } });
99
+ for (const arg of access.args) {
100
+ if (arg.type !== 'Literal' || typeof arg.value !== 'string') {
101
+ context.report({ node: arg, messageId: 'dynamicRight', data: { text: context.sourceCode.getText(arg) } });
102
+ }
103
+ else if (!pattern.test(arg.value)) {
104
+ context.report({ node: arg, messageId: 'badRight', data: { right: `'${arg.value}'`, pattern: rightPattern } });
105
+ }
106
+ }
107
+ }
108
+ else if (plain && !allowed) {
109
+ context.report({ node: param, messageId: 'plainAuth', data: { method: entry, decorator, tokenType, found: plain.name } });
110
+ }
111
+ if (checkParamName && binding.type === 'Identifier' && (access || plain)) {
112
+ const expected = readsParam(member.value.body, binding.name) ? 'authToken' : '_authToken';
113
+ if (binding.name !== expected)
114
+ context.report({ node: binding, messageId: 'paramName', data: { tokenType, expected } });
115
+ }
116
+ }
117
+ }
118
+ },
119
+ };
120
+ },
121
+ });
122
+ //# sourceMappingURL=auth-access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-access.js","sourceRoot":"","sources":["../../src/rules/auth-access.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The database client (`PostgresDbClient`, knex, pg) is a dependency of `…Pg…Repository` classes only.
3
+ * A class depends on it when a constructor/method parameter or a field is typed with it; a plain
4
+ * function depends on it when one of its parameters is. Interfaces and type aliases (module contexts,
5
+ * record shapes) are types, not dependencies, and stay allowed.
6
+ *
7
+ * Options: `allowedClasses` — name patterns of the classes allowed to depend on it
8
+ * (default `['^([A-Z][a-z]+)*Pg[A-Z]\\w*Repository$']` — `Pg` anywhere in a combined prefix: PgX, PgNatsX, NatsPgX), `clients` — `module#ImportedName` specs.
9
+ */
10
+ declare const _default: import("eslint").Rule.RuleModule;
11
+ export default _default;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const db_1 = require("../lib/db");
4
+ const rule_1 = require("../lib/rule");
5
+ /**
6
+ * The database client (`PostgresDbClient`, knex, pg) is a dependency of `…Pg…Repository` classes only.
7
+ * A class depends on it when a constructor/method parameter or a field is typed with it; a plain
8
+ * function depends on it when one of its parameters is. Interfaces and type aliases (module contexts,
9
+ * record shapes) are types, not dependencies, and stay allowed.
10
+ *
11
+ * Options: `allowedClasses` — name patterns of the classes allowed to depend on it
12
+ * (default `['^([A-Z][a-z]+)*Pg[A-Z]\\w*Repository$']` — `Pg` anywhere in a combined prefix: PgX, PgNatsX, NatsPgX), `clients` — `module#ImportedName` specs.
13
+ */
14
+ exports.default = (0, rule_1.defineRule)({
15
+ meta: {
16
+ type: 'problem',
17
+ docs: { description: 'Only …Pg…Repository classes may depend on the database client' },
18
+ messages: {
19
+ classDependency: '"{{name}}" depends on {{client}} — only {{allowed}} classes may. Go through a repository port, or make this a {{allowed}} in infrastructure/repositories/pg/.',
20
+ functionDependency: 'Function "{{name}}" takes {{client}} — only {{allowed}} classes may depend on the database client.',
21
+ },
22
+ schema: [{
23
+ type: 'object',
24
+ properties: {
25
+ allowedClasses: { type: 'array', items: { type: 'string' } },
26
+ clients: { type: 'array', items: { type: 'string' } },
27
+ },
28
+ additionalProperties: false,
29
+ }],
30
+ },
31
+ create(context) {
32
+ const { allowedClasses = ['^([A-Z][a-z]+)*Pg[A-Z]\\w*Repository$'], clients = db_1.DEFAULT_CLIENTS } = context.options[0] ?? {};
33
+ const allowed = allowedClasses.map((pattern) => new RegExp(pattern));
34
+ const allowedLabel = context.options[0]?.allowedClasses ? allowedClasses.join(' / ') : '…Pg…Repository';
35
+ const tracker = (0, db_1.clientTypeTracker)(clients);
36
+ const clientName = (annotation) => context.sourceCode.getText(annotation.typeAnnotation ?? annotation);
37
+ /** First client-typed parameter of a function, if any. */
38
+ const clientParam = (fn) => fn.params.map(db_1.paramBinding).find((binding) => tracker.usesClient(binding.typeAnnotation));
39
+ const checkClass = (classNode) => {
40
+ const name = classNode.id?.name ?? '(anonymous class)';
41
+ if (allowed.some((pattern) => pattern.test(name)))
42
+ return;
43
+ for (const member of classNode.body.body) {
44
+ let annotation = null;
45
+ if (member.type === 'PropertyDefinition' && tracker.usesClient(member.typeAnnotation))
46
+ annotation = member.typeAnnotation;
47
+ if (member.type === 'MethodDefinition')
48
+ annotation = clientParam(member.value)?.typeAnnotation ?? null;
49
+ if (annotation) {
50
+ context.report({ node: member.key ?? member, messageId: 'classDependency', data: { name, client: clientName(annotation), allowed: allowedLabel } });
51
+ return; // one report per class is enough
52
+ }
53
+ }
54
+ };
55
+ const checkFunction = (fn) => {
56
+ if (fn.parent.type === 'MethodDefinition')
57
+ return; // covered by the class check
58
+ const binding = clientParam(fn);
59
+ if (!binding)
60
+ return;
61
+ const name = fn.id?.name ?? (fn.parent.type === 'VariableDeclarator' ? fn.parent.id.name : '(anonymous)');
62
+ context.report({ node: binding, messageId: 'functionDependency', data: { name, client: clientName(binding.typeAnnotation), allowed: allowedLabel } });
63
+ };
64
+ return {
65
+ ImportDeclaration: (node) => tracker.onImport(node),
66
+ ClassDeclaration: checkClass,
67
+ ClassExpression: checkClass,
68
+ FunctionDeclaration: checkFunction,
69
+ FunctionExpression: checkFunction,
70
+ ArrowFunctionExpression: checkFunction,
71
+ };
72
+ },
73
+ });
74
+ //# sourceMappingURL=db-client-dependency.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-client-dependency.js","sourceRoot":"","sources":["../../src/rules/db-client-dependency.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Database writes happen only in the infrastructure layer, inside a `…Pg…Repository` class (Pg anywhere in the prefix).
3
+ * Flags write calls whose chain starts at a DB handle — a field or parameter typed with the client
4
+ * (`this.db(...)`, `db(...)`), a variable holding a query built from one, or `<x>.postgresDbClient` —
5
+ * anywhere else. Writes: insert/update/del/delete/truncate/upsert/increment/decrement/onConflict,
6
+ * transaction, and raw SQL starting with a writing statement. `entity.update(dto)` is not a DB call.
7
+ *
8
+ * Options: `allowedClasses` (default `['^([A-Z][a-z]+)*Pg[A-Z]\\w*Repository$']` — `Pg` anywhere in a combined prefix: PgX, PgNatsX, NatsPgX), `allowedPath` (default
9
+ * `/infrastructure/`), `clients`.
10
+ */
11
+ declare const _default: import("eslint").Rule.RuleModule;
12
+ export default _default;
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const db_1 = require("../lib/db");
4
+ const ast_1 = require("../lib/ast");
5
+ const rule_1 = require("../lib/rule");
6
+ const WRITE_METHODS = new Set(['insert', 'update', 'del', 'delete', 'truncate', 'upsert', 'increment', 'decrement', 'onConflict', 'transaction']);
7
+ const WRITE_SQL = /^\s*(insert|update|delete|truncate|merge|upsert|alter|drop|create|grant|revoke|copy)\b/i;
8
+ /**
9
+ * Database writes happen only in the infrastructure layer, inside a `…Pg…Repository` class (Pg anywhere in the prefix).
10
+ * Flags write calls whose chain starts at a DB handle — a field or parameter typed with the client
11
+ * (`this.db(...)`, `db(...)`), a variable holding a query built from one, or `<x>.postgresDbClient` —
12
+ * anywhere else. Writes: insert/update/del/delete/truncate/upsert/increment/decrement/onConflict,
13
+ * transaction, and raw SQL starting with a writing statement. `entity.update(dto)` is not a DB call.
14
+ *
15
+ * Options: `allowedClasses` (default `['^([A-Z][a-z]+)*Pg[A-Z]\\w*Repository$']` — `Pg` anywhere in a combined prefix: PgX, PgNatsX, NatsPgX), `allowedPath` (default
16
+ * `/infrastructure/`), `clients`.
17
+ */
18
+ exports.default = (0, rule_1.defineRule)({
19
+ meta: {
20
+ type: 'problem',
21
+ docs: { description: 'Allow database writes only in infrastructure …Pg…Repository classes' },
22
+ messages: {
23
+ write: '`{{method}}` writes to the database — writes belong in a …Pg…Repository in infrastructure/{{where}}.',
24
+ rawWrite: 'This raw SQL writes to the database — writes belong in a …Pg…Repository in infrastructure/{{where}}.',
25
+ },
26
+ schema: [{
27
+ type: 'object',
28
+ properties: {
29
+ allowedClasses: { type: 'array', items: { type: 'string' } },
30
+ allowedPath: { type: 'string' },
31
+ clients: { type: 'array', items: { type: 'string' } },
32
+ },
33
+ additionalProperties: false,
34
+ }],
35
+ },
36
+ create(context) {
37
+ const { allowedClasses = ['^([A-Z][a-z]+)*Pg[A-Z]\\w*Repository$'], allowedPath = '/infrastructure/', clients = db_1.DEFAULT_CLIENTS } = context.options[0] ?? {};
38
+ const allowed = allowedClasses.map((pattern) => new RegExp(pattern));
39
+ const inAllowedPath = (0, ast_1.filePath)(context).includes(allowedPath);
40
+ const tracker = (0, db_1.clientTypeTracker)(clients);
41
+ /** Where the write sits, or null when it is in an allowed repository. */
42
+ const misplaced = (node) => {
43
+ const owner = (0, ast_1.findLast)(context.sourceCode.getAncestors(node), (a) => a.type === 'ClassDeclaration' || a.type === 'ClassExpression');
44
+ const name = owner?.id?.name;
45
+ if (inAllowedPath && name && allowed.some((pattern) => pattern.test(name)))
46
+ return null;
47
+ return name ? ` (found in "${name}")` : ' (found outside any class)';
48
+ };
49
+ /** Names holding a DB handle: client-typed fields/parameters and variables derived from them. */
50
+ const handles = new Set();
51
+ const isHandleRoot = (node) => {
52
+ let current = node;
53
+ for (;;) {
54
+ if (current.type === 'CallExpression')
55
+ current = current.callee;
56
+ else if (current.type === 'AwaitExpression' || current.type === 'TSNonNullExpression')
57
+ current = current.argument ?? current.expression;
58
+ else if (current.type === 'MemberExpression') {
59
+ if (!current.computed && current.property.name === 'postgresDbClient')
60
+ return true;
61
+ if (current.object.type === 'ThisExpression')
62
+ return !current.computed && handles.has(current.property.name);
63
+ current = current.object;
64
+ }
65
+ else
66
+ return current.type === 'Identifier' && handles.has(current.name);
67
+ }
68
+ };
69
+ return {
70
+ ImportDeclaration: (node) => tracker.onImport(node),
71
+ ':function'(fn) {
72
+ for (const binding of fn.params.map(db_1.paramBinding)) {
73
+ if (binding.type === 'Identifier' && tracker.usesClient(binding.typeAnnotation))
74
+ handles.add(binding.name);
75
+ }
76
+ },
77
+ PropertyDefinition(node) {
78
+ if (node.key.type === 'Identifier' && tracker.usesClient(node.typeAnnotation))
79
+ handles.add(node.key.name);
80
+ },
81
+ VariableDeclarator(node) {
82
+ if (node.id.type === 'Identifier' && node.init && isHandleRoot(node.init))
83
+ handles.add(node.id.name);
84
+ },
85
+ 'CallExpression[callee.type="MemberExpression"][callee.computed=false]'(node) {
86
+ if (!isHandleRoot(node.callee.object))
87
+ return;
88
+ const method = node.callee.property.name;
89
+ if (WRITE_METHODS.has(method)) {
90
+ const where = misplaced(node);
91
+ if (where !== null)
92
+ context.report({ node: node.callee.property, messageId: 'write', data: { method: `.${method}()`, where } });
93
+ }
94
+ else if (method === 'raw') {
95
+ const sql = node.arguments[0];
96
+ const text = sql?.type === 'Literal' ? String(sql.value) : sql?.type === 'TemplateLiteral' ? sql.quasis[0].value.cooked : '';
97
+ const where = WRITE_SQL.test(text) ? misplaced(node) : null;
98
+ if (where !== null)
99
+ context.report({ node: sql, messageId: 'rawWrite', data: { where } });
100
+ }
101
+ },
102
+ };
103
+ },
104
+ });
105
+ //# sourceMappingURL=db-writes-in-repositories.js.map