Skip to content
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: Directive to Skip File Parsing #118

Closed
wants to merge 2 commits into from
Closed

feat: Directive to Skip File Parsing #118

wants to merge 2 commits into from

Conversation

jeengbe
Copy link

@jeengbe jeengbe commented Apr 1, 2024

Summary

A new /* eslint-ignore-file */ directive that gives files the ability to ignore themselves.

Related Issues

related GitHub Issue

Copy link

linux-foundation-easycla bot commented Apr 1, 2024

CLA Signed


The committers listed above are authorized under a signed CLA.

@ljharb
Copy link

ljharb commented Apr 1, 2024

this seems like a security attack vector. why would i want to permit someone to add a file to my codebase that, unbeknownst to me, isn't verified by eslint?

@jeengbe
Copy link
Author

jeengbe commented Apr 1, 2024

why would i want to permit someone to add a file to my codebase

The file would be generated code, such as an API client generated by typescript-swagger-api, a routes file generated by tsoa or type definitions generated by graphql-codegen.

unbeknownst to me, isn't verified by eslint?

How is this different from files self-ignoring with /* eslint-disable */, are there certain security-focused rules that override eslint-disable?

@ljharb
Copy link

ljharb commented Apr 1, 2024

Fair question, and there's https://www.npmjs.com/package/eslint-plugin-eslint-comments for that, but eslint-disable still allows them to be parsed, which i think is important.

I'd expect generated code to output into a single folder, which could easily be ignored with ignorePatterns.

@jeengbe
Copy link
Author

jeengbe commented Apr 1, 2024

I'd expect generated code to output into a single folder

I must respectfully disagree with you on that point, and evidently many others. In most codebases I've seen so far, generated code is not hosted in a single directory.

Instead, consider the following GraphQL/Tsoa backend:

.
└── src/
    ├── lib/
    │   ├── svc-a/
    │   │   ├── generated/
    │   │   │   └── Api.ts <- typescript-swagger-api
    │   │   └── index.ts
    │   └── svc-b/
    │       ├── generated/
    │       │   └── proto <- gRPC client
    │       └── index.ts
    ├── resolvers/
    │   ├── a/
    │   │   └── resolve.ts
    │   ├── b/
    │   │   └── resolve.ts
    │   └── types.ts <- graphql-codegen
    └── build/
        └── tsoa <- tsoa
            └── routes.ts

A collective generated/ folder is unfeasible and doesn't scale well.

@ljharb
Copy link

ljharb commented Apr 1, 2024

you can ignore **/generated/**, though.

@nzakas nzakas added the Initial Commenting This RFC is in the initial feedback stage label Apr 1, 2024
@jeengbe
Copy link
Author

jeengbe commented Apr 2, 2024

You're right, I missed that.

Regardless, this propsal is not about the functional ability to skip certain files from being parsed. Instead, it's about the file skipping itself fully. Files with /* eslint-disable */ at the top are a waste of resources, by how I understand that directive to work.

@ljharb
Copy link

ljharb commented Apr 2, 2024

I agree with your assessment - however, my experience managing linter settings in large codebases tells me that files overriding eslint themselves is an antipattern (albeit sometimes necessary for some kinds of overrides) and that eslint config is best managed centrally in a root config file. Adding this feature would thus require me to waste further resources banning its use in every project I manage.

@jeengbe
Copy link
Author

jeengbe commented Apr 2, 2024

Could you please elaborate on the difference between /* eslint-disable */ and the proposed directive? Functionally, they are the same, and with this, otherwise already ignored files would no longer slow down the linting process.

I don't have the experience of working in large codebases you do, and in no scenario I can think of, would disable work, but skip-file not.

@ljharb
Copy link

ljharb commented Apr 2, 2024

The difference is that with this new proposal, parse errors can go undetected.

@jeengbe
Copy link
Author

jeengbe commented Apr 2, 2024

I don't see how that is a bad thing. It's not in the interest of the application developer to experience ESLint crashes over files that are ignored anyway. Other than for reporting issues to the library developer, parsing eslint-disabled files has no value to the application developer. If your experience tells otherwise, that would resolve this discussion, I myself don't see when this would not be the case.

@ljharb
Copy link

ljharb commented Apr 2, 2024

A syntax error in production that's not caught in CI can break your entire application; just because you don't want eslint rules on a file doesn't mean you want syntax errors in it.

Can you help me understand a use case where you do NOT care that there's syntax errors in a lintable file?

@jeengbe
Copy link
Author

jeengbe commented Apr 2, 2024

Oh, we might be talking about slightly different things. 😅 With "ESLint crashes", I mean that as a consequence a bug in an ESLint rule, such as here. This caused ESLint to crash on files automatically generated by Tsoa, which the application developer practically has no control over.

When you talk about syntax errors, do you regard ESLint as a syntax checker? I did not at all consider that. To me, ESLint is purely about code style, and syntactic checks are done by the TypeScript compiler.

