Skip to content

refactor: use minimum config schema, remove env from lint#453

Merged
samuelstroschein merged 7 commits intomainfrom
improvements-to-lint
Mar 20, 2023
Merged

refactor: use minimum config schema, remove env from lint#453
samuelstroschein merged 7 commits intomainfrom
improvements-to-lint

Conversation

@samuelstroschein
Copy link
Copy Markdown
Member

@samuelstroschein samuelstroschein commented Mar 20, 2023

Problem

I went through the source code of lint and found numerous things we should likely change. I am to blame as I reviewed and accepted the PR that introduced the issues beneath.

  1. Linting depends on the entire inlang config even though only languages, referenceLanguage, and lint is used.
  2. Linting depends on the env and makes readResources calls under the hood.
  3. Arguments of functions do not use the object pattern, making public API changes difficult.
  4. The surface area, especially of query, seems too large.

Proposal

1. Only use the bare minimum of the config schema

All modules in core are supposed to be used individually as much as possible. By forcing the existence of config properties that are unused like writeResources, the goal is contradicted. Furthermore, testing becomes easier because redundant properties do not need to be mocked.

// current
export const lint = async (config: Config, env: EnvironmentFunctions )

// proposal
export const lint = async (
  config: Pick<Config, "lint" | "languages" | "referenceLanguage">, 
  resources: ast.Resource[], 
  env: EnvironmentFunctions 
)

2. Remove the environment function from lint

  • Exposing the environment functions increases the complexity of lint by a wide margin. A lint rule should not depend on the file system or dynamically importing JavaScript. If the latter use case emerges, we can introduce environment functions again.
  • Testing complexity increases because the env needs to be passed around.
  • No tests and rules in standard-lint-rules use environment functions.
// current
export const lint = async (config: Config, env: EnvironmentFunctions )

// proposal
export const lint = async (
  config: Pick<Config, "lint" | "languages" | "referenceLanguage">, 
  resources: ast.Resource[], 
)

3. Use the object argument pattern for public-facing APIs

Introducing changes like 1 and 2 becomes non-breaking if the "object argument pattern" is used. The order of the arguments becomes irrelevant. Yes, function overloads could be used but using objects as arguments is superior. No type unions need to be created, old arguments can be soft deprecated, ultimately leading to a better DX and maintainability on our side.

// current
export const lint = async (config: Config, env: EnvironmentFunctions )

// proposal
export const lint = async (args: {
  config: Pick<Config, "lint" | "languages" | "referenceLanguage">, 
  resources: ast.Resource[], 
})

4. Remove redundant query functions

The surface area of the query functions is huge. Mainly driven by separating the type error and warning into dedicated functions. The benefit seems to be non-existent.

  • The quantity of function is overwhelming from a DX perspective.
  • The quantity of functions leads to maintenance overhead with close to no benefit.
  • For example, the getLintReportsByLevel("warning") function serves the same functionality as getLintWarnings().
  • Introducing a custom query function for each level has a "big O notation" of n+3.

Current APIs

getLintReports()
getLintReportsByLevel()
getLintErrors() [redundant]
getLintWarnings() [redundant]
getLintReportsWithId() 
getLintErrorsWithId() [redundant]
getLintWarningsWithId() [redundant]
hasLintReports()
hasLintErrors() [redundant]
hasLintWarnings() [redundant]
hasLintReportsWithId()
hasLintErrorsWithId() [redundant]
hasLintWarningsWithId() [redundant]

Proposal a) introduce a type field to all functions

getLintReports(node, ..., level)
getLintReportsByLevel()
getLintReportsWithId() 
hasLintReports()
hasLintReportsWithId()

Proposal b) use the object argument pattern to expose "query arguments" to each function. The API would be severely simplified by only exposing two functions.

PS a good showcase of why the object argument pattern is superior to positional arguments.

getLintReports(node, { level, id, nested } )
hasLintReports(node, { level, id, nested } )

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 20, 2023

🦋 Changeset detected

Latest commit: 56d1010

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@inlang/core Minor
@inlang/ide-extension Minor
@inlang/git-sdk Minor
@inlang/website Minor

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

@render
Copy link
Copy Markdown

render Bot commented Mar 20, 2023

