Skip to content

Commit

Permalink
Add ERB Lint Support (#363)
Browse files Browse the repository at this point in the history
* Paint by numbers copying rubocop

* Build and update comments

* Add debug print for args

* Only warn, and include linter rather than rules

* Add erblint tests

* Add erb-lint to test action

* Switch to using GITHUB_TOKEN

* [auto] Update compiled version

* Skip most setups

* Uncomment other tests

* Fix linter errors

* Run format:fix

* [auto] Update compiled version

* Cleanup code after review

* [auto] Update compiled version

* Add Missed Import

* [auto] Update compiled version

* Fix tests after swapping error for test

* Revert build to use BUILD_ACTION_GITHUB_TOKEN

* Remove default . arg

* [auto] Update compiled version

* Restore default . arg

* [auto] Update compiled version

* Remove arg param and lint all files with erblint

* Fix arg order for erblint

* [auto] Update compiled version

* Make --lint all default for erblint

* [auto] Update compiled version

* Remove commandArgs from testing

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
ryanseamless and github-actions[bot] authored Jan 6, 2022
1 parent 1cd86d9 commit 8170c90
Show file tree
Hide file tree
Showing 16 changed files with 429 additions and 4 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,18 @@ jobs:
restore-keys: |
${{ runner.os }}-gems-
- name: Install Ruby dependencies
- name: Install Ruby dependencies (Rubocop)
run: |
cd ./test/linters/projects/rubocop/
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
- name: Install Ruby dependencies (ERB Lint)
run: |
cd ./test/linters/projects/erblint/
bundle config path vendor/bundle
bundle install --jobs 4 --retry 3
# Swift (only on Linux)

- name: Set up Swift cache (Linux)
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ _**Note:** The behavior of actions like this one is currently limited in the con
- [Mypy](https://mypy.readthedocs.io/)
- [oitnb](https://pypi.org/project/oitnb/)
- **Ruby:**
- [ERB Lint](https://github.com/Shopify/erb-lint)
- [RuboCop](https://rubocop.readthedocs.io)
- **Swift:**
- [swift-format](https://github.com/apple/swift-format) (official)
Expand Down Expand Up @@ -247,7 +248,7 @@ jobs:

### Linter-specific options

`[linter]` can be one of `black`, `eslint`, `flake8`, `gofmt`, `golint`, `mypy`, `oitnb`, `php_codesniffer`, `prettier`, `rubocop`, `stylelint`, `swift_format_official`, `swift_format_lockwood`, `swiftlint` and `xo`:
`[linter]` can be one of `black`, `erblint`, `eslint`, `flake8`, `gofmt`, `golint`, `mypy`, `oitnb`, `php_codesniffer`, `prettier`, `rubocop`, `stylelint`, `swift_format_official`, `swift_format_lockwood`, `swiftlint` and `xo`:

- **`[linter]`:** Enables the linter in your repository. Default: `false`
- **`[linter]_args`**: Additional arguments to pass to the linter. Example: `eslint_args: "--max-warnings 0"` if ESLint checks should fail even if there are no errors and only warnings. Default: `""`
Expand Down Expand Up @@ -286,6 +287,7 @@ Some options are not be available for specific linters:
| Linter | auto-fixing | extensions |
| --------------------- | :---------: | :--------: |
| black | ✅ | ✅ |
| erblint | ❌ | ❌ (erb) |
| eslint | ✅ | ✅ |
| flake8 | ❌ | ✅ |
| gofmt | ✅ | ❌ (go) |
Expand Down
20 changes: 20 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,26 @@ inputs:
required: false
default: ""

erblint:
description: Enable or disable ERB Lint checks
required: false
default: "false"
erblint_args:
description: Additional arguments to pass to the linter
required: false
default: ""
erblint_dir:
description: Directory where the ERB Lint command should be run
required: false
erblint_extensions:
description: Extensions of files to check with ERB Lint
required: false
default: "erb"
erblint_command_prefix:
description: Shell command to prepend to the linter command
required: false
default: ""

# Swift

# Alias of `swift_format_lockwood` (for backward compatibility)
Expand Down
111 changes: 110 additions & 1 deletion dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2156,6 +2156,112 @@ class Black {
module.exports = Black;


/***/ }),

/***/ 9674:
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {

const core = __nccwpck_require__(2186);

const { run } = __nccwpck_require__(9575);
const commandExists = __nccwpck_require__(5265);
const { initLintResult } = __nccwpck_require__(9149);
const { removeTrailingPeriod } = __nccwpck_require__(9321);

/** @typedef {import('../utils/lint-result').LintResult} LintResult */

/**
* https://https://github.com/Shopify/erb-lint
*/
class Erblint {
static get name() {
return "ERB Lint";
}

/**
* Verifies that all required programs are installed. Throws an error if programs are missing
* @param {string} dir - Directory to run the linting program in
* @param {string} prefix - Prefix to the lint command
*/
static async verifySetup(dir, prefix = "") {
// Verify that Ruby is installed (required to execute erblint)
if (!(await commandExists("ruby"))) {
throw new Error("Ruby is not installed");
}
// Verify that erblint is installed
try {
run(`${prefix} erblint -v`, { dir });
} catch (err) {
throw new Error(`${this.name} is not installed`);
}
}

/**
* Runs the linting program and returns the command output
* @param {string} dir - Directory to run the linter in
* @param {string[]} extensions - File extensions which should be linted
* @param {string} args - Additional arguments to pass to the linter
* @param {boolean} fix - Whether the linter should attempt to fix code style issues automatically
* @param {string} prefix - Prefix to the lint command
* @returns {{status: number, stdout: string, stderr: string}} - Output of the lint command
*/
static lint(dir, extensions, args = "--lint-all", fix = false, prefix = "") {
if (extensions.length !== 1 || extensions[0] !== "erb") {
throw new Error(`${this.name} error: File extensions are not configurable`);
}
if (fix) {
core.warning(`${this.name} does not support auto-fixing`);
}

return run(`${prefix} erblint --format json ${args}`, {
dir,
ignoreErrors: true,
});
}

/**
* Parses the output of the lint command. Determines the success of the lint process and the
* severity of the identified code style violations
* @param {string} dir - Directory in which the linter has been run
* @param {{status: number, stdout: string, stderr: string}} output - Output of the lint command
* @returns {LintResult} - Parsed lint result
*/
static parseOutput(dir, output) {
const lintResult = initLintResult();
lintResult.isSuccess = output.status === 0;

let outputJson;
try {
outputJson = JSON.parse(output.stdout);
} catch (err) {
throw Error(
`Error parsing ${this.name} JSON output: ${err.message}. Output: "${output.stdout}"`,
);
}

for (const file of outputJson.files) {
const { path, offenses } = file;
for (const offense of offenses) {
const { message, linter, corrected, location } = offense;
if (!corrected) {
// ERB Lint does not provide severities in its JSON output
lintResult.error.push({
path,
firstLine: location.start_line,
lastLine: location.last_line,
message: `${removeTrailingPeriod(message)} (${linter})`,
});
}
}
}

return lintResult;
}
}

module.exports = Erblint;


/***/ }),

/***/ 7169:
Expand Down Expand Up @@ -2561,6 +2667,7 @@ module.exports = Golint;
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {

const Black = __nccwpck_require__(9844);
const Erblint = __nccwpck_require__(9674);
const ESLint = __nccwpck_require__(7169);
const Flake8 = __nccwpck_require__(3636);
const Gofmt = __nccwpck_require__(7796);
Expand All @@ -2578,6 +2685,7 @@ const XO = __nccwpck_require__(728);

const linters = {
// Linters
erblint: Erblint,
eslint: ESLint,
flake8: Flake8,
golint: Golint,
Expand Down Expand Up @@ -4084,7 +4192,8 @@ async function runAction() {

// Lint and optionally auto-fix the matching files, parse code style violations
core.info(
`Linting ${autoFix ? "and auto-fixing " : ""}files in ${lintDirAbs} with ${linter.name}…`,
`Linting ${autoFix ? "and auto-fixing " : ""}files in ${lintDirAbs} ` +
`with ${linter.name} ${args ? `and args: ${args}` : ""}…`,
);
const lintOutput = linter.lint(lintDirAbs, fileExtList, args, autoFix, prefix);

Expand Down
3 changes: 2 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ async function runAction() {

// Lint and optionally auto-fix the matching files, parse code style violations
core.info(
`Linting ${autoFix ? "and auto-fixing " : ""}files in ${lintDirAbs} with ${linter.name}…`,
`Linting ${autoFix ? "and auto-fixing " : ""}files in ${lintDirAbs} ` +
`with ${linter.name} ${args ? `and args: ${args}` : ""}…`,
);
const lintOutput = linter.lint(lintDirAbs, fileExtList, args, autoFix, prefix);

Expand Down
99 changes: 99 additions & 0 deletions src/linters/erblint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
const core = require("@actions/core");

const { run } = require("../utils/action");
const commandExists = require("../utils/command-exists");
const { initLintResult } = require("../utils/lint-result");
const { removeTrailingPeriod } = require("../utils/string");

/** @typedef {import('../utils/lint-result').LintResult} LintResult */

/**
* https://https://github.com/Shopify/erb-lint
*/
class Erblint {
static get name() {
return "ERB Lint";
}

/**
* Verifies that all required programs are installed. Throws an error if programs are missing
* @param {string} dir - Directory to run the linting program in
* @param {string} prefix - Prefix to the lint command
*/
static async verifySetup(dir, prefix = "") {
// Verify that Ruby is installed (required to execute erblint)
if (!(await commandExists("ruby"))) {
throw new Error("Ruby is not installed");
}
// Verify that erblint is installed
try {
run(`${prefix} erblint -v`, { dir });
} catch (err) {
throw new Error(`${this.name} is not installed`);
}
}

/**
* Runs the linting program and returns the command output
* @param {string} dir - Directory to run the linter in
* @param {string[]} extensions - File extensions which should be linted
* @param {string} args - Additional arguments to pass to the linter
* @param {boolean} fix - Whether the linter should attempt to fix code style issues automatically
* @param {string} prefix - Prefix to the lint command
* @returns {{status: number, stdout: string, stderr: string}} - Output of the lint command
*/
static lint(dir, extensions, args = "--lint-all", fix = false, prefix = "") {
if (extensions.length !== 1 || extensions[0] !== "erb") {
throw new Error(`${this.name} error: File extensions are not configurable`);
}
if (fix) {
core.warning(`${this.name} does not support auto-fixing`);
}

return run(`${prefix} erblint --format json ${args}`, {
dir,
ignoreErrors: true,
});
}

/**
* Parses the output of the lint command. Determines the success of the lint process and the
* severity of the identified code style violations
* @param {string} dir - Directory in which the linter has been run
* @param {{status: number, stdout: string, stderr: string}} output - Output of the lint command
* @returns {LintResult} - Parsed lint result
*/
static parseOutput(dir, output) {
const lintResult = initLintResult();
lintResult.isSuccess = output.status === 0;

let outputJson;
try {
outputJson = JSON.parse(output.stdout);
} catch (err) {
throw Error(
`Error parsing ${this.name} JSON output: ${err.message}. Output: "${output.stdout}"`,
);
}

for (const file of outputJson.files) {
const { path, offenses } = file;
for (const offense of offenses) {
const { message, linter, corrected, location } = offense;
if (!corrected) {
// ERB Lint does not provide severities in its JSON output
lintResult.error.push({
path,
firstLine: location.start_line,
lastLine: location.last_line,
message: `${removeTrailingPeriod(message)} (${linter})`,
});
}
}
}

return lintResult;
}
}

module.exports = Erblint;
2 changes: 2 additions & 0 deletions src/linters/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const Black = require("./black");
const Erblint = require("./erblint");
const ESLint = require("./eslint");
const Flake8 = require("./flake8");
const Gofmt = require("./gofmt");
Expand All @@ -16,6 +17,7 @@ const XO = require("./xo");

const linters = {
// Linters
erblint: Erblint,
eslint: ESLint,
flake8: Flake8,
golint: Golint,
Expand Down
2 changes: 2 additions & 0 deletions test/linters/linters.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { copy, remove } = require("fs-extra");

const { normalizeDates, normalizePaths, createTmpDir } = require("../test-utils");
const blackParams = require("./params/black");
const erblintParams = require("./params/erblint");
const eslintParams = require("./params/eslint");
const eslintTypescriptParams = require("./params/eslint-typescript");
const flake8Params = require("./params/flake8");
Expand All @@ -21,6 +22,7 @@ const xoParams = require("./params/xo");

const linterParams = [
blackParams,
erblintParams,
eslintParams,
eslintTypescriptParams,
flake8Params,
Expand Down
Loading

0 comments on commit 8170c90

Please sign in to comment.