Skip to content

Commit cb70fea

Browse files
committed
re implement
1 parent 4fbe97d commit cb70fea

File tree

6 files changed

+144
-61
lines changed

6 files changed

+144
-61
lines changed

packages/eslint-plugin-svelte/src/rules/no-export-load-in-svelte-module-in-kit-pages.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { TSESTree } from '@typescript-eslint/types';
22
import { createRule } from '../utils/index.js';
3-
import { getSvelteContext } from '../utils/svelte-context.js';
43

54
export default createRule('no-export-load-in-svelte-module-in-kit-pages', {
65
meta: {
@@ -16,11 +15,14 @@ export default createRule('no-export-load-in-svelte-module-in-kit-pages', {
1615
unexpected:
1716
'disallow exporting load functions in `*.svelte` module in SvelteKit page components.'
1817
},
19-
type: 'problem'
18+
type: 'problem',
19+
conditions: [
20+
{
21+
svelteKitFileTypes: ['+page.svelte', '+error.svelte', '+layout.svelte']
22+
}
23+
]
2024
},
2125
create(context) {
22-
const svelteContext = getSvelteContext(context);
23-
if (svelteContext?.svelteKitFileType == null) return {};
2426
let isModule = false;
2527
return {
2628
// <script context="module">

packages/eslint-plugin-svelte/src/rules/valid-prop-names-in-kit-pages.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { AST } from 'svelte-eslint-parser';
22
import type { TSESTree } from '@typescript-eslint/types';
33
import { createRule } from '../utils/index.js';
4-
import { getSvelteContext } from '../utils/svelte-context.js';
54
import type { RuleContext } from '../types.js';
65

76
const EXPECTED_PROP_NAMES = ['data', 'errors', 'form', 'snapshot'];
@@ -35,11 +34,14 @@ export default createRule('valid-prop-names-in-kit-pages', {
3534
messages: {
3635
unexpected: 'disallow props other than data or errors in SvelteKit page components.'
3736
},
38-
type: 'problem'
37+
type: 'problem',
38+
conditions: [
39+
{
40+
svelteKitFileTypes: ['+page.svelte', '+error.svelte', '+layout.svelte']
41+
}
42+
]
3943
},
4044
create(context) {
41-
const svelteContext = getSvelteContext(context);
42-
if (svelteContext?.svelteKitFileType == null) return {};
4345
let isScript = false;
4446
return {
4547
// <script>

packages/eslint-plugin-svelte/src/types.ts

+13
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { Root as SelectorRoot, Node as SelectorNode } from 'postcss-selecto
88
import type { ASTNode, ASTNodeWithParent, ASTNodeListener } from './types-for-node.js';
99
import type * as TS from 'typescript';
1010
import type { SourceLocation } from 'svelte-eslint-parser/lib/ast/common.js';
11+
import type { SvelteContext } from './utils/svelte-context.js';
1112

1213
export type { ASTNode, ASTNodeWithParent, ASTNodeListener };
1314
export interface RuleListener extends ASTNodeListener {
@@ -108,6 +109,18 @@ export interface PartialRuleMetaData {
108109
deprecated?: boolean;
109110
replacedBy?: string[] | { note: string };
110111
type: 'problem' | 'suggestion' | 'layout';
112+
/**
113+
* Conditions to determine whether this rule should be applied.
114+
* Multiple conditions can be specified as array, and the rule will be applied if any one of them matches (logical OR).
115+
* If not specified, the rule will be applied to all files.
116+
*/
117+
conditions?: {
118+
svelteVersion?: string; // e.g.: '>= 5.0.0'
119+
fileTypes?: SvelteContext['fileType'][];
120+
runes?: SvelteContext['runes'];
121+
svelteKitVersion?: string; // e.g.: '>= 1.0.0'
122+
svelteKitFileTypes?: NonNullable<SvelteContext['svelteKitFileType']>[];
123+
}[];
111124
}
112125

113126
export type RuleContext = {

packages/eslint-plugin-svelte/src/utils/index.ts

+75-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,69 @@
1-
import type { RuleModule, PartialRuleModule } from '../types.js';
1+
import type { RuleModule, PartialRuleModule, PartialRuleMetaData, RuleContext } from '../types.js';
2+
import { getSvelteContext, type SvelteContext } from '../utils/svelte-context.js';
3+
import semver from 'semver';
4+
5+
function satisfiesCondition(
6+
condition: NonNullable<PartialRuleMetaData['conditions']>[number],
7+
svelteContext: SvelteContext
8+
): boolean {
9+
if (
10+
condition.svelteVersion != null &&
11+
!semver.satisfies(svelteContext.svelteVersion, condition.svelteVersion)
12+
) {
13+
return false;
14+
}
15+
16+
if (
17+
condition.fileTypes != null &&
18+
condition.fileTypes.length > 0 &&
19+
!condition.fileTypes.includes(svelteContext.fileType)
20+
) {
21+
return false;
22+
}
23+
24+
if (condition.runes != null && condition.runes !== svelteContext.runes) {
25+
return false;
26+
}
27+
28+
if (condition.svelteKitVersion != null) {
29+
if (
30+
svelteContext.svelteKitVersion == null ||
31+
!semver.satisfies(svelteContext.svelteKitVersion, condition.svelteKitVersion)
32+
) {
33+
return false;
34+
}
35+
}
36+
37+
if (condition.svelteKitFileTypes != null && condition.svelteKitFileTypes.length > 0) {
38+
if (
39+
svelteContext.svelteKitFileType == null ||
40+
!condition.svelteKitFileTypes.includes(svelteContext.svelteKitFileType)
41+
) {
42+
return false;
43+
}
44+
}
45+
46+
return true;
47+
}
48+
49+
function shouldRun(
50+
svelteContext: SvelteContext | null,
51+
conditions: PartialRuleMetaData['conditions']
52+
): boolean {
53+
// If svelteContext is null, it means the rule might be executed based on the analysis result of a different parser.
54+
// In this case, always execute the rule.
55+
if (svelteContext == null || conditions == null) {
56+
return true;
57+
}
58+
59+
for (const condition of conditions) {
60+
if (satisfiesCondition(condition, svelteContext)) {
61+
return true;
62+
}
63+
}
64+
65+
return false;
66+
}
267

368
/**
469
* Define the rule.
@@ -16,6 +81,14 @@ export function createRule(ruleName: string, rule: PartialRuleModule): RuleModul
1681
ruleName
1782
}
1883
},
19-
create: rule.create as never
84+
create(context: RuleContext) {
85+
const { conditions } = rule.meta;
86+
const svelteContext = getSvelteContext(context);
87+
if (!shouldRun(svelteContext, conditions)) {
88+
return {};
89+
}
90+
91+
return rule.create(context);
92+
}
2093
};
2194
}

packages/eslint-plugin-svelte/src/utils/svelte-context.ts

+43-48
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@ import { getFilename, getSourceCode } from './compat.js';
66

77
const isRunOnBrowser = !fs.readFileSync;
88

9-
type FileType = '.svelte' | '.svelte.[js|ts]';
109
export type SvelteContext = {
10+
svelteVersion: string;
11+
fileType: '.svelte' | '.svelte.[js|ts]';
12+
runes: boolean;
13+
svelteKitVersion: string | null;
1114
svelteKitFileType:
1215
| '+page.svelte'
1316
| '+page.js'
@@ -18,18 +21,9 @@ export type SvelteContext = {
1821
| '+layout.server.js'
1922
| '+server.js'
2023
| null;
21-
} & (
22-
| {
23-
version: 3 | 4;
24-
}
25-
| {
26-
version: 5;
27-
runes: boolean;
28-
fileType: FileType;
29-
}
30-
);
31-
32-
function getFileType(filePath: string): FileType | null {
24+
};
25+
26+
function getFileType(filePath: string): SvelteContext['fileType'] | null {
3327
if (filePath.endsWith('.svelte')) {
3428
return '.svelte';
3529
}
@@ -79,16 +73,23 @@ function getSvelteKitFileTypeFromFilePath(filePath: string): SvelteContext['svel
7973
}
8074
}
8175

82-
function getSvelteKitFileType(context: RuleContext): SvelteContext['svelteKitFileType'] {
76+
function getSvelteKitContext(
77+
context: RuleContext
78+
): Pick<SvelteContext, 'svelteKitFileType' | 'svelteKitVersion'> {
8379
const filePath = getFilename(context);
84-
85-
// Hack: if it runs on browser, it regards as SvelteKit project.
86-
if (isRunOnBrowser) {
87-
return getSvelteKitFileTypeFromFilePath(filePath);
80+
const svelteKitVersion = gteSvelteKitVersion(filePath);
81+
if (svelteKitVersion == null) {
82+
return {
83+
svelteKitFileType: null,
84+
svelteKitVersion: null
85+
};
8886
}
89-
90-
if (!hasSvelteKit(getFilename(context))) {
91-
return null;
87+
if (isRunOnBrowser) {
88+
return {
89+
svelteKitVersion,
90+
// Judge by only file path if it runs on browser.
91+
svelteKitFileType: getSvelteKitFileTypeFromFilePath(filePath)
92+
};
9293
}
9394

9495
const routes =
@@ -99,10 +100,16 @@ function getSvelteKitFileType(context: RuleContext): SvelteContext['svelteKitFil
99100
const projectRootDir = getProjectRootDir(getFilename(context)) ?? '';
100101

101102
if (!filePath.startsWith(path.join(projectRootDir, routes))) {
102-
return null;
103+
return {
104+
svelteKitVersion,
105+
svelteKitFileType: null
106+
};
103107
}
104108

105-
return getSvelteKitFileTypeFromFilePath(filePath);
109+
return {
110+
svelteKitVersion,
111+
svelteKitFileType: getSvelteKitFileTypeFromFilePath(filePath)
112+
};
106113
}
107114

108115
/**
@@ -113,21 +120,22 @@ function getSvelteKitFileType(context: RuleContext): SvelteContext['svelteKitFil
113120
* @param filePath A file path.
114121
* @returns
115122
*/
116-
function hasSvelteKit(filePath: string): boolean {
123+
function gteSvelteKitVersion(filePath: string): string | null {
117124
// Hack: if it runs on browser, it regards as SvelteKit project.
118-
if (isRunOnBrowser) return true;
125+
if (isRunOnBrowser) return '2.15.1';
119126
try {
120127
const packageJson = getPackageJson(filePath);
121-
if (!packageJson) return false;
128+
if (!packageJson) return null;
122129
if (packageJson.name === 'eslint-plugin-svelte')
123130
// Hack: CI removes `@sveltejs/kit` and it returns false and test failed.
124131
// So always it returns true if it runs on the package.
125-
return true;
126-
return Boolean(
127-
packageJson.dependencies?.['@sveltejs/kit'] ?? packageJson.devDependencies?.['@sveltejs/kit']
128-
);
132+
return '2.15.1';
133+
134+
const version =
135+
packageJson.dependencies?.['@sveltejs/kit'] ?? packageJson.devDependencies?.['@sveltejs/kit'];
136+
return typeof version === 'string' ? version : null;
129137
} catch {
130-
return false;
138+
return null;
131139
}
132140
}
133141

@@ -156,21 +164,7 @@ export function getSvelteContext(context: RuleContext): SvelteContext | null {
156164
}
157165

158166
const filePath = getFilename(context);
159-
const svelteKitFileType = getSvelteKitFileType(context);
160-
161-
if (compilerVersion.startsWith('3')) {
162-
return {
163-
version: 3,
164-
svelteKitFileType
165-
};
166-
}
167-
168-
if (compilerVersion.startsWith('4')) {
169-
return {
170-
version: 4,
171-
svelteKitFileType
172-
};
173-
}
167+
const svelteKitContext = getSvelteKitContext(context);
174168

175169
const runes = svelteParseContext.runes === true;
176170
const fileType = getFileType(filePath);
@@ -179,9 +173,10 @@ export function getSvelteContext(context: RuleContext): SvelteContext | null {
179173
}
180174

181175
return {
182-
version: 5,
176+
svelteVersion: compilerVersion,
183177
runes,
184178
fileType,
185-
svelteKitFileType
179+
svelteKitVersion: svelteKitContext.svelteKitVersion,
180+
svelteKitFileType: svelteKitContext.svelteKitFileType
186181
};
187182
}

packages/eslint-plugin-svelte/tools/new-rule.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ void (async ([ruleId, ...args]) => {
5454
ruleFile,
5555
`import { AST } from 'svelte-eslint-parser';
5656
import { createRule } from '${getModulePath(ruleFile, utilsPath)}';
57-
import { getSvelteContext } from '../utils/svelte.js';
5857
5958
export default createRule('${ruleId}', {
6059
meta: {
@@ -66,10 +65,9 @@ export default createRule('${ruleId}', {
6665
schema: [],
6766
messages: {},
6867
type: 'suggestion', // 'problem', or 'layout',
68+
conditions: [], // Conditions for applying this rule. Leave empty to always execute.
6969
},
7070
create(context) {
71-
// TODO: Please refer to the context regarding Svelte and SvelteKit, and return early if run the rule is unnecessary.
72-
const svelteContext = getSvelteContext(context);
7371
return {}
7472
},
7573
});

0 commit comments

Comments
 (0)