eslint-formatter-gitlab 4.0.0 → 5.1.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/LICENSE.md +2 -2
- package/README.md +21 -11
- package/index.d.ts +11 -0
- package/index.d.ts.map +1 -0
- package/index.js +173 -179
- package/package.json +16 -24
package/LICENSE.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Copyright © 2018 Remco Haszing
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
6
|
-
associated documentation files (the
|
|
6
|
+
associated documentation files (the “Software”), to deal in the Software without restriction,
|
|
7
7
|
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|
8
8
|
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|
9
9
|
furnished to do so, subject to the following conditions:
|
|
@@ -11,7 +11,7 @@ furnished to do so, subject to the following conditions:
|
|
|
11
11
|
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
12
12
|
portions of the Software.
|
|
13
13
|
|
|
14
|
-
THE SOFTWARE IS PROVIDED
|
|
14
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
15
15
|
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
16
16
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
|
17
17
|
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
package/README.md
CHANGED
|
@@ -2,11 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
Show ESLint results directly in the
|
|
4
4
|
[GitLab code quality](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html)
|
|
5
|
-
results
|
|
5
|
+
results.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Requirements](#requirements)
|
|
10
|
+
- [Installation](#installation)
|
|
11
|
+
- [Usage](#usage)
|
|
12
|
+
- [Example](#example)
|
|
13
|
+
- [Configuration](#configuration)
|
|
14
|
+
- [License](#license)
|
|
6
15
|
|
|
7
16
|
## Requirements
|
|
8
17
|
|
|
9
|
-
This package requires at least Node.js
|
|
18
|
+
This package requires at least Node.js 18 and ESLint 5.
|
|
10
19
|
|
|
11
20
|
## Installation
|
|
12
21
|
|
|
@@ -24,7 +33,7 @@ _.gitlab-ci.yml_:
|
|
|
24
33
|
|
|
25
34
|
```yaml
|
|
26
35
|
eslint:
|
|
27
|
-
image: node:
|
|
36
|
+
image: node:20-alpine
|
|
28
37
|
script:
|
|
29
38
|
- npm ci
|
|
30
39
|
- npx eslint --format gitlab .
|
|
@@ -33,8 +42,8 @@ eslint:
|
|
|
33
42
|
codequality: gl-codequality.json
|
|
34
43
|
```
|
|
35
44
|
|
|
36
|
-
The formatter
|
|
37
|
-
|
|
45
|
+
The formatter automatically detects a GitLab CI environment. It detects where to output the code
|
|
46
|
+
quality report based on the GitLab configuration file.
|
|
38
47
|
|
|
39
48
|
## Example
|
|
40
49
|
|
|
@@ -42,12 +51,13 @@ An example of the results can be seen in
|
|
|
42
51
|
[Merge Request !1](https://gitlab.com/remcohaszing/eslint-formatter-gitlab/merge_requests/1) of
|
|
43
52
|
`eslint-formatter-gitlab` itself.
|
|
44
53
|
|
|
45
|
-
## Configuration
|
|
54
|
+
## Configuration
|
|
46
55
|
|
|
47
|
-
ESLint formatters don’t take any configuration options. `eslint-formatter-gitlab` uses GitLab
|
|
48
|
-
environment variables
|
|
49
|
-
|
|
56
|
+
ESLint formatters don’t take any configuration options. `eslint-formatter-gitlab` uses GitLab’s
|
|
57
|
+
[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.
|
|
50
60
|
|
|
51
|
-
|
|
61
|
+
## License
|
|
52
62
|
|
|
53
|
-
[MIT](LICENSE.md)
|
|
63
|
+
[MIT](LICENSE.md) © [Remco Haszing](https://gitlab.com/remcohaszing)
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export = eslintFormatterGitLab;
|
|
2
|
+
/**
|
|
3
|
+
* @param {import('eslint').ESLint.LintResult[]} results
|
|
4
|
+
* The ESLint report results.
|
|
5
|
+
* @param {import('eslint').ESLint.LintResultData} data
|
|
6
|
+
* The ESLint report result data.
|
|
7
|
+
* @returns {string}
|
|
8
|
+
* The ESLint output to print to the console.
|
|
9
|
+
*/
|
|
10
|
+
declare function eslintFormatterGitLab(results: import('eslint').ESLint.LintResult[], data: import('eslint').ESLint.LintResultData): string;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
package/index.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":";AA0PA;;;;;;;GAOG;AACH,gDAPW,OAAO,QAAQ,EAAE,MAAM,CAAC,UAAU,EAAE,QAEpC,OAAO,QAAQ,EAAE,MAAM,CAAC,cAAc,GAEpC,MAAM,CAmBlB"}
|
package/index.js
CHANGED
|
@@ -1,283 +1,277 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @typedef {object} GitLabReports
|
|
9
|
-
* @property {string} [codequality]
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @typedef {object} GitLabArtifacts
|
|
14
|
-
* @property {GitLabReports} [reports]
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* @typedef {object} GitLabJob
|
|
19
|
-
* @property {GitLabArtifacts} [artifacts]
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @typedef {Record<string, GitLabJob>} GitLabCI
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @typedef {object} CodeClimateLines
|
|
28
|
-
* @property {number} begin
|
|
29
|
-
* @property {number} end
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @typedef {object} CodeClimateContents
|
|
34
|
-
* @property {string} body
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* @typedef {object} CodeClimateLocation
|
|
39
|
-
* https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#locations
|
|
40
|
-
* @property {string} path
|
|
41
|
-
* @property {CodeClimateLines} lines
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* @typedef {object} CodeClimateIssue
|
|
46
|
-
* https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#issues
|
|
47
|
-
* @property {'issue'} type
|
|
48
|
-
* @property {string} check_name
|
|
49
|
-
* @property {string} description
|
|
50
|
-
* @property {CodeClimateContents} [contents]
|
|
51
|
-
* @property {'info' | 'minor' | 'major' | 'critical' | 'blocker'} severity
|
|
52
|
-
* @property {string} [fingerprint]
|
|
53
|
-
* @property {CodeClimateLocation} location
|
|
54
|
-
*/
|
|
1
|
+
const { createHash } = require('node:crypto')
|
|
2
|
+
const { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } = require('node:fs')
|
|
3
|
+
const { EOL } = require('node:os')
|
|
4
|
+
const { dirname, join, relative, resolve } = require('node:path')
|
|
55
5
|
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const { EOL } = require('node:os');
|
|
59
|
-
const { dirname, join, relative, resolve } = require('node:path');
|
|
60
|
-
|
|
61
|
-
const chalk = require('chalk');
|
|
62
|
-
const yaml = require('yaml');
|
|
6
|
+
const chalk = require('chalk')
|
|
7
|
+
const yaml = require('yaml')
|
|
63
8
|
|
|
64
9
|
const {
|
|
10
|
+
CI_COMMIT_SHORT_SHA,
|
|
65
11
|
CI_CONFIG_PATH = '.gitlab-ci.yml',
|
|
66
12
|
CI_JOB_NAME,
|
|
67
13
|
CI_PROJECT_DIR = process.cwd(),
|
|
68
14
|
CI_PROJECT_URL,
|
|
69
|
-
CI_COMMIT_SHORT_SHA,
|
|
70
15
|
ESLINT_CODE_QUALITY_REPORT,
|
|
71
|
-
GITLAB_CI
|
|
72
|
-
|
|
73
|
-
} = process.env;
|
|
16
|
+
GITLAB_CI
|
|
17
|
+
} = process.env
|
|
74
18
|
|
|
75
|
-
/**
|
|
76
|
-
* @type {yaml.CollectionTag}
|
|
77
|
-
*/
|
|
19
|
+
/** @type {yaml.CollectionTag} */
|
|
78
20
|
const reference = {
|
|
79
21
|
tag: '!reference',
|
|
80
22
|
collection: 'seq',
|
|
81
23
|
default: false,
|
|
82
24
|
resolve() {
|
|
83
25
|
// We only allow the syntax. We don’t actually resolve the reference.
|
|
84
|
-
}
|
|
85
|
-
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
86
28
|
|
|
87
29
|
/**
|
|
88
|
-
* @returns {string}
|
|
30
|
+
* @returns {string}
|
|
31
|
+
* The output path of the code quality artifact.
|
|
89
32
|
*/
|
|
90
33
|
function getOutputPath() {
|
|
91
|
-
const configPath = join(CI_PROJECT_DIR, CI_CONFIG_PATH)
|
|
34
|
+
const configPath = join(CI_PROJECT_DIR, CI_CONFIG_PATH)
|
|
92
35
|
// GitlabCI allows a custom configuration path which can be a URL or a path relative to another
|
|
93
36
|
// project. In these cases CI_CONFIG_PATH is empty and we'll have to require the user provide
|
|
94
37
|
// ESLINT_CODE_QUALITY_REPORT.
|
|
95
38
|
if (!existsSync(configPath) || !lstatSync(configPath).isFile()) {
|
|
96
39
|
throw new Error(
|
|
97
40
|
'Could not resolve .gitlab-ci.yml to automatically detect report artifact path.' +
|
|
98
|
-
' Please manually provide a path via the ESLINT_CODE_QUALITY_REPORT variable.'
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
const jobs = /** @type {GitLabCI} */ (
|
|
102
|
-
yaml.parse(readFileSync(configPath, 'utf8'), { version: '1.1', customTags: [reference] })
|
|
103
|
-
);
|
|
104
|
-
const { artifacts } = jobs[/** @type {string} */ (CI_JOB_NAME)];
|
|
105
|
-
const location = artifacts?.reports?.codequality;
|
|
106
|
-
const msg = `Expected ${CI_JOB_NAME}.artifacts.reports.codequality to be one exact path`;
|
|
107
|
-
if (!location) {
|
|
108
|
-
throw new Error(`${msg}, but no value was found.`);
|
|
41
|
+
' Please manually provide a path via the ESLINT_CODE_QUALITY_REPORT variable.'
|
|
42
|
+
)
|
|
109
43
|
}
|
|
110
|
-
|
|
111
|
-
|
|
44
|
+
const doc = yaml.parseDocument(readFileSync(configPath, 'utf8'), {
|
|
45
|
+
version: '1.1',
|
|
46
|
+
customTags: [reference]
|
|
47
|
+
})
|
|
48
|
+
const path = [CI_JOB_NAME, 'artifacts', 'reports', 'codequality']
|
|
49
|
+
const location = doc.getIn(path)
|
|
50
|
+
if (typeof location !== 'string' || !location) {
|
|
51
|
+
throw new TypeError(
|
|
52
|
+
`Expected ${path.join('.')} to be one exact path, got: ${JSON.stringify(location)}`
|
|
53
|
+
)
|
|
112
54
|
}
|
|
113
|
-
return resolve(CI_PROJECT_DIR, location)
|
|
55
|
+
return resolve(CI_PROJECT_DIR, location)
|
|
114
56
|
}
|
|
115
57
|
|
|
116
58
|
/**
|
|
117
|
-
* @param {string} filePath
|
|
118
|
-
*
|
|
119
|
-
* @
|
|
59
|
+
* @param {string} filePath
|
|
60
|
+
* The path to the linted file.
|
|
61
|
+
* @param {import('eslint').Linter.LintMessage} message
|
|
62
|
+
* The ESLint report message.
|
|
63
|
+
* @param {Set<string>} hashes
|
|
64
|
+
* Hashes already encountered. Used to avoid duplicate hashes
|
|
65
|
+
* @returns {string}
|
|
66
|
+
* The fingerprint for the ESLint report message.
|
|
120
67
|
*/
|
|
121
|
-
function createFingerprint(filePath, message) {
|
|
122
|
-
const md5 = createHash('md5')
|
|
123
|
-
md5.update(filePath)
|
|
68
|
+
function createFingerprint(filePath, message, hashes) {
|
|
69
|
+
const md5 = createHash('md5')
|
|
70
|
+
md5.update(filePath)
|
|
124
71
|
if (message.ruleId) {
|
|
125
|
-
md5.update(message.ruleId)
|
|
72
|
+
md5.update(message.ruleId)
|
|
73
|
+
}
|
|
74
|
+
md5.update(message.message)
|
|
75
|
+
|
|
76
|
+
// Create copy of hash since md5.digest() will finalize it, not allowing us to .update() again
|
|
77
|
+
let md5Tmp = md5.copy()
|
|
78
|
+
let hash = md5Tmp.digest('hex')
|
|
79
|
+
|
|
80
|
+
while (hashes.has(hash)) {
|
|
81
|
+
// Hash collision. This happens if we encounter the same ESLint message in one file
|
|
82
|
+
// multiple times. Keep generating new hashes until we get a unique one.
|
|
83
|
+
md5.update(hash)
|
|
84
|
+
|
|
85
|
+
md5Tmp = md5.copy()
|
|
86
|
+
hash = md5Tmp.digest('hex')
|
|
126
87
|
}
|
|
127
|
-
|
|
128
|
-
|
|
88
|
+
|
|
89
|
+
hashes.add(hash)
|
|
90
|
+
return hash
|
|
129
91
|
}
|
|
130
92
|
|
|
131
93
|
/**
|
|
132
|
-
* @param {LintResult[]} results
|
|
133
|
-
*
|
|
134
|
-
* @
|
|
94
|
+
* @param {import('eslint').ESLint.LintResult[]} results
|
|
95
|
+
* The ESLint report results.
|
|
96
|
+
* @param {import('eslint').ESLint.LintResultData} data
|
|
97
|
+
* The ESLint report result data.
|
|
98
|
+
* @returns {import('codeclimate-types').Issue[]}
|
|
99
|
+
* The ESLint messages in the form of a GitLab code quality report.
|
|
135
100
|
*/
|
|
136
101
|
function convert(results, data) {
|
|
137
|
-
/** @type {
|
|
138
|
-
const messages = []
|
|
102
|
+
/** @type {import('codeclimate-types').Issue[]} */
|
|
103
|
+
const messages = []
|
|
104
|
+
|
|
105
|
+
/** @type {Set<string>} */
|
|
106
|
+
const hashes = new Set()
|
|
107
|
+
|
|
139
108
|
for (const result of results) {
|
|
140
|
-
|
|
141
|
-
const relativePath = relative(CI_PROJECT_DIR, result.filePath);
|
|
109
|
+
const relativePath = relative(CI_PROJECT_DIR, result.filePath)
|
|
142
110
|
|
|
143
|
-
|
|
111
|
+
for (const message of result.messages) {
|
|
112
|
+
/** @type {import('codeclimate-types').Issue} */
|
|
144
113
|
const issue = {
|
|
145
114
|
type: 'issue',
|
|
115
|
+
categories: ['Style'],
|
|
146
116
|
check_name: message.ruleId ?? '',
|
|
147
117
|
description: message.message,
|
|
148
|
-
severity: message.severity === 2 ? 'major' : 'minor',
|
|
149
|
-
fingerprint: createFingerprint(relativePath, message),
|
|
118
|
+
severity: message.fatal ? 'critical' : message.severity === 2 ? 'major' : 'minor',
|
|
119
|
+
fingerprint: createFingerprint(relativePath, message, hashes),
|
|
150
120
|
location: {
|
|
151
121
|
path: relativePath,
|
|
152
122
|
lines: {
|
|
153
123
|
begin: message.line,
|
|
154
|
-
end: message.endLine ?? message.line
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
};
|
|
158
|
-
const docs = message.ruleId ? data.rulesMeta[message.ruleId]?.docs : undefined;
|
|
159
|
-
if (docs) {
|
|
160
|
-
let body = docs.description || '';
|
|
161
|
-
if (docs.url) {
|
|
162
|
-
if (body) {
|
|
163
|
-
body += '\n\n';
|
|
124
|
+
end: message.endLine ?? message.line
|
|
164
125
|
}
|
|
165
|
-
body += `[${message.ruleId}](${docs.url})`;
|
|
166
126
|
}
|
|
127
|
+
}
|
|
128
|
+
messages.push(issue)
|
|
129
|
+
|
|
130
|
+
if (!message.ruleId) {
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!data.rulesMeta[message.ruleId]) {
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const { docs, type } = data.rulesMeta[message.ruleId]
|
|
139
|
+
if (type === 'problem') {
|
|
140
|
+
issue.categories.unshift('Bug Risk')
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!docs) {
|
|
144
|
+
continue
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let body = docs.description || ''
|
|
148
|
+
if (docs.url) {
|
|
167
149
|
if (body) {
|
|
168
|
-
|
|
150
|
+
body += '\n\n'
|
|
169
151
|
}
|
|
152
|
+
body += `[${message.ruleId}](${docs.url})`
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (body) {
|
|
156
|
+
issue.content = { body }
|
|
170
157
|
}
|
|
171
|
-
messages.push(issue);
|
|
172
158
|
}
|
|
173
159
|
}
|
|
174
|
-
return messages
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* @param {LintMessage} message The ESLint report message.
|
|
179
|
-
* @returns {boolean} `true` if the message is at error level, `false` if it represents a warning
|
|
180
|
-
*/
|
|
181
|
-
function messageIsLevelError(message) {
|
|
182
|
-
return message.fatal || message.severity === 2;
|
|
160
|
+
return messages
|
|
183
161
|
}
|
|
184
162
|
|
|
185
163
|
/**
|
|
186
164
|
* Make a text singular or plural based on the count.
|
|
187
165
|
*
|
|
188
|
-
* @param {number} count
|
|
189
|
-
*
|
|
190
|
-
* @
|
|
166
|
+
* @param {number} count
|
|
167
|
+
* The count of the data.
|
|
168
|
+
* @param {string} text
|
|
169
|
+
* The text to make singular or plural.
|
|
170
|
+
* @returns {string}
|
|
171
|
+
* The formatted text.
|
|
191
172
|
*/
|
|
192
173
|
function plural(count, text) {
|
|
193
|
-
return `${count} ${text}${count === 1 ? '' : 's'}
|
|
174
|
+
return `${count} ${text}${count === 1 ? '' : 's'}`
|
|
194
175
|
}
|
|
195
176
|
|
|
196
177
|
/**
|
|
197
|
-
* @param {LintResult[]} results
|
|
198
|
-
*
|
|
199
|
-
*
|
|
178
|
+
* @param {import('eslint').ESLint.LintResult[]} results
|
|
179
|
+
* The ESLint report results.
|
|
180
|
+
* @returns {string}
|
|
181
|
+
* The ESLint messages converted to a format suitable as output in GitLab CI job logs.
|
|
200
182
|
*/
|
|
201
183
|
function gitlabConsoleFormatter(results) {
|
|
202
184
|
// Severity labels manually padded to have equal lengths and end with spaces
|
|
203
|
-
const
|
|
204
|
-
const
|
|
185
|
+
const labelFatal = `${chalk.magenta('fatal')} `
|
|
186
|
+
const labelError = `${chalk.red('error')} `
|
|
187
|
+
const labelWarn = `${chalk.yellow('warn')} `
|
|
205
188
|
|
|
206
|
-
const lines = ['']
|
|
189
|
+
const lines = ['']
|
|
207
190
|
|
|
208
191
|
/** @type {string | undefined} */
|
|
209
|
-
let gitLabBaseURL
|
|
192
|
+
let gitLabBaseURL
|
|
210
193
|
if (CI_PROJECT_URL && CI_COMMIT_SHORT_SHA) {
|
|
211
|
-
gitLabBaseURL = `${CI_PROJECT_URL}/-/blob/${CI_COMMIT_SHORT_SHA}
|
|
194
|
+
gitLabBaseURL = `${CI_PROJECT_URL}/-/blob/${CI_COMMIT_SHORT_SHA}/`
|
|
212
195
|
}
|
|
213
196
|
|
|
214
|
-
let
|
|
215
|
-
let
|
|
216
|
-
let
|
|
217
|
-
let
|
|
197
|
+
let fatal = 0
|
|
198
|
+
let errors = 0
|
|
199
|
+
let warnings = 0
|
|
200
|
+
let maxRuleIdLength = 0
|
|
201
|
+
let maxMsgLength = 0
|
|
218
202
|
|
|
219
203
|
for (const result of results) {
|
|
204
|
+
fatal += result.fatalErrorCount
|
|
205
|
+
errors += result.errorCount - result.fatalErrorCount
|
|
206
|
+
warnings += result.warningCount
|
|
220
207
|
for (const message of result.messages) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
? Math.max(maxRuleIdLength, message.ruleId.length)
|
|
226
|
-
: maxRuleIdLength;
|
|
227
|
-
maxMsgLength = Math.max(maxMsgLength, message.message.length);
|
|
208
|
+
if (message.ruleId) {
|
|
209
|
+
maxRuleIdLength = Math.max(maxRuleIdLength, message.ruleId.length)
|
|
210
|
+
}
|
|
211
|
+
maxMsgLength = Math.max(maxMsgLength, message.message.length)
|
|
228
212
|
}
|
|
229
213
|
}
|
|
230
214
|
|
|
231
215
|
for (const result of results) {
|
|
232
|
-
const { filePath, messages } = result
|
|
233
|
-
const repoFilePath = relative(CI_PROJECT_DIR, filePath)
|
|
216
|
+
const { filePath, messages } = result
|
|
217
|
+
const repoFilePath = relative(CI_PROJECT_DIR, filePath)
|
|
234
218
|
|
|
235
219
|
for (const message of messages) {
|
|
236
|
-
let line
|
|
237
|
-
line
|
|
238
|
-
line +=
|
|
239
|
-
line += message.message.padEnd(maxMsgLength + 2);
|
|
220
|
+
let line = message.fatal ? labelFatal : message.severity === 1 ? labelWarn : labelError
|
|
221
|
+
line += String(message.ruleId || '').padEnd(maxRuleIdLength + 2)
|
|
222
|
+
line += message.message.padEnd(maxMsgLength + 2)
|
|
240
223
|
|
|
241
224
|
if (gitLabBaseURL) {
|
|
242
225
|
// Create link to referenced file in GitLab
|
|
243
|
-
|
|
244
|
-
|
|
226
|
+
let anchor = `#L${message.line}`
|
|
227
|
+
if (message.endLine != null && message.endLine !== message.line) {
|
|
228
|
+
anchor += `-${message.endLine}`
|
|
229
|
+
}
|
|
230
|
+
line += chalk.blue(`${gitLabBaseURL}${repoFilePath}${anchor}`)
|
|
245
231
|
} else {
|
|
246
|
-
line += `${filePath}:${message.line
|
|
232
|
+
line += `${filePath}:${message.line}:${message.column}`
|
|
247
233
|
}
|
|
248
234
|
|
|
249
|
-
lines.push(line)
|
|
235
|
+
lines.push(line)
|
|
250
236
|
}
|
|
251
237
|
}
|
|
252
238
|
|
|
253
|
-
const total = warnings + errors
|
|
239
|
+
const total = warnings + errors + fatal
|
|
254
240
|
if (total > 0) {
|
|
255
|
-
const details = `(${plural(errors, 'error')}, ${plural(warnings, 'warning')})
|
|
256
|
-
lines.push('', `${chalk.red('✖')} ${plural(total, 'problem')} ${details}`)
|
|
241
|
+
const details = `(${fatal} fatal, ${plural(errors, 'error')}, ${plural(warnings, 'warning')})`
|
|
242
|
+
lines.push('', `${chalk.red('✖')} ${plural(total, 'problem')} ${details}`)
|
|
257
243
|
} else {
|
|
258
|
-
lines.push(`${chalk.green('✔')} No problems found`)
|
|
244
|
+
lines.push(`${chalk.green('✔')} No problems found`)
|
|
259
245
|
}
|
|
260
246
|
|
|
261
|
-
lines.push('')
|
|
262
|
-
return lines.join(EOL)
|
|
247
|
+
lines.push('')
|
|
248
|
+
return lines.join(EOL)
|
|
263
249
|
}
|
|
264
250
|
|
|
265
251
|
/**
|
|
266
|
-
* @param {LintResult[]} results
|
|
267
|
-
*
|
|
252
|
+
* @param {import('eslint').ESLint.LintResult[]} results
|
|
253
|
+
* The ESLint report results.
|
|
254
|
+
* @param {import('eslint').ESLint.LintResultData} data
|
|
255
|
+
* The ESLint report result data.
|
|
256
|
+
* @returns {string}
|
|
257
|
+
* The ESLint output to print to the console.
|
|
268
258
|
*/
|
|
269
|
-
|
|
270
|
-
/*
|
|
271
|
-
if (GITLAB_CI === 'true'
|
|
272
|
-
chalk.level = 1
|
|
259
|
+
function eslintFormatterGitLab(results, data) {
|
|
260
|
+
/* c8 ignore start */
|
|
261
|
+
if (GITLAB_CI === 'true') {
|
|
262
|
+
chalk.level = 1
|
|
273
263
|
}
|
|
264
|
+
|
|
265
|
+
/* c8 ignore stop */
|
|
274
266
|
if (CI_JOB_NAME || ESLINT_CODE_QUALITY_REPORT) {
|
|
275
|
-
const issues = convert(results, data)
|
|
276
|
-
const outputPath = ESLINT_CODE_QUALITY_REPORT || getOutputPath()
|
|
277
|
-
const dir = dirname(outputPath)
|
|
278
|
-
mkdirSync(dir, { recursive: true })
|
|
279
|
-
writeFileSync(outputPath, JSON.stringify(issues, null, 2))
|
|
267
|
+
const issues = convert(results, data)
|
|
268
|
+
const outputPath = ESLINT_CODE_QUALITY_REPORT || getOutputPath()
|
|
269
|
+
const dir = dirname(outputPath)
|
|
270
|
+
mkdirSync(dir, { recursive: true })
|
|
271
|
+
writeFileSync(outputPath, JSON.stringify(issues, null, 2))
|
|
280
272
|
}
|
|
281
273
|
|
|
282
|
-
return gitlabConsoleFormatter(results)
|
|
283
|
-
}
|
|
274
|
+
return gitlabConsoleFormatter(results)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
module.exports = eslintFormatterGitLab
|
package/package.json
CHANGED
|
@@ -1,24 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-formatter-gitlab",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Show ESLint results directly in the GitLab code quality results",
|
|
5
5
|
"author": "Remco Haszing <remcohaszing@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://gitlab.com/remcohaszing/eslint-formatter-gitlab#readme",
|
|
8
|
-
"repository":
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "https://gitlab.com/remcohaszing/eslint-formatter-gitlab.git"
|
|
11
|
-
},
|
|
8
|
+
"repository": "gitlab:remcohaszing/eslint-formatter-gitlab",
|
|
12
9
|
"funding": "https://github.com/sponsors/remcohaszing",
|
|
13
|
-
"bugs":
|
|
14
|
-
"url": "https://gitlab.com/remcohaszing/eslint-formatter-gitlab.git/issues"
|
|
15
|
-
},
|
|
10
|
+
"bugs": "https://gitlab.com/remcohaszing/eslint-formatter-gitlab/-/issues",
|
|
16
11
|
"exports": "./index.js",
|
|
17
12
|
"files": [
|
|
18
|
-
"index
|
|
13
|
+
"index.*"
|
|
19
14
|
],
|
|
20
15
|
"scripts": {
|
|
21
|
-
"
|
|
16
|
+
"prepack": "tsc --build",
|
|
17
|
+
"test": "c8 node --test --test-reporter @reporters/junit --test-reporter-destination=junit.xml --test-reporter spec --test-reporter-destination stdout"
|
|
22
18
|
},
|
|
23
19
|
"keywords": [
|
|
24
20
|
"eslint",
|
|
@@ -27,28 +23,24 @@
|
|
|
27
23
|
"gitlab",
|
|
28
24
|
"gitlab-ci"
|
|
29
25
|
],
|
|
30
|
-
"engines": {
|
|
31
|
-
"node": ">=14.0.0"
|
|
32
|
-
},
|
|
33
26
|
"dependencies": {
|
|
34
27
|
"chalk": "^4.0.0",
|
|
35
28
|
"yaml": "^2.0.0"
|
|
36
29
|
},
|
|
37
30
|
"peerDependencies": {
|
|
38
|
-
"eslint": "
|
|
31
|
+
"eslint": ">=5"
|
|
39
32
|
},
|
|
40
33
|
"devDependencies": {
|
|
34
|
+
"@reporters/junit": "^1.0.0",
|
|
41
35
|
"@types/eslint": "^8.0.0",
|
|
42
|
-
"@types/
|
|
43
|
-
"
|
|
36
|
+
"@types/node": "^20.0.0",
|
|
37
|
+
"c8": "^8.0.0",
|
|
38
|
+
"codeclimate-types": "^0.3.0",
|
|
44
39
|
"eslint": "^8.0.0",
|
|
45
|
-
"eslint-config-remcohaszing": "^
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"memfs": "^3.0.0",
|
|
51
|
-
"prettier": "^2.0.0",
|
|
52
|
-
"typescript": "^4.0.0"
|
|
40
|
+
"eslint-config-remcohaszing": "^10.0.0",
|
|
41
|
+
"prettier": "^3.0.0",
|
|
42
|
+
"remark-cli": "^11.0.0",
|
|
43
|
+
"remark-preset-remcohaszing": "^2.0.0",
|
|
44
|
+
"typescript": "^5.0.0"
|
|
53
45
|
}
|
|
54
46
|
}
|