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.
Files changed (4) hide show
  1. package/README.md +9 -3
  2. package/index.js +153 -45
  3. package/package.json +21 -17
  4. 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:10-alpine
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
- | `ESLINT_FORMATTER` | The ESLint formatter to use for the console output. This defaults to stylish, the default ESLint formatter. |
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 crypto = require('crypto');
2
- const path = require('path');
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
- const { CLIEngine } = require('eslint');
5
- const fs = require('fs-extra');
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
- ESLINT_FORMATTER,
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 jobs = yaml.load(fs.readFileSync(path.join(CI_PROJECT_DIR, CI_CONFIG_PATH), 'utf-8'));
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 == null) {
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 Error(`${msg}, but found an array instead.`);
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
- if (isGlob(location)) {
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 = crypto.createHash('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
- return results.reduce(
50
- (acc, result) => [
51
- ...acc,
52
- ...result.messages.map(message => {
53
- const relativePath = path.relative(CI_PROJECT_DIR, result.filePath);
54
- // https://github.com/codeclimate/spec/blob/master/SPEC.md#data-types
55
- return {
56
- description: message.message,
57
- fingerprint: createFingerprint(relativePath, message),
58
- location: {
59
- path: relativePath,
60
- lines: {
61
- begin: message.line,
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
- fs.outputJsonSync(ESLINT_CODE_QUALITY_REPORT || getOutputPath(), convert(results), {
74
- spaces: 2,
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
- module.exports.getOutputPath = getOutputPath;
81
- module.exports.convert = convert;
188
+ return gitlabConsoleFormatter(results);
189
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-formatter-gitlab",
3
- "version": "1.1.0",
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
- "fs-extra": "^8.1.0",
31
- "is-glob": "^4.0.1",
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.5.0",
39
- "eslint": "^6.1.0",
40
- "eslint-config-airbnb-base": "^13.2.0",
41
- "eslint-config-prettier": "^6.0.0",
42
- "eslint-plugin-import": "^2.18.2",
43
- "eslint-plugin-jest": "^22.15.0",
44
- "eslint-plugin-prettier": "^3.1.0",
45
- "gitlab-npm-audit-parser": "^1.0.3",
46
- "jest": "^24.8.0",
47
- "jest-junit": "^7.0.0",
48
- "prettier": "^1.18.2"
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.