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
7 changes: 7 additions & 0 deletions .changeset/icy-rocks-camp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'eslint-plugin-svelte': patch
---

fix: properly support Windows in `no-unused-props` rule
fix: properly support Windows in `valid-style-parse` rule
fix: properly support Windows in `no-unnecessary-condition` rule
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
12 changes: 9 additions & 3 deletions .github/workflows/NodeCI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ on:
env:
project_root_path: ./packages/eslint-plugin-svelte

defaults:
run:
# Setting every runner to bash simplifies command calls;
# plus, every platform supports it
shell: bash

jobs:
lint:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -51,7 +57,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
os: [ubuntu-latest, windows-latest]
eslint: [8, 9]
node: [18.x, 20.x, 22.x, latest]
steps:
Expand Down Expand Up @@ -84,7 +90,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
os: [ubuntu-latest, windows-latest]
eslint: [9]
node: [18, 20, 22]
steps:
Expand Down Expand Up @@ -115,7 +121,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
os: [ubuntu-latest, windows-latest]
node: [18]
steps:
- name: Checkout
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@
"typescript.tsdk": "node_modules/typescript/lib",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
},
"files.eol": "\n"
}
4 changes: 3 additions & 1 deletion packages/eslint-plugin-svelte/src/rules/no-unused-props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import type ts from 'typescript';
import { findVariable } from '../utils/ast-utils.js';
import { toRegExp } from '../utils/regexp.js';
import { normalize } from 'path';

type PropertyPathArray = string[];
type DeclaredPropertyNames = Set<{ originalName: string; aliasName: string }>;
Expand Down Expand Up @@ -75,11 +76,11 @@

const options = context.options[0] ?? {};

// TODO: Remove in v4

Check warning on line 79 in packages/eslint-plugin-svelte/src/rules/no-unused-props.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected 'todo' comment: 'TODO: Remove in v4'
// MEMO: `ignorePatterns` was a property that only existed from v3.2.0 to v3.2.2.
// From v3.3.0, it was replaced with `ignorePropertyPatterns` and `ignoreTypePatterns`.
if (options.ignorePatterns != null && !isRemovedWarningShown) {
console.warn(

Check warning on line 83 in packages/eslint-plugin-svelte/src/rules/no-unused-props.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
'eslint-plugin-svelte: The `ignorePatterns` option in the `no-unused-props` rule has been removed. Please use `ignorePropertyPatterns` or/and `ignoreTypePatterns` instead.'
);
isRemovedWarningShown = true;
Expand Down Expand Up @@ -122,7 +123,8 @@
const declarations = symbol.getDeclarations();
if (!declarations || declarations.length === 0) return false;

return declarations.every((decl) => decl.getSourceFile().fileName === fileName);
// TypeScript declaration file name is normalized to support Windows style paths
return declarations.every((decl) => normalize(decl.getSourceFile().fileName) === fileName);
}

/**
Expand Down
10 changes: 8 additions & 2 deletions packages/eslint-plugin-svelte/src/rules/valid-style-parse.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createRule } from '../utils/index.js';
import path from 'path';

export default createRule('valid-style-parse', {
meta: {
Expand All @@ -16,15 +17,20 @@ export default createRule('valid-style-parse', {
if (!sourceCode.parserServices.isSvelte) {
return {};
}
const cwd = `${context.cwd ?? process.cwd()}/`;
const cwd = `${context.cwd ?? process.cwd()}${path.sep}`;

return {
SvelteStyleElement(node) {
const styleContext = sourceCode.parserServices.getStyleContext!();
if (styleContext.status === 'parse-error') {
// This will replace backslashes to forward slashes only
// if Node.js reports Windows style path separators
let message = styleContext.error.message.replace(cwd, '');
if (path.sep === '\\') message = message.replace(/\\/g, '/');

context.report({
loc: node.loc,
message: `Error parsing style element. Error message: "${styleContext.error.message.replace(cwd, '')}"`
message: `Error parsing style element. Error message: "${message}"`
});
}
if (styleContext.status === 'unknown-lang') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
// https://github.com/typescript-eslint/typescript-eslint/blob/78467fc1bde9bd2db1e08b3d19f151f4adaff8a9/packages/eslint-plugin/tests/rules/no-unnecessary-condition.test.ts
/* eslint func-style: off, eslint-plugin/consistent-output: off -- respect original */
import * as path from 'path';
import { fileURLToPath } from 'url';
import { RuleTester } from '../../../../utils/eslint-compat.js';
import type * as eslint from 'eslint';
import * as typescriptParser from '@typescript-eslint/parser';

import rule from '../../../../../src/rules/@typescript-eslint/no-unnecessary-condition.js';

const __dirname = path.dirname(new URL(import.meta.url).pathname);
const url = new URL(import.meta.url);
const __dirname = path.dirname(fileURLToPath(url));

