eslint-plugin-sanity 0.0.1
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/LICENSE +21 -0
- package/README.md +131 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +508 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present Sanity.io
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# eslint-plugin-sanity
|
|
2
|
+
|
|
3
|
+
ESLint plugin for linting GROQ queries and Sanity schemas.
|
|
4
|
+
|
|
5
|
+
Works with both **ESLint** and **OxLint**.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install eslint-plugin-sanity
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage with ESLint
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
// eslint.config.js
|
|
17
|
+
import sanity from 'eslint-plugin-sanity'
|
|
18
|
+
|
|
19
|
+
export default [
|
|
20
|
+
sanity.configs.recommended,
|
|
21
|
+
// or for stricter checking:
|
|
22
|
+
// sanity.configs.strict,
|
|
23
|
+
]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage with OxLint
|
|
27
|
+
|
|
28
|
+
OxLint supports ESLint-compatible JS plugins. Our plugin works out of the box:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
// oxlint.config.json
|
|
32
|
+
{
|
|
33
|
+
"jsPlugins": ["eslint-plugin-sanity"],
|
|
34
|
+
"rules": {
|
|
35
|
+
"sanity/groq-join-in-filter": "error",
|
|
36
|
+
"sanity/groq-deep-pagination": "warn"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then run:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
oxlint --config oxlint.config.json src/
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
> **Note**: OxLint JS plugins are experimental. See [OxLint JS Plugins](https://oxc.rs/docs/guide/usage/linter/js-plugins) for details.
|
|
48
|
+
|
|
49
|
+
## Configurations
|
|
50
|
+
|
|
51
|
+
### `recommended`
|
|
52
|
+
|
|
53
|
+
Balanced defaults - errors for serious issues, warnings for improvements.
|
|
54
|
+
|
|
55
|
+
### `strict`
|
|
56
|
+
|
|
57
|
+
All rules enabled as errors.
|
|
58
|
+
|
|
59
|
+
## Rules
|
|
60
|
+
|
|
61
|
+
### GROQ Rules
|
|
62
|
+
|
|
63
|
+
These rules lint GROQ queries in `groq\`...\`` tagged template literals.
|
|
64
|
+
|
|
65
|
+
| Rule | Default | Description |
|
|
66
|
+
| ------------------------------------------ | ------- | ----------------------------------------------- |
|
|
67
|
+
| `sanity/groq-join-in-filter` | error | Avoid `->` inside filters |
|
|
68
|
+
| `sanity/groq-join-to-get-id` | warn | Use `._ref` instead of `->_id` |
|
|
69
|
+
| `sanity/groq-deep-pagination` | warn | Avoid large offsets (>=1000) |
|
|
70
|
+
| `sanity/groq-large-pages` | warn | Avoid fetching >100 results |
|
|
71
|
+
| `sanity/groq-many-joins` | warn | Avoid >10 joins in one query |
|
|
72
|
+
| `sanity/groq-computed-value-in-filter` | error | Avoid computed values in filters |
|
|
73
|
+
| `sanity/groq-non-literal-comparison` | error | Avoid comparing two non-literals |
|
|
74
|
+
| `sanity/groq-order-on-expr` | error | Avoid ordering on computed values |
|
|
75
|
+
| `sanity/groq-repeated-dereference` | info | Avoid repeated `->` on same attribute |
|
|
76
|
+
| `sanity/groq-match-on-id` | info | Avoid match on `_id` with wildcard |
|
|
77
|
+
| `sanity/groq-count-in-correlated-subquery` | info | Avoid `count()` on correlated subqueries |
|
|
78
|
+
| `sanity/groq-very-large-query` | error | Query exceeds 10KB |
|
|
79
|
+
| `sanity/groq-extremely-large-query` | error | Query exceeds 100KB |
|
|
80
|
+
| `sanity/groq-unknown-field` | error | Field doesn't exist in schema (requires schema) |
|
|
81
|
+
| `sanity/groq-invalid-type-filter` | error | Type doesn't exist in schema (requires schema) |
|
|
82
|
+
|
|
83
|
+
### Schema Rules
|
|
84
|
+
|
|
85
|
+
These rules lint Sanity schema definitions using `defineType()` and `defineField()`.
|
|
86
|
+
|
|
87
|
+
| Rule | Default | Description |
|
|
88
|
+
| ------------------------------------------- | ------- | ------------------------------------------- |
|
|
89
|
+
| `sanity/schema-missing-define-type` | error | Must use `defineType()` |
|
|
90
|
+
| `sanity/schema-missing-define-field` | warn | Fields should use `defineField()` |
|
|
91
|
+
| `sanity/schema-missing-icon` | warn | Document types should have icons |
|
|
92
|
+
| `sanity/schema-missing-title` | warn | Types should have titles |
|
|
93
|
+
| `sanity/schema-missing-description` | info | Fields should have descriptions |
|
|
94
|
+
| `sanity/schema-missing-slug-source` | warn | Slug fields need `options.source` |
|
|
95
|
+
| `sanity/schema-reserved-field-name` | error | Avoid reserved field names (`_id`, `_type`) |
|
|
96
|
+
| `sanity/schema-array-missing-constraints` | warn | Arrays should have constraints |
|
|
97
|
+
| `sanity/schema-boolean-instead-of-list` | info | Consider options.list over boolean |
|
|
98
|
+
| `sanity/schema-heading-level-in-schema` | warn | Don't store heading levels |
|
|
99
|
+
| `sanity/schema-unnecessary-reference` | info | Consider embedding instead |
|
|
100
|
+
| `sanity/schema-presentation-field-name` | warn | Avoid presentation-focused names |
|
|
101
|
+
| `sanity/schema-missing-required-validation` | warn | Critical fields need validation |
|
|
102
|
+
|
|
103
|
+
## Schema-Aware Linting
|
|
104
|
+
|
|
105
|
+
For schema-aware rules (`unknown-field`, `invalid-type-filter`), you need to provide a schema:
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
// eslint.config.js
|
|
109
|
+
import sanity from 'eslint-plugin-sanity'
|
|
110
|
+
|
|
111
|
+
export default [
|
|
112
|
+
{
|
|
113
|
+
...sanity.configs.recommended,
|
|
114
|
+
settings: {
|
|
115
|
+
sanity: {
|
|
116
|
+
schemaPath: './schema.json',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Generate `schema.json` with:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npx sanity schema extract
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as eslint from 'eslint';
|
|
2
|
+
import { ESLint, Linter } from 'eslint';
|
|
3
|
+
|
|
4
|
+
declare const rules: {
|
|
5
|
+
[x: string]: eslint.Rule.RuleModule;
|
|
6
|
+
};
|
|
7
|
+
declare const configs: {
|
|
8
|
+
recommended: Linter.Config;
|
|
9
|
+
strict: Linter.Config;
|
|
10
|
+
};
|
|
11
|
+
interface SanityPlugin {
|
|
12
|
+
meta: ESLint.Plugin['meta'];
|
|
13
|
+
rules: typeof rules;
|
|
14
|
+
configs: typeof configs;
|
|
15
|
+
}
|
|
16
|
+
declare const sanityPlugin: SanityPlugin;
|
|
17
|
+
|
|
18
|
+
export { configs, sanityPlugin as default, rules };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
import { rules as groqRules } from "@sanity/groq-lint";
|
|
4
|
+
import { rules as schemaRules2 } from "@sanity/schema-lint";
|
|
5
|
+
|
|
6
|
+
// src/utils/rule-factory.ts
|
|
7
|
+
import { lint, rules as allGroqRules } from "@sanity/groq-lint";
|
|
8
|
+
|
|
9
|
+
// src/utils/groq-extractor.ts
|
|
10
|
+
function isGroqTaggedTemplate(node) {
|
|
11
|
+
const tag = node.tag;
|
|
12
|
+
if (tag.type === "Identifier" && tag.name === "groq") {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
if (tag.type === "MemberExpression" && tag.object.type === "Identifier" && tag.object.name === "groq") {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
function extractGroqString(node) {
|
|
21
|
+
const { quasis, expressions } = node.quasi;
|
|
22
|
+
if (expressions.length === 0) {
|
|
23
|
+
return quasis[0]?.value.cooked ?? quasis[0]?.value.raw ?? "";
|
|
24
|
+
}
|
|
25
|
+
let result = "";
|
|
26
|
+
for (let i = 0; i < quasis.length; i++) {
|
|
27
|
+
result += quasis[i]?.value.cooked ?? quasis[i]?.value.raw ?? "";
|
|
28
|
+
if (i < expressions.length) {
|
|
29
|
+
result += `$__expr${i}__`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/utils/rule-factory.ts
|
|
36
|
+
function buildSingleRuleConfig(ruleId) {
|
|
37
|
+
const config = {};
|
|
38
|
+
for (const rule of allGroqRules) {
|
|
39
|
+
config[rule.id] = rule.id === ruleId;
|
|
40
|
+
}
|
|
41
|
+
return config;
|
|
42
|
+
}
|
|
43
|
+
function createESLintRule(groqRule) {
|
|
44
|
+
return {
|
|
45
|
+
meta: {
|
|
46
|
+
type: groqRule.category === "correctness" ? "problem" : "suggestion",
|
|
47
|
+
docs: {
|
|
48
|
+
description: groqRule.description,
|
|
49
|
+
recommended: groqRule.severity === "error"
|
|
50
|
+
},
|
|
51
|
+
messages: {
|
|
52
|
+
[groqRule.id]: "{{ message }}"
|
|
53
|
+
},
|
|
54
|
+
schema: []
|
|
55
|
+
// No options for now
|
|
56
|
+
},
|
|
57
|
+
create(context) {
|
|
58
|
+
return {
|
|
59
|
+
TaggedTemplateExpression(eslintNode) {
|
|
60
|
+
const node = eslintNode;
|
|
61
|
+
if (!isGroqTaggedTemplate(node)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const query = extractGroqString(node);
|
|
66
|
+
const result = lint(query, { config: { rules: buildSingleRuleConfig(groqRule.id) } });
|
|
67
|
+
for (const finding of result.findings) {
|
|
68
|
+
if (finding.ruleId === groqRule.id) {
|
|
69
|
+
context.report({
|
|
70
|
+
node: eslintNode,
|
|
71
|
+
messageId: groqRule.id,
|
|
72
|
+
data: {
|
|
73
|
+
message: finding.help ? `${finding.message} ${finding.help}` : finding.message
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function createAllRules(groqRules2) {
|
|
86
|
+
const eslintRules = {};
|
|
87
|
+
for (const rule of groqRules2) {
|
|
88
|
+
const eslintRuleId = `groq-${rule.id}`;
|
|
89
|
+
eslintRules[eslintRuleId] = createESLintRule(rule);
|
|
90
|
+
}
|
|
91
|
+
return eslintRules;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/utils/schema-rule-factory.ts
|
|
95
|
+
import { lint as lint2, rules as schemaRules } from "@sanity/schema-lint";
|
|
96
|
+
|
|
97
|
+
// src/utils/schema-extractor.ts
|
|
98
|
+
function isDefineTypeCall(node) {
|
|
99
|
+
const callee = node.callee;
|
|
100
|
+
if (callee.type === "Identifier" && callee.name === "defineType") {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
function isDefineFieldCall(node) {
|
|
106
|
+
const callee = node.callee;
|
|
107
|
+
if (callee.type === "Identifier" && callee.name === "defineField") {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
function toSourceSpan(loc) {
|
|
113
|
+
return {
|
|
114
|
+
start: {
|
|
115
|
+
line: loc.start.line,
|
|
116
|
+
column: loc.start.column + 1,
|
|
117
|
+
// 1-based
|
|
118
|
+
offset: 0
|
|
119
|
+
// We don't have offset info easily
|
|
120
|
+
},
|
|
121
|
+
end: {
|
|
122
|
+
line: loc.end.line,
|
|
123
|
+
column: loc.end.column + 1,
|
|
124
|
+
// 1-based
|
|
125
|
+
offset: 0
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function extractStringValue(node) {
|
|
130
|
+
if (!node) return void 0;
|
|
131
|
+
if (node.type === "Literal" && typeof node.value === "string") {
|
|
132
|
+
return node.value;
|
|
133
|
+
}
|
|
134
|
+
if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
|
|
135
|
+
return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;
|
|
136
|
+
}
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
function extractBooleanValue(node) {
|
|
140
|
+
if (!node) return void 0;
|
|
141
|
+
if (node.type === "Literal" && typeof node.value === "boolean") {
|
|
142
|
+
return node.value;
|
|
143
|
+
}
|
|
144
|
+
return void 0;
|
|
145
|
+
}
|
|
146
|
+
function hasProperty(node, name) {
|
|
147
|
+
return node.properties.some((prop) => {
|
|
148
|
+
if (prop.type === "Property" && prop.key.type === "Identifier") {
|
|
149
|
+
return prop.key.name === name;
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
function getProperty(node, name) {
|
|
155
|
+
for (const prop of node.properties) {
|
|
156
|
+
if (prop.type === "Property" && prop.key.type === "Identifier" && prop.key.name === name) {
|
|
157
|
+
return prop.value;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return void 0;
|
|
161
|
+
}
|
|
162
|
+
function extractFieldOptions(optionsNode) {
|
|
163
|
+
if (!optionsNode || optionsNode.type !== "ObjectExpression") {
|
|
164
|
+
return void 0;
|
|
165
|
+
}
|
|
166
|
+
const options = {};
|
|
167
|
+
for (const prop of optionsNode.properties) {
|
|
168
|
+
if (prop.type === "Property" && prop.key.type === "Identifier") {
|
|
169
|
+
const name = prop.key.name;
|
|
170
|
+
if (name === "source") {
|
|
171
|
+
const source = extractStringValue(prop.value);
|
|
172
|
+
if (source !== void 0) options.source = source;
|
|
173
|
+
} else if (name === "hotspot") {
|
|
174
|
+
const hotspot = extractBooleanValue(prop.value);
|
|
175
|
+
if (hotspot !== void 0) options.hotspot = hotspot;
|
|
176
|
+
} else if (name === "layout") {
|
|
177
|
+
const layout = extractStringValue(prop.value);
|
|
178
|
+
if (layout !== void 0) options.layout = layout;
|
|
179
|
+
} else if (name === "list" && prop.value.type === "ArrayExpression") {
|
|
180
|
+
options.list = prop.value.elements.map((el) => {
|
|
181
|
+
if (!el) return null;
|
|
182
|
+
if (el.type === "Literal") return el.value;
|
|
183
|
+
if (el.type === "ObjectExpression") {
|
|
184
|
+
const value = extractStringValue(getProperty(el, "value"));
|
|
185
|
+
return { value };
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return Object.keys(options).length > 0 ? options : void 0;
|
|
193
|
+
}
|
|
194
|
+
function extractField(node, usesDefineField) {
|
|
195
|
+
let fieldObj;
|
|
196
|
+
if (node.type === "CallExpression" && isDefineFieldCall(node)) {
|
|
197
|
+
const arg = node.arguments[0];
|
|
198
|
+
if (arg?.type === "ObjectExpression") {
|
|
199
|
+
fieldObj = arg;
|
|
200
|
+
}
|
|
201
|
+
} else if (node.type === "ObjectExpression") {
|
|
202
|
+
fieldObj = node;
|
|
203
|
+
usesDefineField.value = false;
|
|
204
|
+
}
|
|
205
|
+
if (!fieldObj) return void 0;
|
|
206
|
+
const name = extractStringValue(getProperty(fieldObj, "name"));
|
|
207
|
+
const type = extractStringValue(getProperty(fieldObj, "type"));
|
|
208
|
+
if (!name || !type) return void 0;
|
|
209
|
+
const title = extractStringValue(getProperty(fieldObj, "title"));
|
|
210
|
+
const description = extractStringValue(getProperty(fieldObj, "description"));
|
|
211
|
+
const options = extractFieldOptions(getProperty(fieldObj, "options"));
|
|
212
|
+
const field = {
|
|
213
|
+
name,
|
|
214
|
+
type,
|
|
215
|
+
hasValidation: hasProperty(fieldObj, "validation"),
|
|
216
|
+
hidden: hasProperty(fieldObj, "hidden"),
|
|
217
|
+
readOnly: hasProperty(fieldObj, "readOnly"),
|
|
218
|
+
span: toSourceSpan(fieldObj.loc),
|
|
219
|
+
...title !== void 0 && { title },
|
|
220
|
+
...description !== void 0 && { description },
|
|
221
|
+
...options !== void 0 && { options }
|
|
222
|
+
};
|
|
223
|
+
if (hasProperty(fieldObj, "deprecated")) {
|
|
224
|
+
const deprecatedNode = getProperty(fieldObj, "deprecated");
|
|
225
|
+
const deprecatedValue = extractStringValue(deprecatedNode);
|
|
226
|
+
field.deprecated = deprecatedValue ?? true;
|
|
227
|
+
}
|
|
228
|
+
return field;
|
|
229
|
+
}
|
|
230
|
+
function extractFields(node) {
|
|
231
|
+
const result = { fields: [], usesDefineField: true };
|
|
232
|
+
if (!node || node.type !== "ArrayExpression") {
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
const usesDefineFieldTracker = { value: true };
|
|
236
|
+
for (const element of node.elements) {
|
|
237
|
+
if (element) {
|
|
238
|
+
const field = extractField(element, usesDefineFieldTracker);
|
|
239
|
+
if (field) {
|
|
240
|
+
result.fields.push(field);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
result.usesDefineField = usesDefineFieldTracker.value;
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
function extractSchemaFromDefineType(node) {
|
|
248
|
+
const arg = node.arguments[0];
|
|
249
|
+
if (!arg || arg.type !== "ObjectExpression") {
|
|
250
|
+
return void 0;
|
|
251
|
+
}
|
|
252
|
+
const name = extractStringValue(getProperty(arg, "name"));
|
|
253
|
+
const type = extractStringValue(getProperty(arg, "type"));
|
|
254
|
+
if (!name || !type) {
|
|
255
|
+
return void 0;
|
|
256
|
+
}
|
|
257
|
+
const title = extractStringValue(getProperty(arg, "title"));
|
|
258
|
+
const description = extractStringValue(getProperty(arg, "description"));
|
|
259
|
+
const fieldsResult = extractFields(getProperty(arg, "fields"));
|
|
260
|
+
const hasFields = fieldsResult.fields.length > 0;
|
|
261
|
+
const schema = {
|
|
262
|
+
name,
|
|
263
|
+
type,
|
|
264
|
+
hasIcon: hasProperty(arg, "icon"),
|
|
265
|
+
hasPreview: hasProperty(arg, "preview"),
|
|
266
|
+
usesDefineType: true,
|
|
267
|
+
span: toSourceSpan(arg.loc),
|
|
268
|
+
...title !== void 0 && { title },
|
|
269
|
+
...description !== void 0 && { description },
|
|
270
|
+
...hasFields && { fields: fieldsResult.fields },
|
|
271
|
+
...hasFields && { usesDefineField: fieldsResult.usesDefineField }
|
|
272
|
+
};
|
|
273
|
+
return schema;
|
|
274
|
+
}
|
|
275
|
+
function extractSchemaFromObject(node) {
|
|
276
|
+
const name = extractStringValue(getProperty(node, "name"));
|
|
277
|
+
const type = extractStringValue(getProperty(node, "type"));
|
|
278
|
+
if (!name || !type) {
|
|
279
|
+
return void 0;
|
|
280
|
+
}
|
|
281
|
+
const title = extractStringValue(getProperty(node, "title"));
|
|
282
|
+
const description = extractStringValue(getProperty(node, "description"));
|
|
283
|
+
const fieldsResult = extractFields(getProperty(node, "fields"));
|
|
284
|
+
const hasFields = fieldsResult.fields.length > 0;
|
|
285
|
+
const schema = {
|
|
286
|
+
name,
|
|
287
|
+
type,
|
|
288
|
+
hasIcon: hasProperty(node, "icon"),
|
|
289
|
+
hasPreview: hasProperty(node, "preview"),
|
|
290
|
+
usesDefineType: false,
|
|
291
|
+
span: toSourceSpan(node.loc),
|
|
292
|
+
...title !== void 0 && { title },
|
|
293
|
+
...description !== void 0 && { description },
|
|
294
|
+
...hasFields && { fields: fieldsResult.fields },
|
|
295
|
+
...hasFields && { usesDefineField: fieldsResult.usesDefineField }
|
|
296
|
+
};
|
|
297
|
+
return schema;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/utils/schema-rule-factory.ts
|
|
301
|
+
function createSchemaESLintRule(schemaRule) {
|
|
302
|
+
return {
|
|
303
|
+
meta: {
|
|
304
|
+
type: schemaRule.category === "correctness" ? "problem" : "suggestion",
|
|
305
|
+
docs: {
|
|
306
|
+
description: schemaRule.description,
|
|
307
|
+
recommended: schemaRule.severity === "error"
|
|
308
|
+
},
|
|
309
|
+
messages: {
|
|
310
|
+
[schemaRule.id]: "{{ message }}"
|
|
311
|
+
},
|
|
312
|
+
schema: []
|
|
313
|
+
// No options for now
|
|
314
|
+
},
|
|
315
|
+
create(context) {
|
|
316
|
+
return {
|
|
317
|
+
// Handle defineType() calls
|
|
318
|
+
CallExpression(eslintNode) {
|
|
319
|
+
const node = eslintNode;
|
|
320
|
+
if (!isDefineTypeCall(node)) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
const schema = extractSchemaFromDefineType(node);
|
|
325
|
+
if (!schema) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const result = lint2(schema, schemaRules, {
|
|
329
|
+
rules: [schemaRule],
|
|
330
|
+
filePath: context.filename
|
|
331
|
+
});
|
|
332
|
+
for (const finding of result.findings) {
|
|
333
|
+
if (finding.ruleId === schemaRule.id) {
|
|
334
|
+
context.report({
|
|
335
|
+
node: eslintNode,
|
|
336
|
+
messageId: schemaRule.id,
|
|
337
|
+
data: {
|
|
338
|
+
message: finding.help ? `${finding.message} ${finding.help}` : finding.message
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
} catch {
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
// Handle export statements with object literals (not using defineType)
|
|
347
|
+
ExportNamedDeclaration(eslintNode) {
|
|
348
|
+
const node = eslintNode;
|
|
349
|
+
if (schemaRule.id !== "missing-define-type") {
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (!node.declaration) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (node.declaration.type === "VariableDeclaration") {
|
|
356
|
+
for (const declarator of node.declaration.declarations) {
|
|
357
|
+
if (declarator.init?.type === "ObjectExpression" && !isWrappedInDefineType(declarator.init)) {
|
|
358
|
+
const schema = extractSchemaFromObject(declarator.init);
|
|
359
|
+
if (schema && (schema.type === "document" || schema.type === "object")) {
|
|
360
|
+
try {
|
|
361
|
+
const result = lint2(schema, schemaRules, {
|
|
362
|
+
rules: [schemaRule],
|
|
363
|
+
filePath: context.filename
|
|
364
|
+
});
|
|
365
|
+
for (const finding of result.findings) {
|
|
366
|
+
if (finding.ruleId === schemaRule.id) {
|
|
367
|
+
context.report({
|
|
368
|
+
node: eslintNode,
|
|
369
|
+
messageId: schemaRule.id,
|
|
370
|
+
data: {
|
|
371
|
+
message: finding.help ? `${finding.message} ${finding.help}` : finding.message
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
} catch {
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
// Handle default exports with object literals
|
|
384
|
+
ExportDefaultDeclaration(eslintNode) {
|
|
385
|
+
const node = eslintNode;
|
|
386
|
+
if (schemaRule.id !== "missing-define-type") {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (node.declaration.type === "ObjectExpression") {
|
|
390
|
+
const schema = extractSchemaFromObject(node.declaration);
|
|
391
|
+
if (schema && (schema.type === "document" || schema.type === "object")) {
|
|
392
|
+
try {
|
|
393
|
+
const result = lint2(schema, schemaRules, {
|
|
394
|
+
rules: [schemaRule],
|
|
395
|
+
filePath: context.filename
|
|
396
|
+
});
|
|
397
|
+
for (const finding of result.findings) {
|
|
398
|
+
if (finding.ruleId === schemaRule.id) {
|
|
399
|
+
context.report({
|
|
400
|
+
node: eslintNode,
|
|
401
|
+
messageId: schemaRule.id,
|
|
402
|
+
data: {
|
|
403
|
+
message: finding.help ? `${finding.message} ${finding.help}` : finding.message
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
function isWrappedInDefineType(_node) {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
function createAllSchemaRules(rules2) {
|
|
421
|
+
const eslintRules = {};
|
|
422
|
+
for (const rule of rules2) {
|
|
423
|
+
const eslintRuleId = `schema-${rule.id}`;
|
|
424
|
+
eslintRules[eslintRuleId] = createSchemaESLintRule(rule);
|
|
425
|
+
}
|
|
426
|
+
return eslintRules;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/index.ts
|
|
430
|
+
var require2 = createRequire(import.meta.url);
|
|
431
|
+
var { version } = require2("../package.json");
|
|
432
|
+
var groqEslintRules = createAllRules(groqRules);
|
|
433
|
+
var schemaEslintRules = createAllSchemaRules(schemaRules2);
|
|
434
|
+
var rules = {
|
|
435
|
+
...groqEslintRules,
|
|
436
|
+
...schemaEslintRules
|
|
437
|
+
};
|
|
438
|
+
var plugin = {
|
|
439
|
+
meta: {
|
|
440
|
+
name: "eslint-plugin-sanity",
|
|
441
|
+
version
|
|
442
|
+
},
|
|
443
|
+
rules
|
|
444
|
+
};
|
|
445
|
+
var recommended = {
|
|
446
|
+
plugins: {
|
|
447
|
+
sanity: plugin
|
|
448
|
+
},
|
|
449
|
+
rules: {
|
|
450
|
+
// === GROQ Rules ===
|
|
451
|
+
// Errors - these are serious performance or correctness issues
|
|
452
|
+
"sanity/groq-join-in-filter": "error",
|
|
453
|
+
// Warnings - performance issues that should be addressed
|
|
454
|
+
"sanity/groq-deep-pagination": "warn",
|
|
455
|
+
"sanity/groq-large-pages": "warn",
|
|
456
|
+
"sanity/groq-many-joins": "warn",
|
|
457
|
+
"sanity/groq-computed-value-in-filter": "warn",
|
|
458
|
+
"sanity/groq-non-literal-comparison": "warn",
|
|
459
|
+
"sanity/groq-order-on-expr": "warn",
|
|
460
|
+
"sanity/groq-very-large-query": "warn",
|
|
461
|
+
"sanity/groq-extremely-large-query": "error",
|
|
462
|
+
// Info - suggestions for improvement (off by default, enable as warnings)
|
|
463
|
+
"sanity/groq-join-to-get-id": "warn",
|
|
464
|
+
"sanity/groq-repeated-dereference": "warn",
|
|
465
|
+
"sanity/groq-match-on-id": "warn",
|
|
466
|
+
"sanity/groq-count-in-correlated-subquery": "warn",
|
|
467
|
+
"sanity/groq-deep-pagination-param": "warn",
|
|
468
|
+
// === Schema Rules ===
|
|
469
|
+
// Errors - correctness issues
|
|
470
|
+
"sanity/schema-missing-define-type": "error",
|
|
471
|
+
"sanity/schema-missing-define-field": "error",
|
|
472
|
+
"sanity/schema-reserved-field-name": "error",
|
|
473
|
+
// Warnings - best practice violations
|
|
474
|
+
"sanity/schema-missing-icon": "warn",
|
|
475
|
+
"sanity/schema-missing-title": "warn",
|
|
476
|
+
"sanity/schema-presentation-field-name": "warn",
|
|
477
|
+
"sanity/schema-missing-slug-source": "warn",
|
|
478
|
+
"sanity/schema-missing-required-validation": "warn",
|
|
479
|
+
"sanity/schema-heading-level-in-schema": "warn",
|
|
480
|
+
// Info - suggestions (off by default)
|
|
481
|
+
"sanity/schema-missing-description": "off",
|
|
482
|
+
"sanity/schema-boolean-instead-of-list": "off",
|
|
483
|
+
"sanity/schema-array-missing-constraints": "off",
|
|
484
|
+
"sanity/schema-unnecessary-reference": "off"
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
var strict = {
|
|
488
|
+
plugins: {
|
|
489
|
+
sanity: plugin
|
|
490
|
+
},
|
|
491
|
+
rules: Object.fromEntries(Object.keys(rules).map((ruleId) => [`sanity/${ruleId}`, "error"]))
|
|
492
|
+
};
|
|
493
|
+
var configs = {
|
|
494
|
+
recommended,
|
|
495
|
+
strict
|
|
496
|
+
};
|
|
497
|
+
var sanityPlugin = {
|
|
498
|
+
meta: plugin.meta,
|
|
499
|
+
rules,
|
|
500
|
+
configs
|
|
501
|
+
};
|
|
502
|
+
var index_default = sanityPlugin;
|
|
503
|
+
export {
|
|
504
|
+
configs,
|
|
505
|
+
index_default as default,
|
|
506
|
+
rules
|
|
507
|
+
};
|
|
508
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/utils/rule-factory.ts","../src/utils/groq-extractor.ts","../src/utils/schema-rule-factory.ts","../src/utils/schema-extractor.ts"],"sourcesContent":["/**\n * ESLint plugin for Sanity\n *\n * This plugin provides rules for linting GROQ queries and schema definitions\n * in JavaScript/TypeScript files.\n *\n * @example\n * ```js\n * // eslint.config.js\n * import sanity from 'eslint-plugin-sanity'\n *\n * export default [\n * {\n * plugins: { sanity },\n * rules: {\n * 'sanity/groq-join-in-filter': 'error',\n * 'sanity/schema-missing-icon': 'warn',\n * },\n * },\n * ]\n * ```\n *\n * Or use the recommended config:\n * ```js\n * import sanity from 'eslint-plugin-sanity'\n *\n * export default [\n * sanity.configs.recommended,\n * ]\n * ```\n */\n\nimport type { ESLint, Linter } from 'eslint'\nimport { createRequire } from 'node:module'\nimport { rules as groqRules } from '@sanity/groq-lint'\nimport { rules as schemaRules } from '@sanity/schema-lint'\nimport { createAllRules } from './utils/rule-factory'\nimport { createAllSchemaRules } from './utils/schema-rule-factory'\n\nconst require = createRequire(import.meta.url)\nconst { version } = require('../package.json') as { version: string }\n\n// Create ESLint rules from all GROQ lint rules\nconst groqEslintRules = createAllRules(groqRules)\n\n// Create ESLint rules from all schema lint rules\nconst schemaEslintRules = createAllSchemaRules(schemaRules)\n\n// Combine all rules\nconst rules = {\n ...groqEslintRules,\n ...schemaEslintRules,\n}\n\n// Build the plugin object\nconst plugin: ESLint.Plugin = {\n meta: {\n name: 'eslint-plugin-sanity',\n version,\n },\n rules,\n}\n\n// Create recommended config\nconst recommended: Linter.Config = {\n plugins: {\n sanity: plugin,\n },\n rules: {\n // === GROQ Rules ===\n\n // Errors - these are serious performance or correctness issues\n 'sanity/groq-join-in-filter': 'error',\n\n // Warnings - performance issues that should be addressed\n 'sanity/groq-deep-pagination': 'warn',\n 'sanity/groq-large-pages': 'warn',\n 'sanity/groq-many-joins': 'warn',\n 'sanity/groq-computed-value-in-filter': 'warn',\n 'sanity/groq-non-literal-comparison': 'warn',\n 'sanity/groq-order-on-expr': 'warn',\n 'sanity/groq-very-large-query': 'warn',\n 'sanity/groq-extremely-large-query': 'error',\n\n // Info - suggestions for improvement (off by default, enable as warnings)\n 'sanity/groq-join-to-get-id': 'warn',\n 'sanity/groq-repeated-dereference': 'warn',\n 'sanity/groq-match-on-id': 'warn',\n 'sanity/groq-count-in-correlated-subquery': 'warn',\n 'sanity/groq-deep-pagination-param': 'warn',\n\n // === Schema Rules ===\n\n // Errors - correctness issues\n 'sanity/schema-missing-define-type': 'error',\n 'sanity/schema-missing-define-field': 'error',\n 'sanity/schema-reserved-field-name': 'error',\n\n // Warnings - best practice violations\n 'sanity/schema-missing-icon': 'warn',\n 'sanity/schema-missing-title': 'warn',\n 'sanity/schema-presentation-field-name': 'warn',\n 'sanity/schema-missing-slug-source': 'warn',\n 'sanity/schema-missing-required-validation': 'warn',\n 'sanity/schema-heading-level-in-schema': 'warn',\n\n // Info - suggestions (off by default)\n 'sanity/schema-missing-description': 'off',\n 'sanity/schema-boolean-instead-of-list': 'off',\n 'sanity/schema-array-missing-constraints': 'off',\n 'sanity/schema-unnecessary-reference': 'off',\n },\n}\n\n// Create strict config (all rules as errors)\nconst strict: Linter.Config = {\n plugins: {\n sanity: plugin,\n },\n rules: Object.fromEntries(Object.keys(rules).map((ruleId) => [`sanity/${ruleId}`, 'error'])),\n}\n\n// Configs with explicit type annotation\nconst configs: { recommended: Linter.Config; strict: Linter.Config } = {\n recommended,\n strict,\n}\n\n// Plugin type\ninterface SanityPlugin {\n meta: ESLint.Plugin['meta']\n rules: typeof rules\n configs: typeof configs\n}\n\n// Default export for ESLint flat config\nconst sanityPlugin: SanityPlugin = {\n meta: plugin.meta,\n rules,\n configs,\n}\n\nexport default sanityPlugin\n\n// Named exports for flexibility\nexport { rules, configs }\n","import type { Rule as ESLintRule } from 'eslint'\nimport type { TSESTree } from '@typescript-eslint/types'\nimport type { Rule as GroqRule } from '@sanity/groq-lint'\nimport { lint, rules as allGroqRules } from '@sanity/groq-lint'\nimport { isGroqTaggedTemplate, extractGroqString } from './groq-extractor'\n\n/**\n * Build a config that enables only the specified rule\n */\nfunction buildSingleRuleConfig(ruleId: string): Record<string, boolean> {\n const config: Record<string, boolean> = {}\n for (const rule of allGroqRules) {\n config[rule.id] = rule.id === ruleId\n }\n return config\n}\n\n/**\n * Create an ESLint rule from a GROQ lint rule.\n */\nexport function createESLintRule(groqRule: GroqRule): ESLintRule.RuleModule {\n return {\n meta: {\n type: groqRule.category === 'correctness' ? 'problem' : 'suggestion',\n docs: {\n description: groqRule.description,\n recommended: groqRule.severity === 'error',\n },\n messages: {\n [groqRule.id]: '{{ message }}',\n },\n schema: [], // No options for now\n },\n\n create(context) {\n return {\n TaggedTemplateExpression(eslintNode: ESLintRule.Node) {\n // Cast to our TSESTree type for type-safe property access\n const node = eslintNode as unknown as TSESTree.TaggedTemplateExpression\n if (!isGroqTaggedTemplate(node)) {\n return\n }\n\n try {\n const query = extractGroqString(node)\n const result = lint(query, { config: { rules: buildSingleRuleConfig(groqRule.id) } })\n\n for (const finding of result.findings) {\n if (finding.ruleId === groqRule.id) {\n context.report({\n node: eslintNode,\n messageId: groqRule.id,\n data: {\n message: finding.help ? `${finding.message} ${finding.help}` : finding.message,\n },\n })\n }\n }\n } catch {\n // Parse error - don't report, let the user see it in runtime\n }\n },\n }\n },\n }\n}\n\n/**\n * Create all ESLint rules from GROQ lint rules.\n */\nexport function createAllRules(groqRules: GroqRule[]): Record<string, ESLintRule.RuleModule> {\n const eslintRules: Record<string, ESLintRule.RuleModule> = {}\n\n for (const rule of groqRules) {\n // Convert rule ID from snake_case to kebab-case for ESLint convention\n const eslintRuleId = `groq-${rule.id}`\n eslintRules[eslintRuleId] = createESLintRule(rule)\n }\n\n return eslintRules\n}\n","import type { TSESTree } from '@typescript-eslint/types'\n\n/**\n * Check if a node is a tagged template literal with a GROQ tag.\n * Matches: groq`...`, groq.something`...`\n */\nexport function isGroqTaggedTemplate(node: TSESTree.TaggedTemplateExpression): boolean {\n const tag = node.tag\n\n // groq`...`\n if (tag.type === 'Identifier' && tag.name === 'groq') {\n return true\n }\n\n // groq.something`...` (for groq.experimental etc)\n if (\n tag.type === 'MemberExpression' &&\n tag.object.type === 'Identifier' &&\n tag.object.name === 'groq'\n ) {\n return true\n }\n\n return false\n}\n\n/**\n * Extract the GROQ query string from a tagged template literal.\n * Handles template literals with expressions by replacing them with placeholders.\n */\nexport function extractGroqString(node: TSESTree.TaggedTemplateExpression): string {\n const { quasis, expressions } = node.quasi\n\n // Simple case: no expressions\n if (expressions.length === 0) {\n return quasis[0]?.value.cooked ?? quasis[0]?.value.raw ?? ''\n }\n\n // Build the string with placeholders for expressions\n let result = ''\n for (let i = 0; i < quasis.length; i++) {\n result += quasis[i]?.value.cooked ?? quasis[i]?.value.raw ?? ''\n if (i < expressions.length) {\n // Replace expression with a parameter placeholder\n // This allows the query to still parse while marking where expressions are\n result += `$__expr${i}__`\n }\n }\n\n return result\n}\n\n/**\n * Get the source location for reporting errors.\n * Returns the location of the template literal content, not the tag.\n */\nexport function getTemplateLocation(\n node: TSESTree.TaggedTemplateExpression\n): TSESTree.SourceLocation {\n return node.quasi.loc\n}\n","import type { Rule as ESLintRule } from 'eslint'\nimport type { TSESTree } from '@typescript-eslint/types'\nimport type { SchemaRule } from '@sanity/schema-lint'\nimport { lint, rules as schemaRules } from '@sanity/schema-lint'\nimport {\n isDefineTypeCall,\n extractSchemaFromDefineType,\n extractSchemaFromObject,\n} from './schema-extractor'\n\n/**\n * Create an ESLint rule from a schema lint rule.\n */\nexport function createSchemaESLintRule(schemaRule: SchemaRule): ESLintRule.RuleModule {\n return {\n meta: {\n type: schemaRule.category === 'correctness' ? 'problem' : 'suggestion',\n docs: {\n description: schemaRule.description,\n recommended: schemaRule.severity === 'error',\n },\n messages: {\n [schemaRule.id]: '{{ message }}',\n },\n schema: [], // No options for now\n },\n\n create(context) {\n return {\n // Handle defineType() calls\n CallExpression(eslintNode: ESLintRule.Node) {\n // Cast to our TSESTree type for type-safe property access\n const node = eslintNode as unknown as TSESTree.CallExpression\n if (!isDefineTypeCall(node)) {\n return\n }\n\n try {\n const schema = extractSchemaFromDefineType(node)\n if (!schema) {\n return\n }\n\n const result = lint(schema, schemaRules, {\n rules: [schemaRule],\n filePath: context.filename,\n })\n\n for (const finding of result.findings) {\n if (finding.ruleId === schemaRule.id) {\n context.report({\n node: eslintNode,\n messageId: schemaRule.id,\n data: {\n message: finding.help ? `${finding.message} ${finding.help}` : finding.message,\n },\n })\n }\n }\n } catch {\n // Parse error - don't report\n }\n },\n\n // Handle export statements with object literals (not using defineType)\n ExportNamedDeclaration(eslintNode: ESLintRule.Node) {\n // Cast to our TSESTree type for type-safe property access\n const node = eslintNode as unknown as TSESTree.ExportNamedDeclaration\n\n // Only check the missing-define-type rule for non-defineType exports\n if (schemaRule.id !== 'missing-define-type') {\n return\n }\n\n if (!node.declaration) {\n return\n }\n\n // export const foo = { name: '...', type: '...' }\n if (node.declaration.type === 'VariableDeclaration') {\n for (const declarator of node.declaration.declarations) {\n if (\n declarator.init?.type === 'ObjectExpression' &&\n !isWrappedInDefineType(declarator.init)\n ) {\n const schema = extractSchemaFromObject(declarator.init)\n if (schema && (schema.type === 'document' || schema.type === 'object')) {\n try {\n const result = lint(schema, schemaRules, {\n rules: [schemaRule],\n filePath: context.filename,\n })\n\n for (const finding of result.findings) {\n if (finding.ruleId === schemaRule.id) {\n context.report({\n node: eslintNode,\n messageId: schemaRule.id,\n data: {\n message: finding.help\n ? `${finding.message} ${finding.help}`\n : finding.message,\n },\n })\n }\n }\n } catch {\n // Parse error - don't report\n }\n }\n }\n }\n }\n },\n\n // Handle default exports with object literals\n ExportDefaultDeclaration(eslintNode: ESLintRule.Node) {\n // Cast to our TSESTree type for type-safe property access\n const node = eslintNode as unknown as TSESTree.ExportDefaultDeclaration\n\n // Only check the missing-define-type rule for non-defineType exports\n if (schemaRule.id !== 'missing-define-type') {\n return\n }\n\n if (node.declaration.type === 'ObjectExpression') {\n const schema = extractSchemaFromObject(node.declaration)\n if (schema && (schema.type === 'document' || schema.type === 'object')) {\n try {\n const result = lint(schema, schemaRules, {\n rules: [schemaRule],\n filePath: context.filename,\n })\n\n for (const finding of result.findings) {\n if (finding.ruleId === schemaRule.id) {\n context.report({\n node: eslintNode,\n messageId: schemaRule.id,\n data: {\n message: finding.help\n ? `${finding.message} ${finding.help}`\n : finding.message,\n },\n })\n }\n }\n } catch {\n // Parse error - don't report\n }\n }\n }\n },\n }\n },\n }\n}\n\n/**\n * Check if an object expression's parent is a defineType() call\n */\nfunction isWrappedInDefineType(_node: TSESTree.ObjectExpression): boolean {\n // This is a simplified check - in practice we check by seeing if\n // the parent is a CallExpression with defineType callee\n // For now we rely on the CallExpression handler\n return false\n}\n\n/**\n * Create all ESLint rules from schema lint rules.\n */\nexport function createAllSchemaRules(rules: SchemaRule[]): Record<string, ESLintRule.RuleModule> {\n const eslintRules: Record<string, ESLintRule.RuleModule> = {}\n\n for (const rule of rules) {\n // Use schema- prefix to distinguish from groq- rules\n const eslintRuleId = `schema-${rule.id}`\n eslintRules[eslintRuleId] = createSchemaESLintRule(rule)\n }\n\n return eslintRules\n}\n","import type { TSESTree } from '@typescript-eslint/types'\nimport type { SchemaType, SchemaField } from '@sanity/schema-lint'\nimport type { SourceSpan } from '@sanity/lint-core'\n\n/**\n * Check if a node is a defineType() call\n */\nexport function isDefineTypeCall(node: TSESTree.CallExpression): boolean {\n const callee = node.callee\n\n // defineType({ ... })\n if (callee.type === 'Identifier' && callee.name === 'defineType') {\n return true\n }\n\n return false\n}\n\n/**\n * Check if a node is a defineField() call\n */\nexport function isDefineFieldCall(node: TSESTree.CallExpression): boolean {\n const callee = node.callee\n\n // defineField({ ... })\n if (callee.type === 'Identifier' && callee.name === 'defineField') {\n return true\n }\n\n return false\n}\n\n/**\n * Convert ESLint location to our SourceSpan format\n */\nfunction toSourceSpan(loc: TSESTree.SourceLocation): SourceSpan {\n return {\n start: {\n line: loc.start.line,\n column: loc.start.column + 1, // 1-based\n offset: 0, // We don't have offset info easily\n },\n end: {\n line: loc.end.line,\n column: loc.end.column + 1, // 1-based\n offset: 0,\n },\n }\n}\n\n/**\n * Extract a string value from an AST node\n */\nfunction extractStringValue(node: TSESTree.Node | undefined): string | undefined {\n if (!node) return undefined\n\n if (node.type === 'Literal' && typeof node.value === 'string') {\n return node.value\n }\n\n // Handle template literals without expressions\n if (node.type === 'TemplateLiteral' && node.expressions.length === 0) {\n return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw\n }\n\n return undefined\n}\n\n/**\n * Extract a boolean value from an AST node\n */\nfunction extractBooleanValue(node: TSESTree.Node | undefined): boolean | undefined {\n if (!node) return undefined\n\n if (node.type === 'Literal' && typeof node.value === 'boolean') {\n return node.value\n }\n\n return undefined\n}\n\n/**\n * Check if a property exists in an object expression\n */\nfunction hasProperty(node: TSESTree.ObjectExpression, name: string): boolean {\n return node.properties.some((prop) => {\n if (prop.type === 'Property' && prop.key.type === 'Identifier') {\n return prop.key.name === name\n }\n return false\n })\n}\n\n/**\n * Get a property value from an object expression\n */\nfunction getProperty(node: TSESTree.ObjectExpression, name: string): TSESTree.Node | undefined {\n for (const prop of node.properties) {\n if (prop.type === 'Property' && prop.key.type === 'Identifier' && prop.key.name === name) {\n return prop.value\n }\n }\n return undefined\n}\n\n/**\n * Extract field options from an object expression\n */\nfunction extractFieldOptions(\n optionsNode: TSESTree.Node | undefined\n): SchemaField['options'] | undefined {\n if (!optionsNode || optionsNode.type !== 'ObjectExpression') {\n return undefined\n }\n\n const options: NonNullable<SchemaField['options']> = {}\n\n for (const prop of optionsNode.properties) {\n if (prop.type === 'Property' && prop.key.type === 'Identifier') {\n const name = prop.key.name\n\n if (name === 'source') {\n const source = extractStringValue(prop.value)\n if (source !== undefined) options.source = source\n } else if (name === 'hotspot') {\n const hotspot = extractBooleanValue(prop.value)\n if (hotspot !== undefined) options.hotspot = hotspot\n } else if (name === 'layout') {\n const layout = extractStringValue(prop.value)\n if (layout !== undefined) options.layout = layout\n } else if (name === 'list' && prop.value.type === 'ArrayExpression') {\n options.list = prop.value.elements.map((el) => {\n if (!el) return null\n if (el.type === 'Literal') return el.value\n if (el.type === 'ObjectExpression') {\n const value = extractStringValue(getProperty(el, 'value'))\n return { value }\n }\n return null\n })\n }\n }\n }\n\n return Object.keys(options).length > 0 ? options : undefined\n}\n\n/**\n * Extract a field from an object expression (either raw or wrapped in defineField)\n */\nfunction extractField(\n node: TSESTree.Node,\n usesDefineField: { value: boolean }\n): SchemaField | undefined {\n let fieldObj: TSESTree.ObjectExpression | undefined\n\n // Check if it's wrapped in defineField()\n if (node.type === 'CallExpression' && isDefineFieldCall(node)) {\n const arg = node.arguments[0]\n if (arg?.type === 'ObjectExpression') {\n fieldObj = arg\n }\n } else if (node.type === 'ObjectExpression') {\n fieldObj = node\n usesDefineField.value = false // At least one field doesn't use defineField\n }\n\n if (!fieldObj) return undefined\n\n const name = extractStringValue(getProperty(fieldObj, 'name'))\n const type = extractStringValue(getProperty(fieldObj, 'type'))\n\n if (!name || !type) return undefined\n\n const title = extractStringValue(getProperty(fieldObj, 'title'))\n const description = extractStringValue(getProperty(fieldObj, 'description'))\n const options = extractFieldOptions(getProperty(fieldObj, 'options'))\n\n const field: SchemaField = {\n name,\n type,\n hasValidation: hasProperty(fieldObj, 'validation'),\n hidden: hasProperty(fieldObj, 'hidden'),\n readOnly: hasProperty(fieldObj, 'readOnly'),\n span: toSourceSpan(fieldObj.loc),\n ...(title !== undefined && { title }),\n ...(description !== undefined && { description }),\n ...(options !== undefined && { options }),\n }\n\n // Check for deprecated\n if (hasProperty(fieldObj, 'deprecated')) {\n const deprecatedNode = getProperty(fieldObj, 'deprecated')\n const deprecatedValue = extractStringValue(deprecatedNode)\n field.deprecated = deprecatedValue ?? true\n }\n\n return field\n}\n\n/**\n * Extract fields from an array expression\n */\nfunction extractFields(node: TSESTree.Node | undefined): {\n fields: SchemaField[]\n usesDefineField: boolean\n} {\n const result = { fields: [] as SchemaField[], usesDefineField: true }\n\n if (!node || node.type !== 'ArrayExpression') {\n return result\n }\n\n const usesDefineFieldTracker = { value: true }\n\n for (const element of node.elements) {\n if (element) {\n const field = extractField(element, usesDefineFieldTracker)\n if (field) {\n result.fields.push(field)\n }\n }\n }\n\n result.usesDefineField = usesDefineFieldTracker.value\n\n return result\n}\n\n/**\n * Extract schema type from a defineType() call\n */\nexport function extractSchemaFromDefineType(node: TSESTree.CallExpression): SchemaType | undefined {\n const arg = node.arguments[0]\n\n if (!arg || arg.type !== 'ObjectExpression') {\n return undefined\n }\n\n const name = extractStringValue(getProperty(arg, 'name'))\n const type = extractStringValue(getProperty(arg, 'type'))\n\n if (!name || !type) {\n return undefined\n }\n\n const title = extractStringValue(getProperty(arg, 'title'))\n const description = extractStringValue(getProperty(arg, 'description'))\n const fieldsResult = extractFields(getProperty(arg, 'fields'))\n const hasFields = fieldsResult.fields.length > 0\n\n const schema: SchemaType = {\n name,\n type,\n hasIcon: hasProperty(arg, 'icon'),\n hasPreview: hasProperty(arg, 'preview'),\n usesDefineType: true,\n span: toSourceSpan(arg.loc),\n ...(title !== undefined && { title }),\n ...(description !== undefined && { description }),\n ...(hasFields && { fields: fieldsResult.fields }),\n ...(hasFields && { usesDefineField: fieldsResult.usesDefineField }),\n }\n\n return schema\n}\n\n/**\n * Extract schema type from a plain object literal (not wrapped in defineType)\n */\nexport function extractSchemaFromObject(node: TSESTree.ObjectExpression): SchemaType | undefined {\n const name = extractStringValue(getProperty(node, 'name'))\n const type = extractStringValue(getProperty(node, 'type'))\n\n if (!name || !type) {\n return undefined\n }\n\n // Only process if it looks like a Sanity schema (has name and type)\n const title = extractStringValue(getProperty(node, 'title'))\n const description = extractStringValue(getProperty(node, 'description'))\n const fieldsResult = extractFields(getProperty(node, 'fields'))\n const hasFields = fieldsResult.fields.length > 0\n\n const schema: SchemaType = {\n name,\n type,\n hasIcon: hasProperty(node, 'icon'),\n hasPreview: hasProperty(node, 'preview'),\n usesDefineType: false,\n span: toSourceSpan(node.loc),\n ...(title !== undefined && { title }),\n ...(description !== undefined && { description }),\n ...(hasFields && { fields: fieldsResult.fields }),\n ...(hasFields && { usesDefineField: fieldsResult.usesDefineField }),\n }\n\n return schema\n}\n"],"mappings":"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","names":["schemaRules","groqRules","lint","lint","rules","require","schemaRules"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eslint-plugin-sanity",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "ESLint plugin for Sanity Lint - lint GROQ queries and schema definitions",
|
|
5
|
+
"author": "Sanity.io <hello@sanity.io>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/sanity-io/sanity-lint.git",
|
|
10
|
+
"directory": "packages/eslint-plugin"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/sanity-io/sanity-lint#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/sanity-io/sanity-lint/issues"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@sanity/lint-core": "0.0.1",
|
|
30
|
+
"@sanity/schema-lint": "0.0.1",
|
|
31
|
+
"@sanity/groq-lint": "0.0.1"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"eslint": ">=8.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/eslint": "^9.6.1",
|
|
38
|
+
"@typescript-eslint/types": "^8.0.0",
|
|
39
|
+
"eslint": "^9.17.0",
|
|
40
|
+
"tsup": "^8.3.5",
|
|
41
|
+
"typescript": "^5.7.2"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"eslint",
|
|
45
|
+
"eslint-plugin",
|
|
46
|
+
"sanity",
|
|
47
|
+
"groq",
|
|
48
|
+
"schema",
|
|
49
|
+
"lint"
|
|
50
|
+
],
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"dev": "tsup --watch",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"clean": "rm -rf dist"
|
|
56
|
+
}
|
|
57
|
+
}
|