eslint-formatter-gitlab 5.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,22 +1,26 @@
1
1
  # ESLint Formatter for GitLab
2
2
 
3
+ [![gitlab pipeline](https://gitlab.com/remcohaszing/eslint-formatter-gitlab/badges/main/pipeline.svg)](https://gitlab.com/remcohaszing/eslint-formatter-gitlab/-/pipelines)
4
+ [![code coverage](https://gitlab.com/remcohaszing/eslint-formatter-gitlab/badges/main/coverage.svg)](https://gitlab.com/remcohaszing/eslint-formatter-gitlab/-/pipelines)
5
+ [![sponsors](https://img.shields.io/github/sponsors/remcohaszing)](https://github.com/sponsors/remcohaszing)
6
+ [![npm version](https://img.shields.io/npm/v/eslint-formatter-gitlab)](https://www.npmjs.com/package/eslint-formatter-gitlab)
7
+ [![npm downloads](https://img.shields.io/npm/dm/eslint-formatter-gitlab)](https://www.npmjs.com/package/eslint-formatter-gitlab)
8
+
9
+ <img alt="" height="256" src="https://gitlab.com/remcohaszing/eslint-formatter-gitlab/-/avatar">
10
+
3
11
  Show ESLint results directly in the
4
- [GitLab code quality](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html)
5
- results.
12
+ [GitLab code quality](https://docs.gitlab.com/ee/ci/testing/code_quality.html) results.
6
13
 
7
14
  ## Table of Contents
8
15
 
9
- - [Requirements](#requirements)
10
16
  - [Installation](#installation)
11
17
  - [Usage](#usage)
18
+ - [Programmatic usage](#programmatic-usage)
12
19
  - [Example](#example)
13
20
  - [Configuration](#configuration)
21
+ - [Compatibility](#compatibility)
14
22
  - [License](#license)
15
23
 
16
- ## Requirements
17
-
18
- This package requires at least Node.js 18 and ESLint 5.
19
-
20
24
  ## Installation
21
25
 
22
26
  Install `eslint` and `eslint-formatter-gitlab` using your package manager.
@@ -29,7 +33,7 @@ npm install --save-dev eslint eslint-formatter-gitlab
29
33
 
30
34
  Define a GitLab job to run `eslint`.
31
35
 
32
- _.gitlab-ci.yml_:
36
+ `.gitlab-ci.yml`:
33
37
 
34
38
  ```yaml
35
39
  eslint:
@@ -43,7 +47,21 @@ eslint:
43
47
  ```
44
48
 
45
49
  The formatter automatically detects a GitLab CI environment. It detects where to output the code
46
- quality report based on the GitLab configuration file.
50
+ quality report based on the GitLab configuration file. It also prints ESLint issues to the GitLab
51
+ job console with links.
52
+
53
+ ### Programmatic usage
54
+
55
+ The formatter can be used programmatically using ESLint.
56
+
57
+ ```js
58
+ import { ESLint } from 'eslint'
59
+
60
+ const eslint = new ESLint()
61
+ const formatter = await eslint.loadFormatter('gitlab')
62
+ const results = await eslint.lintFiles([])
63
+ const formatted = await formatter.format(results)
64
+ ```
47
65
 
48
66
  ## Example
49
67
 
@@ -55,8 +73,20 @@ An example of the results can be seen in
55
73
 
56
74
  ESLint formatters don’t take any configuration options. `eslint-formatter-gitlab` uses GitLab’s
57
75
  [predefined environment variables](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html)
58
- to configure the output. In addition, the environment variable `ESLINT_CODE_QUALITY_REPORT` is used
59
- to override the location to store the code quality report.
76
+ to configure the output. The following predefined environment variables are used:
77
+
78
+ - `CI_COMMIT_SHORT_SHA` to generate a link in the console output.
79
+ - `CI_CONFIG_PATH` to determine the GitLab CI configuration file to use. (Default: `.gitlab-ci.yml`)
80
+ - `CI_JOB_NAME` to determine which job configuration to read the code quality report path from.
81
+ - `CI_PROJECT_DIR` To determine relative paths. (Default: current working directory)
82
+ - `CI_PROJECT_URL` to generate a link in the console output.
83
+
84
+ In addition, the environment variable `ESLINT_CODE_QUALITY_REPORT` is used to override the location
85
+ to store the code quality report.
86
+
87
+ ## Compatibility
88
+
89
+ This package is compatible with Node.js 20 or greater and ESLint 9 or greater.
60
90
 
61
91
  ## License
62
92
 
@@ -1,31 +1,16 @@
1
1
  /**
2
- * @typedef {import('eslint').ESLint.LintResult} LintResult
3
- * @typedef {import('eslint').ESLint.LintResultData} LintResultData
4
- * @typedef {import('eslint').Linter.LintMessage} LintMessage
5
- * @typedef {import('codeclimate-types').Issue} Issue
2
+ * @import { Issue } from 'codeclimate-types'
3
+ * @import { ESLint, Linter } from 'eslint'
6
4
  */
7
5
 
8
- const { createHash } = require('node:crypto')
9
- const { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } = require('node:fs')
10
- const { EOL } = require('node:os')
11
- const { dirname, join, relative, resolve } = require('node:path')
6
+ import { createHash } from 'node:crypto'
7
+ import { mkdir, readFile, writeFile } from 'node:fs/promises'
8
+ import { dirname, join, relative, resolve } from 'node:path'
9
+ import { styleText } from 'node:util'
12
10
 
13
- const chalk = require('chalk')
14
- const yaml = require('yaml')
11
+ import yaml from 'yaml'
15
12
 
16
- const {
17
- CI_COMMIT_SHORT_SHA,
18
- CI_CONFIG_PATH = '.gitlab-ci.yml',
19
- CI_JOB_NAME,
20
- CI_PROJECT_DIR = process.cwd(),
21
- CI_PROJECT_URL,
22
- ESLINT_CODE_QUALITY_REPORT,
23
- GITLAB_CI
24
- } = process.env
25
-
26
- /**
27
- * @type {yaml.CollectionTag}
28
- */
13
+ /** @type {yaml.CollectionTag} */
29
14
  const reference = {
30
15
  tag: '!reference',
31
16
  collection: 'seq',
@@ -36,38 +21,50 @@ const reference = {
36
21
  }
37
22
 
38
23
  /**
39
- * @returns {string} The output path of the code quality artifact.
24
+ * @param {string} projectDir
25
+ * The GitLab project directory.
26
+ * @param {string | undefined} jobName
27
+ * The GitLab CI job name.
28
+ * @returns {Promise<string>}
29
+ * The output path of the code quality artifact.
40
30
  */
41
- function getOutputPath() {
42
- const configPath = join(CI_PROJECT_DIR, CI_CONFIG_PATH)
31
+ async function getOutputPath(projectDir, jobName) {
32
+ const configPath = join(projectDir, process.env.CI_CONFIG_PATH ?? '.gitlab-ci.yml')
43
33
  // GitlabCI allows a custom configuration path which can be a URL or a path relative to another
44
34
  // project. In these cases CI_CONFIG_PATH is empty and we'll have to require the user provide
45
35
  // ESLINT_CODE_QUALITY_REPORT.
46
- if (!existsSync(configPath) || !lstatSync(configPath).isFile()) {
36
+ let configContents
37
+ try {
38
+ configContents = await readFile(configPath, 'utf8')
39
+ } catch {
47
40
  throw new Error(
48
41
  'Could not resolve .gitlab-ci.yml to automatically detect report artifact path.' +
49
42
  ' Please manually provide a path via the ESLINT_CODE_QUALITY_REPORT variable.'
50
43
  )
51
44
  }
52
- const doc = yaml.parseDocument(readFileSync(configPath, 'utf8'), {
45
+ const doc = yaml.parseDocument(configContents, {
53
46
  version: '1.1',
54
47
  customTags: [reference]
55
48
  })
56
- const path = [CI_JOB_NAME, 'artifacts', 'reports', 'codequality']
49
+ const path = [jobName, 'artifacts', 'reports', 'codequality']
57
50
  const location = doc.getIn(path)
58
51
  if (typeof location !== 'string' || !location) {
59
52
  throw new TypeError(
60
53
  `Expected ${path.join('.')} to be one exact path, got: ${JSON.stringify(location)}`
61
54
  )
62
55
  }
63
- return resolve(CI_PROJECT_DIR, location)
56
+ return resolve(projectDir, location)
64
57
  }
65
58
 
66
59
  /**
67
- * @param {string} filePath The path to the linted file.
68
- * @param {LintMessage} message The ESLint report message.
69
- * @param {Set<string>} hashes Hashes already encountered. Used to avoid duplicate hashes
70
- * @returns {string} The fingerprint for the ESLint report message.
60
+ * @param {string} filePath
61
+ * The path to the linted file.
62
+ * @param {Linter.LintMessage} message
63
+ * The ESLint report message.
64
+ * @param {Set<string>} hashes
65
+ * Hashes already encountered. Used to avoid duplicate hashes
66
+ * @returns {string}
67
+ * The fingerprint for the ESLint report message.
71
68
  */
72
69
  function createFingerprint(filePath, message, hashes) {
73
70
  const md5 = createHash('md5')
@@ -95,11 +92,16 @@ function createFingerprint(filePath, message, hashes) {
95
92
  }
96
93
 
97
94
  /**
98
- * @param {LintResult[]} results The ESLint report results.
99
- * @param {LintResultData} data The ESLint report result data.
100
- * @returns {Issue[]} The ESLint messages in the form of a GitLab code quality report.
95
+ * @param {ESLint.LintResult[]} results
96
+ * The ESLint report results.
97
+ * @param {ESLint.LintResultData} data
98
+ * The ESLint report result data.
99
+ * @param {string} projectDir
100
+ * The GitLab project directory.
101
+ * @returns {Issue[]}
102
+ * The ESLint messages in the form of a GitLab code quality report.
101
103
  */
102
- function convert(results, data) {
104
+ function convert(results, data, projectDir) {
103
105
  /** @type {Issue[]} */
104
106
  const messages = []
105
107
 
@@ -107,7 +109,7 @@ function convert(results, data) {
107
109
  const hashes = new Set()
108
110
 
109
111
  for (const result of results) {
110
- const relativePath = relative(CI_PROJECT_DIR, result.filePath)
112
+ const relativePath = relative(projectDir, result.filePath)
111
113
 
112
114
  for (const message of result.messages) {
113
115
  /** @type {Issue} */
@@ -126,26 +128,36 @@ function convert(results, data) {
126
128
  }
127
129
  }
128
130
  }
129
- if (message.ruleId && message.ruleId in data.rulesMeta) {
130
- const { docs, type } = data.rulesMeta[message.ruleId]
131
- if (type === 'problem') {
132
- issue.categories.unshift('Bug Risk')
133
- }
131
+ messages.push(issue)
134
132
 
135
- if (docs) {
136
- let body = docs.description || ''
137
- if (docs.url) {
138
- if (body) {
139
- body += '\n\n'
140
- }
141
- body += `[${message.ruleId}](${docs.url})`
142
- }
143
- if (body) {
144
- issue.content = { body }
145
- }
133
+ if (!message.ruleId) {
134
+ continue
135
+ }
136
+
137
+ if (!data.rulesMeta[message.ruleId]) {
138
+ continue
139
+ }
140
+
141
+ const { docs, type } = data.rulesMeta[message.ruleId]
142
+ if (type === 'problem') {
143
+ issue.categories.unshift('Bug Risk')
144
+ }
145
+
146
+ if (!docs) {
147
+ continue
148
+ }
149
+
150
+ let body = docs.description || ''
151
+ if (docs.url) {
152
+ if (body) {
153
+ body += '\n\n'
146
154
  }
155
+ body += `[${message.ruleId}](${docs.url})`
156
+ }
157
+
158
+ if (body) {
159
+ issue.content = { body }
147
160
  }
148
- messages.push(issue)
149
161
  }
150
162
  }
151
163
  return messages
@@ -154,31 +166,39 @@ function convert(results, data) {
154
166
  /**
155
167
  * Make a text singular or plural based on the count.
156
168
  *
157
- * @param {number} count The count of the data.
158
- * @param {string} text The text to make singular or plural.
159
- * @returns {string} The formatted text.
169
+ * @param {number} count
170
+ * The count of the data.
171
+ * @param {string} text
172
+ * The text to make singular or plural.
173
+ * @returns {string}
174
+ * The formatted text.
160
175
  */
161
176
  function plural(count, text) {
162
177
  return `${count} ${text}${count === 1 ? '' : 's'}`
163
178
  }
164
179
 
165
180
  /**
166
- * @param {LintResult[]} results The ESLint report results.
167
- * @returns {string} The ESLint messages converted to a format
168
- * suitable as output in GitLab CI job logs.
181
+ * @param {ESLint.LintResult[]} results
182
+ * The ESLint report results.
183
+ * @param {string} projectDir
184
+ * The GitLab project directory.
185
+ * @returns {string}
186
+ * The ESLint messages converted to a format suitable as output in GitLab CI job logs.
169
187
  */
170
- function gitlabConsoleFormatter(results) {
188
+ function gitlabConsoleFormatter(results, projectDir) {
171
189
  // Severity labels manually padded to have equal lengths and end with spaces
172
- const labelFatal = `${chalk.magenta('fatal')} `
173
- const labelError = `${chalk.red('error')} `
174
- const labelWarn = `${chalk.yellow('warn')} `
190
+ const labelFatal = `${styleText('magenta', 'fatal')} `
191
+ const labelError = `${styleText('red', 'error')} `
192
+ const labelWarn = `${styleText('yellow', 'warn')} `
175
193
 
176
194
  const lines = ['']
177
195
 
178
196
  /** @type {string | undefined} */
179
197
  let gitLabBaseURL
180
- if (CI_PROJECT_URL && CI_COMMIT_SHORT_SHA) {
181
- gitLabBaseURL = `${CI_PROJECT_URL}/-/blob/${CI_COMMIT_SHORT_SHA}/`
198
+ const projectUrl = process.env.CI_PROJECT_URL
199
+ const commitSha = process.env.CI_COMMIT_SHORT_SHA
200
+ if (projectUrl && commitSha) {
201
+ gitLabBaseURL = `${projectUrl}/-/blob/${commitSha}/`
182
202
  }
183
203
 
184
204
  let fatal = 0
@@ -201,7 +221,7 @@ function gitlabConsoleFormatter(results) {
201
221
 
202
222
  for (const result of results) {
203
223
  const { filePath, messages } = result
204
- const repoFilePath = relative(CI_PROJECT_DIR, filePath)
224
+ const repoFilePath = relative(projectDir, filePath)
205
225
 
206
226
  for (const message of messages) {
207
227
  let line = message.fatal ? labelFatal : message.severity === 1 ? labelWarn : labelError
@@ -214,7 +234,7 @@ function gitlabConsoleFormatter(results) {
214
234
  if (message.endLine != null && message.endLine !== message.line) {
215
235
  anchor += `-${message.endLine}`
216
236
  }
217
- line += chalk.blue(`${gitLabBaseURL}${repoFilePath}${anchor}`)
237
+ line += styleText('blue', `${gitLabBaseURL}${repoFilePath}${anchor}`)
218
238
  } else {
219
239
  line += `${filePath}:${message.line}:${message.column}`
220
240
  }
@@ -226,33 +246,36 @@ function gitlabConsoleFormatter(results) {
226
246
  const total = warnings + errors + fatal
227
247
  if (total > 0) {
228
248
  const details = `(${fatal} fatal, ${plural(errors, 'error')}, ${plural(warnings, 'warning')})`
229
- lines.push('', `${chalk.red('✖')} ${plural(total, 'problem')} ${details}`)
249
+ lines.push('', `${styleText('red', '✖')} ${plural(total, 'problem')} ${details}`)
230
250
  } else {
231
- lines.push(`${chalk.green('✔')} No problems found`)
251
+ lines.push(`${styleText('green', '✔')} No problems found`)
232
252
  }
233
253
 
234
254
  lines.push('')
235
- return lines.join(EOL)
255
+ return lines.join('\n')
236
256
  }
237
257
 
238
258
  /**
239
- * @param {LintResult[]} results The ESLint report results.
240
- * @param {LintResultData} data The ESLint report result data.
259
+ * @param {ESLint.LintResult[]} results
260
+ * The ESLint report results.
261
+ * @param {ESLint.LintResultData} data
262
+ * The ESLint report result data.
263
+ * @returns {Promise<string>}
264
+ * The ESLint output to print to the console.
241
265
  */
242
- module.exports = (results, data) => {
243
- /* c8 ignore start */
244
- if (GITLAB_CI === 'true') {
245
- chalk.level = 1
246
- }
247
-
248
- /* c8 ignore stop */
249
- if (CI_JOB_NAME || ESLINT_CODE_QUALITY_REPORT) {
250
- const issues = convert(results, data)
251
- const outputPath = ESLINT_CODE_QUALITY_REPORT || getOutputPath()
266
+ async function eslintFormatterGitLab(results, data) {
267
+ let outputPath = process.env.ESLINT_CODE_QUALITY_REPORT
268
+ const projectDir = process.env.CI_PROJECT_DIR ?? data.cwd
269
+ const jobName = process.env.CI_JOB_NAME
270
+ if (jobName || outputPath) {
271
+ const issues = convert(results, data, projectDir)
272
+ outputPath ||= await getOutputPath(projectDir, jobName)
252
273
  const dir = dirname(outputPath)
253
- mkdirSync(dir, { recursive: true })
254
- writeFileSync(outputPath, JSON.stringify(issues, null, 2))
274
+ await mkdir(dir, { recursive: true })
275
+ await writeFile(outputPath, `${JSON.stringify(issues, null, 2)}\n`)
255
276
  }
256
277
 
257
- return gitlabConsoleFormatter(results)
278
+ return gitlabConsoleFormatter(results, projectDir)
258
279
  }
280
+
281
+ export default eslintFormatterGitLab
package/package.json CHANGED
@@ -1,19 +1,27 @@
1
1
  {
2
2
  "name": "eslint-formatter-gitlab",
3
- "version": "5.0.0",
3
+ "version": "6.0.0",
4
4
  "description": "Show ESLint results directly in the GitLab code quality results",
5
+ "type": "module",
5
6
  "author": "Remco Haszing <remcohaszing@gmail.com>",
6
7
  "license": "MIT",
7
8
  "homepage": "https://gitlab.com/remcohaszing/eslint-formatter-gitlab#readme",
8
9
  "repository": "gitlab:remcohaszing/eslint-formatter-gitlab",
9
10
  "funding": "https://github.com/sponsors/remcohaszing",
10
11
  "bugs": "https://gitlab.com/remcohaszing/eslint-formatter-gitlab/-/issues",
11
- "exports": "./index.js",
12
+ "main": "./lib/eslint-formatter-gitlab.js",
13
+ "types": "./types/eslint-formatter-gitlab.d.ts",
14
+ "exports": {
15
+ "types": "./types/eslint-formatter-gitlab.d.ts",
16
+ "default": "./lib/eslint-formatter-gitlab.js"
17
+ },
12
18
  "files": [
13
- "index.js"
19
+ "lib",
20
+ "types"
14
21
  ],
15
22
  "scripts": {
16
- "test": "c8 node --test --test-reporter @reporters/junit --test-reporter-destination=junit.xml --test-reporter spec --test-reporter-destination stdout"
23
+ "prepack": "tsc --build",
24
+ "test": "c8 node --test --test-reporter junit --test-reporter-destination=junit.xml --test-reporter spec --test-reporter-destination stdout"
17
25
  },
18
26
  "keywords": [
19
27
  "eslint",
@@ -23,23 +31,19 @@
23
31
  "gitlab-ci"
24
32
  ],
25
33
  "dependencies": {
26
- "chalk": "^4.0.0",
27
34
  "yaml": "^2.0.0"
28
35
  },
29
36
  "peerDependencies": {
30
- "eslint": "^5 || ^6 || ^7 || ^8"
37
+ "eslint": ">=9"
31
38
  },
32
39
  "devDependencies": {
33
- "@reporters/junit": "^1.0.0",
34
- "@types/eslint": "^8.0.0",
35
- "@types/node": "^20.0.0",
36
- "c8": "^8.0.0",
40
+ "@remcohaszing/eslint": "^11.0.0",
41
+ "@types/node": "^22.0.0",
42
+ "c8": "^10.0.0",
37
43
  "codeclimate-types": "^0.3.0",
38
- "eslint": "^8.0.0",
39
- "eslint-config-remcohaszing": "^9.0.0",
40
- "prettier": "^2.0.0",
41
- "remark-cli": "^11.0.0",
42
- "remark-preset-remcohaszing": "^1.0.0",
44
+ "prettier": "^3.0.0",
45
+ "remark-cli": "^12.0.0",
46
+ "remark-preset-remcohaszing": "^3.0.0",
43
47
  "typescript": "^5.0.0"
44
48
  }
45
49
  }
@@ -0,0 +1,12 @@
1
+ export default eslintFormatterGitLab;
2
+ /**
3
+ * @param {ESLint.LintResult[]} results
4
+ * The ESLint report results.
5
+ * @param {ESLint.LintResultData} data
6
+ * The ESLint report result data.
7
+ * @returns {Promise<string>}
8
+ * The ESLint output to print to the console.
9
+ */
10
+ declare function eslintFormatterGitLab(results: ESLint.LintResult[], data: ESLint.LintResultData): Promise<string>;
11
+ import type { ESLint } from 'eslint';
12
+ //# sourceMappingURL=eslint-formatter-gitlab.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eslint-formatter-gitlab.d.ts","sourceRoot":"","sources":["../lib/eslint-formatter-gitlab.js"],"names":[],"mappings":";AAiQA;;;;;;;GAOG;AACH,gDAPW,iBAAiB,EAAE,QAEnB,qBAAqB,GAEnB,OAAO,CAAC,MAAM,CAAC,CAgB3B;4BApRkC,QAAQ"}