refactor: simplify core/lint#459
Conversation
🦋 Changeset detectedLatest commit: 2faba51 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Your Render PR Server URL is https://inlang-website-pr-459.onrender.com. Follow its progress at https://dashboard.render.com/web/srv-cgctj0q84i2237bosppg. |
Context can't be passed around from nodes to childs without adjusting the target and reference nodes.
The generic output was unused.
Open questions
I encountered numerous problems because However, debugging became harder and consuming apps like the editor or CLI will never now if a linting process failed. An easy fix could be to return a tuple: const [ resources, errors ] = await lint()But the following pattern wouldn't work anymore (likely doesn't anyways because resources would have type let resources = readResources()
resources = await lint(resources)If we decide for a
From an API perspective, a second setup function is redundant, but exposing "setup" variables and functions that need to passed outside of the Option A: A rule from
|
Redundant concept.
|
@ivanhofer Props to your type writing skills! Took me hours to understand some but impressive what degree of type safety you get source code. Sometimes going a bit over the edge though ;) I think the lint design is more future-proof and easier to explain now. Docs are not ready yet. I am awaiting your input on the open questions before I continue. PS this task shouldn't have taken so long. Was probably not worth it. |
ivanhofer
left a comment
There was a problem hiding this comment.
I'm sorry that you had to make that much changes. When you told me to create a sophisticated lint process, I included everything that came to my mind to be sure to cover 99% of all needs and hopefully "never" touch it again or introduce breaking changes.
And that's the reason why some things are more complicated then they could be.
In reality you did want a simple linting process.
Some notes on your points:
remove setup
setup is meant for initializing something asynchronously. If you need to make fetch calls to an API, you may need to authorize first. Not every API will have a dedicated npm package. Without the setup function, you would need to write special logic to do the authorization before the first API call. It was also meant to pass options that are meant for the whole linting process.
We could make the setup function optional to still support that use case. Passing those options mentioned above can also happen at another place.
remove teardown
Was meant to free up resources. It is optional an can be used if someone needs it. Nobody is required to use it, if he doesn't need it.
remove the concept of payload
Again, something optional that can be used if someone needs it. It is not required to use it.
Your code will not work, because Pattern is called after Resource. If you did want to support this, we would need the leave functionality again. And for exact such use cases the leave functionality is meant for. Most people won't use it, but if they need it it is there.
Another thing regarding your code example. This will work fine if you just have a single Pattern and Resource, but you will probably have hundreds of Nodes. How do you do that? Write hundreds of variables? Create an array? How do you know what index is the correct one?
With the payload concept you could pass the Resource to the Pattern visitor and directly call the report function there.
Or maybe just throw an error if > 5 Patterns contain bad language. Maybe you are fine with a single one or you are in a migration process and want to be sure that no additional error get's added, but you are temporary fine with the existing ones. But you don't want to fail your CI process.
remove the concept of booleans for a rule level
I would say that most beginner don't know this syntax:
[
...!isTodayMonday ? [missingKeyrule()] : [],
]They will probably don't be able to disable rules conditionally. Or at least struggle with it.
I agree true is weird, but we could leave the existing behavior and additionally add the 'default' keyword.
replace the concept of context with top-level arguments
context was meant to be extendable and will have a fix option in the future.
Passing it like you did is a great option. The createLintRule was one of the last additions. Therefore this option was not considered, because everything else was already ready and working.
One "downside" is, that a creator of lint rules is required to use the createLintRule function. But the function is a really good wrapper that handles some stuff for a developer and is is certainly recommended to use it.
No end-to-end test existed
Where would you expect such a test?
linter.ts is the only place where it makes sense and those are e2e tests.
Example 1
createLintRule is (was) not required and calling the function resulted in the same object.
Test was probably written before the createLintRule function was introduced.
Example 2
Why is this test wrong? It just makes sure that the returned object of the callback you add to the createLintRule function get's passed to the resulting lint rule.
Focus on code coverage instead of writing (duplicate) unit tests for every function like "for 'Message' test if 'leave' is not present" and "for 'Pattern' test if 'leave' is not present". The test is identical. Writing and maintaining both tests seems unnecessary. The underlying logic is identical.
If you would refactor the leave function inside Pattern, how would you know that you didn't break the existing behavior? If you don't test it, you won't know.
should lint return errors
It makes sense to return errors instead of just logging them. If it is something exposed externally, a tuple makes sense.
how should the config be passed to a lint rule?
So we now need again a second "setup" function?
Certainly option A.
out comment lint collections for now
It is there and it already works. Sure we can just don't expose it for now and wait until someone demands it.
don't allow rules to define the lint level?
Rules should be configurable! An error for Team A may be just a warning for Team B. Or while migrating something you may wish to just have a warning for a while. And once migration is done, you can change it to an error.
Users face a hidden lint level that might make them wonder why certain things are errors or warnings.
We could say that rules can't specify a default level and everything is a warning, but I would not do that. The additionalKey rule is a good hint that you should take a deeper look, but it should not fail CI, because it does no real harm if it is there.
explicit behavior for close to no additional "cost"
The less an end user needs to do the easier the adoption will be.
Conclusion
I would not remove any functionality. I would just make setup optional, pass those arguments somewhere else and add "default" to the options that a lint rule accepts.
I am to blame. As we both figured out, the RFC process did not state goals and non-goals :) Plus, the
The createLintRule is awesome! Most simplifications stem from the createLintRule which was added last. I think that is why I used the lints and thought "wait this goes way simpler". The current design reminds me of React class components while the Regarding this PR, there are some miscommunicationsAll features that existed before still exist. The mental model for rule writers is easier though by leveraging the
From a DX perspective, the model of writing rules is simpler with just one setup function.
My point was that I finish this version of the PR. Afterward, we can compare the mental model with the current version and decide which one is easier. |
|
Oh wow, I just stumbled on a great syntax for entering and leaving nodes that is easier to explain and future-proofs the api. const myRule = createLintRule("foo.x", ({ settings, report }) => {
return {
visitors: {
Resource: ({ onLeave }) => {
+ // the state is local to a visitor without the concept of payloads. just use plain JS.
+ const isError = true;
+ // on leave (and many other features) could be exposed via a callback
+ onLeave(() => {
+ if (isError) report({ node: this, message: "foo" });
});
},
},
};
}); |
Not really. Take a look at this: https://auth0.com/docs/secure/tokens/access-tokens/get-management-api-access-tokens-for-production.
This is a good idea if you want to share state on the same level. I would remove the Once minor thing that got lost by removing A lot of the things mentioned above were not clear. I'll wait until you are ready with the refactoring. |
With the current way I approach this, no. I can make it async. Will do.
This would collide with the concept of returning "skip" and lead to a concept that needs to be explained in general. The callback pattern
Roger 👍 |
|
NOTE TO MYSELF I think I found the design improvement that makes lint much simpler. The setup function should return the visitors, not be a separate property. This resembles ESLint and has one major advantage: The double setup function described in #457 is eliminated without forcing the usage of // current design
export type LintRule = {
id: LintRuleId,
level: LintRuleLevel,
setup: (args) => MaybePromise<unknown>
visitors: NodeVisitors
}// this pull request
export type LintRule = {
id: LintRuleId,
level: LintRuleLevel,
setup: (args) => Promise<Visitors>
} |
We should wait for user demand to nail a solution.
The `utilities` lib is intended for external usage. Furthermore, most utilties were not used.
The utilities like `createMessage` should likely be part of `ast`. Exposing them only for lint seems a short term solution. But, if we expose those utiltites in lint now, we can't remove them in the future. Thus, remove for now.
Encountered numerous problems because `lint` implicitly catches errors. That behavior makes sense. Just because one rule is throwing, the linting process shouldn't abort. However, debugging became harder and consuming apps like the editor or CLI will never now if a linting process failed.
|
@ivanhofer Done ✅
|
node fs imports don't work in core
node fs imports don't work in core
node fs imports don't work in core
* refactor: remove setup and teardown * work in progress commit * refactor: remove the concept of passing booleans to the lint level * fix: propagation of context Context can't be passed around from nodes to childs without adjusting the target and reference nodes. * chore: simplify type The generic output was unused. * test: slightly more realistic test * refactor: remove context Redundant concept. * init alternative * refactor: define setup function in `LintRule` #459 (comment) * clean up * refactor: remove rule collections 😢 We should wait for user demand to nail a solution. * update readme * refactor: remove unusued utilities The `utilities` lib is intended for external usage. Furthermore, most utilties were not used. * add: typesafety to createLintRule settings * chore: fix type imports * chore: fix typescript compilation * chore: remove test utilities export The utilities like `createMessage` should likely be part of `ast`. Exposing them only for lint seems a short term solution. But, if we expose those utiltites in lint now, we can't remove them in the future. Thus, remove for now. * refactor: return errors to enable apps to handle errors Encountered numerous problems because `lint` implicitly catches errors. That behavior makes sense. Just because one rule is throwing, the linting process shouldn't abort. However, debugging became harder and consuming apps like the editor or CLI will never now if a linting process failed. * fix typo Co-authored-by: Hofer Ivan <ivan.hofer@outlook.com> * fix typo Co-authored-by: Hofer Ivan <ivan.hofer@outlook.com> * show example lint rule with settings * chore: remove todo * fix: only pass message to a node * chore: add comment to remove node types * refactor: narrow settings type * refactor: createLintRule only uses args #459 (comment) * chore: don't use node globals * refactor: remove node dependency entirely node fs imports don't work in core * refactor: remove node dependency entirely node fs imports don't work in core * refactor: remove node dependency entirely node fs imports don't work in core * fix: pass correct args * Create .changeset/hungry-carpets-build.md --------- Co-authored-by: Hofer Ivan <ivan.hofer@outlook.com>
commit b8cdf3f Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon Mar 27 18:43:22 2023 +0200 Version Packages (#456) Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> commit e9e9ce5 Author: Samuel Stroschein <35429197+samuelstroschein@users.noreply.github.com> Date: Mon Mar 27 18:35:32 2023 +0200 refactor: simplify core/lint (#459) * refactor: remove setup and teardown * work in progress commit * refactor: remove the concept of passing booleans to the lint level * fix: propagation of context Context can't be passed around from nodes to childs without adjusting the target and reference nodes. * chore: simplify type The generic output was unused. * test: slightly more realistic test * refactor: remove context Redundant concept. * init alternative * refactor: define setup function in `LintRule` #459 (comment) * clean up * refactor: remove rule collections 😢 We should wait for user demand to nail a solution. * update readme * refactor: remove unusued utilities The `utilities` lib is intended for external usage. Furthermore, most utilties were not used. * add: typesafety to createLintRule settings * chore: fix type imports * chore: fix typescript compilation * chore: remove test utilities export The utilities like `createMessage` should likely be part of `ast`. Exposing them only for lint seems a short term solution. But, if we expose those utiltites in lint now, we can't remove them in the future. Thus, remove for now. * refactor: return errors to enable apps to handle errors Encountered numerous problems because `lint` implicitly catches errors. That behavior makes sense. Just because one rule is throwing, the linting process shouldn't abort. However, debugging became harder and consuming apps like the editor or CLI will never now if a linting process failed. * fix typo Co-authored-by: Hofer Ivan <ivan.hofer@outlook.com> * fix typo Co-authored-by: Hofer Ivan <ivan.hofer@outlook.com> * show example lint rule with settings * chore: remove todo * fix: only pass message to a node * chore: add comment to remove node types * refactor: narrow settings type * refactor: createLintRule only uses args #459 (comment) * chore: don't use node globals * refactor: remove node dependency entirely node fs imports don't work in core * refactor: remove node dependency entirely node fs imports don't work in core * refactor: remove node dependency entirely node fs imports don't work in core * fix: pass correct args * Create .changeset/hungry-carpets-build.md --------- Co-authored-by: Hofer Ivan <ivan.hofer@outlook.com> commit f87f300 Author: Boian Ivanov <boian.ivanov44@gmail.com> Date: Mon Mar 27 15:28:45 2023 +0100 chore: linting errors (#469) * fix: linting errors * chore: add changeset commit 2d3c8cc Author: NiklasBuchfink <niklas.buchfink@web.de> Date: Mon Mar 27 08:31:09 2023 +0000 refactor: svg logo in website layout commit b13dfa8 Author: Ivan Hofer <ivan.hofer@outlook.com> Date: Sat Mar 25 11:07:37 2023 +0100 run npm i commit 704c83d Author: Hofer Ivan <ivan.hofer@outlook.com> Date: Sat Mar 25 10:16:04 2023 +0100 add sdk-js RFC (#460) commit 4992681 Author: Niklas Buchfink <59048346+NiklasBuchfink@users.noreply.github.com> Date: Thu Mar 23 19:12:45 2023 +0100 Editor condense textarea (#461) commit 277e5ae Author: NiklasBuchfink <niklas.buchfink@web.de> Date: Tue Mar 21 10:05:51 2023 +0000 add: discord invite to footer commit 0a2de81 Author: NiklasBuchfink <niklas.buchfink@web.de> Date: Mon Mar 20 16:13:46 2023 +0000 add discord invite icon to header commit a0b85eb Author: Samuel Stroschein <35429197+samuelstroschein@users.noreply.github.com> Date: Mon Mar 20 12:52:43 2023 +0100 refactor: improve core/lint (#453) * refactor: use minimum config schema, remove env from lint * chore: format fix * refactor: only expose 2 query functions * chore: add changeset * chore: remove unnecessary optional chaining * refactor: don't return undefined in lint #453 (comment) * refactor: immutable lint commit e3fb6a3 Author: NiklasBuchfink <niklas.buchfink@web.de> Date: Mon Mar 20 10:14:06 2023 +0000 fix contact mail in footer commit 1a6ccec Author: Nikita <nikita.voloboev@gmail.com> Date: Mon Mar 20 02:25:45 2023 +0100 add: do shallow clone (#454) * do git clone with depth 1 * add git shallow clone for fast UI response with fetching rest of history in background * comment * fix prettier commit 6c8a576 Author: Samuel Stroschein <35429197+samuelstroschein@users.noreply.github.com> Date: Sun Mar 19 12:53:22 2023 +0100 Update repositories.ts knadh/listmonk#1189 commit 6dbdfc7 Author: Robin Bühler <git+r@obin.ch> Date: Fri Mar 17 14:32:45 2023 +0000 chore: add ide-extension publication to ci-cd env commit a4581da Author: Robin Bühler <git+r@obin.ch> Date: Fri Mar 17 14:23:39 2023 +0000 chroe: change vsce token env commit 53423d6 Author: Robin Bühler <git+r@obin.ch> Date: Fri Mar 17 13:57:41 2023 +0000 chore: run vsce with npx commit 042bfab Author: Robin Bühler <git+r@obin.ch> Date: Fri Mar 17 13:51:23 2023 +0000 chore: enhance publis-ide-extension-to-marketplace.yml commit bdbbf8e Author: Robin Bühler <git+r@obin.ch> Date: Fri Mar 17 13:38:49 2023 +0000 chore: update package-lock.json commit 8f73244 Author: Robin Bühler <1105080+openscript@users.noreply.github.com> Date: Fri Mar 17 14:35:56 2023 +0100 Set up continuous deployment for ide-extension (#446) * chore: enable tests * chore: name core publish flow * chore: add publish workflow for ide-extension * chore: format * chore: remove ovsx and adjust vsce token name * chore: enhance package naming * chore: enhance prepare package script commit 2983569 Author: Nils Jacobsen <58360188+NilsJacobsen@users.noreply.github.com> Date: Thu Mar 16 07:36:22 2023 +0100 Text fixes (#449) * index changes after initial pull * feat: add folderstructure for sections #416 * setup landingpage structure * feat: add Section Wrapper for landingpage #416 * Test second hero * feat: add slate colors to ds #416 * #416 relocate landingpage files in website/index * feat: add bg grid component wrapper #416 * #416 add button & navigation * fix button type error * feat: add hero layout #416 * feat: add credibilty section and editor #416 * feat: add vscode extension section and cli section and footer #416 * #416 add useNagition to Button component * #416 add custom header for landingpage * feat: add responsive layout for footer, hero, editor and vscode #316 * #416 add chevron to Buttons * #416 add responsive GetStarted section * #416 add functions to Button * #416 add signal to toggle CLI section * feat: add responsive cli section * #416 fix menu background * #416 optimize Lighthouse score * #416 fix nav for all pages * #416 update wording * highlighting for copytext * feat: add product videos #416 * fix text & text-width * body text width * fix: editor layout #416 * fix: padding on editor #416 * fix: changes bellong to suggestions of code review #416 * fix: small wording fixes #416 --------- Co-authored-by: NiklasBuchfink <niklas.buchfink@web.de> commit d913ef2 Author: Nils Jacobsen <58360188+NilsJacobsen@users.noreply.github.com> Date: Tue Mar 14 07:04:01 2023 +0100 New landing page (#444) * index changes after initial pull * feat: add folderstructure for sections #416 * setup landingpage structure * feat: add Section Wrapper for landingpage #416 * Test second hero * feat: add slate colors to ds #416 * #416 relocate landingpage files in website/index * feat: add bg grid component wrapper #416 * #416 add button & navigation * fix button type error * feat: add hero layout #416 * feat: add credibilty section and editor #416 * feat: add vscode extension section and cli section and footer #416 * #416 add useNagition to Button component * #416 add custom header for landingpage * feat: add responsive layout for footer, hero, editor and vscode #316 * #416 add chevron to Buttons * #416 add responsive GetStarted section * #416 add functions to Button * #416 add signal to toggle CLI section * feat: add responsive cli section * #416 fix menu background * #416 optimize Lighthouse score * #416 fix nav for all pages * #416 update wording * highlighting for copytext * feat: add product videos #416 * fix text & text-width * body text width * fix: editor layout #416 * fix: padding on editor #416 * fix: changes bellong to suggestions of code review #416 --------- Co-authored-by: NiklasBuchfink <niklas.buchfink@web.de> commit 6500378 Author: NiklasBuchfink <niklas.buchfink@web.de> Date: Thu Mar 9 15:30:09 2023 +0000 commit e75807a Author: Samuel Stroschein <35429197+samuelstroschein@users.noreply.github.com> Date: Thu Mar 9 13:49:06 2023 +0000 chore: update turborepo Avoiding exec runtime error in docker container. commit 9ddd3da Author: Jannes Blobel <72493222+jannesblobel@users.noreply.github.com> Date: Thu Mar 9 00:10:30 2023 +0100 Update config.md
* refactor: remove setup and teardown * work in progress commit * refactor: remove the concept of passing booleans to the lint level * fix: propagation of context Context can't be passed around from nodes to childs without adjusting the target and reference nodes. * chore: simplify type The generic output was unused. * test: slightly more realistic test * refactor: remove context Redundant concept. * init alternative * refactor: define setup function in `LintRule` #459 (comment) * clean up * refactor: remove rule collections 😢 We should wait for user demand to nail a solution. * update readme * refactor: remove unusued utilities The `utilities` lib is intended for external usage. Furthermore, most utilties were not used. * add: typesafety to createLintRule settings * chore: fix type imports * chore: fix typescript compilation * chore: remove test utilities export The utilities like `createMessage` should likely be part of `ast`. Exposing them only for lint seems a short term solution. But, if we expose those utiltites in lint now, we can't remove them in the future. Thus, remove for now. * refactor: return errors to enable apps to handle errors Encountered numerous problems because `lint` implicitly catches errors. That behavior makes sense. Just because one rule is throwing, the linting process shouldn't abort. However, debugging became harder and consuming apps like the editor or CLI will never now if a linting process failed. * fix typo Co-authored-by: Hofer Ivan <ivan.hofer@outlook.com> * fix typo Co-authored-by: Hofer Ivan <ivan.hofer@outlook.com> * show example lint rule with settings * chore: remove todo * fix: only pass message to a node * chore: add comment to remove node types * refactor: narrow settings type * refactor: createLintRule only uses args #459 (comment) * chore: don't use node globals * refactor: remove node dependency entirely node fs imports don't work in core * refactor: remove node dependency entirely node fs imports don't work in core * refactor: remove node dependency entirely node fs imports don't work in core * fix: pass correct args * Create .changeset/hungry-carpets-build.md --------- Co-authored-by: Hofer Ivan <ivan.hofer@outlook.com>
* refactor: remove setup and teardown * work in progress commit * refactor: remove the concept of passing booleans to the lint level * fix: propagation of context Context can't be passed around from nodes to childs without adjusting the target and reference nodes. * chore: simplify type The generic output was unused. * test: slightly more realistic test * refactor: remove context Redundant concept. * init alternative * refactor: define setup function in `LintRule` #459 (comment) * clean up * refactor: remove rule collections 😢 We should wait for user demand to nail a solution. * update readme * refactor: remove unusued utilities The `utilities` lib is intended for external usage. Furthermore, most utilties were not used. * add: typesafety to createLintRule settings * chore: fix type imports * chore: fix typescript compilation * chore: remove test utilities export The utilities like `createMessage` should likely be part of `ast`. Exposing them only for lint seems a short term solution. But, if we expose those utiltites in lint now, we can't remove them in the future. Thus, remove for now. * refactor: return errors to enable apps to handle errors Encountered numerous problems because `lint` implicitly catches errors. That behavior makes sense. Just because one rule is throwing, the linting process shouldn't abort. However, debugging became harder and consuming apps like the editor or CLI will never now if a linting process failed. * fix typo Co-authored-by: Hofer Ivan <ivan.hofer@outlook.com> * fix typo Co-authored-by: Hofer Ivan <ivan.hofer@outlook.com> * show example lint rule with settings * chore: remove todo * fix: only pass message to a node * chore: add comment to remove node types * refactor: narrow settings type * refactor: createLintRule only uses args #459 (comment) * chore: don't use node globals * refactor: remove node dependency entirely node fs imports don't work in core * refactor: remove node dependency entirely node fs imports don't work in core * refactor: remove node dependency entirely node fs imports don't work in core * fix: pass correct args * Create .changeset/hungry-carpets-build.md --------- Co-authored-by: Hofer Ivan <ivan.hofer@outlook.com>
This PR introduces the following changes
All changes intend to reduce complexity, mostly by exposing less features, and wait for user feedback on edge cases before we expose features that might not align with user needs but need to be maintained by us. As a side effect, the DX is expected to increase due to simpler docs.
remove setup ☑️
The
createLintRulefunction is the setup function, see #457 (comment).remove teardown ☑️
See #457 (comment)
remove the concept of
payload☑️The concept of payload is redundant due to the
createLintRulefunction.State exists within the scope of the
createLintRulefunction. There is no need for a dedicated payload concept that needs to be explained and maintained.const rule = createLintRule("error.rule", "error", () => { + const patternIsInvalid = false; return { visitors: { Pattern: ({ target, report }) => { report({ node: target, message: "Test" }); + patternIsInvalid = true; }, Resource: ({ target, report }) => { + if (patternIsInvalid) { report({ message: "Error in Pattern" }); } }, }, }; });remove the concept of booleans for a rule level ☑️
The concept of passing booleans to a rule level unjustifiably increases comprehension and code-base complexity. There were two reasons for passing booleans to a lint rule:
This concept has been refactored to use "default" instead.
Disabling rules with a boolean flag is unnecessary. Outcommenting a rule is more intuitive and decreases the complexity of lint.
For conditionals, inline conditionals can be used. Given, the syntax is inferior. But, will users use conditionals for rules? If so, they can achieve the desired effect in plain JS. If users strongly demand disabling rules via the lint level, we can re-introduce it.
replace the concept of
contextwith top-level arguments ☑️The create lint rule exposes a report function and partial inlang config in the callback with the argument object pattern.
report-> less things to explainsettingsargument, allowing us to extend the callback arguments based on user requests.export const additionalKeyRule: LintRuleInitializer = createLintRule( "inlang.additionalKey", "warn", + ({ report, config }) => { visitors: { Resource: ({ target }) => { if (target && target.languageTag.name === config.referenceLanguage) return "skip"; }, }, }, }; } );Additional information
It seems like complexity originated because the
createLintRulewas an afterthought [that made original concepts redundant].[Problem] Not a single end-to-end test existed; the majority of tests had little value
Many tests used mock rules even though the difference between setting up a mock rule and a real rule is neglectable:
Example 1
used in the tests
alternative
Example 2
The following test passed even though the implementation was broken. Visitors have been replaced with an empty object at one point. The test passed regardless due to the artificial testing conditions. A real-world usage would pass object with visitors, subsequently failing this test.
Learning