@shivaedev/quality 0.27.1 → 0.28.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.
- package/CHANGELOG.md +6 -0
- package/README.md +20 -0
- package/dist/rules/builtIn.d.ts +7 -1
- package/dist/rules/builtIn.d.ts.map +1 -1
- package/dist/rules/builtIn.js +16 -0
- package/dist/rules/builtIn.js.map +1 -1
- package/dist/rules/stories/authSql.d.ts +4 -0
- package/dist/rules/stories/authSql.d.ts.map +1 -0
- package/dist/rules/stories/authSql.js +102 -0
- package/dist/rules/stories/authSql.js.map +1 -0
- package/dist/rules/stories/httpStatus.d.ts +4 -0
- package/dist/rules/stories/httpStatus.d.ts.map +1 -0
- package/dist/rules/stories/httpStatus.js +47 -0
- package/dist/rules/stories/httpStatus.js.map +1 -0
- package/dist/rules/stories/matchesAny.d.ts +2 -0
- package/dist/rules/stories/matchesAny.d.ts.map +1 -0
- package/dist/rules/stories/matchesAny.js +5 -0
- package/dist/rules/stories/matchesAny.js.map +1 -0
- package/dist/rules/stories/namedValues.d.ts +2 -0
- package/dist/rules/stories/namedValues.d.ts.map +1 -0
- package/dist/rules/stories/namedValues.js +15 -0
- package/dist/rules/stories/namedValues.js.map +1 -0
- package/dist/rules/stories/noExpect.d.ts +2 -0
- package/dist/rules/stories/noExpect.d.ts.map +1 -0
- package/dist/rules/stories/noExpect.js +33 -0
- package/dist/rules/stories/noExpect.js.map +1 -0
- package/dist/rules/stories/noLimitImports.d.ts +2 -0
- package/dist/rules/stories/noLimitImports.d.ts.map +1 -0
- package/dist/rules/stories/noLimitImports.js +66 -0
- package/dist/rules/stories/noLimitImports.js.map +1 -0
- package/dist/rules/stories/queueSql.d.ts +2 -0
- package/dist/rules/stories/queueSql.d.ts.map +1 -0
- package/dist/rules/stories/queueSql.js +15 -0
- package/dist/rules/stories/queueSql.js.map +1 -0
- package/dist/rules/stories/specHelpers.d.ts +2 -0
- package/dist/rules/stories/specHelpers.d.ts.map +1 -0
- package/dist/rules/stories/specHelpers.js +9 -0
- package/dist/rules/stories/specHelpers.js.map +1 -0
- package/dist/rules/stories/sqlText.d.ts +8 -0
- package/dist/rules/stories/sqlText.d.ts.map +1 -0
- package/dist/rules/stories/sqlText.js +86 -0
- package/dist/rules/stories/sqlText.js.map +1 -0
- package/dist/rules/stories/supportImports.d.ts +4 -0
- package/dist/rules/stories/supportImports.d.ts.map +1 -0
- package/dist/rules/stories/supportImports.js +28 -0
- package/dist/rules/stories/supportImports.js.map +1 -0
- package/dist/rules/stories/syntax.d.ts +11 -0
- package/dist/rules/stories/syntax.d.ts.map +1 -0
- package/dist/rules/stories/syntax.js +71 -0
- package/dist/rules/stories/syntax.js.map +1 -0
- package/package.json +1 -1
- package/src/rules/builtIn.ts +16 -0
- package/src/rules/stories/authSql.ts +117 -0
- package/src/rules/stories/httpStatus.ts +56 -0
- package/src/rules/stories/matchesAny.ts +5 -0
- package/src/rules/stories/namedValues.ts +21 -0
- package/src/rules/stories/noExpect.ts +39 -0
- package/src/rules/stories/noLimitImports.ts +80 -0
- package/src/rules/stories/queueSql.ts +18 -0
- package/src/rules/stories/specHelpers.ts +10 -0
- package/src/rules/stories/sqlText.ts +100 -0
- package/src/rules/stories/supportImports.ts +31 -0
- package/src/rules/stories/syntax.ts +80 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.28.0 - 2026-10-18
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Enforce story boundaries: queue and Better Auth SQL ownership, expectations outside test-support, helpers outside specs, an allowlist of public modules for test-support, HTTP error translation at named edge modules, named numeric expectations and stories independent of implementation caps. Configure `boundaries/auth-sql.options.owners`, `boundaries/http-status.options.edge` and `tests/support-imports.options.modules` with repository paths or glob patterns.
|
|
8
|
+
|
|
3
9
|
## 0.27.1 - 2026-10-18
|
|
4
10
|
|
|
5
11
|
### Changed
|
package/README.md
CHANGED
|
@@ -134,6 +134,26 @@ Every package tests its real engine through its own story kit, so setup and acti
|
|
|
134
134
|
|
|
135
135
|
Quality's `tests/story-setup` rule reports top-level test functions named with the words `seed`, `make`, `build` or `setup`, and fixture-writing calls imported from Node's `fs` or `fs/promises`. It leaves source and `test-support/` files alone. This is a check for those syntax patterns; a passing test file still needs review of what it tests.
|
|
136
136
|
|
|
137
|
+
Specs keep expectations visible. `tests/spec-helpers` moves all module-level helper functions out of specs and into the kit; `tests/no-expect-in-support` moves `expect` calls into the spec. `tests/named-values` rejects numeric literals passed to `toBe` or `toHaveLength` in specs: assert the observed items or give the value a domain name. `tests/no-limit-imports` rejects importing `MAX_*` constants from a `limits` module into specs, so a story proves behavior instead of copying an implementation cap.
|
|
138
|
+
|
|
139
|
+
`tests/support-imports` checks resolved repository imports from `test-support/` against `options.modules`, an allowlist of repository-relative module paths or glob patterns. Imports within test-support and imports of external packages remain available for the harness. Name only the public surface the kit drives, such as the host, schemas and queue definitions. The default empty list allows no production repository modules; aliases, dynamic imports and re-exports resolve through the same import graph as the other import rules.
|
|
140
|
+
|
|
141
|
+
`boundaries/queue-sql` rejects SQL mentioning the private `pgboss.` schema. Apps use their queue definitions and let workers process jobs; there is no queue-purge API by design. `boundaries/auth-sql` rejects SQL accessing Better Auth's `user`, `account`, `session` and `verification` tables. Apps use platform's auth API; `options.owners` names platform's own auth implementation and its migrations. These rules inspect SQL templates, literal query calls and query variables, and inventoried `.sql` files. They do not evaluate arbitrary runtime SQL construction.
|
|
142
|
+
|
|
143
|
+
`boundaries/http-status` rejects comparisons with HTTP error status numbers, switch cases and literal status-list membership outside `options.edge`. Domain code handles typed errors; the named HTTP edge translates them into transport responses. Process exit codes remain ordinary domain values.
|
|
144
|
+
|
|
145
|
+
Declare these architectural facts in the repository's quality config; paths and patterns are relative to that config's directory:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
export default defineConfig({
|
|
149
|
+
rules: {
|
|
150
|
+
"boundaries/auth-sql": { options: { owners: ["platform/auth/**", "platform/auth-migrations/**"] } },
|
|
151
|
+
"boundaries/http-status": { options: { edge: ["src/http/**"] } },
|
|
152
|
+
"tests/support-imports": { options: { modules: ["src/host.ts", "src/schemas.ts", "src/queues.ts"] } },
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
137
157
|
### 3. Every day: repair findings and keep debt current
|
|
138
158
|
|
|
139
159
|
```sh
|
package/dist/rules/builtIn.d.ts
CHANGED
|
@@ -28,5 +28,11 @@ export declare const builtInRules: readonly [import("../rule.ts").Rule<"structur
|
|
|
28
28
|
readonly suites?: readonly string[];
|
|
29
29
|
}>, import("../rule.ts").Rule<"tests/colocated", {
|
|
30
30
|
readonly suites?: readonly string[];
|
|
31
|
-
}>, import("../rule.ts").Rule<"tests/story-setup", undefined>, import("../rule.ts").Rule<"tests/story-specs", undefined
|
|
31
|
+
}>, import("../rule.ts").Rule<"tests/story-setup", undefined>, import("../rule.ts").Rule<"tests/story-specs", undefined>, import("../rule.ts").Rule<"boundaries/queue-sql", undefined>, import("../rule.ts").Rule<"boundaries/auth-sql", {
|
|
32
|
+
readonly owners?: readonly string[];
|
|
33
|
+
}>, import("../rule.ts").Rule<"tests/no-expect-in-support", undefined>, import("../rule.ts").Rule<"tests/spec-helpers", undefined>, import("../rule.ts").Rule<"tests/support-imports", {
|
|
34
|
+
readonly modules?: readonly string[];
|
|
35
|
+
}>, import("../rule.ts").Rule<"boundaries/http-status", {
|
|
36
|
+
readonly edge?: readonly string[];
|
|
37
|
+
}>, import("../rule.ts").Rule<"tests/named-values", undefined>, import("../rule.ts").Rule<"tests/no-limit-imports", undefined>];
|
|
32
38
|
//# sourceMappingURL=builtIn.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtIn.d.ts","sourceRoot":"","sources":["../../src/rules/builtIn.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"builtIn.d.ts","sourceRoot":"","sources":["../../src/rules/builtIn.ts"],"names":[],"mappings":"AA0CA,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+HA0Cf,CAAC"}
|
package/dist/rules/builtIn.js
CHANGED
|
@@ -16,8 +16,16 @@ import { importsManifest } from "#rules/imports/manifest.ts";
|
|
|
16
16
|
import { noTestCode } from "#rules/imports/noTestCode.ts";
|
|
17
17
|
import { importsResolvable } from "#rules/imports/resolvable.ts";
|
|
18
18
|
import { importsRuntime } from "#rules/imports/runtime.ts";
|
|
19
|
+
import { authSql } from "#rules/stories/authSql.ts";
|
|
20
|
+
import { httpStatus } from "#rules/stories/httpStatus.ts";
|
|
21
|
+
import { namedValues } from "#rules/stories/namedValues.ts";
|
|
22
|
+
import { noExpect } from "#rules/stories/noExpect.ts";
|
|
23
|
+
import { noLimitImports } from "#rules/stories/noLimitImports.ts";
|
|
24
|
+
import { queueSql } from "#rules/stories/queueSql.ts";
|
|
19
25
|
import { storySetup } from "#rules/stories/setup.ts";
|
|
26
|
+
import { specHelpers } from "#rules/stories/specHelpers.ts";
|
|
20
27
|
import { storySpecs } from "#rules/stories/specs.ts";
|
|
28
|
+
import { supportImports } from "#rules/stories/supportImports.ts";
|
|
21
29
|
import { biomeOverrides } from "#rules/suppressions/biomeOverrides.ts";
|
|
22
30
|
import { biomeRecommended } from "#rules/suppressions/biomeRecommended.ts";
|
|
23
31
|
import { noDoubleCast } from "#rules/suppressions/noDoubleCast.ts";
|
|
@@ -65,5 +73,13 @@ export const builtInRules = [
|
|
|
65
73
|
testsColocated,
|
|
66
74
|
storySetup,
|
|
67
75
|
storySpecs,
|
|
76
|
+
queueSql,
|
|
77
|
+
authSql,
|
|
78
|
+
noExpect,
|
|
79
|
+
specHelpers,
|
|
80
|
+
supportImports,
|
|
81
|
+
httpStatus,
|
|
82
|
+
namedValues,
|
|
83
|
+
noLimitImports,
|
|
68
84
|
];
|
|
69
85
|
//# sourceMappingURL=builtIn.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtIn.js","sourceRoot":"","sources":["../../src/rules/builtIn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,6CAA6C,CAAC;AACnF,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,MAAM,CAAC,MAAM,YAAY,GAAG;IAC3B,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,eAAe;IACf,aAAa;IACb,QAAQ;IACR,MAAM;IACN,mBAAmB;IACnB,UAAU;IACV,QAAQ;IACR,YAAY;IACZ,oBAAoB;IACpB,cAAc;IACd,gBAAgB;IAChB,KAAK;IACL,YAAY;IACZ,YAAY;IACZ,cAAc;IACd,iBAAiB;IACjB,eAAe;IACf,cAAc;IACd,UAAU;IACV,eAAe;IACf,oBAAoB;IACpB,gBAAgB;IAChB,WAAW;IACX,YAAY;IACZ,UAAU;IACV,WAAW;IACX,cAAc;IACd,UAAU;IACV,UAAU;
|
|
1
|
+
{"version":3,"file":"builtIn.js","sourceRoot":"","sources":["../../src/rules/builtIn.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authSql.d.ts","sourceRoot":"","sources":["../../../src/rules/stories/authSql.ts"],"names":[],"mappings":"AAuGA,eAAO,MAAM,OAAO;;EAalB,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import { defineRule } from "#rule.ts";
|
|
3
|
+
import { matchesAny } from "./matchesAny.js";
|
|
4
|
+
import { sqlTexts } from "./sqlText.js";
|
|
5
|
+
const AuthSqlOptions = Schema.Struct({
|
|
6
|
+
owners: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
|
|
7
|
+
});
|
|
8
|
+
const TOKENS = /--[^\n]*|\/\*[\s\S]*?\*\/|'(?:''|[^'])*'|"(?:""|[^"])*"|[\w$]+|[.,;()]/gu;
|
|
9
|
+
const TABLE_START = new Set(["from", "join", "update", "into", "table", "truncate", "references", "using"]);
|
|
10
|
+
const TABLE_END = new Set(["where", "group", "order", "having", "limit", "returning", "set", "values", "on", ";"]);
|
|
11
|
+
const SKIP = new Set(["if", "not", "exists", "only", "table"]);
|
|
12
|
+
const AUTH_TABLES = new Set(["user", "account", "session", "verification"]);
|
|
13
|
+
const EXPRESSION_FROM = new Set(["extract", "overlay", "substring", "trim"]);
|
|
14
|
+
const NON_TABLE_TARGETS = new Set([
|
|
15
|
+
"all",
|
|
16
|
+
"database",
|
|
17
|
+
"domain",
|
|
18
|
+
"foreign",
|
|
19
|
+
"function",
|
|
20
|
+
"language",
|
|
21
|
+
"large",
|
|
22
|
+
"parameter",
|
|
23
|
+
"procedure",
|
|
24
|
+
"routine",
|
|
25
|
+
"schema",
|
|
26
|
+
"sequence",
|
|
27
|
+
"tablespace",
|
|
28
|
+
"type",
|
|
29
|
+
]);
|
|
30
|
+
function tableAt(tokens, index) {
|
|
31
|
+
let next = index;
|
|
32
|
+
while (SKIP.has(tokens[next]?.toLowerCase() ?? "")) {
|
|
33
|
+
const only = tokens[next]?.toLowerCase() === "only";
|
|
34
|
+
next += 1;
|
|
35
|
+
if (only && tokens[next] === "(") {
|
|
36
|
+
next += 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
while (tokens[next + 1] === ".") {
|
|
40
|
+
next += 2;
|
|
41
|
+
}
|
|
42
|
+
return (tokens[next] ?? "").replaceAll('"', "").toLowerCase();
|
|
43
|
+
}
|
|
44
|
+
function tableBoundary(tokens, index, statementStart, fromIsTable) {
|
|
45
|
+
const word = tokens[index]?.toLowerCase() ?? "";
|
|
46
|
+
const permissions = ["grant", "revoke"].includes(tokens[statementStart]?.toLowerCase() ?? "");
|
|
47
|
+
const roleTarget = permissions && (word === "to" || word === "from");
|
|
48
|
+
const permissionTarget = permissions && word === "on" && !NON_TABLE_TARGETS.has(tokens[index + 1]?.toLowerCase() ?? "");
|
|
49
|
+
const indexTarget = word === "on" && tokens.slice(statementStart, index).some((part) => part.toLowerCase() === "index");
|
|
50
|
+
const tableClause = TABLE_START.has(word) && (word !== "from" || fromIsTable);
|
|
51
|
+
return {
|
|
52
|
+
ends: TABLE_END.has(word) || roleTarget,
|
|
53
|
+
starts: !roleTarget && (tableClause || indexTarget || permissionTarget),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function advanceContext(context, token, index, previous) {
|
|
57
|
+
if (token === ";") {
|
|
58
|
+
context.statementStart = index + 1;
|
|
59
|
+
context.expressionFromDepths.clear();
|
|
60
|
+
}
|
|
61
|
+
if (token === "(") {
|
|
62
|
+
context.depth += 1;
|
|
63
|
+
if (EXPRESSION_FROM.has(previous?.toLowerCase() ?? "")) {
|
|
64
|
+
context.expressionFromDepths.add(context.depth);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (token === ")") {
|
|
68
|
+
context.tablesAtDepth.delete(context.depth);
|
|
69
|
+
context.expressionFromDepths.delete(context.depth);
|
|
70
|
+
context.depth -= 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function authTableIn(text) {
|
|
74
|
+
const tokens = [...text.matchAll(TOKENS)]
|
|
75
|
+
.map(([token]) => token)
|
|
76
|
+
.filter((token) => !(token.startsWith("--") || token.startsWith("/*") || token.startsWith("'")));
|
|
77
|
+
const context = { depth: 0, expressionFromDepths: new Set(), statementStart: 0, tablesAtDepth: new Set() };
|
|
78
|
+
return tokens.some((token, index) => {
|
|
79
|
+
advanceContext(context, token, index, tokens[index - 1]);
|
|
80
|
+
const boundary = tableBoundary(tokens, index, context.statementStart, !context.expressionFromDepths.has(context.depth));
|
|
81
|
+
if (boundary.ends) {
|
|
82
|
+
context.tablesAtDepth.delete(context.depth);
|
|
83
|
+
}
|
|
84
|
+
if (boundary.starts) {
|
|
85
|
+
context.tablesAtDepth.add(context.depth);
|
|
86
|
+
}
|
|
87
|
+
return (boundary.starts || (token === "," && context.tablesAtDepth.has(context.depth))) && AUTH_TABLES.has(tableAt(tokens, index + 1));
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export const authSql = defineRule({
|
|
91
|
+
check: async ({ options, ...inputs }) => (await sqlTexts(inputs))
|
|
92
|
+
.filter((query) => !matchesAny(query.file, options.owners) && authTableIn(query.text))
|
|
93
|
+
.map((query) => ({
|
|
94
|
+
file: query.file,
|
|
95
|
+
line: query.line,
|
|
96
|
+
message: "SQL accesses a Better Auth table (user, account, session or verification). Use platform's auth API; only platform's auth implementation and its migrations, named in the repository's auth-sql owners, own those tables.",
|
|
97
|
+
})),
|
|
98
|
+
description: "Apps use platform's auth API instead of querying or modifying Better Auth's tables.",
|
|
99
|
+
id: "boundaries/auth-sql",
|
|
100
|
+
options: Schema.toStandardSchemaV1(AuthSqlOptions, { parseOptions: { errors: "all", onExcessProperty: "error" } }),
|
|
101
|
+
});
|
|
102
|
+
//# sourceMappingURL=authSql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authSql.js","sourceRoot":"","sources":["../../../src/rules/stories/authSql.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"httpStatus.d.ts","sourceRoot":"","sources":["../../../src/rules/stories/httpStatus.ts"],"names":[],"mappings":"AA+CA,eAAO,MAAM,UAAU;;EAQrB,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Effect, Schema } from "effect";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
import { defineRule } from "#rule.ts";
|
|
4
|
+
import { matchesAny } from "./matchesAny.js";
|
|
5
|
+
import { memberName, nodesWhere, numericValue, syntaxFindings, unwrapped } from "./syntax.js";
|
|
6
|
+
const MESSAGE = "Branches on an HTTP error status outside the HTTP edge. Handle the typed domain error here and translate it to an HTTP status in the configured edge module.";
|
|
7
|
+
const HttpStatusOptions = Schema.Struct({
|
|
8
|
+
edge: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
|
|
9
|
+
});
|
|
10
|
+
const COMPARISONS = new Set([
|
|
11
|
+
ts.SyntaxKind.EqualsEqualsToken,
|
|
12
|
+
ts.SyntaxKind.EqualsEqualsEqualsToken,
|
|
13
|
+
ts.SyntaxKind.ExclamationEqualsToken,
|
|
14
|
+
ts.SyntaxKind.ExclamationEqualsEqualsToken,
|
|
15
|
+
ts.SyntaxKind.LessThanToken,
|
|
16
|
+
ts.SyntaxKind.LessThanEqualsToken,
|
|
17
|
+
ts.SyntaxKind.GreaterThanToken,
|
|
18
|
+
ts.SyntaxKind.GreaterThanEqualsToken,
|
|
19
|
+
]);
|
|
20
|
+
function isErrorStatus(node) {
|
|
21
|
+
const number = numericValue(node);
|
|
22
|
+
return number !== undefined && number >= 400 && number <= 599;
|
|
23
|
+
}
|
|
24
|
+
function statusBranch(node) {
|
|
25
|
+
if (ts.isBinaryExpression(node) && COMPARISONS.has(node.operatorToken.kind)) {
|
|
26
|
+
return (memberName(node.left) === "status" && isErrorStatus(node.right)) || (memberName(node.right) === "status" && isErrorStatus(node.left));
|
|
27
|
+
}
|
|
28
|
+
if (ts.isCaseClause(node) && isErrorStatus(node.expression) && ts.isCaseBlock(node.parent) && ts.isSwitchStatement(node.parent.parent)) {
|
|
29
|
+
return memberName(node.parent.parent.expression) === "status";
|
|
30
|
+
}
|
|
31
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "includes") {
|
|
32
|
+
const receiver = unwrapped(node.expression.expression);
|
|
33
|
+
return (ts.isArrayLiteralExpression(receiver)
|
|
34
|
+
&& receiver.elements.some(isErrorStatus)
|
|
35
|
+
&& node.arguments.some((argument) => memberName(argument) === "status"));
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
export const httpStatus = defineRule({
|
|
40
|
+
check: ({ sources, options }) => sources
|
|
41
|
+
.filter((file) => !matchesAny(file.path, options.edge))
|
|
42
|
+
.flatMap((file) => syntaxFindings(file, (source) => nodesWhere(source, statusBranch), MESSAGE)),
|
|
43
|
+
description: "HTTP status numbers belong at the repository's HTTP edge; domain code handles typed errors.",
|
|
44
|
+
id: "boundaries/http-status",
|
|
45
|
+
options: Schema.toStandardSchemaV1(HttpStatusOptions, { parseOptions: { errors: "all", onExcessProperty: "error" } }),
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=httpStatus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"httpStatus.js","sourceRoot":"","sources":["../../../src/rules/stories/httpStatus.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matchesAny.d.ts","sourceRoot":"","sources":["../../../src/rules/stories/matchesAny.ts"],"names":[],"mappings":"AAEA,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAE7E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matchesAny.js","sourceRoot":"","sources":["../../../src/rules/stories/matchesAny.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,QAA2B;IACnE,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;AAC/D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"namedValues.d.ts","sourceRoot":"","sources":["../../../src/rules/stories/namedValues.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,WAAW,0DAKtB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { defineRule } from "#rule.ts";
|
|
3
|
+
import { isSpec, memberName, nodesWhere, numericValue, syntaxFindings } from "./syntax.js";
|
|
4
|
+
const MESSAGE = "Passes a numeric literal to toBe or toHaveLength. Assert the observed items, or give the expected value a domain name before asserting it.";
|
|
5
|
+
function numericAssertion(node) {
|
|
6
|
+
return (ts.isCallExpression(node)
|
|
7
|
+
&& ["toBe", "toHaveLength"].includes(memberName(node.expression) ?? "")
|
|
8
|
+
&& node.arguments.some((argument) => numericValue(argument) !== undefined));
|
|
9
|
+
}
|
|
10
|
+
export const namedValues = defineRule({
|
|
11
|
+
check: ({ sources }) => sources.filter((file) => isSpec(file.path)).flatMap((file) => syntaxFindings(file, (source) => nodesWhere(source, numericAssertion), MESSAGE)),
|
|
12
|
+
description: "Stories assert observed items or named values instead of bare counts and numbers.",
|
|
13
|
+
id: "tests/named-values",
|
|
14
|
+
});
|
|
15
|
+
//# sourceMappingURL=namedValues.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"namedValues.js","sourceRoot":"","sources":["../../../src/rules/stories/namedValues.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE3F,MAAM,OAAO,GACZ,4IAA4I,CAAC;AAE9I,SAAS,gBAAgB,CAAC,IAAa;IACtC,OAAO,CACN,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC;WACtB,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;WACpE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,SAAS,CAAC,CAC1E,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC;IACrC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CACtB,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAAE,OAAO,CAAC,CAAC;IAC/I,WAAW,EAAE,mFAAmF;IAChG,EAAE,EAAE,oBAAoB;CACxB,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noExpect.d.ts","sourceRoot":"","sources":["../../../src/rules/stories/noExpect.ts"],"names":[],"mappings":"AAkCA,eAAO,MAAM,QAAQ,kEAInB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { isTestSupport } from "#naming/testName.ts";
|
|
3
|
+
import { defineRule } from "#rule.ts";
|
|
4
|
+
import { memberName, nodesWhere, syntaxFindings, unwrapped } from "./syntax.js";
|
|
5
|
+
const MESSAGE = "Calls expect in test-support. Return the engine's observed state from the verb and put the expectation in the spec; throw if an action cannot run.";
|
|
6
|
+
function assertionReference(expression, names) {
|
|
7
|
+
const node = unwrapped(expression);
|
|
8
|
+
if (ts.isIdentifier(node)) {
|
|
9
|
+
return names.has(node.text);
|
|
10
|
+
}
|
|
11
|
+
return (memberName(node) === "expect"
|
|
12
|
+
|| ((ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node)) && assertionReference(node.expression, names)));
|
|
13
|
+
}
|
|
14
|
+
function assertionsIn(source) {
|
|
15
|
+
const names = new Set(["expect"]);
|
|
16
|
+
for (const statement of source.statements) {
|
|
17
|
+
const bindings = ts.isImportDeclaration(statement) ? statement.importClause?.namedBindings : undefined;
|
|
18
|
+
if (bindings !== undefined && ts.isNamedImports(bindings)) {
|
|
19
|
+
for (const element of bindings.elements) {
|
|
20
|
+
if ((element.propertyName ?? element.name).text === "expect") {
|
|
21
|
+
names.add(element.name.text);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return nodesWhere(source, (node) => ts.isCallExpression(node) && assertionReference(node.expression, names));
|
|
27
|
+
}
|
|
28
|
+
export const noExpect = defineRule({
|
|
29
|
+
check: ({ sources }) => sources.filter((file) => isTestSupport(file.path)).flatMap((file) => syntaxFindings(file, assertionsIn, MESSAGE)),
|
|
30
|
+
description: "Test-support performs actions and exposes observations; specs own expectations.",
|
|
31
|
+
id: "tests/no-expect-in-support",
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=noExpect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noExpect.js","sourceRoot":"","sources":["../../../src/rules/stories/noExpect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEhF,MAAM,OAAO,GACZ,oJAAoJ,CAAC;AAEtJ,SAAS,kBAAkB,CAAC,UAAyB,EAAE,KAA0B;IAChF,MAAM,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;IACnC,IAAI,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,CACN,UAAU,CAAC,IAAI,CAAC,KAAK,QAAQ;WAC1B,CAAC,CAAC,EAAE,CAAC,0BAA0B,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,yBAAyB,CAAC,IAAI,CAAC,CAAC,IAAI,kBAAkB,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAC9H,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,MAAqB;IAC1C,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClC,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QAC3C,MAAM,QAAQ,GAAG,EAAE,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC;QACvG,IAAI,QAAQ,KAAK,SAAS,IAAI,EAAE,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC3D,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;gBACzC,IAAI,CAAC,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC9D,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC9B,CAAC;YACF,CAAC;QACF,CAAC;IACF,CAAC;IACD,OAAO,UAAU,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,kBAAkB,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC;AAC9G,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC;IAClC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IACzI,WAAW,EAAE,iFAAiF;IAC9F,EAAE,EAAE,4BAA4B;CAChC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noLimitImports.d.ts","sourceRoot":"","sources":["../../../src/rules/stories/noLimitImports.ts"],"names":[],"mappings":"AA2EA,eAAO,MAAM,cAAc,8DAIzB,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { defineRule } from "#rule.ts";
|
|
3
|
+
import { isSpec, memberName, nodesWhere, syntaxFindings, unwrapped } from "./syntax.js";
|
|
4
|
+
const MESSAGE = "Imports a MAX_* constant from a limits module. Tell a story about the observed behavior instead of asserting a copy of the implementation's cap.";
|
|
5
|
+
const LIMITS_MODULE = /(?:^|[/#])limits(?:\.[cm]?[jt]s)?(?:\/|$)/u;
|
|
6
|
+
const MAX_CONSTANT = /^MAX_/u;
|
|
7
|
+
function literalLimitsImport(expression) {
|
|
8
|
+
const node = unwrapped(expression);
|
|
9
|
+
if (ts.isAwaitExpression(node)) {
|
|
10
|
+
return literalLimitsImport(node.expression);
|
|
11
|
+
}
|
|
12
|
+
return (ts.isCallExpression(node)
|
|
13
|
+
&& node.expression.kind === ts.SyntaxKind.ImportKeyword
|
|
14
|
+
&& node.arguments[0] !== undefined
|
|
15
|
+
&& ts.isStringLiteralLike(node.arguments[0])
|
|
16
|
+
&& LIMITS_MODULE.test(node.arguments[0].text));
|
|
17
|
+
}
|
|
18
|
+
function capBinding(binding) {
|
|
19
|
+
if (binding.dotDotDotToken !== undefined) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
const name = binding.propertyName ?? binding.name;
|
|
23
|
+
if (ts.isComputedPropertyName(name)) {
|
|
24
|
+
return ts.isStringLiteralLike(name.expression) && MAX_CONSTANT.test(name.expression.text);
|
|
25
|
+
}
|
|
26
|
+
return (ts.isIdentifier(name) || ts.isStringLiteralLike(name)) && MAX_CONSTANT.test(name.text);
|
|
27
|
+
}
|
|
28
|
+
function capImports(source) {
|
|
29
|
+
const namespaces = new Set();
|
|
30
|
+
const imports = source.statements.flatMap((statement) => {
|
|
31
|
+
if (!(ts.isImportDeclaration(statement) && ts.isStringLiteralLike(statement.moduleSpecifier) && LIMITS_MODULE.test(statement.moduleSpecifier.text))) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
const bindings = statement.importClause?.namedBindings;
|
|
35
|
+
if (bindings !== undefined && ts.isNamespaceImport(bindings)) {
|
|
36
|
+
namespaces.add(bindings.name.text);
|
|
37
|
+
}
|
|
38
|
+
return bindings !== undefined && ts.isNamedImports(bindings)
|
|
39
|
+
? bindings.elements.filter((element) => MAX_CONSTANT.test((element.propertyName ?? element.name).text))
|
|
40
|
+
: [];
|
|
41
|
+
});
|
|
42
|
+
const dynamic = nodesWhere(source, ts.isVariableDeclaration).flatMap((node) => {
|
|
43
|
+
if (!ts.isVariableDeclaration(node) || node.initializer === undefined || !literalLimitsImport(node.initializer)) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
if (ts.isIdentifier(node.name)) {
|
|
47
|
+
namespaces.add(node.name.text);
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
return ts.isObjectBindingPattern(node.name) ? node.name.elements.filter(capBinding) : [];
|
|
51
|
+
});
|
|
52
|
+
return [
|
|
53
|
+
...imports,
|
|
54
|
+
...dynamic,
|
|
55
|
+
...nodesWhere(source, (node) => (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node))
|
|
56
|
+
&& ts.isIdentifier(node.expression)
|
|
57
|
+
&& namespaces.has(node.expression.text)
|
|
58
|
+
&& MAX_CONSTANT.test(memberName(node) ?? "")),
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
export const noLimitImports = defineRule({
|
|
62
|
+
check: ({ sources }) => sources.filter((file) => isSpec(file.path)).flatMap((file) => syntaxFindings(file, capImports, MESSAGE)),
|
|
63
|
+
description: "Stories prove behavior instead of importing implementation caps from limits modules.",
|
|
64
|
+
id: "tests/no-limit-imports",
|
|
65
|
+
});
|
|
66
|
+
//# sourceMappingURL=noLimitImports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"noLimitImports.js","sourceRoot":"","sources":["../../../src/rules/stories/noLimitImports.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queueSql.d.ts","sourceRoot":"","sources":["../../../src/rules/stories/queueSql.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,QAAQ,4DAYnB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineRule } from "#rule.ts";
|
|
2
|
+
import { sqlTexts } from "./sqlText.js";
|
|
3
|
+
const QUEUE_SCHEMA = /\bpgboss\b"?\s*\./iu;
|
|
4
|
+
export const queueSql = defineRule({
|
|
5
|
+
check: async (inputs) => (await sqlTexts(inputs))
|
|
6
|
+
.filter((query) => QUEUE_SCHEMA.test(query.text))
|
|
7
|
+
.map((query) => ({
|
|
8
|
+
file: query.file,
|
|
9
|
+
line: query.line,
|
|
10
|
+
message: "SQL mentions pgboss. Use the app's public queue definitions and let workers process jobs; apps never edit or purge queue tables, and there is no queue-purge API by design.",
|
|
11
|
+
})),
|
|
12
|
+
description: "Apps never read, edit or purge pg-boss's private queue schema.",
|
|
13
|
+
id: "boundaries/queue-sql",
|
|
14
|
+
});
|
|
15
|
+
//# sourceMappingURL=queueSql.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queueSql.js","sourceRoot":"","sources":["../../../src/rules/stories/queueSql.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,MAAM,YAAY,GAAG,qBAAqB,CAAC;AAE3C,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC;IAClC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CACvB,CAAC,MAAM,QAAQ,CAAC,MAAM,CAAC,CAAC;SACtB,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;SAChD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,OAAO,EACN,6KAA6K;KAC9K,CAAC,CAAC;IACL,WAAW,EAAE,gEAAgE;IAC7E,EAAE,EAAE,sBAAsB;CAC1B,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specHelpers.d.ts","sourceRoot":"","sources":["../../../src/rules/stories/specHelpers.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,WAAW,0DAItB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { defineRule } from "#rule.ts";
|
|
2
|
+
import { isSpec, syntaxFindings, topLevelHelpers } from "./syntax.js";
|
|
3
|
+
const MESSAGE = "Declares a top-level helper in a spec. Move the action into the story kit's verbs or the setup into its traits in test-support/.";
|
|
4
|
+
export const specHelpers = defineRule({
|
|
5
|
+
check: ({ sources }) => sources.filter((file) => isSpec(file.path)).flatMap((file) => syntaxFindings(file, topLevelHelpers, MESSAGE)),
|
|
6
|
+
description: "Specs state expectations; story-kit traits and verbs in test-support own helpers.",
|
|
7
|
+
id: "tests/spec-helpers",
|
|
8
|
+
});
|
|
9
|
+
//# sourceMappingURL=specHelpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specHelpers.js","sourceRoot":"","sources":["../../../src/rules/stories/specHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,OAAO,GAAG,kIAAkI,CAAC;AAEnJ,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC;IACrC,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;IACrI,WAAW,EAAE,mFAAmF;IAChG,EAAE,EAAE,oBAAoB;CACxB,CAAC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RuleInputs } from "#rule.ts";
|
|
2
|
+
export interface SqlText {
|
|
3
|
+
readonly file: string;
|
|
4
|
+
readonly line: number;
|
|
5
|
+
readonly text: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function sqlTexts(inputs: RuleInputs): Promise<readonly SqlText[]>;
|
|
8
|
+
//# sourceMappingURL=sqlText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlText.d.ts","sourceRoot":"","sources":["../../../src/rules/stories/sqlText.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAI3C,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAyED,wBAAsB,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,OAAO,EAAE,CAAC,CAiB9E"}
|