@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.
@@ -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",
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.58.0",
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
- "globals": "^17.4.0",
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": {