Skip to content

Commit 780055a

Browse files
authored
Init preprocessor for html-eslint (#13)
* Add another processor for HTML files (usage with html-eslint) * Add npm link tip to dev guide * Add tip for npm ls command * Rename processor to make distinguishable between object and string * Fix wrong import * Add command to remove npm link * Update Readme with new HTML config * Improve wording of docstring * Reflect new processor name in legacy format * Move HTML section down & give broader context * Clarify where to add the `ignores` if wanted
1 parent baa2035 commit 780055a

File tree

7 files changed

+112
-47
lines changed

7 files changed

+112
-47
lines changed

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,8 @@
1818
//////////////////////////////////////
1919
"git.inputValidation": "warn",
2020
"git.inputValidationSubjectLength": 50,
21-
"git.inputValidationLength": 72
21+
"git.inputValidationLength": 72,
22+
"cSpell.words": [
23+
"lintable"
24+
]
2225
}

DEV.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,44 @@
11
# Some developer guidelines
22

3+
## Install as npm package locally to test
4+
5+
Also see [this answer](https://stackoverflow.com/a/28392481/9655481). Basically, inside another project where you want to test the plugin, you can install it as an npm package via a local path:
6+
7+
```bash
8+
npm link /path/to/eslint-plugin-erb
9+
```
10+
11+
Other useful commands:
12+
13+
```bash
14+
npm ls --global
15+
```
16+
17+
Finally to remove the link:
18+
19+
```bash
20+
npm unlink eslint-plugin-erb
21+
```
322

423
## Merge strategies
524

625
- Feature branches to `dev`: squash commit
726
- Continuous Release from `dev` to `main`: standard merge commit
827
- Hotfixes: branch off `main`, merge PR into `main` via squash commit, then merge back `main` to `dev` via standard merge commit.
928

10-
1129
## Create a new release (and publish to npm)
1230

1331
As this is only a small project, we haven't automated publishing to the NPM registry yet and instead rely on the following manual workflow.
1432

1533
- Make sure the tests pass locally: `npm test`
1634
- Make another commit on the `dev` branch bumping the npm version in the `package.json`. For that, use:
35+
1736
```sh
1837
npm run bump-version -- [<newversion> | major | minor | patch]
1938
```
39+
2040
- ⚠ Copy the version specifier from `package.json` into the `index.js` meta information object.
2141
- Once the `dev` branch is ready, open a PR (Pull request) called "Continuous Release <version.number>" and give it the "release" label. Merge this PR into `main`.
2242
- Create a new release via the GitHub UI and assign a new tag alongside that.
2343
- Fetch the tag locally (`git fetch`) and publish to npm via `npm run publish-final`. You probably have to login to npm first (`npm login`).
2444
- Enjoy ✌ Check that the release is available [here on npm](https://www.npmjs.com/package/eslint-plugin-erb).
25-
26-

README.md

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
**Lint your JavaScript code inside ERB files (`.js.erb`).**
44
A zero-dependency plugin for [ESLint](https://eslint.org/).
5+
<br>Also lints your **HTML code** in `.html.erb` if you want to.
56

67
![showcase-erb-lint-gif](https://github.com/Splines/eslint-plugin-erb/assets/37160523/623d6007-b4f5-41ce-be76-5bc0208ed636?raw=true)
78

8-
99
> **Warning**
1010
> v2.0.0 is breaking. We use the new ESLint flat config format. Use `erb:recommended-legacy` if you want to keep using the old `.eslintrc.js` format.
1111
@@ -19,19 +19,15 @@ Install the plugin alongside [ESLint](https://eslint.org/docs/latest/use/getting
1919
npm install --save-dev eslint eslint-plugin-erb
2020
```
2121

22-
2322
### Configure
2423

25-
Starting of v9 ESLint provides a [new flat config format](https://eslint.org/docs/latest/use/configure/configuration-files-new) (`eslint.config.js`). Also see the [configuration migration guide](https://eslint.org/docs/latest/use/configure/migration-guide). Use it as follows and it will automatically lint all your `.js.erb` files:
24+
Starting of v9 ESLint provides a [new flat config format](https://eslint.org/docs/latest/use/configure/configuration-files-new) (`eslint.config.js`). Also see the [configuration migration guide](https://eslint.org/docs/latest/use/configure/migration-guide). Use it as follows and it will automatically lint all your **JavaScript code** in `.js.erb` files:
2625

2726
```js
2827
// eslint.config.js
2928
import erb from "eslint-plugin-erb";
3029

3130
export default [
32-
// if you are using VSCode, don't forget to put
33-
// "eslint.experimental.useFlatConfig": true
34-
// in your settings.json
3531
erb.configs.recommended,
3632
{
3733
linterOptions: {
@@ -46,7 +42,6 @@ export default [
4642
// your other configuration options
4743
}
4844
];
49-
5045
```
5146

5247
<details>
@@ -120,25 +115,20 @@ export default [
120115

121116
</details>
122117

123-
124118
<details>
125119

126120
<summary>Alternative way to configure the processor</summary>
127121

128122
With this variant you have a bit more control over what is going on, e.g. you could name your files `.js.special-erb` and still lint them (if they contain JS and ERB syntax).
129123

130-
131124
```js
132125
// eslint.config.js
133126
import erb from "eslint-plugin-erb";
134127

135128
export default [
136-
// if you are using VSCode, don't forget to put
137-
// "eslint.experimental.useFlatConfig": true
138-
// in your settings.json
139129
{
140130
files: ["**/*.js.erb"],
141-
processor: erb.processors.erbProcessor,
131+
processor: erb.processors.processorJs,
142132
},
143133
{
144134
linterOptions: {
@@ -157,10 +147,6 @@ export default [
157147

158148
</details>
159149

160-
161-
162-
163-
164150
<details>
165151
<summary>Legacy: you can still use the old `.eslintrc.js` format</summary>
166152

@@ -173,7 +159,7 @@ module.exports = {
173159
};
174160
```
175161

176-
Or you can configure the processor manually (advanced):
162+
Or you can configure the processor manually:
177163

178164
```js
179165
// .eslintrc.js
@@ -182,23 +168,47 @@ module.exports = {
182168
overrides: [
183169
{
184170
files: ["**/*.js.erb"],
185-
processor: "erb/erbProcessor"
171+
processor: "erb/processorJs"
186172
}
187173
]
188174
};
189175
```
190176

191177
</details>
192178

179+
If you also want to lint **HTML code** in `.html.erb` files, you can use our preprocessor in conjunction with the amazing [`html-eslint`](https://html-eslint.org/) plugin. Install `html-eslint`, then add the following to your ESLint config file (flat config format):
193180

181+
```js
182+
// eslint.config.js
183+
import erb from "eslint-plugin-erb";
194184

185+
export default [
186+
// your other configurations...
187+
{
188+
processor: erb.processors["processorHtml"],
189+
...html.configs["flat/recommended"],
190+
files: ["**/*.html", "**/*.html.erb"],
191+
rules: {
192+
...html.configs["flat/recommended"].rules,
193+
"@html-eslint/indent": ["error", 2],
194+
// other rules...
195+
},
196+
}
197+
];
198+
```
195199

200+
Additionally, you might want to add the following option to the other objects (`{}`) in `export default []` (at the same level like the `files` key above), since other rules might be incompatible with HTML files:
201+
202+
```js
203+
ignores: ["**/*.html**"],
204+
```
196205

197206
## Editor Integrations
198207

199208
The [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) for VSCode has built-in support for the ERB processor once you've configured it in your `.eslintrc.js` file as shown above.
200209

201210
If you're using VSCode, you may find this `settings.json` options useful:
211+
202212
```jsonc
203213
{
204214
"editor.formatOnSave": false, // it still autosaves with the options below
@@ -208,7 +218,6 @@ If you're using VSCode, you may find this `settings.json` options useful:
208218
// https://eslint.style/guide/faq#how-to-auto-format-on-save
209219
// https://github.com/microsoft/vscode-eslint#settings-options
210220
"eslint.format.enable": true,
211-
"eslint.experimental.useFlatConfig": true, // use the new flat config format
212221
"[javascript]": {
213222
"editor.formatOnSave": false, // to avoid formatting twice (ESLint + VSCode)
214223
"editor.defaultFormatter": "dbaeumer.vscode-eslint" // use ESLint plugin
@@ -230,8 +239,8 @@ If you're using VSCode, you may find this `settings.json` options useful:
230239
}
231240
```
232241

233-
234242
## Limitations
243+
235244
- Does not account for code indentation inside `if/else` ERB statements, e.g.
236245
this snippet
237246

@@ -240,7 +249,9 @@ this snippet
240249
console.log("You are lucky 🍀");
241250
<% end %>
242251
```
252+
243253
will be autofixed to
254+
244255
```js
245256
<% if you_feel_lucky %>
246257
console.log("You are lucky 🍀");

lib/index.js

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
1-
// Load processor
2-
const preprocess = require("./preprocess.js");
1+
const { preprocessJs, preprocessHtml } = require("./preprocess.js");
32
const postprocess = require("./postprocess.js");
4-
const processor = {
5-
preprocess,
3+
4+
// Load processors
5+
const processorJs = {
6+
meta: {
7+
name: "processJs",
8+
},
9+
preprocess: preprocessJs,
10+
postprocess,
11+
supportsAutofix: true,
12+
};
13+
const processorHtml = {
14+
meta: {
15+
name: "processHtml",
16+
},
17+
preprocess: preprocessHtml,
618
postprocess,
719
supportsAutofix: true,
820
};
@@ -16,21 +28,22 @@ const plugin = {
1628
configs: {
1729
"recommended": {
1830
files: ["**/*.js.erb"],
19-
processor: processor,
31+
processor: processorJs,
2032
},
2133
// for the old non-flat config ESLint API
2234
"recommended-legacy": {
2335
plugins: ["erb"],
2436
overrides: [
2537
{
2638
files: ["**/*.js.erb"],
27-
processor: "erb/erbProcessor",
39+
processor: "erb/processorJs",
2840
},
2941
],
3042
},
3143
},
3244
processors: {
33-
erbProcessor: processor,
45+
processorJs: processorJs,
46+
processorHtml: processorHtml,
3447
},
3548
};
3649

lib/preprocess.js

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ const { indexToColumn } = require("./file_coordinates.js");
88
// how annoying is that kind of import in JS ?!
99
var OffsetMap = require("./offset_map.js").OffsetMap;
1010

11-
const erbRegex = /<%[\s\S]*?%>/g;
12-
const DUMMY_STR = "/* eslint-disable */{}/* eslint-enable */";
13-
const DUMMY_LEN = DUMMY_STR.length;
11+
const ERB_REGEX = /<%[\s\S]*?%>/g;
1412

1513
/**
1614
* Transforms the given text into lintable text. We do this by stripping out
@@ -19,17 +17,19 @@ const DUMMY_LEN = DUMMY_STR.length;
1917
* location of messages in the postprocess step later.
2018
* @param {string} text text of the file
2119
* @param {string} filename filename of the file
22-
* @returns {Array<{ filename: string, text: string }>} source code blocks to lint.
20+
* @param {string} dummyString dummy string to replace ERB tags with
21+
* (this is language-specific)
22+
* @returns {Array<{ filename: string, text: string }>} source code blocks to lint
2323
*/
24-
function preprocess(text, filename) {
24+
function preprocess(text, filename, dummyString) {
2525
let lintableTextArr = text.split("");
2626

2727
let match;
2828
let numAddLines = 0;
2929
let numDiffChars = 0;
3030
const offsetMap = new OffsetMap();
3131

32-
while ((match = erbRegex.exec(text)) !== null) {
32+
while ((match = ERB_REGEX.exec(text)) !== null) {
3333
// Match information
3434
const startIndex = match.index;
3535
const matchText = match[0];
@@ -42,16 +42,16 @@ function preprocess(text, filename) {
4242

4343
// Columns
4444
const coordStartIndex = indexToColumn(text, startIndex);
45-
const endColumnAfter = coordStartIndex.column + DUMMY_LEN;
45+
const endColumnAfter = coordStartIndex.column + dummyString.length;
4646
const coordEndIndex = indexToColumn(text, endIndex);
4747
const endColumnBefore = coordEndIndex.column;
4848
const numAddColumns = endColumnBefore - endColumnAfter;
4949

50-
replaceTextWithDummy(lintableTextArr, startIndex, matchLength - 1);
50+
replaceTextWithDummy(lintableTextArr, startIndex, matchLength - 1, dummyString);
5151

5252
// Store in map
5353
const lineAfter = coordEndIndex.line - numAddLines;
54-
numDiffChars += DUMMY_LEN - matchLength;
54+
numDiffChars += dummyString.length - matchLength;
5555
const endIndexAfter = endIndex + numDiffChars;
5656
offsetMap.addMapping(endIndexAfter, lineAfter, numAddLines, numAddColumns);
5757
}
@@ -61,12 +61,32 @@ function preprocess(text, filename) {
6161
return [lintableText];
6262
}
6363

64-
// works in-place
65-
function replaceTextWithDummy(lintableTextArr, startIndex, length) {
66-
lintableTextArr[startIndex] = DUMMY_STR;
64+
/**
65+
* In-place replaces the text (as array) at the given index, for a given length,
66+
* with a dummy string.
67+
*
68+
* Note that the dummy string is inserted at the given index as one big string.
69+
* For the length of the match, subsequent characters are replaced with empty
70+
* strings in the array.
71+
*/
72+
function replaceTextWithDummy(lintableTextArr, startIndex, length, dummyString) {
73+
lintableTextArr[startIndex] = dummyString;
6774
const replaceArgs = Array(length).join(".").split(".");
6875
// -> results in ['', '', '', '', ...]
6976
lintableTextArr.splice(startIndex + 1, length, ...replaceArgs);
7077
}
7178

72-
module.exports = preprocess;
79+
function preprocessJs(text, filename) {
80+
const dummyString = "/* eslint-disable */{}/* eslint-enable */";
81+
return preprocess(text, filename, dummyString);
82+
}
83+
84+
function preprocessHtml(text, filename) {
85+
const dummyString = "<!-- -->";
86+
return preprocess(text, filename, dummyString);
87+
}
88+
89+
module.exports = {
90+
preprocessJs,
91+
preprocessHtml,
92+
};

tests/postprocess.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const fs = require("fs");
22

33
const assert = require("chai").assert;
4-
const pre = require("../lib/preprocess.js");
4+
const { preprocessJs: pre } = require("../lib/preprocess.js");
55
const post = require("../lib/postprocess.js");
66
const cache = require("../lib/cache.js");
77

tests/preprocess.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const fs = require("fs");
22

33
const assert = require("chai").assert;
4-
const p = require("../lib/preprocess.js");
4+
const { preprocessJs: p } = require("../lib/preprocess.js");
55
const cache = require("../lib/cache.js");
66

77
describe("preprocess", () => {

0 commit comments

Comments
 (0)