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

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import formatterPretty from 'eslint-formatter-pretty';
import getStdin from 'get-stdin';
import meow from 'meow';
import {pathExists} from 'path-exists';
import {tsExtensions} from './lib/constants.js';
import findCacheDirectory from 'find-cache-directory';
import {cacheDirName, tsExtensions} from './lib/constants.js';
import type {LinterOptions, XoConfigOptions} from './lib/types.js';
import {Xo} from './lib/xo.js';
import openReport from './lib/open-report.js';
Expand Down Expand Up @@ -181,7 +182,8 @@ if (cliOptions.stdin) {
if (cliOptions.stdinFilename && tsExtensions.includes(path.extname(cliOptions.stdinFilename).slice(1))) {
const absoluteFilePath = path.resolve(cliOptions.cwd, cliOptions.stdinFilename);
if (!await pathExists(absoluteFilePath)) {
cliOptions.stdinFilename = path.join(cliOptions.cwd, 'node_modules', '.cache', 'xo-linter', path.basename(absoluteFilePath));
const cacheDir = findCacheDirectory({name: cacheDirName, cwd: linterOptions.cwd}) ?? path.join(cliOptions.cwd, 'node_modules', '.cache', cacheDirName);
cliOptions.stdinFilename = path.join(cacheDir, path.basename(absoluteFilePath));
shouldRemoveStdInFile = true;
baseXoConfigOptions.ignores = [
'!**/node_modules/**',
Expand Down Expand Up @@ -216,7 +218,7 @@ if (cliOptions.stdin) {
}

const xo = new Xo(linterOptions, baseXoConfigOptions);
await log(await xo.lintText(stdin, {filePath: cliOptions.stdinFilename, warnIgnored: true}));
await log(await xo.lintText(stdin, {filePath: cliOptions.stdinFilename, warnIgnored: false}));
if (shouldRemoveStdInFile) {
await fs.rm(cliOptions.stdinFilename);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/handle-ts-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export async function handleTsconfig({cwd, files}: {cwd: string; files: string[]
// If we match on excluded, then we definitively know that there is no tsconfig match.
if (Array.isArray(tsConfig.exclude)) {
const exclude = Array.isArray(tsConfig.exclude) ? tsConfig.exclude : [];
hasMatch = !micromatch.isMatch(filePath, exclude, micromatchOptions);
hasMatch = !micromatch.contains(filePath, exclude, micromatchOptions);
} else {
// Not explicitly excluded and included by tsconfig defaults
hasMatch = true;
Expand All @@ -52,7 +52,7 @@ export async function handleTsconfig({cwd, files}: {cwd: string; files: string[]
const exclude = Array.isArray(tsConfig.exclude) ? tsConfig.exclude : [];
// If we also have an exlcude we need to check all the arrays, (files, include, exclude)
// this check not excluded and included in one of the file/include array
hasMatch = !micromatch.isMatch(filePath, exclude, micromatchOptions) && micromatch.isMatch(filePath, [...include, ...files], micromatchOptions);
hasMatch = !micromatch.contains(filePath, exclude, micromatchOptions) && micromatch.isMatch(filePath, [...include, ...files], micromatchOptions);
}

if (!hasMatch) {
Expand Down
3 changes: 2 additions & 1 deletion lib/xo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ export class Xo {

this.xoConfig = [
this.baseXoConfig,
...flatOptions,
// Ensure resolved options do not mutate between runs
...structuredClone(flatOptions),
];

// Split off the TS rules in a special case, so that you won't get errors
Expand Down
1 change: 0 additions & 1 deletion scripts/setup-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ await fs.writeFile(
strictNullChecks: true,
lib: ['DOM', 'DOM.Iterable', 'ES2022'],
},
files: [path.join(cwd, 'test.ts')],
exclude: ['node_modules'],
}),
);
Expand Down
15 changes: 0 additions & 15 deletions test/helpers/copy-test-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,6 @@ export const copyTestProject = async () => {

await fs.cp(testCwd, newCwd, {recursive: true});

// Create a tsconfig.json file
await fs.writeFile(
path.join(newCwd, 'tsconfig.json'),
JSON.stringify({
compilerOptions: {
module: 'node16',
target: 'ES2022',
strictNullChecks: true,
lib: ['DOM', 'DOM.Iterable', 'ES2022'],
},
files: [path.join(newCwd, 'test.ts')],
exclude: ['node_modules'],
}),
);

return newCwd;
};

185 changes: 112 additions & 73 deletions test/xo/lint-text.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ test('no config > js > semi', async t => {
});

test('no config > ts > semi', async t => {
const filePath = path.join(t.context.cwd, 'test.ts');
const {results} = await new Xo({cwd: t.context.cwd}).lintText(
dedent`console.log('hello')\n`,
{filePath},
);
const {cwd} = t.context;
const filePath = path.join(cwd, 'test.ts');
const text = dedent`console.log('hello')\n`;
await fs.writeFile(filePath, text, 'utf8');
const {results} = await new Xo({cwd}).lintText(text, {filePath});

t.is(results?.[0]?.messages?.length, 1);
t.is(results?.[0]?.messages?.[0]?.ruleId, '@stylistic/semi');
Expand Down Expand Up @@ -59,9 +59,10 @@ test('flat config > js > semi', async t => {
});

test('flat config > ts > semi', async t => {
const filePath = path.join(t.context.cwd, 'test.ts');
const {cwd} = t.context;
const filePath = path.join(cwd, 'test.ts');
await fs.writeFile(
path.join(t.context.cwd, 'xo.config.js'),
path.join(cwd, 'xo.config.js'),
dedent`
export default [
{
Expand All @@ -71,10 +72,10 @@ test('flat config > ts > semi', async t => {
`,
'utf8',
);
const xo = new Xo({cwd: t.context.cwd});
const {results} = await xo.lintText(dedent`console.log('hello');\n`, {
filePath,
});
const text = dedent`console.log('hello');\n`;
await fs.writeFile(filePath, text, 'utf8');
const xo = new Xo({cwd});
const {results} = await xo.lintText(text, {filePath});
t.is(results?.[0]?.messages?.length, 1);
t.is(results?.[0]?.messages?.[0]?.ruleId, '@stylistic/semi');
});
Expand Down Expand Up @@ -139,10 +140,11 @@ test('flat config > js > space', async t => {
});

test('flat config > ts > space', async t => {
const filePath = path.join(t.context.cwd, 'test.ts');
const {cwd} = t.context;
const filePath = path.join(cwd, 'test.ts');

await fs.writeFile(
path.join(t.context.cwd, 'xo.config.js'),
path.join(cwd, 'xo.config.js'),
dedent`
export default [
{
Expand All @@ -153,17 +155,15 @@ test('flat config > ts > space', async t => {
'utf8',
);

const xo = new Xo({cwd: t.context.cwd});
const {results} = await xo.lintText(
dedent`
export function foo() {
console.log('hello');
}\n
`,
{
filePath,
},
);
const text = dedent`
export function foo() {
console.log('hello');
}\n
`;
await fs.writeFile(filePath, text, 'utf8');

const xo = new Xo({cwd});
const {results} = await xo.lintText(text, {filePath});
t.is(results?.[0]?.messages.length, 1);
t.is(results?.[0]?.messages?.[0]?.messageId, 'wrongIndentation');
t.is(results?.[0]?.messages?.[0]?.ruleId, '@stylistic/indent');
Expand All @@ -188,18 +188,17 @@ test('plugin > js > no-use-extend-native', async t => {
);
});

test('pliugin > ts > no-use-extend-native', async t => {
test('plugin > ts > no-use-extend-native', async t => {
const {cwd} = t.context;
const tsFilePath = path.join(t.context.cwd, 'test.ts');
const {results} = await new Xo({cwd}).lintText(
dedent`
import {util} from 'node:util';
const filePath = path.join(cwd, 'test.ts');
const text = dedent`
import {util} from 'node:util';

// eslint-disable-next-line @typescript-eslint/no-unsafe-call
util.isBoolean('50bda47b09923e045759db8e8dd01a0bacd97370'.shortHash() === '50bdcs47');\n
`,
{filePath: tsFilePath},
);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
util.isBoolean('50bda47b09923e045759db8e8dd01a0bacd97370'.shortHash() === '50bdcs47');\n
`;
await fs.writeFile(filePath, text, 'utf8');
const {results} = await new Xo({cwd}).lintText(text, {filePath});
t.true(results[0]?.messages?.length === 1);
t.truthy(results[0]?.messages?.[0]);
t.is(
Expand Down Expand Up @@ -229,15 +228,14 @@ test('plugin > js > eslint-plugin-import import-x/order', async t => {
test('plugin > ts > eslint-plugin-import import-x/order', async t => {
const {cwd} = t.context;
const filePath = path.join(cwd, 'test.ts');
const {results} = await new Xo({cwd}).lintText(
dedent`
import foo from 'foo';
import util from 'node:util';
const text = dedent`
import foo from 'foo';
import util from 'node:util';

util.inspect(foo);\n
`,
{filePath},
);
util.inspect(foo);\n
`;
await fs.writeFile(filePath, text, 'utf8');
const {results} = await new Xo({cwd}).lintText(text, {filePath});
t.true(results[0]?.messages?.length === 1);
t.truthy(results[0]?.messages?.[0]);
t.is(results[0]?.messages?.[0]?.ruleId, 'import-x/order');
Expand All @@ -262,14 +260,13 @@ test('plugin > js > eslint-plugin-import import-x/extensions', async t => {
test('plugin > ts > eslint-plugin-import import-x/extensions', async t => {
const {cwd} = t.context;
const filePath = path.join(cwd, 'test.ts');
const {results} = await new Xo({cwd}).lintText(
dedent`
import foo from './foo';
const text = dedent`
import foo from './foo';

console.log(foo);\n
`,
{filePath},
);
console.log(foo);\n
`;
await fs.writeFile(filePath, text, 'utf8');
const {results} = await new Xo({cwd}).lintText(text, {filePath});
t.true(results[0]?.messages?.length === 1);
t.truthy(results[0]?.messages?.[0]);
t.is(results[0]?.messages?.[0]?.ruleId, 'import-x/extensions');
Expand All @@ -278,14 +275,13 @@ test('plugin > ts > eslint-plugin-import import-x/extensions', async t => {
test('plugin > ts > eslint-plugin-import import-x/no-absolute-path', async t => {
const {cwd} = t.context;
const filePath = path.join(cwd, 'test.ts');
const {results} = await new Xo({cwd}).lintText(
dedent`
import foo from '/foo';
const text = dedent`
import foo from '/foo';

console.log(foo);\n
`,
{filePath},
);
console.log(foo);\n
`;
await fs.writeFile(filePath, text, 'utf8');
const {results} = await new Xo({cwd}).lintText(text, {filePath});
t.true(results[0]?.messages?.some(({ruleId}) => ruleId === 'import-x/no-absolute-path'));
});

Expand Down Expand Up @@ -318,13 +314,12 @@ test('plugin > js > eslint-plugin-n n/prefer-global/process', async t => {

test('plugin > ts > eslint-plugin-n n/prefer-global/process', async t => {
const {cwd} = t.context;
const tsFilePath = path.join(cwd, 'test.ts');
const {results} = await new Xo({cwd}).lintText(
dedent`
process.cwd();\n
`,
{filePath: tsFilePath},
);
const filePath = path.join(cwd, 'test.ts');
const text = dedent`
process.cwd();\n
`;
await fs.writeFile(filePath, text, 'utf8');
const {results} = await new Xo({cwd}).lintText(text, {filePath});
t.true(results[0]?.messages?.length === 1);
t.truthy(results[0]?.messages?.[0]);
t.is(results[0]?.messages?.[0]?.ruleId, 'n/prefer-global/process');
Expand All @@ -351,17 +346,61 @@ test('plugin > js > eslint-plugin-eslint-comments enable-duplicate-disable', asy
test('plugin > ts > eslint-plugin-eslint-comments no-duplicate-disable', async t => {
const {cwd} = t.context;
const tsFilePath = path.join(cwd, 'test.ts');
const {results} = await new Xo({
cwd,
}).lintText(
dedent`
/* eslint-disable no-undef */
export const foo = 10; // eslint-disable-line no-undef
\n
`,
{filePath: tsFilePath},
);
const text = dedent`
/* eslint-disable no-undef */
export const foo = 10; // eslint-disable-line no-undef
\n
`;
await fs.writeFile(tsFilePath, text, 'utf8');
const {results} = await new Xo({cwd}).lintText(text, {filePath: tsFilePath});
t.true(results[0]?.errorCount === 1);
t.true(results[0]?.messages.some(({ruleId}) =>
ruleId === '@eslint-community/eslint-comments/no-duplicate-disable'));
});

test('lint-text can be ran multiple times in a row with top level typescript rules', async t => {
const {cwd} = t.context;

const filePath = path.join(cwd, 'test.ts');
// Text should violate the @typescript-eslint/naming-convention rule
const text = dedent`
const fooBar = 10;
const FooBar = 10;
const FOO_BAR = 10;
const foo_bar = 10;\n
`;

// We must write tsfiles to disk for the typescript rules to apply
await fs.writeFile(filePath, text, 'utf8');

const {results: resultsNoConfig} = await Xo.lintText(text, {cwd, filePath});
// Ensure that with no config, the text is linted and errors are found
t.true(resultsNoConfig[0]?.errorCount === 3);

await fs.writeFile(
path.join(cwd, 'xo.config.ts'),
dedent`
export default [
{
rules: {
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/no-unused-vars': 'off'
},
}
];\n
`,
'utf8',
);

// Now with a config that turns off the naming-convention rule, the text should not have any errors
// and should not have any messages when ran multiple times
const {results} = await Xo.lintText(text, {cwd, filePath});
t.is(results[0]?.errorCount, 0);
t.true(results[0]?.messages?.length === 0);
const {results: results2} = await Xo.lintText(text, {cwd, filePath});
t.is(results2[0]?.errorCount, 0);
t.true(results2[0]?.messages?.length === 0);
const {results: results3} = await Xo.lintText(text, {cwd, filePath});
t.is(results3[0]?.errorCount, 0);
t.true(results3[0]?.messages?.length === 0);
});