mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-15 14:06:35 +00:00
chore: update lint rules
This commit is contained in:
@@ -8,15 +8,15 @@
|
||||
// (see __tests__/helpers.ts). The fixture dictionary has exactly five
|
||||
// tokens; anything else must be reported even if it exists in the REAL
|
||||
// web/src/app.css.
|
||||
import { RuleTester, Linter } from "eslint";
|
||||
import { RuleTester } from "eslint";
|
||||
import svelteParser from "svelte-eslint-parser";
|
||||
import tseslint from "typescript-eslint";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import noHardcoded from "../design-tokens/no-hardcoded-in-svelte.js";
|
||||
import noCategoryMismatch from "../design-tokens/no-category-mismatch.js";
|
||||
import noHardcoded from "../design-tokens/no-hardcoded-in-svelte.js";
|
||||
import noTokenDefinition from "../design-tokens/no-token-definition-in-svelte.js";
|
||||
import noUndefined from "../design-tokens/no-undefined-in-svelte.js";
|
||||
import { verifyInFixtures, type FlatConfig } from "./helpers.js";
|
||||
import { asRuleModule, verifyInFixtures, type FlatConfig } from "./helpers.js";
|
||||
|
||||
const parserOptions = {
|
||||
parser: tseslint.parser,
|
||||
@@ -45,7 +45,7 @@ const frame = (style: string) => ({
|
||||
});
|
||||
|
||||
describe("design-tokens/no-hardcoded-in-svelte", () => {
|
||||
ruleTester.run("no-hardcoded-in-svelte", noHardcoded, {
|
||||
ruleTester.run("no-hardcoded-in-svelte", asRuleModule(noHardcoded), {
|
||||
valid: [
|
||||
frame(".box { color: var(--color-fg); }"),
|
||||
frame(".box { padding: var(--space-3); }"),
|
||||
@@ -97,7 +97,7 @@ describe("design-tokens/no-hardcoded-in-svelte", () => {
|
||||
});
|
||||
|
||||
describe("design-tokens/no-category-mismatch", () => {
|
||||
ruleTester.run("no-category-mismatch", noCategoryMismatch, {
|
||||
ruleTester.run("no-category-mismatch", asRuleModule(noCategoryMismatch), {
|
||||
valid: [
|
||||
frame(".box { padding: var(--space-1); }"),
|
||||
frame(".box { color: var(--color-fg); }"),
|
||||
@@ -117,27 +117,31 @@ describe("design-tokens/no-category-mismatch", () => {
|
||||
});
|
||||
|
||||
describe("design-tokens/no-token-definition-in-svelte", () => {
|
||||
ruleTester.run("no-token-definition-in-svelte", noTokenDefinition, {
|
||||
valid: [
|
||||
frame(".box { --local: var(--color-fg); }"),
|
||||
frame(".box { --local: calc(var(--space-1) * 2); }"),
|
||||
frame(".box { --ratio: 1.5; }"),
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
...frame(".box { --local: #ff0000; }"),
|
||||
errors: [{ messageId: "tokenPrimitive" }],
|
||||
},
|
||||
{
|
||||
...frame(".box { --local: 12px; }"),
|
||||
errors: [{ messageId: "tokenPrimitive" }],
|
||||
},
|
||||
{
|
||||
...frame(".box { --local: oklch(0.5 0.1 240); }"),
|
||||
errors: [{ messageId: "tokenPrimitive" }],
|
||||
},
|
||||
],
|
||||
});
|
||||
ruleTester.run(
|
||||
"no-token-definition-in-svelte",
|
||||
asRuleModule(noTokenDefinition),
|
||||
{
|
||||
valid: [
|
||||
frame(".box { --local: var(--color-fg); }"),
|
||||
frame(".box { --local: calc(var(--space-1) * 2); }"),
|
||||
frame(".box { --ratio: 1.5; }"),
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
...frame(".box { --local: #ff0000; }"),
|
||||
errors: [{ messageId: "tokenPrimitive" }],
|
||||
},
|
||||
{
|
||||
...frame(".box { --local: 12px; }"),
|
||||
errors: [{ messageId: "tokenPrimitive" }],
|
||||
},
|
||||
{
|
||||
...frame(".box { --local: oklch(0.5 0.1 240); }"),
|
||||
errors: [{ messageId: "tokenPrimitive" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("design-tokens/no-undefined-in-svelte", () => {
|
||||
@@ -145,7 +149,9 @@ describe("design-tokens/no-undefined-in-svelte", () => {
|
||||
{
|
||||
files: ["**/*.svelte"],
|
||||
plugins: {
|
||||
"design-tokens": { rules: { "no-undefined-in-svelte": noUndefined } },
|
||||
"design-tokens": {
|
||||
rules: { "no-undefined-in-svelte": asRuleModule(noUndefined) },
|
||||
},
|
||||
},
|
||||
rules: { "design-tokens/no-undefined-in-svelte": "error" },
|
||||
languageOptions: {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// `verifyInFixtures` runs a rule with cwd fixed to `__fixtures__/`, so both
|
||||
// work without touching process.cwd() or the real src/ tree.
|
||||
import { Linter } from "eslint";
|
||||
import type { Rule } from "eslint";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
@@ -32,3 +33,18 @@ export function verifyInFixtures(
|
||||
const linter = new Linter({ configType: "flat", cwd: FIXTURES_DIR });
|
||||
return linter.verify(code, config, path.join(FIXTURES_SRC, relFile));
|
||||
}
|
||||
|
||||
/** Wrap a script body into a minimal Svelte component (runs in runes mode). */
|
||||
export function svelteComponent(script: string): string {
|
||||
return `<main>Hello</main>\n\n<script lang="ts">\n${script}\n</script>\n`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Narrow an inferred JS rule to the typed `Rule.RuleModule`. The plugin rule
|
||||
* files are plain JS, so TypeScript infers a widened shape (`meta.type: string`)
|
||||
* that `RuleTester.run` rejects; the shape is structurally correct at runtime,
|
||||
* which is why existing rules keep their JS inference and only the tests cast.
|
||||
*/
|
||||
export function asRuleModule(rule: unknown): Rule.RuleModule {
|
||||
return rule as Rule.RuleModule;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
// Tests for the conventions/interface-props rule
|
||||
// (web/eslint-plugins/conventions/interface-props.js).
|
||||
//
|
||||
// The rule is a pure AST check (no filesystem), so it runs through the standard
|
||||
// RuleTester with svelte-eslint-parser + the TS sub-parser. No fixtures needed.
|
||||
import { RuleTester } from "eslint";
|
||||
import svelteParser from "svelte-eslint-parser";
|
||||
import tseslint from "typescript-eslint";
|
||||
import { describe, it } from "vitest";
|
||||
import interfaceProps from "../conventions/interface-props.js";
|
||||
import { asRuleModule, svelteComponent } from "./helpers.js";
|
||||
|
||||
RuleTester.describe = describe;
|
||||
RuleTester.it = it;
|
||||
|
||||
const ruleTester = new RuleTester({
|
||||
languageOptions: {
|
||||
parser: svelteParser,
|
||||
parserOptions: { parser: tseslint.parser },
|
||||
},
|
||||
});
|
||||
|
||||
const frame = (script: string) => ({
|
||||
code: svelteComponent(script),
|
||||
filename: "Component.svelte",
|
||||
});
|
||||
|
||||
ruleTester.run("interface-props", asRuleModule(interfaceProps), {
|
||||
valid: [
|
||||
{ code: "<main>Hello</main>", filename: "Component.svelte" },
|
||||
frame("let x = 1;"),
|
||||
// $state / $derived calls are unrelated, not props.
|
||||
frame("const s = $state(0);"),
|
||||
frame("const doubled = $derived(s * 2);"),
|
||||
frame(
|
||||
"interface Props { label: string }\nlet { label }: Props = $props();",
|
||||
),
|
||||
frame(
|
||||
"interface Props { label: string }\nlet { label = 'x' }: Props = $props();",
|
||||
),
|
||||
frame(
|
||||
"interface Props { value: string }\nlet { value = $bindable() }: Props = $props();",
|
||||
),
|
||||
// Interface declared after the destructuring is still fine.
|
||||
frame(
|
||||
"let { label }: Props = $props();\ninterface Props { label: string }",
|
||||
),
|
||||
frame(
|
||||
"interface Props { id: string; rest?: unknown }\nlet { id, ...rest }: Props = $props();",
|
||||
),
|
||||
frame(
|
||||
"import type { Snippet } from 'svelte';\n" +
|
||||
"interface Props { children?: Snippet }\n" +
|
||||
"let { children }: Props = $props();",
|
||||
),
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
// Untyped destructuring with a local interface -> autofixable.
|
||||
...frame("interface Props { label: string }\nlet { label } = $props();"),
|
||||
output: svelteComponent(
|
||||
"interface Props { label: string }\nlet { label }: Props = $props();",
|
||||
),
|
||||
errors: [{ messageId: "untypedDestructure" }],
|
||||
},
|
||||
{
|
||||
// Untyped destructuring without an interface -> report, no fix.
|
||||
...frame("let { label } = $props();"),
|
||||
errors: [{ messageId: "untypedDestructure" }],
|
||||
},
|
||||
{
|
||||
// Binding the whole props object instead of destructuring.
|
||||
...frame("let props = $props();"),
|
||||
errors: [{ messageId: "noDestructure" }],
|
||||
},
|
||||
{
|
||||
// Inline generic is banned outright.
|
||||
...frame("const props = $props<{ a: string }>();"),
|
||||
errors: [{ messageId: "inlineGeneric" }],
|
||||
},
|
||||
{
|
||||
// A different type name than the local interface Props.
|
||||
...frame(
|
||||
"interface Props { a: string }\nlet { a }: StageProps = $props();",
|
||||
),
|
||||
errors: [{ messageId: "notNamedProps" }],
|
||||
},
|
||||
{
|
||||
// Inline object props type instead of interface Props.
|
||||
...frame("let { a }: { a: string } = $props();"),
|
||||
errors: [{ messageId: "inlineObjectType" }],
|
||||
},
|
||||
{
|
||||
// Reference to Props without any local declaration.
|
||||
...frame("let { a }: Props = $props();"),
|
||||
errors: [{ messageId: "missingInterface" }],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -11,12 +11,16 @@
|
||||
// shared-> everything else: core/, i18n/, theme, ...
|
||||
import { describe, expect, it } from "vitest";
|
||||
import noMixedImports from "../isolation/no-mixed-imports.js";
|
||||
import { verifyInFixtures, type FlatConfig } from "./helpers.js";
|
||||
import { asRuleModule, verifyInFixtures, type FlatConfig } from "./helpers.js";
|
||||
|
||||
const isolationConfig: FlatConfig = [
|
||||
{
|
||||
files: ["**/*.{ts,svelte}"],
|
||||
plugins: { isolation: { rules: { "no-mixed-imports": noMixedImports } } },
|
||||
plugins: {
|
||||
isolation: {
|
||||
rules: { "no-mixed-imports": asRuleModule(noMixedImports) },
|
||||
},
|
||||
},
|
||||
rules: { "isolation/no-mixed-imports": "error" },
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// Tests for the conventions/no-string-union-alias rule
|
||||
// (web/eslint-plugins/conventions/no-string-union-alias.js).
|
||||
//
|
||||
// The rule is a pure AST check (no filesystem), so it runs through the standard
|
||||
// RuleTester: plain .ts via the tseslint parser, and (because the rule is also
|
||||
// enabled on new .svelte scripts) .svelte via the svelte tester. No fixtures.
|
||||
import { RuleTester } from "eslint";
|
||||
import svelteParser from "svelte-eslint-parser";
|
||||
import tseslint from "typescript-eslint";
|
||||
import { describe, it } from "vitest";
|
||||
import noStringUnionAlias from "../conventions/no-string-union-alias.js";
|
||||
import { asRuleModule, svelteComponent } from "./helpers.js";
|
||||
|
||||
RuleTester.describe = describe;
|
||||
RuleTester.it = it;
|
||||
|
||||
const tsTester = new RuleTester({
|
||||
languageOptions: {
|
||||
parser: tseslint.parser,
|
||||
},
|
||||
});
|
||||
|
||||
const svelteTester = new RuleTester({
|
||||
languageOptions: {
|
||||
parser: svelteParser,
|
||||
parserOptions: { parser: tseslint.parser },
|
||||
},
|
||||
});
|
||||
|
||||
const frame = (script: string) => ({
|
||||
code: svelteComponent(script),
|
||||
filename: "Component.svelte",
|
||||
});
|
||||
|
||||
tsTester.run("no-string-union-alias", asRuleModule(noStringUnionAlias), {
|
||||
valid: [
|
||||
{ code: "type X = 1 | 2;", filename: "types.ts" },
|
||||
{ code: "type X = 'a' | number;", filename: "types.ts" },
|
||||
{ code: "type X = string;", filename: "types.ts" },
|
||||
{ code: "const KIND = { a: 1, b: 2 } as const;", filename: "types.ts" },
|
||||
{ code: "function f(x: 'a' | 'b') {}", filename: "types.ts" },
|
||||
{ code: "const x: 'a' | 'b' = 'a';", filename: "types.ts" },
|
||||
{ code: "type X = 'a' | TemplateStrings;", filename: "types.ts" },
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: "type Kind = 'a' | 'b' | 'c';",
|
||||
filename: "types.ts",
|
||||
errors: [{ messageId: "stringUnion" }],
|
||||
},
|
||||
{
|
||||
// Nested/parenthesized unions still resolve to string literals.
|
||||
code: "type Kind = ('a' | 'b') | 'c';",
|
||||
filename: "types.ts",
|
||||
errors: [{ messageId: "stringUnion" }],
|
||||
},
|
||||
{
|
||||
code: "type Lang =\n\t'en'\n\t| 'ru';",
|
||||
filename: "types.ts",
|
||||
errors: [{ messageId: "stringUnion" }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// The rule also runs on new .svelte files (their scripts are TS).
|
||||
svelteTester.run(
|
||||
"no-string-union-alias (svelte script)",
|
||||
asRuleModule(noStringUnionAlias),
|
||||
{
|
||||
valid: [frame("type X = 1 | 2;"), frame("const M = { a: 1 } as const;")],
|
||||
invalid: [
|
||||
{
|
||||
...frame("type Kind = 'a' | 'b' | 'c';"),
|
||||
errors: [{ messageId: "stringUnion" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user