@veda-hq/eslint-plugin-shuddhi 0.2.0 → 0.6.4
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
|
@@ -50,7 +50,7 @@ of globs, with a `namespace` parameter:
|
|
|
50
50
|
| input parsing | `no-hand-rolled-argv`, `env-through-schema`, `strict-scalar-parse`, `schema-parse-untrusted`, `schema-satisfies-contract`, `prisma-json-through-schema`, `no-regex-structured-parse` | `inputParsingConfigs` |
|
|
51
51
|
| server boundary | `nest-body-dto`, `dto-field-constraints`, `no-local-api-contract`, `dto-implements-contract`, `no-capability-literal`, `compare-and-set` | `boundaryBlocks` |
|
|
52
52
|
| NestJS and Prisma | `no-raw-sql-interpolation`, `nest-http-exception`, `prisma-transaction-multiwrite`, `nest-param-pipe`, `no-unbound-test-app`, `require-orderby-findfirst`, `no-self-mock-sut` | none |
|
|
53
|
-
| test lint |
|
|
53
|
+
| test lint | 43 rules on test structure, vacuous assertions (among them `jsonnull-in-matcher`: a Prisma null sentinel inside `toMatchObject` or `objectContaining`), process hygiene, fixtures and fakes; and for scripts, `no-import-meta-dirname-cjs` (`import.meta.dirname` in a file tsx runs as CommonJS) | `testLintBlocks` |
|
|
54
54
|
|
|
55
55
|
Every name of a repository's own helper (the safe-logging module, the child
|
|
56
56
|
process runner, the argument parser, the contracts package) is a rule option,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veda-hq/eslint-plugin-shuddhi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "ESLint rules for code coding agents write: error handling, input parsing, NestJS and Prisma boundaries, and test quality. Each rule closes a defect class reviewers kept finding after push.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
package/src/test-lint/configs.js
CHANGED
|
@@ -59,6 +59,7 @@ export const TEST_FILE_RULES = {
|
|
|
59
59
|
[id('no-partial-git-mask')]: 'error',
|
|
60
60
|
[id('env-via-stub')]: 'error',
|
|
61
61
|
[id('detached-teardown')]: 'error',
|
|
62
|
+
[id('jsonnull-in-matcher')]: 'error',
|
|
62
63
|
};
|
|
63
64
|
|
|
64
65
|
/** Rules for Nest server tests only. */
|
|
@@ -67,10 +68,14 @@ export const SERVER_TEST_RULES = {
|
|
|
67
68
|
[id('e2e-uses-supertest')]: 'error',
|
|
68
69
|
};
|
|
69
70
|
|
|
70
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Rules for tooling scripts (SHELL-09): the non-test sources `testLintBlocks`
|
|
73
|
+
* binds to its `scripts` globs.
|
|
74
|
+
*/
|
|
71
75
|
export const SCRIPT_RULES = {
|
|
72
76
|
[id('no-process-exit')]: 'error',
|
|
73
77
|
[id('cli-entry-helper')]: 'error',
|
|
78
|
+
[id('no-import-meta-dirname-cjs')]: 'error',
|
|
74
79
|
};
|
|
75
80
|
|
|
76
81
|
/** Rules for Detox end-to-end suites (TEST-WEAK-01). */
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SHELL-09, the module format a script runs in: `import.meta.dirname` and
|
|
3
|
+
* `import.meta.filename` in a file that runs as CommonJS
|
|
4
|
+
* (`no-import-meta-dirname-cjs`). `import-meta-cjs.test.ts` owns it.
|
|
5
|
+
*
|
|
6
|
+
* WHY. tsx runs a `.ts`, `.tsx` or `.js` file as CommonJS when its nearest
|
|
7
|
+
* package.json has no `"type": "module"` (or there is none). Its CJS
|
|
8
|
+
* transform defines `import.meta.url` (except in `.cts` and `.cjs` files)
|
|
9
|
+
* and nothing else, so `import.meta.dirname` is undefined and
|
|
10
|
+
* `resolve(import.meta.dirname, ...)` throws when the script loads. Vitest
|
|
11
|
+
* runs the same file as ESM, where both exist, so the script's tests pass.
|
|
12
|
+
* Measured on tsx 4.21.
|
|
13
|
+
*
|
|
14
|
+
* SCOPE. `.mts` and `.mjs` files always run as ESM and are never reported;
|
|
15
|
+
* `.cts` and `.cjs` files always run as CommonJS and are reported whatever
|
|
16
|
+
* the package says. Test files (`*.test.*`, `*.spec.*`) are skipped: Vitest
|
|
17
|
+
* runs them as ESM. A package.json that cannot be read or parsed leaves the
|
|
18
|
+
* format unknown, and nothing is reported.
|
|
19
|
+
*
|
|
20
|
+
* ACCEPTED LIMIT. A `.js` file is reported as tsx runs it. Plain Node (not
|
|
21
|
+
* tsx) may run a `.js` file in a package with no `type` field as ESM, from
|
|
22
|
+
* its `import.meta` syntax (module syntax detection); that is not measured
|
|
23
|
+
* here, and such a file may be a false positive.
|
|
24
|
+
*/
|
|
25
|
+
import { readFileSync } from 'node:fs';
|
|
26
|
+
import { basename, dirname, extname, join, resolve } from 'node:path';
|
|
27
|
+
|
|
28
|
+
import { propertyName, WRAPPERS } from './helpers.js';
|
|
29
|
+
|
|
30
|
+
/** Extensions whose format the nearest package.json decides. */
|
|
31
|
+
const BY_PACKAGE = new Set(['.ts', '.tsx', '.js']);
|
|
32
|
+
|
|
33
|
+
/** Extensions that always run as CommonJS. */
|
|
34
|
+
const ALWAYS_CJS = new Set(['.cts', '.cjs']);
|
|
35
|
+
|
|
36
|
+
/** Vitest runs these as ESM. */
|
|
37
|
+
const TEST_FILE = /\.(?:test|spec)\./;
|
|
38
|
+
|
|
39
|
+
/** The `import.meta` properties tsx leaves undefined under CommonJS. */
|
|
40
|
+
const REPLACEMENTS = {
|
|
41
|
+
cjsPackage: {
|
|
42
|
+
dirname: 'dirname(fileURLToPath(import.meta.url))',
|
|
43
|
+
filename: 'fileURLToPath(import.meta.url)',
|
|
44
|
+
},
|
|
45
|
+
cjsFile: { dirname: '__dirname', filename: '__filename' },
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A package.json's text, null when there is none at `path`, or undefined
|
|
50
|
+
* when it exists but cannot be read.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} path
|
|
53
|
+
*/
|
|
54
|
+
function readPackageJson(path) {
|
|
55
|
+
try {
|
|
56
|
+
return readFileSync(path, 'utf8');
|
|
57
|
+
} catch (error) {
|
|
58
|
+
// fail-open: a missing file means no package.json here, any other read failure an unknown format; the rule then reports nothing.
|
|
59
|
+
return error.code === 'ENOENT' ? null : undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A lookup of the module format a directory's files run in: `'module'`,
|
|
65
|
+
* `'commonjs'` (no package.json up to the root, or a nearest one with no
|
|
66
|
+
* `"type": "module"`) or `'unknown'` (the nearest one cannot be read or
|
|
67
|
+
* parsed). Each directory is looked up once: every directory a walk passes
|
|
68
|
+
* is cached with its answer.
|
|
69
|
+
*
|
|
70
|
+
* @param {(path: string) => string | null | undefined} [read]
|
|
71
|
+
*/
|
|
72
|
+
function packageTypeLookup(read = readPackageJson) {
|
|
73
|
+
const cache = new Map();
|
|
74
|
+
return function packageTypeOf(start) {
|
|
75
|
+
const passed = [];
|
|
76
|
+
let type = 'commonjs';
|
|
77
|
+
for (let dir = start; ; dir = dirname(dir)) {
|
|
78
|
+
if (cache.has(dir)) {
|
|
79
|
+
type = cache.get(dir);
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
passed.push(dir);
|
|
83
|
+
const text = read(join(dir, 'package.json'));
|
|
84
|
+
if (text !== null) {
|
|
85
|
+
type = formatOf(text);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
if (dirname(dir) === dir) break;
|
|
89
|
+
}
|
|
90
|
+
for (const dir of passed) cache.set(dir, type);
|
|
91
|
+
return type;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The format a package.json's text sets, or `'unknown'` when unreadable. */
|
|
96
|
+
function formatOf(text) {
|
|
97
|
+
if (text === undefined) return 'unknown';
|
|
98
|
+
try {
|
|
99
|
+
return JSON.parse(text)?.type === 'module' ? 'module' : 'commonjs';
|
|
100
|
+
} catch {
|
|
101
|
+
// fail-open: a package.json that does not parse leaves the format unknown, and the rule reports nothing.
|
|
102
|
+
return 'unknown';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The lookup the rule uses: one per lint process. */
|
|
107
|
+
const packageTypeOf = packageTypeLookup();
|
|
108
|
+
|
|
109
|
+
const noImportMetaDirnameCjs = {
|
|
110
|
+
meta: {
|
|
111
|
+
type: 'problem',
|
|
112
|
+
docs: {
|
|
113
|
+
description:
|
|
114
|
+
'import.meta.dirname and import.meta.filename in a file that tsx runs as CommonJS, where they are undefined (SHELL-09)',
|
|
115
|
+
},
|
|
116
|
+
schema: [],
|
|
117
|
+
messages: {
|
|
118
|
+
cjsPackage:
|
|
119
|
+
'import.meta.{{property}} is undefined when this file runs as CommonJS (its nearest package.json, if any, has no "type": "module"; tsx runs it as CJS and defines only import.meta.url there). Use {{replacement}}.',
|
|
120
|
+
cjsFile:
|
|
121
|
+
'import.meta.{{property}} is undefined in a {{extension}} file, which always runs as CommonJS (tsx defines not even import.meta.url there). Use {{replacement}}.',
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
create(context) {
|
|
125
|
+
const given = context.physicalFilename ?? context.filename;
|
|
126
|
+
// `<input>` and `<text>`: ESLint's names for code with no file.
|
|
127
|
+
if (/^<.*>$/.test(given)) return {};
|
|
128
|
+
const filename = resolve(context.cwd, given);
|
|
129
|
+
const name = basename(filename);
|
|
130
|
+
if (TEST_FILE.test(name)) return {};
|
|
131
|
+
const extension = extname(name);
|
|
132
|
+
if (!ALWAYS_CJS.has(extension) && !BY_PACKAGE.has(extension)) return {};
|
|
133
|
+
/** The message for this file, decided on the first hit. */
|
|
134
|
+
let messageId;
|
|
135
|
+
const messageFor = () => {
|
|
136
|
+
if (messageId === undefined) {
|
|
137
|
+
messageId = ALWAYS_CJS.has(extension)
|
|
138
|
+
? 'cjsFile'
|
|
139
|
+
: packageTypeOf(dirname(filename)) === 'commonjs'
|
|
140
|
+
? 'cjsPackage'
|
|
141
|
+
: null;
|
|
142
|
+
}
|
|
143
|
+
return messageId;
|
|
144
|
+
};
|
|
145
|
+
const report = (node, property) => {
|
|
146
|
+
if (property !== 'dirname' && property !== 'filename') return;
|
|
147
|
+
const id = messageFor();
|
|
148
|
+
if (id === null) return;
|
|
149
|
+
context.report({
|
|
150
|
+
node,
|
|
151
|
+
messageId: id,
|
|
152
|
+
data: {
|
|
153
|
+
property,
|
|
154
|
+
extension,
|
|
155
|
+
replacement: REPLACEMENTS[id][property],
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
return {
|
|
160
|
+
MetaProperty(node) {
|
|
161
|
+
if (node.meta.name !== 'import' || node.property.name !== 'meta') {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
// Through type-only wrappers: `(import.meta as Meta).dirname`.
|
|
165
|
+
let inner = node;
|
|
166
|
+
while (
|
|
167
|
+
WRAPPERS.has(inner.parent.type) &&
|
|
168
|
+
inner.parent.expression === inner
|
|
169
|
+
) {
|
|
170
|
+
inner = inner.parent;
|
|
171
|
+
}
|
|
172
|
+
const parent = inner.parent;
|
|
173
|
+
if (parent.type === 'MemberExpression' && parent.object === inner) {
|
|
174
|
+
report(parent, propertyName(parent));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// `const { dirname } = import.meta`
|
|
178
|
+
if (
|
|
179
|
+
parent.type === 'VariableDeclarator' &&
|
|
180
|
+
parent.init === inner &&
|
|
181
|
+
parent.id.type === 'ObjectPattern'
|
|
182
|
+
) {
|
|
183
|
+
for (const property of parent.id.properties) {
|
|
184
|
+
if (property.type === 'Property' && !property.computed) {
|
|
185
|
+
const { key } = property;
|
|
186
|
+
report(property, key.type === 'Identifier' ? key.name : key.value);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export { noImportMetaDirnameCjs, packageTypeLookup };
|
package/src/test-lint/index.js
CHANGED
|
@@ -13,8 +13,11 @@
|
|
|
13
13
|
* a module here, owned by the colocated suite of the same name:
|
|
14
14
|
* `vacuous-assertion` (TEST-WEAK-03), `test-structure` (TEST-STRUCT-02),
|
|
15
15
|
* `process-hygiene` (SHELL-09 and TEST-STRUCT-11), `sql-fake` (TEST-WEAK-10)
|
|
16
|
-
* and `detox` (TEST-WEAK-01)
|
|
17
|
-
*
|
|
16
|
+
* and `detox` (TEST-WEAK-01), with two single-rule modules:
|
|
17
|
+
* `jsonnull-in-matcher` (a TEST-WEAK-03 shape: a Prisma null sentinel inside
|
|
18
|
+
* a partial matcher) and `import-meta-cjs` (a SHELL-09 script rule:
|
|
19
|
+
* `import.meta.dirname` in a file tsx runs as CommonJS). `helpers.js` holds
|
|
20
|
+
* only what two or more families read; a family imports nothing from another.
|
|
18
21
|
*
|
|
19
22
|
* `c05.js` adds three to the same test-file block: a test that returns before
|
|
20
23
|
* its assertions with none on the return path, a recursive removal of a path
|
|
@@ -63,6 +66,8 @@ import {
|
|
|
63
66
|
variableOf,
|
|
64
67
|
WRAPPERS,
|
|
65
68
|
} from './helpers.js';
|
|
69
|
+
import { noImportMetaDirnameCjs } from './import-meta-cjs.js';
|
|
70
|
+
import { jsonnullInMatcher } from './jsonnull-in-matcher.js';
|
|
66
71
|
import {
|
|
67
72
|
cliEntryHelper,
|
|
68
73
|
detachedTeardown,
|
|
@@ -199,6 +204,8 @@ export const rules = {
|
|
|
199
204
|
'spawn-timeout': spawnTimeout,
|
|
200
205
|
'no-raw-group-kill': noRawGroupKill,
|
|
201
206
|
'detached-teardown': detachedTeardown,
|
|
207
|
+
'jsonnull-in-matcher': jsonnullInMatcher,
|
|
208
|
+
'no-import-meta-dirname-cjs': noImportMetaDirnameCjs,
|
|
202
209
|
};
|
|
203
210
|
|
|
204
211
|
/** The c05 candidate extensions (`c05-candidates.js`), in catalogue order. */
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TEST-WEAK-03, one more vacuous shape: a Prisma null sentinel inside a
|
|
3
|
+
* partial matcher (`jsonnull-in-matcher`). `jsonnull-in-matcher.test.ts`
|
|
4
|
+
* owns it.
|
|
5
|
+
*
|
|
6
|
+
* WHY. `Prisma.JsonNull`, `Prisma.DbNull` and `Prisma.AnyNull` are class
|
|
7
|
+
* instances with no own enumerable keys. A partial matcher compares an
|
|
8
|
+
* expected object by its keys, so the sentinel has nothing to compare:
|
|
9
|
+
* inside `toMatchObject` it matches any stored value (5, null, an object,
|
|
10
|
+
* the other sentinel), and inside `expect.objectContaining` any value that
|
|
11
|
+
* is an object with no keys (`Prisma.DbNull`, `Prisma.JsonNull`, `{}`), so it
|
|
12
|
+
* cannot tell JSON null from database NULL. Measured on Vitest 4.1 with the
|
|
13
|
+
* Prisma runtime's own sentinel instances. `toBe` and `toStrictEqual`
|
|
14
|
+
* compare the instance itself.
|
|
15
|
+
*
|
|
16
|
+
* SCOPE. The sentinel is reported where it is the matcher's argument or is
|
|
17
|
+
* reached from it through object property values, array elements, spreads,
|
|
18
|
+
* type-only wrappers and `const` bindings (three deep). A nested call (an
|
|
19
|
+
* inner `expect.objectContaining`) is a matcher of its own and is checked
|
|
20
|
+
* when that call is visited.
|
|
21
|
+
*/
|
|
22
|
+
import {
|
|
23
|
+
calledMethod,
|
|
24
|
+
constInit,
|
|
25
|
+
propertyName,
|
|
26
|
+
unwrap,
|
|
27
|
+
variableOf,
|
|
28
|
+
} from './helpers.js';
|
|
29
|
+
|
|
30
|
+
/** The sentinels with no enumerable keys. */
|
|
31
|
+
const PRISMA_NULLS = new Set(['JsonNull', 'DbNull', 'AnyNull']);
|
|
32
|
+
|
|
33
|
+
/** How deep a `const` binding is followed from a matcher argument. */
|
|
34
|
+
const BINDING_DEPTH = 3;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The sentinel's name when `node` is one: `Prisma.JsonNull`, a named import
|
|
38
|
+
* of it from `@prisma/client` (or a subpath), or a `const { JsonNull } =
|
|
39
|
+
* Prisma` binding; otherwise null.
|
|
40
|
+
*/
|
|
41
|
+
function prismaNullName(node, context) {
|
|
42
|
+
if (node.type === 'MemberExpression') {
|
|
43
|
+
const name = propertyName(node);
|
|
44
|
+
const object = unwrap(node.object);
|
|
45
|
+
return object.type === 'Identifier' &&
|
|
46
|
+
object.name === 'Prisma' &&
|
|
47
|
+
PRISMA_NULLS.has(name)
|
|
48
|
+
? name
|
|
49
|
+
: null;
|
|
50
|
+
}
|
|
51
|
+
if (node.type !== 'Identifier') return null;
|
|
52
|
+
const variable = variableOf(node, context);
|
|
53
|
+
if (!variable || variable.defs.length !== 1) return null;
|
|
54
|
+
const [def] = variable.defs;
|
|
55
|
+
if (def.type === 'ImportBinding') {
|
|
56
|
+
const imported =
|
|
57
|
+
def.node.type === 'ImportSpecifier' ? def.node.imported : null;
|
|
58
|
+
const name = imported?.name ?? imported?.value;
|
|
59
|
+
return /^@prisma\/client(\/|$)/.test(def.parent.source.value) &&
|
|
60
|
+
PRISMA_NULLS.has(name)
|
|
61
|
+
? name
|
|
62
|
+
: null;
|
|
63
|
+
}
|
|
64
|
+
// `const { JsonNull } = Prisma`, or renamed: `const { DbNull: dbNull } = Prisma`.
|
|
65
|
+
const init = def.type === 'Variable' ? unwrap(def.node.init) : null;
|
|
66
|
+
if (
|
|
67
|
+
init?.type !== 'Identifier' ||
|
|
68
|
+
init.name !== 'Prisma' ||
|
|
69
|
+
def.node.id.type !== 'ObjectPattern'
|
|
70
|
+
) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const property = def.node.id.properties.find(
|
|
74
|
+
entry => entry.type === 'Property' && unwrap(entry.value) === def.name,
|
|
75
|
+
);
|
|
76
|
+
const key = property && !property.computed ? property.key.name : null;
|
|
77
|
+
return PRISMA_NULLS.has(key) ? key : null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The partial matcher a call is, or null: `toMatchObject` or `objectContaining`. */
|
|
81
|
+
function partialMatcher(call) {
|
|
82
|
+
const method = calledMethod(call);
|
|
83
|
+
if (method === 'toMatchObject' || method === 'objectContaining') return method;
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const jsonnullInMatcher = {
|
|
88
|
+
meta: {
|
|
89
|
+
type: 'problem',
|
|
90
|
+
docs: {
|
|
91
|
+
description:
|
|
92
|
+
'A Prisma null sentinel inside toMatchObject or objectContaining, where it has no keys to compare (TEST-WEAK-03)',
|
|
93
|
+
},
|
|
94
|
+
schema: [],
|
|
95
|
+
messages: {
|
|
96
|
+
toMatchObject:
|
|
97
|
+
'Prisma.{{name}} matches any value inside toMatchObject (it has no enumerable keys). Assert the field on its own: expect(data.col).toBe(Prisma.{{name}}) for a value given to Prisma, expect(row.col).toBeNull() for a row read back.',
|
|
98
|
+
objectContaining:
|
|
99
|
+
'Prisma.{{name}} matches any object without keys inside objectContaining (it has no enumerable keys), so Prisma.DbNull, Prisma.JsonNull and {} all pass. Assert the field on its own: expect(data.col).toBe(Prisma.{{name}}) for a value given to Prisma, expect(row.col).toBeNull() for a row read back.',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
create(context) {
|
|
103
|
+
const reported = new Set();
|
|
104
|
+
|
|
105
|
+
/** Report every sentinel reached from `node`, the argument of `matcher`. */
|
|
106
|
+
function visit(node, matcher, depth) {
|
|
107
|
+
const value = unwrap(node);
|
|
108
|
+
if (!value) return;
|
|
109
|
+
switch (value.type) {
|
|
110
|
+
case 'ObjectExpression':
|
|
111
|
+
for (const property of value.properties) {
|
|
112
|
+
visit(
|
|
113
|
+
property.type === 'Property' ? property.value : property.argument,
|
|
114
|
+
matcher,
|
|
115
|
+
depth,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
case 'ArrayExpression':
|
|
120
|
+
for (const element of value.elements) visit(element, matcher, depth);
|
|
121
|
+
return;
|
|
122
|
+
case 'SpreadElement':
|
|
123
|
+
visit(value.argument, matcher, depth);
|
|
124
|
+
return;
|
|
125
|
+
default:
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
const name = prismaNullName(value, context);
|
|
129
|
+
if (name !== null) {
|
|
130
|
+
if (reported.has(value)) return;
|
|
131
|
+
reported.add(value);
|
|
132
|
+
context.report({ node: value, messageId: matcher, data: { name } });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (value.type === 'Identifier' && depth < BINDING_DEPTH) {
|
|
136
|
+
visit(constInit(value, context), matcher, depth + 1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
CallExpression(node) {
|
|
142
|
+
const matcher = partialMatcher(node);
|
|
143
|
+
if (matcher === null) return;
|
|
144
|
+
for (const argument of node.arguments) visit(argument, matcher, 0);
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export { jsonnullInMatcher };
|