@nitra/eslint-config 3.4.3 → 3.4.8
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/graphql-eslint-anchor.js +137 -0
- package/index.js +106 -1
- package/package.json +11 -3
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Обхід обмеження graphql-eslint: loadOnDiskGraphQLConfig(filePath) для віртуальних
|
|
3
|
+
* шляхів виду …/Component.vue/…/script.js має отримувати шлях до фізичного .vue, інакше rootDir
|
|
4
|
+
* стає некоректним і eslint не бачить схему з .graphqlrc.yml.
|
|
5
|
+
* @see upstream graphql-eslint (MIT) on GitHub
|
|
6
|
+
*/
|
|
7
|
+
import { relative } from 'node:path'
|
|
8
|
+
import { gqlPluckFromCodeStringSync } from '@graphql-tools/graphql-tag-pluck'
|
|
9
|
+
import { asArray } from '@graphql-tools/utils'
|
|
10
|
+
import graphqlEslintPlugin from '@graphql-eslint/eslint-plugin'
|
|
11
|
+
import { loadOnDiskGraphQLConfig } from '@graphql-eslint/eslint-plugin/graphql-config'
|
|
12
|
+
import { version } from '@graphql-eslint/eslint-plugin/meta'
|
|
13
|
+
import { CWD, REPORT_ON_FIRST_CHARACTER } from '@graphql-eslint/eslint-plugin/utils'
|
|
14
|
+
|
|
15
|
+
const blocksMap = /* @__PURE__ */ new Map()
|
|
16
|
+
let onDiskConfig
|
|
17
|
+
let onDiskConfigLoaded = false
|
|
18
|
+
const RELEVANT_KEYWORDS = ['gql', 'graphql', 'GraphQL']
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Шлях для graphql-config: для віртуальних блоків Vue — фізичний .vue, інакше той самий filePath.
|
|
22
|
+
* @param {string} filePath шлях файлу, який обробляє processor
|
|
23
|
+
* @returns {string} шлях для loadOnDiskGraphQLConfig та getProjectForFile
|
|
24
|
+
*/
|
|
25
|
+
export function graphQLConfigPathAnchor(filePath = '') {
|
|
26
|
+
if (filePath.includes('.vue/')) {
|
|
27
|
+
return filePath.slice(0, filePath.indexOf('.vue/') + '.vue'.length)
|
|
28
|
+
}
|
|
29
|
+
return filePath
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Processor як у graphql-eslint, зі стабільним anchor для graphql-config. */
|
|
33
|
+
export const graphqlCodefileProcessor = {
|
|
34
|
+
meta: {
|
|
35
|
+
name: '@nitra/graphql-codefile-processor',
|
|
36
|
+
version
|
|
37
|
+
},
|
|
38
|
+
supportsAutofix: true,
|
|
39
|
+
preprocess(code, filePath) {
|
|
40
|
+
if (process.env.ESLINT_USE_FLAT_CONFIG !== 'false' && filePath.endsWith('.vue')) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
'Processing of `.vue` files is no longer supported, follow the new official vue example for ESLint flat config https://github.com/graphql-hive/graphql-eslint/tree/master/examples/vue-code-file'
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
if (!onDiskConfigLoaded) {
|
|
46
|
+
onDiskConfig = loadOnDiskGraphQLConfig(graphQLConfigPathAnchor(filePath))
|
|
47
|
+
onDiskConfigLoaded = true
|
|
48
|
+
}
|
|
49
|
+
let keywords = RELEVANT_KEYWORDS
|
|
50
|
+
const anchor = graphQLConfigPathAnchor(filePath)
|
|
51
|
+
const pluckConfig = onDiskConfig?.getProjectForFile(anchor)?.extensions?.pluckConfig
|
|
52
|
+
if (pluckConfig) {
|
|
53
|
+
const { modules = [], globalGqlIdentifierName = ['gql', 'graphql'], gqlMagicComment = 'GraphQL' } = pluckConfig
|
|
54
|
+
const mods = modules.map(({ identifier }) => identifier).filter(v => !!v)
|
|
55
|
+
const result = [...mods, ...asArray(globalGqlIdentifierName), gqlMagicComment]
|
|
56
|
+
keywords = [...new Set(result)]
|
|
57
|
+
}
|
|
58
|
+
if (keywords.every(keyword => !code.includes(keyword))) {
|
|
59
|
+
return [code]
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const sources = gqlPluckFromCodeStringSync(filePath, code, {
|
|
63
|
+
skipIndent: true,
|
|
64
|
+
...pluckConfig
|
|
65
|
+
})
|
|
66
|
+
const blocks = sources.map(item => ({
|
|
67
|
+
filename: 'document.graphql',
|
|
68
|
+
text: item.body,
|
|
69
|
+
lineOffset: item.locationOffset.line - 1,
|
|
70
|
+
offset: item.locationOffset.index + 1
|
|
71
|
+
}))
|
|
72
|
+
blocksMap.set(filePath, blocks)
|
|
73
|
+
return [...blocks, code]
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error instanceof Error) {
|
|
76
|
+
error.message = `[graphql-eslint] Error while preprocessing "${relative(CWD, filePath)}" file
|
|
77
|
+
|
|
78
|
+
${error.message}`
|
|
79
|
+
}
|
|
80
|
+
console.error(error)
|
|
81
|
+
return [code]
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
postprocess(messages, filePath) {
|
|
85
|
+
const blocks = blocksMap.get(filePath) || []
|
|
86
|
+
for (const [i, block] of blocks.entries()) {
|
|
87
|
+
const { lineOffset, offset } = block
|
|
88
|
+
for (const message of messages[i] || []) {
|
|
89
|
+
const isVueOrSvelte = /\.(vue|svelte)$/.test(filePath)
|
|
90
|
+
if (isVueOrSvelte) {
|
|
91
|
+
delete message.endLine
|
|
92
|
+
delete message.endColumn
|
|
93
|
+
delete message.fix
|
|
94
|
+
delete message.suggestions
|
|
95
|
+
Object.assign(message, REPORT_ON_FIRST_CHARACTER)
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
message.line += lineOffset
|
|
99
|
+
if (typeof message.endLine === 'number') {
|
|
100
|
+
message.endLine += lineOffset
|
|
101
|
+
}
|
|
102
|
+
if (message.fix) {
|
|
103
|
+
message.fix.range[0] += offset
|
|
104
|
+
message.fix.range[1] += offset
|
|
105
|
+
}
|
|
106
|
+
for (const suggestion of message.suggestions || []) {
|
|
107
|
+
const [start, end] = suggestion.fix.range
|
|
108
|
+
suggestion.fix.range = [start + offset, end + offset]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const result = messages.flat()
|
|
113
|
+
return result.toSorted((a, b) => a.line - b.line || a.column - b.column)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const baseParser = graphqlEslintPlugin.parser
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Парсер graphql-eslint: той самий anchor для filePath, щоб loadGraphQLConfig знаходив проєкт для віртуальних document.graphql з-під Vue.
|
|
121
|
+
* @param {string} code вміст GraphQL-документа
|
|
122
|
+
* @param {import('eslint').Linter.ParserOptions} options опції ESLint (filePath тощо)
|
|
123
|
+
* @returns {ReturnType<typeof baseParser.parseForESLint>} AST і parserServices для graphql-eslint
|
|
124
|
+
*/
|
|
125
|
+
export function graphqlParseForESLintWithAnchor(code, options = {}) {
|
|
126
|
+
const filePath = options.filePath || ''
|
|
127
|
+
return baseParser.parseForESLint(code, {
|
|
128
|
+
...options,
|
|
129
|
+
filePath: graphQLConfigPathAnchor(filePath)
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Парсер для flat config (обгортка над graphql-eslint). */
|
|
134
|
+
export const graphqlParserWithConfigAnchor = {
|
|
135
|
+
meta: baseParser.meta,
|
|
136
|
+
parseForESLint: graphqlParseForESLintWithAnchor
|
|
137
|
+
}
|
package/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { fixupPluginRules } from '@eslint/compat'
|
|
1
2
|
import js from '@eslint/js'
|
|
3
|
+
import graphqlEslintPlugin from '@graphql-eslint/eslint-plugin'
|
|
4
|
+
import { graphqlCodefileProcessor, graphqlParserWithConfigAnchor } from './graphql-eslint-anchor.js'
|
|
2
5
|
import microsoftSdl from '@microsoft/eslint-plugin-sdl'
|
|
3
6
|
import { flatConfigs as importPlugin } from 'eslint-plugin-import'
|
|
4
7
|
import jsdocPlugin from 'eslint-plugin-jsdoc'
|
|
@@ -10,11 +13,58 @@ import securityPlugin from 'eslint-plugin-security'
|
|
|
10
13
|
import unicornPlugin from 'eslint-plugin-unicorn'
|
|
11
14
|
import vuePlugin from 'eslint-plugin-vue'
|
|
12
15
|
import { configs as ymlConfigs } from 'eslint-plugin-yml'
|
|
16
|
+
import { mergeProcessors } from 'eslint-merge-processors'
|
|
17
|
+
import processorVueBlocks from 'eslint-processor-vue-blocks'
|
|
13
18
|
import globals from 'globals'
|
|
14
19
|
|
|
15
20
|
/** Glob-патерни файлів для eslint-plugin-unicorn (JS-подібні джерела; без сирих YAML/Markdown). */
|
|
16
21
|
const UNICORN_FILES = ['**/*.{js,mjs,cjs,vue}']
|
|
17
22
|
|
|
23
|
+
/** Віртуальні документи з gql/graphql у .js/.vue (processor); ESLint обробляє їх як окремі `*.graphql` файли. */
|
|
24
|
+
const GRAPHQL_EXTRACTED_DOCUMENT_FILES = ['**/*.graphql']
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Правила для витягнутих GraphQL-блоків: пресет operations-recommended (поля, типи, змінні).
|
|
28
|
+
* Схема та documents підтягуються через graphql-config (файл налаштувань у корені пакета з операціями, зазвичай YAML).
|
|
29
|
+
*/
|
|
30
|
+
const graphqlExtractedDocumentLint = {
|
|
31
|
+
files: GRAPHQL_EXTRACTED_DOCUMENT_FILES,
|
|
32
|
+
languageOptions: {
|
|
33
|
+
parser: graphqlParserWithConfigAnchor
|
|
34
|
+
},
|
|
35
|
+
plugins: {
|
|
36
|
+
// ESLint 10: graphql-eslint ще викликає context.getSourceCode() у частині правил — fixup з @eslint/compat.
|
|
37
|
+
'@graphql-eslint': fixupPluginRules(graphqlEslintPlugin)
|
|
38
|
+
},
|
|
39
|
+
rules: {
|
|
40
|
+
...graphqlEslintPlugin.configs['flat/operations-recommended'].rules
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Вимкнення unique-operation-name лише для віртуальних документів з-під SFC: mergeProcessors дає кілька
|
|
46
|
+
* блоків script/template з одного `.vue`, graphql-eslint витягує gql з кожного — хибні «дублікати» імені.
|
|
47
|
+
* Шляхи виглядають як `…/Component.vue/1_script.js/0_document.graphql` (див. debug ESLint `eslint:linter`).
|
|
48
|
+
*/
|
|
49
|
+
const graphqlExtractedDocumentLintVueUniqueOperationOverride = {
|
|
50
|
+
files: ['**/*.vue/**/*.graphql'],
|
|
51
|
+
rules: {
|
|
52
|
+
'@graphql-eslint/unique-operation-name': 'off'
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Processor для .vue у flat config: vue + віртуальні JS-блоки для graphql-eslint (офіційний приклад). */
|
|
57
|
+
const vueGraphqlMergedProcessor = mergeProcessors([
|
|
58
|
+
vuePlugin.processors.vue,
|
|
59
|
+
processorVueBlocks({
|
|
60
|
+
blocks: {
|
|
61
|
+
script: true,
|
|
62
|
+
scriptSetup: true,
|
|
63
|
+
customBlocks: true
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
])
|
|
67
|
+
|
|
18
68
|
/**
|
|
19
69
|
* Обмежує масив flat-конфігів ESLint заданими `files` (копія кожного елемента).
|
|
20
70
|
* @param {import('eslint').Linter.FlatConfig[]} configs вхідні конфіги
|
|
@@ -37,7 +87,7 @@ const importPluginEcmaLatest = {
|
|
|
37
87
|
const all = [
|
|
38
88
|
{
|
|
39
89
|
// До дефолтних "**/node_modules/", ".git/" додаємо
|
|
40
|
-
ignores: ['.yarn/**', '**/dist/**']
|
|
90
|
+
ignores: ['.yarn/**', '**/dist/**', '**/schema.graphql']
|
|
41
91
|
},
|
|
42
92
|
// Загальні правила для всіх Yaml файлів проекту
|
|
43
93
|
...ymlConfigs['flat/recommended'],
|
|
@@ -219,6 +269,37 @@ const vue2 = [
|
|
|
219
269
|
}
|
|
220
270
|
]
|
|
221
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Увімкнути graphql-eslint processor лише для .js (gql у коді).
|
|
274
|
+
* @param {import('eslint').Linter.FlatConfig[]} result масив конфігів
|
|
275
|
+
* @param {string[]} dirs кореневі директорії
|
|
276
|
+
*/
|
|
277
|
+
function pushGraphqlJsProcessors(result, dirs) {
|
|
278
|
+
if (!dirs?.length) {
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
result.push({
|
|
282
|
+
files: dirs.map(name => `${name}/**/*.js`),
|
|
283
|
+
processor: graphqlCodefileProcessor
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Merge processor для .vue (vue + vue-blocks + graphql) — має бути останнім серед конфігів для `*.vue`,
|
|
289
|
+
* інакше пресет eslint-plugin-vue (`processor: "vue/vue"`) перезапише його.
|
|
290
|
+
* @param {import('eslint').Linter.FlatConfig[]} result масив конфігів
|
|
291
|
+
* @param {string[]} dirs кореневі директорії Vue-пакетів
|
|
292
|
+
*/
|
|
293
|
+
function pushGraphqlVueMergedProcessorLast(result, dirs) {
|
|
294
|
+
if (!dirs?.length) {
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
result.push({
|
|
298
|
+
files: dirs.map(name => `${name}/**/*.vue`),
|
|
299
|
+
processor: vueGraphqlMergedProcessor
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
|
|
222
303
|
/**
|
|
223
304
|
* Додає до result конфіги для Vue-проєктів (vite, .vue, .js).
|
|
224
305
|
* @param {import('eslint').Linter.FlatConfig[]} result масив конфігів
|
|
@@ -252,6 +333,9 @@ function pushVueConfigs(result, dirs, options = {}) {
|
|
|
252
333
|
// oxlint-disable-next-line unicorn/no-object-as-default-parameter
|
|
253
334
|
export function getConfig(params = { node: [], vue: [], vue2: [] }) {
|
|
254
335
|
const result = [...all]
|
|
336
|
+
let graphqlCodefile = false
|
|
337
|
+
/** Директорії Vue/Vue2, для яких потрібен merge processor після oxlint. */
|
|
338
|
+
const graphqlVueDirsLast = []
|
|
255
339
|
|
|
256
340
|
if (params.node?.length) {
|
|
257
341
|
const files = params.node.map(name => `${name}/**/*.js`)
|
|
@@ -259,16 +343,37 @@ export function getConfig(params = { node: [], vue: [], vue2: [] }) {
|
|
|
259
343
|
...node.map(configObject => ({ files, ...configObject })),
|
|
260
344
|
...flatConfigsWithFiles(microsoftSdlNodeConfigsWithoutN, files)
|
|
261
345
|
)
|
|
346
|
+
// Каталог `npm` пакета конфігу зазвичай без graphql-config; перший preprocess інакше кешує null і ламає демо/vue.
|
|
347
|
+
const graphqlNodeDirs = params.node.filter(d => d !== 'npm')
|
|
348
|
+
if (graphqlNodeDirs.length) {
|
|
349
|
+
pushGraphqlJsProcessors(result, graphqlNodeDirs)
|
|
350
|
+
graphqlCodefile = true
|
|
351
|
+
}
|
|
262
352
|
}
|
|
263
353
|
|
|
264
354
|
if (params.vue?.length) {
|
|
265
355
|
pushVueConfigs(result, params.vue, { includeVite: true })
|
|
356
|
+
pushGraphqlJsProcessors(result, params.vue)
|
|
357
|
+
graphqlVueDirsLast.push(...params.vue)
|
|
358
|
+
graphqlCodefile = true
|
|
266
359
|
}
|
|
267
360
|
|
|
268
361
|
if (params.vue2?.length) {
|
|
269
362
|
pushVueConfigs(result, params.vue2, { extraVueConfigs: vue2 })
|
|
363
|
+
pushGraphqlJsProcessors(result, params.vue2)
|
|
364
|
+
graphqlVueDirsLast.push(...params.vue2)
|
|
365
|
+
graphqlCodefile = true
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (graphqlCodefile) {
|
|
369
|
+
result.push(graphqlExtractedDocumentLint, graphqlExtractedDocumentLintVueUniqueOperationOverride)
|
|
270
370
|
}
|
|
271
371
|
|
|
272
372
|
result.push(...oxlint.configs['flat/recommended'])
|
|
373
|
+
|
|
374
|
+
if (graphqlVueDirsLast.length) {
|
|
375
|
+
pushGraphqlVueMergedProcessorLast(result, graphqlVueDirsLast)
|
|
376
|
+
}
|
|
377
|
+
|
|
273
378
|
return result
|
|
274
379
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nitra/eslint-config",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.8",
|
|
4
4
|
"description": "An ESLint shareable config for projects using Vue and Node",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"url": "git@github.com:nitra/eslint-config.git"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
|
+
"graphql-eslint-anchor.js",
|
|
17
18
|
"index.d.ts",
|
|
18
19
|
"index.js"
|
|
19
20
|
],
|
|
@@ -21,20 +22,27 @@
|
|
|
21
22
|
"types": "./index.d.ts",
|
|
22
23
|
"exports": "./index.js",
|
|
23
24
|
"dependencies": {
|
|
25
|
+
"@eslint/compat": "^2.0.5",
|
|
24
26
|
"@eslint/js": "^10.0.1",
|
|
27
|
+
"@graphql-eslint/eslint-plugin": "^4.4.0",
|
|
28
|
+
"@graphql-tools/graphql-tag-pluck": "^8.3.4",
|
|
29
|
+
"@graphql-tools/utils": "^10.0.0",
|
|
25
30
|
"@microsoft/eslint-plugin-sdl": "^1.1.0",
|
|
26
31
|
"eslint": "^10.2.0",
|
|
32
|
+
"eslint-merge-processors": "^2.0.0",
|
|
27
33
|
"eslint-plugin-import": "^2.32.0",
|
|
28
34
|
"eslint-plugin-jsdoc": "^62.9.0",
|
|
29
35
|
"eslint-plugin-jsonc": "^3.1.2",
|
|
30
36
|
"eslint-plugin-markdown": "^5.1.0",
|
|
31
37
|
"eslint-plugin-n": "^17.24.0",
|
|
32
|
-
"eslint-plugin-oxlint": "^1.
|
|
38
|
+
"eslint-plugin-oxlint": "^1.59.0",
|
|
33
39
|
"eslint-plugin-security": "^4.0.0",
|
|
34
40
|
"eslint-plugin-unicorn": "^64.0.0",
|
|
35
41
|
"eslint-plugin-vue": "^10.8.0",
|
|
36
42
|
"eslint-plugin-yml": "^3.3.1",
|
|
37
|
-
"
|
|
43
|
+
"eslint-processor-vue-blocks": "^2.0.0",
|
|
44
|
+
"globals": "^17.5.0",
|
|
45
|
+
"graphql": "^16.11.0",
|
|
38
46
|
"vue-eslint-parser": "^10.4.0"
|
|
39
47
|
},
|
|
40
48
|
"engines": {
|