-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Autofixes in processors #7510
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
Comments
I don't think My original proposal simply assumed that range information would need to be modified in fix objects (possibly by a Previous proposal (in case anyone wants to see the history): #5121 |
Before modifying processors to do more, I'd actually like to rethink them completely. The way they work now is a bit confusing, both in the way they are defined and how they are automatically applied as soon as the plugin is loaded. I know this is tangential to the topic at hand, I'd just prefer not to build on top of what seems like a slightly broken paradigm. If we're going to open the hood, let's see how we can make things better and that may, in turn, make it easier to do autofixing (which I do think we should figure out how to do). |
OK. Fair enough. Although I don't think those two issues intersect much. It's not hard to come up with the proposal (or even implementation) of a new format for processors. My concern is mostly around backwards-compatibility. If we will have to support both formats for a while, that would be tough... |
I don't think we'd preserve backwards compatibility if we redo processors. Just so I can get a better sense for what you're thinking, can you show an example of how a processor would use your proposed API in this issue? |
@nzakas Sure. Instead of providing an object with two properties (preprocess and postprocess), it will now have to provide three properties (preprocess, postprocess and autoFix). First two stay exactly the same as they where before. A simple example of // suppose that array below is generated by preprocess function and describes locations of each extract block.
var codeBlocksMap = [{
start: 152,
end: 835
}, { ... }
];
function autoFix(fixedCode, filename) {
var output = originalFileContent;
fixedCode.forEach(function (codeBlock, index) {
output = output.substr(0, codeBlocksMap[index].start) +
codeBlock + output.substr(codeBlocsMap[index].end + 1);
}
return output;
} Which basically means, |
For what it's worth, this has been a huge problem for adoption of eslint for Google's Catapult project (the UI for developer tracing w/in Google Chrome). The project uses Polymer heavily and thus requires the html plugin/processor. Turning on the "1 true brace style" resulted in something like 5000 style violations in the project, which would require a massive effort to fix. @nzakas, is there any way that we could add the ability for
all before |
(Also adding @platinumazure, who I've discussed this briefly with before and who expressed interest in a possible fix.) |
@zeptonaut The way I suggested above to implement |
I want to throw one more thing out for possible discussion-- it's not critical to processor auto-fix directly, but it could help with plugin development and I think it's worth considering if we're talking about redesigning processors. I really think it would be awesome if ESLint (or a new repo in the organization) provided APIs that basically represented string transforms. Basically, the APIs would be very similar to using either If we're already thinking about rewriting processors, can we do anything to support processor developers to make their task easier? |
@platinumazure I think that's a good idea for a stand-alone package. I don't think it should be part of ESLint, however. But it would definitely be helpful to processor developers. |
@platinumazure that definitely seems like it'd be useful, but I agree with @ilyavolodin (with my limited knowledge) that it's probably an issue that is (and should be) decoupled from this one. |
The main reason that I'm concerned with @ilyavolodin's proposal is that it forces the responsibility for accurately applying the fixes onto each plugin owner, whereas the logic for each of those is likely to be very similar. I have a slightly different straw man proposal: Are there any known plugins where the line given to eslint doesn't map directly to a line in a source file, plus some configurable offset? It seems like this is by far the most common case. For example, consider this HTML file:
The HTML plugin will just give the following to eslint: {
// The code that's returned from the processor.
source: ["let my_var = 0;"],
// Lets eslint known that line 0, character 0 in `source` translates to
// line 4, character 6 in index.html.
source_map = ["0,0:4:6"]
} If you're familiar with Javascript source maps that are used to help, for example, Chrome devtools debug compiled code, the concept is very similar. If no |
@zeptonaut This is really limiting approach which will basically disable any ability to fix large number of possible processors. Consider two examples below: index.aspx
<html>
<head><title>My title</title></head>
<body>
<script>
let my_var = '<%= myServerSideVariable %>';
</script>
</body>
</html> <html>
<body>
<script>
let a = 22;
</script>
<script>
a += 1;
console.log(a);
</script>
</body>
</html> Neither of the two examples would be fixable with your proposal, because in the first example we need to substitute `<%= myServerSideVar %> with something parsable in JavaScript. And in the second example, we want to combine two blocks of code and serve it as a single input into ESLint. |
@ilyavolodin Thanks - I appreciate the feedback on this idea. For your first example, what sort of transformation would a .aspx plugin normally do before handing this off to eslint? As you mentioned, the part within the script tag right now isn't parsable by Javascript or eslint, so I'm not familiar what type of work the plugin would normally do before handing this off to eslint. As for the second block, I think that my approach would handle that well. In terms of the object returned in my previous example, it would look something like: {
// The code that's returned from the processor.
source: [
"let a = 22;",
"a += 1;",
"console.log(a);"
],
source_map = [
"0,0:3,6" // "let a = 22;" begins at line 3, character 6 of the HTML file
"0,0:6,6" // "a += 1" begins at line 6, character 6 of the HTML file
"0,0:7,6" // "console.log(a);" begins at line 7, character 6 of the HTML file
]
} Maybe what you meant was that if, instead of having three separate strings in the source array, you just had: source: ["let a = 22;\na += 1;\nconsole.log(a);"] then it wouldn't work. I think that the way to handle this would be to allow some way in the source map to say "different parts of this source come from different parts of the raw file", which is how Javascript source maps handle this problem. This could be done trivially with something like: {
// The code that's returned from the processor.
source: ["let a = 22;\na += 1;\nconsole.log(a);"],
source_map = [
// "let a = 22;" begins at line 3, character 6 of the HTML file
// "a += 1" begins at line 6, character 6 of the HTML file
// "console.log(a);" begins at line 7, character 6 of the HTML file
"0,0:3,6 | 1,0:6,6 | 2,0:7,6"
]
} In this format, each element in source_map = [
"0,0:3,6 | 1,0:6,6",
"0,0:2,4"
] then it means:
This should allow you to have a single Does that make sense? |
@zeptonaut Your basic idea makes sense and I like it. I just want to point out that in the current implementation, returning an array of sources implies that each source is independent, so in plugins like eslint-plugin-html and eslint-plugin-microtemplates (where a value could be declared in global scope in one block, and then used in another block), those have to export one source. That said, if we want to allow a "source" to be an array of strings, such that a multiple-block processor could send an array of arrays of strings to ESLint, then I think source maps could be supported in the way you propose. I would certainly love to be able to emit multiple "source parts" from my microtemplates plugin rather than having to concatenate everything together and remember the mappings. |
Ah, okay. I was thinking that emitting multiple sources per plugin per source file was already supported. Maybe we should focus now on making a solution that works for the current situation but could easily be extended to allow emission of multiple source strings in the future? (I think this solution might fit that bill.) |
@zeptonaut What I was trying to show with the examples above is that there might be many different things that processors would do, and we can't predict them all. Somebody even managed to write a processor that under the hood executes JSHint just so they could lint JSON files. While we provide facilities to make processors happen, we don't want to either do too much, or limit possible processors in the future (those two are almost always have direct correlation).
In the step number 6 is where you can user JavaScript source maps to restore the full content of the file based on the output of ESLint autofix. But it will be up to the processor creator to do that. Somebody could write a reusable library (like @platinumazure suggested) that would do all of the processor steps automatically (or almost automatically). |
Fair enough: I guess if an easy-to-use library can be written that handles the source mapping, I'd be all for deferring that logic to such a library rather than sticking it in eslint proper. With the proposed contract, though, I think it'd be exceedingly hard to write a library that manages this. Suppose that you have an HTML file: <html>
<script>
var a = 0;
a++;
console.log(a);
var b = 0;
</script>
<body><h1>Foo</h1></body>
<script>
var c = 4;
console.log(c);
</script>
</html> and you give eslint: var a = 0;
a++;
console.log(a);
var b = 0;
var c = 4;
console.log(c); and eslint gives autofix back: let a = 0;
a++;
console.log(a);
let c = 4;
console.log(c); how do you go about merging that without additional metadata given to you by eslint about where that code came from? It seems like trying to use diffs to apply these changes is likely to be exceedingly difficult for plugin authors to get right. |
@zeptonaut And that example is exactly why I don't want to bring mapping functionality into ESLint. There are a lot of different ways to handle the code above. You might want to combine it into a single |
I'm not sure that I understand. Given the above situation, how would a plugin author even go about reconstructing the code correctly? |
@zeptonaut It's not pretty, but you can check out platinumazure/eslint-plugin-microtemplates to see how I'm trying to manage it. It can definitely be improved and I think ESLint (or at least a project in the ESLint org) could help, but I also agree that ESLint shouldn't try too hard to get involved with that tracking because of the sheer diversity of input files that might need to be processed by ESLint. |
@platinumazure I now see the advantage of having a single file being able to emit an array of strings, each of which aren't independent. How does eslint handle this problem when one file uses globals in another? Do you know which rules are most affected by knowing the contents of other parts of the file? It seems like all of the problems associated with emitting multiple strings per file would also be just as applicable to importing javascript from other files, which eslint obviously already handles. |
@zeptonaut ESLint actually does not know how to use the contents of other files when linting one file. Instead, we have some constructs that just tell the linter how to behave in the face of uncertainty. Examples:
In any case, an HTML file (whether processed by eslint-plugin-html, eslint-plugin-microtemplates, or something else) would still need |
Got it. Given all of this, I think that @ilyavolodin's original proposition makes sense: basically allow plugin authors figure out how to merge different pieces of the same HTML file (like they do now), and have eslint hand them a I think that the difficulty of this will likely discourage some plugin authors from actually implementing |
Well, this was my motivation for suggesting a stateful transform library earlier-- even adjusting line and column numbers in the current |
@zeptonaut @platinumazure I think you guys I mostly focusing on simple processors (HTML, Vue, etc.) where you can just extract JavaScript directly as a subset of a file. But processors can be a lot more complicated. For example, some format that has variable substitution or a template that gets compiled into JavaScript code, or maybe somebody would be crazy enough to write a processor that converts ActionScript to JavaScript. While the library that you are talking about would be helpful to cover simple cases of extraction, you can't predict what other things people might want to do... (To be clear, I'm not opposed to a library like that. Although you could probably just use JavaScript source mapping library for that. I also don't think this library should be part of ESLint org. But it will most likely be pretty helpful to some processor creators out there). |
I'm fine with that: I see no problem with eslint requiring folks that need the library to include it from an external source. |
Author of I am not very proud of the way I rewrote the plugin to support the Anyway, let's focus on the topic of the issue. My vision is quite simple: plugin developers currently have to remap message lines and columns in the This behavior could be "opt-in": a plugin with fix range remap support could export a Maybe using locations (line/column) everywhere (message start/end and fix range) instead of offsets or conversely would be simpler for plugin developers. I initially tried to use source-map to get original message locations, but it doesn't provide a way to retrieve original offset (for fix ranges). That's why I wrote a small library similar to magic-string that fits my needs. |
@BenoitZugmeyer If I'm understanding it correctly, what you are proposing is very close to what I suggested above. Except I separated autofixing into it's own function ( |
Not really, no. The
I can provide a prototype PR if it makes things easier to understand. |
Ahh... I see. So basically you want |
This was accepted in today's TSC meeting. |
I'd like to reopen discussion about support for autofixing in processors. Now that autofixing is stabilized and we have more rules that can be fixed automatically, it feels like it would be helpful to support it for processors. This is continuation of the discussion from #5121
My proposal is to create an additional function called
autoFix
in the processor definition. It will take two arguments, an array of strings with fixed text and filename. It's job would be to combine passed in fixed text and original file text and return complete text of the modified file. ESLint will, at that point, write that file to disk.I'll open WIP PR with the changes. My only concern is using autofix in processors through API. Since API just gets back a list of messages, and not fixed text, location of those messages will be off. I'm not sure what would be a good approach to fix this issue, we can either strip autofix information from the messages, or require yet another function from the processor to fix location information for messages (or maybe require that
postprocess
fix not only message locations, but also autofix locations as well?).The text was updated successfully, but these errors were encountered: