-
-
Notifications
You must be signed in to change notification settings - Fork 70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Prelint plugins #105
base: main
Are you sure you want to change the base?
Conversation
Does this include a way for a rule to declare a dependency on a prelint, and implicitly "add" it to the prelint list if not already present? |
@ljharb no. Rules cannot force users to add other rules, prelints, or configurations. At least to start, rules should specify what they’re expecting in their docs. Plug-ins can, of course, publish configs that contain appropriate “known good” settings. |
designs/2023-prelints/README.md
Outdated
|
||
context.createVirtualFile({ | ||
filename: "0.js", | ||
body: removeIndents( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slightly off-topic - I would envision that most tools would want to strip indent in the same way - might be good to provide a util from eslint itself that does this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Certainly worth thinking about -- also worth thinking about: does ESLint just do it automatically for you based on the indentOffset
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated the proposal to automatically adjust indents.
Gotcha - that's fine, it just means this doesn't solve the use case of allowing rules to execute a "before" step. |
@ljharb is there a specific use case you have in mind? |
Yes, I want eslint-plugin-import's rules to be able to indicate that they need an "initialization" step, to cache the module graph, and for eslint to automatically queue up the Promise ordering as appropriate, and to skip the initialization step when no active rules require it. |
Ah okay, thanks for the explanation. I'll need to think about that use case a bit more, but I don't think it fits within this RFC. |
designs/2023-prelints/README.md
Outdated
/** | ||
* The number to add to any violations that occur in the first line of | ||
* the virtual file. 0 if undefined. | ||
*/ | ||
columnStart?: number; | ||
|
||
/** | ||
* The number to add to the column number of each violation in the body. | ||
* 0 if undefined. | ||
*/ | ||
indentOffset?: number; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to tell the ESLint engine the negative column offset of a line of text?
For example, if I have HTML like the following (for a 2-space indent), if I want the CSS in the style
attribute to have an indentOffset
of 4
, the font-size
will be placed at a column offset of -4
.
<div>
<div style="
color: red;
font-size: 16px;
">
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason I want to use 4
for indentOffset
is because I want to convey the base position for the indent rule. If it's 0
, I don't think the indent rule work well. It's probably indented like this:
<div>
<div style="
color: red;
font-size: 16px;
">
I think people actually want indent like this:
<div>
<div style="
color: red;
font-size: 16px;
">
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to tell the ESLint engine the negative column offset of a line of text?
Hmmm, I'm not sure I understand why you'd want to do this. If you have this HTML:
<div>
<div style="
color: red;
font-size: 16px;
">
And you want to extract the CSS, for linting, I'd think you'd want ESLint to see the CSS as this:
color: red;
font-size: 16px;
So you'd say:
context.createVirtualFile({
body: removeIndents(cssNode.text), // get the text you want somehow
lineStart: 3, // add this number to any reported lines
columnStart: 0, // add this number to any columns reported in the first line
indentOffset: 0 // don't adjust indents
});
So then you'd take the CSS virtual file, normalize the indents, then that would get merged back into the HTML.
Would that work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I think I didn't explain well 😓. (Prelint may be an unrelated topic.)
How do you think indentation rules for CSS should be implemented to handle indentation correctly given text like this?
<div>
<div style="
color: red;
font-size: 16px;
">
I would expect it to be indented like this:
<div>
<div style="
color: red;
font-size: 16px;
">
However, I think the current system would probably indent like this:
<div>
<div style="
color: red;
font-size: 16px;
">
Because the CSS indentation rules don't know the proper starting position.
I thought it could be solved by indenting based on 0 if a negative column offset could be used, but there may be another way to solve it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see what you mean. Indeed, the current design doesn't allow for that. I need to think a bit more about how to handle this.
I think negative indents would be a bit confusing, as a negative indent would mean "add indents" while a positive indent would mean "remove indents".
It might be the case that we need some sort of callback function to allow prelints to further modify a fixed result before inserting into the original file.
I'll think more on this and see what I can come up with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, so it turns out this will work, just not in the most obvious way.
First, we actually need two different rules, one for CSS and one for HTML. Let's call them css/indent
to indent CSS and html/indent
to indent HTML.
So to start, we have this code:
<div>
<div style="
color: red;
font-size: 16px;
">
The prelint would extract the CSS into a text fragment that looks like this:
color: red;
font-size: 16px;
The css/indent
rule would fix the indentation to this:
color: red;
font-size: 16px;
Then, this CSS would be inserted back into the HTML, looking like this:
<div>
<div style="
color: red;
font-size: 16px;
">
When the html/indent
rule then runs, it would end up fixing to this:
<div>
<div style="
color: red;
font-size: 16px;
">
Basically, we'd be combining the fixes from css/indent
and html/indent
together either in one pass (if technically possible) or at most in two passes (ESLint does multiple passes when attempting to autofix, so this is not new).
@JamesHenry this RFC might be of interest to you, as well. |
Thanks @nzakas I can comment on the big 3 projects I work on: Based on my understanding, I think it would just give an alternative to the use of processors for For |
@JamesHenry thanks for the feedback. Indeed, I'm viewing this as a more flexible replacement for processors rather than something radically different. The primary different is that it would allow us to lint both the original file and all of the virtual files, whereas processors only allow linting of virtual files. |
Updated to simplify the creation of fragments out of a file. You no longer need to manually manipulate the text before passing it back to ESLint. Let me know what you think. @eslint/eslint-tsc still waiting for the first round of feedback on this. |
} | ||
``` | ||
|
||
Each prelint follows the same basic structure of as a rule, only it cannot report violations and instead can only inspect the AST, use `SourceCode` to make any necessary changes, and create fragments as necessary. Otherwise, it looks the same as a rule, such as: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do we mean by "use SourceCode
to make any necessary changes", what kind of changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be language-dependent. In JavaScript, we have markVariableAsUsed()
as an example.
// prelint with options | ||
"markdown/js": { | ||
codeBlockTags: ["js", "javascript", "ecmascript"] | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens when multiple config entries that specify options for markdown/js
match the given file, are the options objects merged or does the last one overwrite previous ones?
Alternatively, since prelints have access to configured settings
(PrelintContext.settings
), maybe we don't need to support prelint options because they can be specified inside settings for the plugin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Options are merged in the same way that rule options are merged.
settings
has always been a hack and I wouldn't want to lean on it any more.
|
||
In order to make all of this work, we'll need to make the following changes in the core: | ||
|
||
1. `linter.js` will need to be updated to treat fragments the same as processor code blocks (recursively linting) and to do a first traversal of the file using prelints. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
linter.js
also needs to be updated to postprocess lint messages from fragments? In particular, to calculate locations and fix ranges.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! I'm going to circle back and do some more details around the implementation once I finish up the languages RFC.
Hi, Just wanted to confirm--would this support multiple levels of nesting with languages? My use case would be for So after parsing the main JavaScript AST, we'd want to find the |
Yes, based on your config it could go multiple levels deep. So if you have a Markdown file that has a JavaScript block, that would get pulled out, and then any prelints configured for JavaScript files would run. So, if the JavaScript block contained a block of GraphQL, a prelint could pull that out as separate from the Markdown and JavaScript that it was embedded within. |
Summary
This RFC introduces the concept of a "prelint", which is like a rule but it runs before any linting happens, allowing plugins to modify the behavior of subsequent linting runs.
This requires #99 to function and was split off from that RFC.
Related Issues
#99
eslint/eslint#14745