Or concretely, your concern is that ESLint would no longer catch syntax errors in a self-ignoring file, which would cause that file to go to production otherwise unverified?

@ljharb
Copy link

ljharb commented Apr 2, 2024

Yes, eslint even with no rules enabled is a syntax checker, and this is a very important function it serves. Not everyone uses TypeScript.

Yes, that is my concern.


Many libraries that generate code generate code with leading `/* eslint-disable */` directives at the top of generated files. However, those files are still parsed and validated, which slows down the linting process needlessly. Potentially large and complex files are processed, even though their results are useless to the application developer. In the worst case, bugs in linting rules (see [this issue about a broken linting rule](https://github.com/eslint/eslint/issues/18022)) crash the entire ESLint process over artifacts an application developer has no control over. The problem is that `/* eslint-disable */` merely mutes linting results.

This directive will work alongside the `.eslintignore` file in that it too will indicate which file be parsed or not. It extends the concept of ignoring source files by allowing files (specifically generated files) to exclude themselves.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .eslintignore file is deprecated. All ignores are now in the eslint.config.js file.


This directive will work alongside the `.eslintignore` file in that it too will indicate which file be parsed or not. It extends the concept of ignoring source files by allowing files (specifically generated files) to exclude themselves.

Lastly, library devlopers seldom actually mean "parse, but mute errors" when `/* eslint-disable */` is added at the top of files. While it the best option currently available, "completely skip this file" is actually meant.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious if you have any data to back up this assertion?

In my experience, I don't believe this is true. ESLint will still validate the syntax in this case.


A more sophisticated algorithm may be chosen at a later point in the lifecycle of this RFC.

In practice, the imaginary
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please look at the actual source code to determine where changes should be made. This is part of the RFC process.

declare function parseFileImpl(fileContent: string): AST;
```

How the rest of the system deals with `null` results from parsing a source file is not in the scope of this RFC.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it is. We need a complete understanding of how such a change would function inside of ESLint.


How the rest of the system deals with `null` results from parsing a source file is not in the scope of this RFC.

If an `/* eslint-ignore-file */` directive is encountered while parsing a file, a non-fatal error shall be raised, and the directive shall have no further effect.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the point was that we wouldn't parse the file? But here you're saying we will in order to find the comment? Can you clarify?

And what does it mean for a non-fatal error to be raised? Do you mean it would be look like a lint violation? Would the user get any output?


## Documentation

TBD. The primary audience is a library author. The point **Drawbacks > Bardwards Compatibility** shall be highlighted.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please look through our docs and find what would need to be updated.


Library authors will need to prefix their generated code with both `/* eslint-ignore-file */` and `/* eslint-disable */` directives in order to not break existing projects that have not upgraded their ESLint core to a version that supports the `/* eslint-ignore-file */` directive. Otherwise, updating libraries without updating ESLint accordingly will lead to possible linging errors on generated source files that were previously muted.

2. Primitive Parser
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we parse at all? Why not just use a regular expression to see if the text exists in the source?

/* eslint-ignore-file */
```

This is an intentional middleground between keeping the analysis as simple (and fast) as reasonable and correctness, which would require parsing read comments to some degree.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per your motivation, shouldn't the directive always be first in a generated file?


## Alternatives

1. As highlighted in **Motivation**, everything described in this RPC is functionally already possible with `.eslintignore` files.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is already possible using an ignore file, then why would we add another way to do the same thing?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that this is no longer possible with a local ignore file and strict unused-directive check is on by default I'd like to revisit this, should I open an new proposal?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible with ignorePatterns in a config, which is a replacement for .eslintignore. Why isn't that sufficient?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's hard to locate and modify the top-level eslint.config.js programmatically for a tool that generates files, but easy to add a directive to the generated file or stick an .eslintignore file next to the files.

Currently tools like TanStack Router generate files in a codebase with ignore directives but eslint 9 makes these less useful because they may trigger unused ignore directive warnings. It'd be useful to 0reserve a way to ignore a file from the file itself that doesn't trigger these unused disable warnings which are now enabled by default.

@nzakas
Copy link
Member

nzakas commented Apr 23, 2024

@jeengbe are you still working on this? There is feedback to address.

@jeengbe
Copy link
Author

jeengbe commented Apr 24, 2024

@jeengbe are you still working on this? There is feedback to address.

Yes, apologies, no, I completely forgot about this issue.

The comments are valid points and I don't have a strong enough commitment to this proposal to advocate for it further. I would also need some help with the internal effect of this change since I do not know how ESLint functions internally.

I propose we close this PR unless you think otherwise.

@nzakas
Copy link
Member

nzakas commented Apr 26, 2024

That sounds good. I think using the current approach of ignoring files is probably the best way to go to address this use case. Thanks for your time and thoughtfulness.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Initial Commenting This RFC is in the initial feedback stage
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants