Skip to content

feat(biome_js_analyze): useImportExtensions - add importMapping options - #3274

Merged
ematipico merged 18 commits into
biomejs:mainfrom
drdaemos:feat/import_mappings_option
Jul 1, 2024
Merged

ematipico merged 18 commits into
biomejs:mainfrom
drdaemos:feat/import_mappings_option

Conversation

@drdaemos

Copy link
Copy Markdown
Contributor

Summary

This is a solution for #2967.

In TypeScript-based projects that use a ES Module import system, useImportExtensions might suggest an incorrect import extension, depending on the project configuration and runtime used. TypeScript requires you to manually specify .js or .mjs extensions (see microsoft/TypeScript#49083 (comment)) even in .ts or .mts files.

This change allows to specify importMappings option for useImportExtensions to override the default Biome suggestions.

Test Plan

I have added a separate spec invalidWithImportMappings.ts to verify that the custom import mappings take precedence over the default logic and apply correct mapping to both JS/TS and *SX components.

For the following linter configuration:

{
    "rules": {
        "nursery": {
			"useImportExtensions": {
				"level": "error",
				"options": {
            	    "importMappings": [
                	    {
                      		"fileExt": ["ts"],
                       		"importExt": "js",
                       		"componentImportExt": "jsx"
                       	}
                    ]
                }
			}
		}
	}
}

All .ts files will attempt to either import .js or .jsx files, depending on the filename.

@github-actions github-actions Bot added A-Linter Area: linter L-JavaScript Language: JavaScript and super languages labels Jun 24, 2024
@github-actions github-actions Bot added the A-Changelog Area: changelog label Jun 24, 2024
@drdaemos
drdaemos marked this pull request as ready for review June 24, 2024 10:08
@drdaemos drdaemos changed the title feat(biome_js_analyze): useImportExtensions - import mapping options feat(biome_js_analyze): useImportExtensions - add importMapping options Jun 24, 2024
"required": ["componentImportExt", "fileExt", "importExt"],
"properties": {
"componentImportExt": {
"description": "Extension that should be used for component file imports",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

what is the difference between component file and import in this context?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Rule defines component files as ones starting with an upper-case letter - I guess it is common for React / Vue components - hence the ability to use a different jsx / tsx extension.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This was a stop gap solution until Biome has infra to check filesystem. Bit unfortunate we need to expose that option, but not sure more general solution is possible.

Though maybe if user picks .js we could auto pick .jsx for those component imports, and likewise .ts -> .tsx? Or would that be too much magic?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a more or less advanced configuration - personally I would prefer to just have an explicit configuration.

@codspeed

codspeed Bot commented Jun 24, 2024 •

Copy link
Copy Markdown
Contributor

CodSpeed Performance Report

Merging #3274 will not alter performance

Comparing drdaemos:feat/import_mappings_option (29673a4) with drdaemos:feat/import_mappings_option (5603764)

Summary

✅ 90 untouched benchmarks

///
/// ### Options
///
/// Use the options to specify correct import extensions for your project based on the linted

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we mention difference between browser/deno bun bundler/pure tsc somewhere and when you would use such option?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oh, that's a rabbit hole which I don't feel comfortable to describe fully and conclusively. I would be glad to hear suggestions on what we can mention here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree with @drdaemos here. There's a whole world here, and I don't think the rule should discuss it. However, if there are some useful resources, we could add them in the future.

Comment thread crates/biome_js_analyze/src/lint/nursery/use_import_extensions.rs Outdated
Comment thread crates/biome_js_analyze/src/lint/nursery/use_import_extensions.rs Outdated
/// List of inspected file extensions that are subject to this mapping
pub file_ext: Vec<String>,
/// Extension that should be used for module imports
pub import_ext: String,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These extensions are only used for suggestions. I can't really see them used for anything else in a future in this rule (might be wrong).

So should it be named suggested_import_extension?

@kibertoad kibertoad Jun 24, 2024 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What do you mean? Isn't the whole idea behind this PR to fix the auto-fix functionality, which currently uses the extension that is wrong for Node.js environment?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This rule only checks for imports without extensions. Biome does not have infra yet, to check for filesystem. This PR won't fix wrong extensions. It will only suggested user selected extension when there isn't one.

I can see checking for wrong extensions added in a future, so my naming change can be ignored, though I imagine that won't be easy to get right in all cases.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

OK, so to clarify: biome check --fix --unsafe currently does add missing extension when there isn't one at all. That is expected, and after this change the user-specified extension will be applied. Is that correct?

What you are saying is that if e. g. original file is "file.ts", and the import in code is "file.rtf", then Biome will say that it's perfectly fine, right?

@minht11 minht11 Jun 24, 2024 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes to both. import { named } from './file.rtf' is valid import as far as this rule sees it.

"required": ["componentImportExt", "fileExt", "importExt"],
"properties": {
"componentImportExt": {
"description": "Extension that should be used for component file imports",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This was a stop gap solution until Biome has infra to check filesystem. Bit unfortunate we need to expose that option, but not sure more general solution is possible.

Though maybe if user picks .js we could auto pick .jsx for those component imports, and likewise .ts -> .tsx? Or would that be too much magic?

Co-authored-by: Justinas Delinda <8914032+minht11@users.noreply.github.com>
@Conaclos

Copy link
Copy Markdown
Member

We could use a hash map for the mapping:

{
  "rules": {
    "nursery": {
      "useImportExtensions": {
        "level": "error",
        "options": {
          "suggestedExtensions": {
            "ts": {
              "import": "js",
              "component": "jsx"
            }
          }
        }
      }
    }
  }
}

@kibertoad

Copy link
Copy Markdown

@Conaclos So should we keep the explicit mapping? Based on your comment on the issue, I thought you prefer a simple binary toggle.

@Conaclos

Copy link
Copy Markdown
Member

So should we keep the explicit mapping? Based on your comment on the issue, I thought you prefer a simple binary toggle.

I am waiting other feedback before deciding.
If we keep the map we could switch to what I propose here.

@drdaemos

Copy link
Copy Markdown
Contributor Author

I have updated the options format based on @Conaclos' suggestion. I guess having an explicit mapping option is the best bet until Biome can detect actual imports used in runtime or replicates the whole logic of how ES modules should be imported depending on the project configuration and used bundlers.

Comment thread crates/biome_js_analyze/src/lint/nursery/use_import_extensions.rs Outdated
Comment thread crates/biome_js_analyze/src/lint/nursery/use_import_extensions.rs Outdated
Comment on lines +84 to +87
/// "ts": {
/// "import": "js",
/// "component": "jsx"
/// }

@ematipico ematipico Jun 25, 2024 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I believe we should be more descriptive here. We should explain the example, and we should explain what import is and how it affects the code action, and we should explain what component is and how component affects the code action.

Usually, we follow this format:

  1. Talk about the options
  2. Explain the example e.g. "If you want to do this ... then you should define this and that", "If you have this other situation ... then you should use define this option in this way"
  3. Code block of the example

To give you better feedback. I am reading this for the first time, and I still don't understand how these options work. For example "ts" as a key isn't explained. Is it an input? An output?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we could add some rationale. We could inform users that it is a temporary workaround. Ideally we should query the extension of the imported file and choose an extension based on that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have updated the description and tried to provide a deeper explanation for the reasoning and way to use that in a project. Do you mind taking another look?

Comment thread crates/biome_js_analyze/src/lint/nursery/use_import_extensions.rs Outdated
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct SuggestedExtensionMapping {
/// Extension that should be used for module imports
pub import: String,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would call this module, because that's exactly what it is.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Indeed, module seems better.

Comment thread crates/biome_js_analyze/src/lint/nursery/use_import_extensions.rs
#[derive(Clone, Debug, Default, Deserializable, Deserialize, Serialize, Eq, PartialEq)]
#[cfg_attr(feature = "schemars", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct UseImportExtensionsOptions {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What do you think about a configuration like this:

{
	"suggestedExtensions": [
		{
			"inputExtensions": ["ts", "tsx"],
			"import": "js",
			"component": "jsx"
		}
	]
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's what I had intially before this comment: #3274 (comment).

I'm not really sure which option is better, both seem sufficient to case for handling backend code + tsc

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I have no big preference. However, using a hash map is easier to query in the code. I think we should not aim for perfection here, because the mapping should be a temporary workaround. We should not spend too much time on that.

drdaemos and others added 3 commits June 25, 2024 16:16
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
@kibertoad

Copy link
Copy Markdown

@ematipico @Conaclos Is anything missing on the PR?

@ematipico

Copy link
Copy Markdown
Member

@ematipico @Conaclos Is anything missing on the PR?

Some of my comments haven't been addressed. Mainly documentation.

drdaemos added 2 commits June 28, 2024 17:28
# Conflicts:
#	CHANGELOG.md
#	crates/biome_js_analyze/src/lint/nursery/use_import_extensions.rs
@kibertoad

Copy link
Copy Markdown

@ematipico Documentation was updated now, I believe

@ematipico ematipico left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good, just one nit around docs and then we can merge it

Comment on lines +79 to +81
/// Mainly, this is a temporary workaround that allows Biome to propose correct import extensions
/// for TypeScript projects that use ES Modules. TypeScript requires you to specify imports to
/// the actual files used in runtime: `.js` or `.mjs` (see more here: https://github.com/microsoft/TypeScript/issues/49083#issuecomment-1435399267).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would move this paragraph after the code block of the options. Also, add a :::caution block, so the docs will render this with a warning block:

/// :::caution
/// Mainly, this is a temporary ...
/// :::

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reordered and added the warning block around.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Changelog Area: changelog A-Linter Area: linter L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants