eslint-formatter-gitlab 1.1.0 → 3.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 +9 -3
- package/index.js +153 -45
- package/package.json +21 -17
- package/CHANGELOG.md +0 -33
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
7
|
-
This requires at least GitLab Starter 11.5 and at least ESLint 5.
|
|
7
|
+
This requires at least GitLab Bronze or Starter 11.5 and at least ESLint 5.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
@@ -20,7 +20,7 @@ _.gitlab-ci.yml_:
|
|
|
20
20
|
|
|
21
21
|
```yaml
|
|
22
22
|
eslint:
|
|
23
|
-
image: node:
|
|
23
|
+
image: node:14-alpine
|
|
24
24
|
script:
|
|
25
25
|
- npm ci
|
|
26
26
|
- npx eslint --format gitlab .
|
|
@@ -44,7 +44,13 @@ configuration, options are passed using environment variables.
|
|
|
44
44
|
| Environment Variable | Description |
|
|
45
45
|
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
46
46
|
| `ESLINT_CODE_QUALITY_REPORT` | The location to store the code quality report. By default it will detect the location of the codequality artifact defined in the GitLab CI configuration file. |
|
|
47
|
-
|
|
47
|
+
|
|
48
|
+
## Upgrading
|
|
49
|
+
|
|
50
|
+
### to v3
|
|
51
|
+
|
|
52
|
+
- Support for the environment variable `ESLINT_FORMATTER` has been removed, console output now
|
|
53
|
+
always uses a builtin formatter.
|
|
48
54
|
|
|
49
55
|
[gitlab code quality]: https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html
|
|
50
56
|
[merge request !1]: https://gitlab.com/remcohaszing/eslint-formatter-gitlab/merge_requests/1
|
package/index.js
CHANGED
|
@@ -1,42 +1,57 @@
|
|
|
1
|
-
const
|
|
2
|
-
const
|
|
1
|
+
const { createHash } = require('crypto');
|
|
2
|
+
const { existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } = require('fs');
|
|
3
|
+
const { EOL } = require('os');
|
|
4
|
+
const { dirname, join, relative, resolve } = require('path');
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const isGlob = require('is-glob');
|
|
6
|
+
// eslint-disable-next-line unicorn/import-style
|
|
7
|
+
const chalk = require('chalk');
|
|
7
8
|
const yaml = require('js-yaml');
|
|
8
9
|
|
|
9
10
|
const {
|
|
10
|
-
// Used as a fallback for local testing.
|
|
11
11
|
CI_CONFIG_PATH = '.gitlab-ci.yml',
|
|
12
12
|
CI_JOB_NAME,
|
|
13
13
|
CI_PROJECT_DIR = process.cwd(),
|
|
14
|
+
CI_PROJECT_URL,
|
|
15
|
+
CI_COMMIT_SHORT_SHA,
|
|
14
16
|
ESLINT_CODE_QUALITY_REPORT,
|
|
15
|
-
|
|
17
|
+
GITLAB_CI,
|
|
18
|
+
NODE_ENV,
|
|
16
19
|
} = process.env;
|
|
17
20
|
|
|
21
|
+
/**
|
|
22
|
+
* @returns {string} The output path of the code quality artifact.
|
|
23
|
+
*/
|
|
18
24
|
function getOutputPath() {
|
|
19
|
-
const
|
|
25
|
+
const configPath = join(CI_PROJECT_DIR, CI_CONFIG_PATH);
|
|
26
|
+
// GitlabCI allows a custom configuration path which can be a URL or a path relative to another
|
|
27
|
+
// project. In these cases CI_CONFIG_PATH is empty and we'll have to require the user provide
|
|
28
|
+
// ESLINT_CODE_QUALITY_REPORT.
|
|
29
|
+
if (!existsSync(configPath) || !lstatSync(configPath).isFile()) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
'Could not resolve .gitlab-ci.yml to automatically detect report artifact path.' +
|
|
32
|
+
' Please manually provide a path via the ESLINT_CODE_QUALITY_REPORT variable.',
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const jobs = yaml.load(readFileSync(configPath, 'utf-8'));
|
|
20
36
|
const { artifacts } = jobs[CI_JOB_NAME];
|
|
21
37
|
const location = artifacts && artifacts.reports && artifacts.reports.codequality;
|
|
22
38
|
const msg = `Expected ${CI_JOB_NAME}.artifacts.reports.codequality to be one exact path`;
|
|
23
|
-
if (location
|
|
39
|
+
if (!location) {
|
|
24
40
|
throw new Error(`${msg}, but no value was found.`);
|
|
25
41
|
}
|
|
26
42
|
if (Array.isArray(location)) {
|
|
27
|
-
throw new
|
|
28
|
-
}
|
|
29
|
-
if (typeof location !== 'string') {
|
|
30
|
-
throw new Error(`${msg}, but found ${JSON.stringify(location)} instead.`);
|
|
43
|
+
throw new TypeError(`${msg}, but found an array instead.`);
|
|
31
44
|
}
|
|
32
|
-
|
|
33
|
-
throw new Error(`${msg}, but found a glob instead.`);
|
|
34
|
-
}
|
|
35
|
-
return path.resolve(CI_PROJECT_DIR, location);
|
|
45
|
+
return resolve(CI_PROJECT_DIR, location);
|
|
36
46
|
}
|
|
37
47
|
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} filePath - The path to the linted file.
|
|
50
|
+
* @param {object} message - The ESLint report message.
|
|
51
|
+
* @returns {string} The fingerprint for the ESLint report message.
|
|
52
|
+
*/
|
|
38
53
|
function createFingerprint(filePath, message) {
|
|
39
|
-
const md5 =
|
|
54
|
+
const md5 = createHash('md5');
|
|
40
55
|
md5.update(filePath);
|
|
41
56
|
if (message.ruleId) {
|
|
42
57
|
md5.update(message.ruleId);
|
|
@@ -45,37 +60,130 @@ function createFingerprint(filePath, message) {
|
|
|
45
60
|
return md5.digest('hex');
|
|
46
61
|
}
|
|
47
62
|
|
|
63
|
+
/**
|
|
64
|
+
* @param {object[]} results - The ESLint report results.
|
|
65
|
+
* @returns {object[]} The ESLint messages in the form of a GitLab code quality report.
|
|
66
|
+
*/
|
|
48
67
|
function convert(results) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
},
|
|
68
|
+
const messages = [];
|
|
69
|
+
for (const result of results) {
|
|
70
|
+
for (const message of result.messages) {
|
|
71
|
+
const relativePath = relative(CI_PROJECT_DIR, result.filePath);
|
|
72
|
+
// https://github.com/codeclimate/spec/blob/master/SPEC.md#data-types
|
|
73
|
+
messages.push({
|
|
74
|
+
description: message.message,
|
|
75
|
+
severity: message.severity === 2 ? 'major' : 'minor',
|
|
76
|
+
fingerprint: createFingerprint(relativePath, message),
|
|
77
|
+
location: {
|
|
78
|
+
path: relativePath,
|
|
79
|
+
lines: {
|
|
80
|
+
begin: message.line,
|
|
63
81
|
},
|
|
64
|
-
}
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return messages;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {object} message - The ESLint report message.
|
|
91
|
+
* @returns {boolean} `true` if the message is at error level, `false` if it represents a warning
|
|
92
|
+
*/
|
|
93
|
+
function messageIsLevelError(message) {
|
|
94
|
+
return message.fatal || message.severity === 2;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {object[]} results - The ESLint report results.
|
|
99
|
+
* @returns {object} Statistics about the number of problems at various levels
|
|
100
|
+
* and length of contained description strings.
|
|
101
|
+
*/
|
|
102
|
+
function calculateResultsStats(results) {
|
|
103
|
+
const stats = { total: 0, errors: 0, warnings: 0, maxRuleIdLength: 0, maxMsgLength: 0 };
|
|
104
|
+
|
|
105
|
+
for (const result of results) {
|
|
106
|
+
for (const message of result.messages) {
|
|
107
|
+
const isError = messageIsLevelError(message);
|
|
108
|
+
stats.errors += isError ? 1 : 0;
|
|
109
|
+
stats.warnings += isError ? 0 : 1;
|
|
110
|
+
stats.maxRuleIdLength = message.ruleId
|
|
111
|
+
? Math.max(stats.maxRuleIdLength, message.ruleId.length)
|
|
112
|
+
: stats.maxRuleIdLength;
|
|
113
|
+
stats.maxMsgLength = Math.max(stats.maxMsgLength, message.message.length);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
stats.total = stats.warnings + stats.errors;
|
|
118
|
+
|
|
119
|
+
return stats;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const plural = (count, text) => `${count} ${text}${count === 1 ? '' : 's'}`;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {object[]} results - The ESLint report results.
|
|
126
|
+
* @returns {string} The ESLint messages converted to a format
|
|
127
|
+
* suitable as output in GitLab CI job logs.
|
|
128
|
+
*/
|
|
129
|
+
function gitlabConsoleFormatter(results) {
|
|
130
|
+
// Severity labels manually padded to have equal lengths and end with spaces
|
|
131
|
+
const labelError = `${chalk.red('error')} `;
|
|
132
|
+
const labelWarn = `${chalk.yellow('warn')} `;
|
|
133
|
+
|
|
134
|
+
const lines = [''];
|
|
135
|
+
|
|
136
|
+
let gitLabBaseURL;
|
|
137
|
+
if (CI_PROJECT_URL && CI_COMMIT_SHORT_SHA) {
|
|
138
|
+
gitLabBaseURL = `${CI_PROJECT_URL}/-/blob/${CI_COMMIT_SHORT_SHA}/`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const stats = calculateResultsStats(results);
|
|
142
|
+
|
|
143
|
+
for (const result of results) {
|
|
144
|
+
const { filePath, messages } = result;
|
|
145
|
+
const repoFilePath = relative(CI_PROJECT_DIR, filePath);
|
|
146
|
+
|
|
147
|
+
for (const message of messages) {
|
|
148
|
+
let line;
|
|
149
|
+
line = messageIsLevelError(message) ? labelError : labelWarn;
|
|
150
|
+
line += String(message.ruleId ? message.ruleId : '').padEnd(stats.maxRuleIdLength + 2);
|
|
151
|
+
line += message.message.padEnd(stats.maxMsgLength + 2);
|
|
152
|
+
|
|
153
|
+
if (gitLabBaseURL) {
|
|
154
|
+
// Create link to referenced file in GitLab
|
|
155
|
+
const anchor = message.line === undefined ? '' : `#L${message.line}`;
|
|
156
|
+
line += chalk.blue(`${gitLabBaseURL}${repoFilePath}${anchor}`);
|
|
157
|
+
} else {
|
|
158
|
+
line += `${filePath}:${message.line || 0}:${message.column || 0}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
lines.push(line);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (stats.total > 0) {
|
|
166
|
+
const details = `(${plural(stats.errors, 'error')}, ${plural(stats.warnings, 'warning')})`;
|
|
167
|
+
lines.push('', `${chalk.red('✖')} ${plural(stats.total, 'problem')} ${details}`);
|
|
168
|
+
} else {
|
|
169
|
+
lines.push(`${chalk.green('✔')} No problems found`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
lines.push('');
|
|
173
|
+
return lines.join(EOL);
|
|
69
174
|
}
|
|
70
175
|
|
|
71
|
-
module.exports = results => {
|
|
176
|
+
module.exports = (results) => {
|
|
177
|
+
if (GITLAB_CI === 'true' && NODE_ENV !== 'test') {
|
|
178
|
+
chalk.level = 1;
|
|
179
|
+
}
|
|
72
180
|
if (CI_JOB_NAME || ESLINT_CODE_QUALITY_REPORT) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
181
|
+
const data = convert(results);
|
|
182
|
+
const outputPath = ESLINT_CODE_QUALITY_REPORT || getOutputPath();
|
|
183
|
+
const dir = dirname(outputPath);
|
|
184
|
+
mkdirSync(dir, { recursive: true });
|
|
185
|
+
writeFileSync(outputPath, JSON.stringify(data, null, 2));
|
|
76
186
|
}
|
|
77
|
-
return CLIEngine.getFormatter(ESLINT_FORMATTER)(results);
|
|
78
|
-
};
|
|
79
187
|
|
|
80
|
-
|
|
81
|
-
|
|
188
|
+
return gitlabConsoleFormatter(results);
|
|
189
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-formatter-gitlab",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.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",
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"index.js"
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
|
-
"prettier": "prettier --check '**/*.{json,md,yml}'",
|
|
20
19
|
"test": "jest"
|
|
21
20
|
},
|
|
22
21
|
"keywords": [
|
|
@@ -27,24 +26,29 @@
|
|
|
27
26
|
"gitlab-ci"
|
|
28
27
|
],
|
|
29
28
|
"dependencies": {
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"js-yaml": "^3.13.1"
|
|
29
|
+
"chalk": "^4.0.0",
|
|
30
|
+
"js-yaml": "^4.0.0"
|
|
33
31
|
},
|
|
34
32
|
"peerDependencies": {
|
|
35
|
-
"eslint": "^5 || ^6"
|
|
33
|
+
"eslint": "^5 || ^6 || ^7 || ^8"
|
|
36
34
|
},
|
|
37
35
|
"devDependencies": {
|
|
38
|
-
"codecov": "^3.
|
|
39
|
-
"eslint": "^
|
|
40
|
-
"eslint-config-
|
|
41
|
-
"eslint-
|
|
42
|
-
"eslint-plugin-import": "^2.
|
|
43
|
-
"eslint-plugin-jest": "^
|
|
44
|
-
"eslint-plugin-
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"prettier": "^
|
|
36
|
+
"codecov": "^3.0.0",
|
|
37
|
+
"eslint": "^7.0.0",
|
|
38
|
+
"eslint-config-remcohaszing": "^3.0.0",
|
|
39
|
+
"eslint-plugin-eslint-comments": "^3.0.0",
|
|
40
|
+
"eslint-plugin-import": "^2.0.0",
|
|
41
|
+
"eslint-plugin-jest": "^24.0.0",
|
|
42
|
+
"eslint-plugin-jest-formatting": "^3.0.0",
|
|
43
|
+
"eslint-plugin-jsdoc": "^36.0.0",
|
|
44
|
+
"eslint-plugin-markdown": "^2.0.0",
|
|
45
|
+
"eslint-plugin-node": "^11.0.0",
|
|
46
|
+
"eslint-plugin-prettier": "^4.0.0",
|
|
47
|
+
"eslint-plugin-sort-destructure-keys": "^1.0.0",
|
|
48
|
+
"eslint-plugin-unicorn": "^36.0.0",
|
|
49
|
+
"jest": "^27.0.0",
|
|
50
|
+
"jest-junit": "^13.0.0",
|
|
51
|
+
"memfs": "^3.0.0",
|
|
52
|
+
"prettier": "^2.0.0"
|
|
49
53
|
}
|
|
50
54
|
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
5
|
-
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project
|
|
6
|
-
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
-
|
|
8
|
-
## [Unreleased]
|
|
9
|
-
|
|
10
|
-
## [1.1.0] - 2019-08-08
|
|
11
|
-
|
|
12
|
-
### Changed
|
|
13
|
-
|
|
14
|
-
- Support ESLint 6.
|
|
15
|
-
|
|
16
|
-
## [1.0.2] - 2018-12-13
|
|
17
|
-
|
|
18
|
-
### Fixes
|
|
19
|
-
|
|
20
|
-
- Fix automated release process.
|
|
21
|
-
|
|
22
|
-
## [1.0.1] - 2018-12-13
|
|
23
|
-
|
|
24
|
-
### Added
|
|
25
|
-
|
|
26
|
-
- Tests.
|
|
27
|
-
- Link to example merge request.
|
|
28
|
-
|
|
29
|
-
## [1.0.0] - 2018-11-29
|
|
30
|
-
|
|
31
|
-
### Added
|
|
32
|
-
|
|
33
|
-
- Initial release.
|