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

fix(typescript-estree): change mjs/mts files to always be parsed as ESM #9121

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

Conversation

goldentrash
Copy link

@goldentrash goldentrash commented May 19, 2024

PR Checklist

Overview

Change mjs/mts files to always be parsed by ESM

How I solved this problem - new apporach

  • Adjust setExternalModuleIndicator to always return true if the file is mjs/mts.
  • Please see the comments below and additional comments I made in the issue for change in thinking

How I solved this problem

  • If the extension of the file is mjs/mts via ParseOptions.filePath, add an empty export at the end of the source code: (export {};)
  • This method was inspired by a long-time favorite method for resolving isolatedModules errors that originate from tsconfig settings.
  • According to MDN documentation, this method does not affect functionality or logic.
    • MDN: Note that export {} does not export an empty object — it's a no-op declaration that exports nothing (an empty name list).
  • I have confirmed that this fixes the issue mentioned in the Bug(typescript-estree): always parse mts/mjs as ESM for non-type-aware parsing #9101, see the issue for details.

Reasons not to modify convertNode and createSourceFile.

  • In /tyscript-estree/src/convert.ts/Converter/convertNode, modifying sourceType when it is case SyntaxKind.SourceFile does not make sense, I have left the details in the issue.
  • Because createSourceFile relies on an external module called typescript (ts.createSourceFile), tweaking the parameters of this module is the only way to keep it out of scope of typescript-eslint.

Why I thought it was in-appropriate to look for another API instead of ts.createSourceFile.

  • Currently, typescript-estree relies heavily on the return value of ts.createSourceFile.
  • Therefore, changing the API requires a change large enough to be categorized as a breaking-change.

If you have any feedback on function/variable naming or code block repositioning, I'll take it on board and fix the PR.

@typescript-eslint
Copy link
Contributor

Thanks for the PR, @goldentrash!

typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community.

The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately.

Thanks again!


🙏 Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently on https://opencollective.com/typescript-eslint.

Copy link

netlify bot commented May 19, 2024

Deploy Preview for typescript-eslint ready!

Name Link
🔨 Latest commit 0027099
🔍 Latest deploy log https://app.netlify.com/sites/typescript-eslint/deploys/664d73df67f64c00081180d2
😎 Deploy Preview https://deploy-preview-9121--typescript-eslint.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 99 (🟢 up 7 from production)
Accessibility: 100 (no change from production)
Best Practices: 92 (no change from production)
SEO: 98 (no change from production)
PWA: 80 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link

nx-cloud bot commented May 19, 2024

☁️ Nx Cloud Report

CI is running/has finished running commands for commit 0027099. As they complete they will appear below. Click to see the status, the terminal output, and the build insights.

📂 See all runs for this CI Pipeline Execution


✅ Successfully ran 31 targets

Sent with 💌 from NxCloud.

@goldentrash goldentrash changed the title fix(typescript-estree): change mjs/mts files to always be parsed by ESM fix(typescript-estree): change mjs/mts files to always be parsed as ESM May 20, 2024
* Ensure that `mjs`/`mts` files are always treated as ESM
*/
if (typeof code === 'string' && isESM(options)) {
code += '\n' + 'export {}';
Copy link
Member

Choose a reason for hiding this comment

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

We cannot change the code at all.
Doing so changes the resulting AST and its ranges.
This may also cause lint rules to report on code that doesn't exist.

We need to accomplish this without mutating the passed code at all.

Copy link
Author

Choose a reason for hiding this comment

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

Ah! I hadn't thought of that, you're right.

I'll think of another way to do it.

Thanks for the review :)

@goldentrash
Copy link
Author

Hi @bradzacher , I've spent the last few days thinking of other ways to achieve our goal without modifying the code.
From the typescript playground, I knew that ts can properly recognize mjs/mts files as ESM, so I tried to analyze the Typescript code a bit more.

I found the code below and it seems to be acceptable.

/** @internal */
export function getSetExternalModuleIndicator(options: CompilerOptions): (file: SourceFile) => void {
    // TODO: Should this callback be cached?
    switch (getEmitModuleDetectionKind(options)) {
        case ModuleDetectionKind.Force:
            // All non-declaration files are modules, declaration files still do the usual isFileProbablyExternalModule
            return (file: SourceFile) => {
                file.externalModuleIndicator = isFileProbablyExternalModule(file) || !file.isDeclarationFile || undefined;
            };
        case ModuleDetectionKind.Legacy:
            // Files are modules if they have imports, exports, or import.meta
            return (file: SourceFile) => {
                file.externalModuleIndicator = isFileProbablyExternalModule(file);
            };
        case ModuleDetectionKind.Auto:
            // If module is nodenext or node16, all esm format files are modules
            // If jsx is react-jsx or react-jsxdev then jsx tags force module-ness
            // otherwise, the presence of import or export statments (or import.meta) implies module-ness
            const checks: ((file: SourceFile, options: CompilerOptions) => Node | true | undefined)[] = [isFileProbablyExternalModule];
            if (options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev) {
                checks.push(isFileModuleFromUsingJSXTag);
            }
            checks.push(isFileForcedToBeModuleByFormat);
            const combined = or(...checks);
            const callback = (file: SourceFile) => void (file.externalModuleIndicator = combined(file, options));
            return callback;
    }
}

Would this be acceptable?

@bradzacher bradzacher added the bug Something isn't working label May 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bug(typescript-estree): always parse mts/mjs as ESM for non-type-aware parsing
2 participants