@samuelstroschein
Copy link
Copy Markdown
Member Author

samuelstroschein commented Mar 20, 2023

@ivanhofer I refactored the code, please take a look.

  • i implemented all suggestions above and went for proposal b with regards to the utility functions
  • 100% test coverage remains
  • reducing the utility functions lead to a large code footprint reduction
  • the standard lint rules should not be impacted by this PR

Learnings

1. I will not rush/LGTM pull requests that expose externally facing APIs.

Exposing 15 utility functions that are more or less one-liner functions increased the source code by over 500 lines. Those 15 functions have been merged into 1 function + 1 utility function, leading to severely fewer lines of code and increasing the DX by requiring to explain less.

Furthermore, I should have seen that you exposed environment functions in the PR but didn't. I have no idea why.

2. I will push harder against extracting one-liner utility functions in the future.

The infamous getLintRulesFromConfig debate made a return when I refactored the code, read #366 (comment) for context. By extracting this one-liner into a dedicated function, you wrote a test that added >30 lines of code. I learned my lesson. I will push hard against extracting one-liner utility functions in the future as they bloat the source code.

The test of the getLintRulesFromConfig utility function:

CleanShot 2023-03-20 at 01 04 33@2x

@samuelstroschein samuelstroschein marked this pull request as ready for review March 20, 2023 01:44
@ivanhofer
Copy link
Copy Markdown
Contributor

  1. Only use the bare minimum of the config schema

I was lazy on that on. It makes sense to just pick the props that are really required.

  1. Remove the environment function from lint

Sure, we can add it if it is needed.

  1. Use the object argument pattern for public-facing APIs

Makes sense. I will note that for the future.

  1. Remove redundant query functions

This was one of the decisions I always was unsure about. Having a single function looks definitely cleaner 👍


Great changes!

Just the thing with the ressources should be considered.

Comment thread source-code/core/src/lint/linter.ts Outdated
Comment thread source-code/core/src/lint/linter.ts
Comment thread source-code/core/src/lint/linter.ts
@samuelstroschein
Copy link
Copy Markdown
Member Author

CleanShot 2023-03-20 at 11 11 03@2x

@ivanhofer#3454 interesting observation. I was adding a return type to lint.

1. Should we return undefined if no lints exists or return the original resource?

// returning undefined makes the consumption harder
const resourcesWithLints = await lint({ config, resources })
if (resourcesWithLints === undefined){
  console.log("no lints")
}
// can't do because lint reports does not expect undefined (rightfully so)
if (hasLintReports(resourcesWithLints) === false){
  //
}
// returning the resources in any case enables the consumer to overwrite the "old resources"  
resources = await lint({ config, resources })
if (hasLintResports(resources){
  // do something
}

2. Why can the lint information be undefined?

type LintInformation = {
	lint?: LintReport[]
}

The DX of lint increases if developers are not forced to do two conditional checks:

  1. If lint is undefined
  2. If lint is an empty array

current

if (node.lint && node.lint.length > 0){
   // do something
}

proposal

// lint is always defined after running through `lint`, avoiding undefined conditionals
if (node.lint > 0) {
   // do something
}

@ivanhofer
Copy link
Copy Markdown
Contributor

  1. good catch.. we should return the unmodified Resources instead of undefined
    • That's why we have the query functions.
    • We would need to iterate over all resources at least one time to add that prop. Wasting some compute resources.

@samuelstroschein
Copy link
Copy Markdown
Member Author

good catch.. we should return the unmodified Resources instead of undefined

I'll implement that!

That's why we have the query functions.
We would need to iterate over all resources at least one time to add that prop. Wasting some compute resources.

Fair. Minor improvement for little upside. I will not implement always defined lints.

@samuelstroschein samuelstroschein merged commit a0b85eb into main Mar 20, 2023
@samuelstroschein samuelstroschein deleted the improvements-to-lint branch March 20, 2023 11:52
@github-actions github-actions Bot mentioned this pull request Mar 20, 2023
samuelstroschein added a commit that referenced this pull request Mar 27, 2023
* 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
samuelstroschein added a commit that referenced this pull request Mar 27, 2023
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
felixhaeberle pushed a commit that referenced this pull request Apr 4, 2023
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants