Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

spence-s
Copy link
Contributor

@spence-s spence-s commented May 26, 2025

  • fixes a bug when one xo instance is used to run lintText multiple times, where some ts rules would get stripped after the first lint run. We now clone the config before each run.
  • fixes matching on tsconfig files by using contains instead of isMatch from micromatch. This fixes matching on 'node_modules' which is common on excludes. I think isMatch may still be correct for files and include.
  • removes files from tsconfigs during test. This doesn't change anything for tests.
  • lint-text is more reliable for ts files when the file is written to disk, which we now do. This fixed some issues of getting different results between ubuntu and mac.
  • correctly use find-cache-directory for ts files --stdin caching

@spence-s spence-s force-pushed the main branch 2 times, most recently from 7fd294f to 80c3244 Compare May 26, 2025 21:56
@som-sm
Copy link
Contributor

som-sm commented May 29, 2025

Yesterday, I was migrating type-fest’s XO configuration from package.json to a dedicated xo.config.js file. During this process, I noticed something odd: the XO VSCode extension stopped respecting @typescript-eslint rules when the configuration was moved to xo.config.js, even though it worked perfectly when the config was inside package.json.

And, after spending an absurd amount of time debugging, I realised that xo was mutating the config (as the PR already mentions) in setXoConfig (L213 and L216) while segregating @typescript-eslint rules from other rules.

xo/lib/xo.ts

Lines 213 to 216 in 27bf5fb

config.rules = Object.fromEntries(otherRules);
// These rules should still apply to all files
config.files = [allFilesGlob];


Here's a minimal reproducible example that highlights the issue well:

// xo.config.js

const xoConfig = [
  {
    rules: {
      '@typescript-eslint/no-empty-object-type': 'off',
      '@stylistic/function-paren-newline': 'off',
    },
  },
];

export default xoConfig;
async function main() {
  const XO = (await import('xo')).default;

  const xo = new XO({ cwd: 'some/path' });
  await xo.lintText('', { filePath: 'some/path' });

  console.log(xo.xoConfig);
}

main().then(() => main()); // Call `main` twice 

The output for the first console.log has the rules correctly split:

[
  {},
  {
    rules: { '@typescript-eslint/no-empty-object-type': 'off' },
    files: ['**/*.{ts,tsx,cts,mts}'],
  },
  {
    rules: { '@stylistic/function-paren-newline': 'off' },
    files: ['**/*.{js,jsx,mjs,cjs,ts,tsx,cts,mts}'],
  },
];

However, during the split, the original config gets mutated:

  • L213 removes the @typescript-eslint rules from the original rules object.
  • And similarly, L216 adds the files property to the original object.

So, when the second instance of Xo is created, and setXoConfig is invoked, the flatOptions that it receives is the mutated one.

xo/lib/xo.ts

Lines 163 to 165 in 27bf5fb

const {flatOptions, flatConfigPath} = await resolveXoConfig({
...this.linterOptions,
});

Hence, the second console.log prints the following:

[
  {},
  {
    rules: { '@stylistic/function-paren-newline': 'off' },
    files: ['**/*.{js,jsx,mjs,cjs,ts,tsx,cts,mts}'],
  },
];

So, here's a fix I came up with:

- // Set the other rules to the original config
- config.rules = Object.fromEntries(otherRules);

- // These rules should still apply to all files
- config.files = [allFilesGlob];

- return [tsConfig, config];

+ const otherConfig: XoConfigItem = {
+     ...config,
+     // Set the other rules to the original config
+     rules: Object.fromEntries(otherRules),
+ };

+ // These rules should still apply to all files
+ otherConfig.files = [allFilesGlob];

+ return [tsConfig, otherConfig];

It simply avoids mutating the original config, and this also seems to pass all the newly added tests.


Wish I read seen this PR before delving into this 😅, but anyways, thought I'd share my debugging here and it might add some value.

@spence-s
Copy link
Contributor Author

@som-sm Thanks much for digging in!

Funny enough this wasn't happening in the pre-release, something changed when the deps were updated for this v1 release but its not clear to me what would have caused it.

@sindresorhus sindresorhus merged commit e63d7a5 into xojs:main May 29, 2025
3 checks passed
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.

3 participants