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(js_semantic): support jsxFactory and jsxFragmentFactory #3761

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

h-a-n-a
Copy link
Contributor

@h-a-n-a h-a-n-a commented Sep 2, 2024

Summary

closes #3682

Supported option javascript.jsxFactory and javascript.jsxFragmentFactory.

For javascript.jsxFactory, it controls the JSX factory name and for javascript.jsxFragmentFactory, it controls the JSX fragment factory name. Both of them work as long as javascript.jsxRuntime is set to ReactClassic.

JSX usages are now scope-aware. It can track in-scope usages.

// With `jsxFactory` set to `"h"`
function App() {
  const h = () => {};
  return <div></div> // ✅
}
<div></div> // ❌ Cannot find variable `h`

Changes to rule noUnusedImport

For input,

import { h, Fragment } from "preact";
<div />;
<div></div>;
<></>

with option:

{
  "linter": {
    "rules": {
      "correctness": {
        "noUnusedImports": "error"
      }
    }
  },
  "javascript": {
    "jsxRuntime": "reactClassic",
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment"
  }
}

Previously:

Both h and Fragment are marked as unused.

Currently:

Both h and Fragment are marked as used.

Changes to rule noUndeclaredVariables

For input,

function App({ children }) {
	return <div>{children}</div>;
}

<></>

with option:

{
  "javascript": {
    "jsxRuntime": "reactClassic",
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment"
  }
}

Previously:

Linter will not complain. As, by default, for "reactClassic", we want to make it compatible with both modern and legacy compilers.

Currently:

Linter reads jsxFactory and jsxFragmentFactory. It will complain both h and Fragment are not imported. If jsxFactory and jsxFragmentFactory are resolved to namespace React, linter will not complain for compatibility.

Checklist

  • Add jsx_factory and jsx_fragment_factory support for SemanticEventExtractor (scope-aware)
  • Support parsing qualified syntax of jsx_factory and jsx_fragment_factory
  • Add an ad-hoc validation that emits an error/warning in case of incorrect configuration

BREAKING CHANGES

  • UnresolvedReference::tree is removed as the binding does not guarantee it to be a JsIdentifier anymore. It could be a JSX element or a JSX fragment.
  • SemanticEventExtractor::enter added a context for passing down jsxFactory and jsxFragmentFactory. It's a little bit weird to add. I would be grateful if you could help me find a better solution.

Test Plan

Tests are added.

@github-actions github-actions bot added A-CLI Area: CLI A-Project Area: project A-Linter Area: linter L-JavaScript Language: JavaScript and super languages labels Sep 2, 2024
Copy link

codspeed-hq bot commented Sep 2, 2024

CodSpeed Performance Report

Merging #3761 will not alter performance

Comparing h-a-n-a:issue-3682 (f5f7ab0) with main (bcad101)

Summary

✅ 93 untouched benchmarks


impl Default for JsxFactory {
fn default() -> Self {
Self("React".to_string())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

JsxFactory only stores its namespace. We eagerly parse the expression to support configuration validation.
Using React instead of React.createElement or React.Fragment to make it more performant.

}
}

fn parse_jsx_factory(value: &str) -> Option<JsxFactory> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know if this is the right place to put, thinking of this is a quite general utility.

@@ -189,8 +192,16 @@ impl UnresolvedReference {
&self.data.binding_node_by_start[&reference.range.start()]
}

pub fn tree(&self) -> AnyJsIdentifierUsage {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This will be considered as a breaking change as the binding does not guarantee it to be a JsIdentifier anymore. It could be a JSX element or a JSX fragment. Also biome_js_semantic releases the crate.

@h-a-n-a h-a-n-a marked this pull request as ready for review September 4, 2024 11:16
@h-a-n-a
Copy link
Contributor Author

h-a-n-a commented Sep 4, 2024

I'm also thinking of adding a test case for configuration. It will be great if someone can tell me the best place to put it.

@h-a-n-a
Copy link
Contributor Author

h-a-n-a commented Sep 6, 2024

@ematipico Hi! This PR is ready for review. But it contains some breaking changes which seems hard to avoid. Would you please help me see if there's a better choice to do that? ❤️

@ematipico ematipico requested review from a team September 10, 2024 04:41
@Conaclos
Copy link
Member

Conaclos commented Sep 10, 2024

Hi @h-a-n-a,

If I understand correctly, you create a binding between the imported jsxFactory and jsxFragmentFactory and all visited jsxEleemnt and jsxFragment respectively.

I understand what you are trying to achieve here, but I have mixed feelings because it is not really a binding: usually a binding binds a declaration to its usages. Here the usage will be generated by a compiler ; it is not present yet.

We could perhaps create a dedicated event for this use case. Let me think about it and come back later this week when I have a clearer view.

@h-a-n-a
Copy link
Contributor Author

h-a-n-a commented Sep 11, 2024

Hi @h-a-n-a,

If I understand correctly, you create a binding between the imported jsxFactory and jsxFragmentFactory and all visited jsxEleemnt and jsxFragment respectively.

I understand what you are trying to achieve here, but I have mixed feelings because it is not really a binding: usually a binding binds a declaration to its usages. Here the usage will be generated by a compiler ; it is not present yet.

We could perhaps create a dedicated event for this use case. Let me think about it and come back later this week when I have a clearer view.

I had the same feeling too when I was implementing this feature. I had to do all kinds of breaking changes to make this work even like casting the UnresolvedReference to a non-js binding syntax.

Thank you for your reply and I'd be happy to fix this in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-CLI Area: CLI A-Linter Area: linter A-Project Area: project L-JavaScript Language: JavaScript and super languages
Projects
None yet
Development

Successfully merging this pull request may close these issues.

🐛 noUnusedImports supports custom jsxFactory
2 participants