diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts index f58b636f9..0565b6759 100644 --- a/packages/eslint-plugin-svelte/src/rule-types.ts +++ b/packages/eslint-plugin-svelte/src/rule-types.ts @@ -382,6 +382,11 @@ export interface RuleOptions { * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/sort-attributes/ */ 'svelte/sort-attributes'?: Linter.RuleEntry + /** + * enforce order of elements in Svelte scripts section + * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/sort-scripts-elements/ + */ + 'svelte/sort-scripts-sections'?: Linter.RuleEntry /** * enforce consistent spacing after the `` in a HTML comment * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/spaced-html-comment/ @@ -417,9 +422,9 @@ export interface RuleOptions { /* ======= Declarations ======= */ // ----- svelte/@typescript-eslint/no-unnecessary-condition ----- type SvelteTypescriptEslintNoUnnecessaryCondition = []|[{ - + allowConstantLoopConditions?: boolean - + allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean }] // ----- svelte/block-lang ----- @@ -442,7 +447,7 @@ type SvelteCommentDirective = []|[{ // ----- svelte/consistent-selector-style ----- type SvelteConsistentSelectorStyle = []|[{ checkGlobal?: boolean - + style?: []|[("class" | "id" | "type")]|[("class" | "id" | "type"), ("class" | "id" | "type")]|[("class" | "id" | "type"), ("class" | "id" | "type"), ("class" | "id" | "type")] }] // ----- svelte/first-attribute-linebreak ----- @@ -537,11 +542,11 @@ type SvelteNoReactiveReassign = []|[{ }] // ----- svelte/no-restricted-html-elements ----- type SvelteNoRestrictedHtmlElements = [(string | { - + elements?: [string, ...(string)[]] message?: string }), ...((string | { - + elements?: [string, ...(string)[]] message?: string }))[]] @@ -557,7 +562,7 @@ type SvelteNoTrailingSpaces = []|[{ }] // ----- svelte/no-unknown-style-directive-property ----- type SvelteNoUnknownStyleDirectiveProperty = []|[{ - + ignoreProperties?: [string, ...(string)[]] ignorePrefixed?: boolean }] @@ -613,6 +618,12 @@ type SvelteSortAttributes = []|[{ })[] alphabetical?: boolean }] +// ----- svelte/sort-scripts-elements ----- +type SvelteSortScriptsElements = []|[{ + order?: (string | [string, ...(string)[]] | { + match: (string | [string, ...(string)[]]) + })[] +}] // ----- svelte/spaced-html-comment ----- type SvelteSpacedHtmlComment = []|[("always" | "never")] // ----- svelte/valid-compile ----- diff --git a/packages/eslint-plugin-svelte/src/rules/sort-scripts-elements.ts b/packages/eslint-plugin-svelte/src/rules/sort-scripts-elements.ts new file mode 100644 index 000000000..09e4099ed --- /dev/null +++ b/packages/eslint-plugin-svelte/src/rules/sort-scripts-elements.ts @@ -0,0 +1,127 @@ +import { createRule } from '../utils/index.js'; +import type { RuleContext } from '../types.js'; +import type { AST } from 'svelte-eslint-parser'; +import type { Range } from 'svelte-eslint-parser/lib/ast/common.js'; + +const DEFAULT_ORDER: string[] = [ + 'ImportDeclaration', + 'TSTypeAliasDeclaration', + 'TSInterfaceDeclaration', + 'VariableDeclaration', + 'FunctionDeclaration' +]; + +// creating and exporting the rule +export default createRule('sort-scripts-elements', { + meta: { + docs: { + description: 'enforce order of elements in the Svelte scripts sections', + category: 'Stylistic Issues', + recommended: false, + conflictWithPrettier: false + }, + schema: [], + messages: { + scriptsIsNotSorted: 'Scripts is not sorted.' + }, + type: 'layout', + fixable: 'code' + }, + create(context: RuleContext) { + const sourceCode = context.sourceCode; + const MAPPING = new Map(DEFAULT_ORDER.map((value, index) => [value, index])); + + return { + SvelteScriptElement(node: AST.SvelteScriptElement) { + // do not accept scripts without closing tags + if (node.endTag === null) return; + const svelteEndTag: AST.SvelteEndTag = node.endTag; + + // collect scripts statement + const statements = node.body; + // do not sort when we only have one elements + if (statements.length <= 1) return; + + const seens = new Set(); + let current: string = statements[0].type; + + let sortingRequired = false; + for (let i = 0; i < statements.length; i++) { + // if we are the same as previous + if (seens.has(statements[i].type) && statements[i].type === current) { + continue; + } + + if (i > 0) { + const previousOrderIndex = MAPPING.get(current); + const currentOrderIndex = MAPPING.get(statements[i].type); + if (previousOrderIndex !== undefined && currentOrderIndex !== undefined) { + if (previousOrderIndex > currentOrderIndex) { + sortingRequired = true; + break; + } + } + } + + // mark the node type as seen + seens.add(statements[i].type); + current = statements[i].type; + } + + if (!sortingRequired) return; + + context.report({ + node, + messageId: 'scriptsIsNotSorted', + fix: (fixer) => { + const foo: { order: number; text: string }[] = []; + + const ranges: Range[] = []; + for (let i = 0; i < statements.length; i++) { + // the first token start after the previous statement + let start: number; + if (i === 0) { + // special case: first statement, we copy everything from diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/invalid/default/state-before-interface-output.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/invalid/default/state-before-interface-output.svelte new file mode 100644 index 000000000..cab130e76 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/invalid/default/state-before-interface-output.svelte @@ -0,0 +1,6 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/invalid/default/type-before-import-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/invalid/default/type-before-import-errors.yaml new file mode 100644 index 000000000..1590fd280 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/invalid/default/type-before-import-errors.yaml @@ -0,0 +1,4 @@ +- message: Scripts is not sorted. + line: 1 + column: 1 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/invalid/default/type-before-import-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/invalid/default/type-before-import-input.svelte new file mode 100644 index 000000000..860f687f6 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/invalid/default/type-before-import-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/invalid/default/type-before-import-output.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/invalid/default/type-before-import-output.svelte new file mode 100644 index 000000000..42813f269 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/invalid/default/type-before-import-output.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/valid/default/comment-before-import-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/valid/default/comment-before-import-input.svelte new file mode 100644 index 000000000..e2192ac97 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/valid/default/comment-before-import-input.svelte @@ -0,0 +1,4 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/valid/default/single-import-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/valid/default/single-import-input.svelte new file mode 100644 index 000000000..c388cd9bd --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/sort-scripts-elements/valid/default/single-import-input.svelte @@ -0,0 +1,3 @@ + diff --git a/packages/eslint-plugin-svelte/tests/src/rules/sort-scripts-elements.ts b/packages/eslint-plugin-svelte/tests/src/rules/sort-scripts-elements.ts new file mode 100644 index 000000000..f5da7b042 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/src/rules/sort-scripts-elements.ts @@ -0,0 +1,12 @@ +import { RuleTester } from '../../utils/eslint-compat.js'; +import rule from '../../../src/rules/sort-scripts-elements.js'; +import { loadTestCases } from '../../utils/utils.js'; + +const tester = new RuleTester({ + languageOptions: { + ecmaVersion:"latest", + sourceType: 'module' + } +}); + +tester.run('sort-scripts-elements', rule as any, loadTestCases('sort-scripts-elements'));