function getFixturesRootDir(): string {
return path.join(__dirname, 'fixtures');
Expand Down
6 changes: 4 additions & 2 deletions packages/eslint-plugin-svelte/tests/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import * as svelteParser from 'svelte-eslint-parser';
import * as typescriptParser from '@typescript-eslint/parser';
import Module from 'module';
import globals from 'globals';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(new URL(import.meta.url).pathname);
const __dirname = path.dirname(fileURLToPath(new URL(import.meta.url)));
const require = Module.createRequire(import.meta.url);

/**
Expand Down Expand Up @@ -304,7 +305,8 @@ function writeFixtures(
}

function getConfig(ruleName: string, inputFile: string) {
const filename = inputFile.slice(inputFile.indexOf(ruleName));
// ruleName is normalized to support Windows style paths
const filename = inputFile.slice(inputFile.indexOf(path.normalize(ruleName)));
const code = fs.readFileSync(inputFile, 'utf8');
let config;
let configFile = [
Expand Down
6 changes: 3 additions & 3 deletions packages/eslint-plugin-svelte/tools/lib/changesets-util.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import getReleasePlan from '@changesets/get-release-plan';
import path from 'path';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(new URL(import.meta.url).pathname);
const cwdURL = new URL('../../../..', import.meta.url);

/** Get new version string from changesets */
export async function getNewVersion(): Promise<string> {
const releasePlan = await getReleasePlan(path.resolve(__dirname, '../../../..'));
const releasePlan = await getReleasePlan(fileURLToPath(cwdURL));

return releasePlan.releases.find(({ name }) => name === 'eslint-plugin-svelte')!.newVersion;
}
14 changes: 6 additions & 8 deletions packages/eslint-plugin-svelte/tools/lib/load-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ import path from 'path';
import fs from 'fs';
import type { RuleModule } from '../../src/types.js';

const __dirname = path.dirname(new URL(import.meta.url).pathname);
const rulesLibRootURL = new URL('../../src/rules/', import.meta.url);

/**
* Get the all rules
* @returns {Array} The all rules
*/
async function readRules() {
const rulesLibRoot = path.resolve(__dirname, '../../src/rules');
const rules: RuleModule[] = [];
for (const name of iterateTsFiles()) {
const module = await import(path.join(rulesLibRoot, name));
const module = await import(new URL(name, rulesLibRootURL).href);
const rule: RuleModule = module && module.default;
if (!rule || typeof rule.create !== 'function') {
continue;
Expand All @@ -27,19 +26,18 @@ export const rules = await readRules();

/** Iterate ts files */
function* iterateTsFiles() {
const rulesLibRoot = path.resolve(__dirname, '../../src/rules');
const files = fs.readdirSync(rulesLibRoot);
const files = fs.readdirSync(rulesLibRootURL);

while (files.length) {
const file = files.shift()!;
if (file.endsWith('.ts')) {
yield file;
continue;
}
const filePath = path.join(rulesLibRoot, file);
if (!fs.statSync(filePath).isDirectory()) {
const filePathURL = new URL(file, rulesLibRootURL);
if (!fs.statSync(filePathURL).isDirectory()) {
continue;
}
files.unshift(...fs.readdirSync(filePath).map((n) => path.join(file, n)));
files.unshift(...fs.readdirSync(filePathURL).map((n) => path.join(file, n)));
}
}
17 changes: 10 additions & 7 deletions packages/eslint-plugin-svelte/tools/lib/write.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
import fs from 'fs';
import { ESLint } from 'eslint';
import prettier from 'prettier';
import { fileURLToPath } from 'url';

/**
* Write file and format it with ESLint
*/
export function writeAndFormat(fileName: string, content: string): Promise<void> {
fs.writeFileSync(fileName, content, 'utf8');
export async function writeAndFormat(fileURL: URL, content: string): Promise<void> {
fs.writeFileSync(fileURL, content, 'utf8');

const filePath = fileURLToPath(fileURL);
return prettier
.resolveConfig(fileName)
.resolveConfig(fileURL)
.then((prettierrc) => {
if (!prettierrc) {
return content;
}
return prettier.format(content, { filepath: fileName, ...prettierrc });
return prettier.format(content, { filepath: filePath, ...prettierrc });
})
.then((formatted) => {
fs.writeFileSync(fileName, formatted, 'utf8');
fs.writeFileSync(fileURL, formatted, 'utf8');
const eslint = new ESLint({ fix: true });
return eslint.lintText(formatted, { filePath: fileName });
return eslint.lintText(formatted, { filePath });
})
.then(([result]) => {
if (result.output) {
fs.writeFileSync(fileName, result.output, 'utf8');
fs.writeFileSync(fileURL, result.output, 'utf8');
}
});
}
52 changes: 26 additions & 26 deletions packages/eslint-plugin-svelte/tools/new-rule.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import path from 'node:path';
import fs from 'node:fs';
import cp from 'node:child_process';
import url from 'node:url';
import { writeAndFormat } from './lib/write.js';

const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
import { fileURLToPath } from 'node:url';

const logger = console;

Expand All @@ -21,39 +18,39 @@ void (async ([ruleId, ...args]) => {
process.exitCode = 1;
return;
}
const utilsPath = path.resolve(__dirname, `../src/utils/index.ts`);
const testUtilsPath = path.resolve(__dirname, `../tests/utils/utils.ts`);
const utilsURL = new URL(`../src/utils/index.ts`, import.meta.url);
const testUtilsURL = new URL(`../tests/utils/utils.ts`, import.meta.url);

const ruleFile = path.resolve(__dirname, `../src/rules/${ruleId}.ts`);
const testFile = path.resolve(__dirname, `../tests/src/rules/${ruleId}.ts`);
const docFile = path.resolve(__dirname, `../../../docs/rules/${ruleId}.md`);
const fixturesRoot = path.resolve(__dirname, `../tests/fixtures/rules/${ruleId}/`);
const ruleFileURL = new URL(`../src/rules/${ruleId}.ts`, import.meta.url);
const testFileURL = new URL(`../tests/src/rules/${ruleId}.ts`, import.meta.url);
const docFileURL = new URL(`../../../docs/rules/${ruleId}.md`, import.meta.url);
const fixturesRootURL = new URL(`../tests/fixtures/rules/${ruleId}/`, import.meta.url);
try {
fs.mkdirSync(path.dirname(ruleFile), { recursive: true });
fs.mkdirSync(new URL('./', ruleFileURL), { recursive: true });
} catch {
// ignore
}
try {
fs.mkdirSync(path.dirname(testFile), { recursive: true });
fs.mkdirSync(new URL('./', testFileURL), { recursive: true });
} catch {
// ignore
}
try {
fs.mkdirSync(path.dirname(docFile), { recursive: true });
fs.mkdirSync(new URL('./', docFileURL), { recursive: true });
} catch {
// ignore
}
try {
fs.mkdirSync(path.resolve(fixturesRoot, 'valid'), { recursive: true });
fs.mkdirSync(path.resolve(fixturesRoot, 'invalid'), { recursive: true });
fs.mkdirSync(new URL('./valid', fixturesRootURL), { recursive: true });
fs.mkdirSync(new URL('./invalid', fixturesRootURL), { recursive: true });
} catch {
// ignore
}

await writeAndFormat(
ruleFile,
ruleFileURL,
`import { AST } from 'svelte-eslint-parser';
import { createRule } from '${getModulePath(ruleFile, utilsPath)}';
import { createRule } from '${getModulePath(ruleFileURL, utilsURL)}';

export default createRule('${ruleId}', {
meta: {
Expand All @@ -74,10 +71,10 @@ export default createRule('${ruleId}', {
`
);
await writeAndFormat(
testFile,
testFileURL,
`import { RuleTester } from '../../utils/eslint-compat.js';
import rule from '${getModulePath(testFile, ruleFile)}';
import { loadTestCases } from '${getModulePath(testFile, testUtilsPath)}';
import rule from '${getModulePath(testFileURL, ruleFileURL)}';
import { loadTestCases } from '${getModulePath(testFileURL, testUtilsURL)}';

const tester = new RuleTester({
languageOptions: {
Expand All @@ -90,7 +87,7 @@ tester.run('${ruleId}', rule as any, loadTestCases('${ruleId}'));
`
);
await writeAndFormat(
docFile,
docFileURL,
`# (svelte/${ruleId})

> description
Expand Down Expand Up @@ -139,17 +136,20 @@ This rule reports ???.
try {
// Use code -v to know if vscode is installed and do not print anything to the console
cp.execSync('code -v', { stdio: 'ignore' });
cp.execSync(`code "${ruleFile}"`);
cp.execSync(`code "${testFile}"`);
cp.execSync(`code "${docFile}"`);
cp.execSync(`code "${fileURLToPath(ruleFileURL)}"`);
cp.execSync(`code "${fileURLToPath(testFileURL)}"`);
cp.execSync(`code "${fileURLToPath(docFileURL)}"`);
} catch {
logger.error('Unable to find code command. Will not open files with VSCode.');
}
})(process.argv.slice(2));

/** Get module path */
function getModulePath(from: string, module: string): string {
return path.relative(path.dirname(from), module).replace(/.ts$/u, '.js');
function getModulePath(from: URL, module: URL): string {
const fromDir = fileURLToPath(new URL('./', from));
const modulePath = fileURLToPath(module);

return path.relative(fromDir, modulePath).replace(/\\/g, '/').replace(/.ts$/u, '.js');
}

/** Argument parsing */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import path from 'path';
import renderRulesTableContent from './render-rules.js';
import { writeAndFormat } from './lib/write.js';

const __dirname = path.dirname(new URL(import.meta.url).pathname);

// -----------------------------------------------------------------------------
const readmeFilePath = path.resolve(__dirname, '../../../docs/rules.md');
const readmeFileURL = new URL('../../../docs/rules.md', import.meta.url);
void writeAndFormat(
readmeFilePath,
readmeFileURL,
`---
sidebarDepth: 0
---
Expand Down
Loading
Loading