From a95d368b450421155f5caa4b8694165dd86f414e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Fri, 1 Nov 2024 14:52:37 +0100 Subject: [PATCH 1/9] chore: duplicated no-goto-without-base into no-navigation-without-base --- README.md | 1 + docs/rules.md | 7 +- docs/rules/no-navigation-without-base.md | 64 +++++++++ .../eslint-plugin-svelte/src/rule-types.ts | 5 + .../src/rules/no-navigation-without-base.ts | 134 ++++++++++++++++++ .../eslint-plugin-svelte/src/utils/rules.ts | 2 + .../invalid/aliased-goto01-errors.yaml | 4 + .../invalid/aliased-goto01-input.svelte | 5 + .../invalid/base-not-prefixed01-errors.yaml | 8 ++ .../invalid/base-not-prefixed01-input.svelte | 7 + .../invalid/no-base01-errors.yaml | 4 + .../invalid/no-base01-input.svelte | 5 + .../valid/absolute-uri01-input.svelte | 6 + .../valid/base-aliased01-input.svelte | 8 ++ .../valid/base-prefixed01-input.svelte | 8 ++ .../src/rules/no-navigation-without-base.ts | 12 ++ 16 files changed, 277 insertions(+), 3 deletions(-) create mode 100644 docs/rules/no-navigation-without-base.md create mode 100644 packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/aliased-goto01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/aliased-goto01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/base-not-prefixed01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/base-not-prefixed01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/no-base01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/no-base01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/absolute-uri01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/base-aliased01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/base-prefixed01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/src/rules/no-navigation-without-base.ts diff --git a/README.md b/README.md index 9682cbf66..dbe925da4 100644 --- a/README.md +++ b/README.md @@ -474,6 +474,7 @@ These rules relate to SvelteKit and its best Practices. | Rule ID | Description | | |:--------|:------------|:---| | [svelte/no-goto-without-base](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-goto-without-base/) | disallow using goto() without the base path | | +| [svelte/no-navigation-without-base](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-navigation-without-base/) | disallow using goto() without the base path | | ## Experimental diff --git a/docs/rules.md b/docs/rules.md index fb067e57c..bd29cca41 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -108,9 +108,10 @@ These rules extend the rules provided by ESLint itself, or other plugins to work These rules relate to SvelteKit and its best Practices. -| Rule ID | Description | | -| :------------------------------------------------------------- | :------------------------------------------ | :-- | -| [svelte/no-goto-without-base](./rules/no-goto-without-base.md) | disallow using goto() without the base path | | +| Rule ID | Description | | +| :------------------------------------------------------------------------- | :------------------------------------------ | :-- | +| [svelte/no-goto-without-base](./rules/no-goto-without-base.md) | disallow using goto() without the base path | | +| [svelte/no-navigation-without-base](./rules/no-navigation-without-base.md) | disallow using goto() without the base path | | ## Experimental diff --git a/docs/rules/no-navigation-without-base.md b/docs/rules/no-navigation-without-base.md new file mode 100644 index 000000000..39c81631b --- /dev/null +++ b/docs/rules/no-navigation-without-base.md @@ -0,0 +1,64 @@ +--- +pageClass: 'rule-details' +sidebarDepth: 0 +title: 'svelte/no-navigation-without-base' +description: 'disallow using goto() without the base path' +since: 'v2.36.0-next.9' +--- + +# svelte/no-navigation-without-base + +> disallow using goto() without the base path + +## :book: Rule Details + +This rule reports navigation using SvelteKit's `goto()` function without prefixing a relative URL with the base path. If a non-prefixed relative URL is used for navigation, the `goto` function navigates away from the base path, which is usually not what you wanted to do (for external URLs, `window.location = url` should be used instead). + + + + + +```svelte + +``` + + + +## :wrench: Options + +Nothing. + +## :books: Further Reading + +- [`goto()` documentation](https://kit.svelte.dev/docs/modules#$app-navigation-goto) +- [`base` documentation](https://kit.svelte.dev/docs/modules#$app-paths-base) + +## :rocket: Version + +This rule was introduced in eslint-plugin-svelte v2.36.0-next.9 + +## :mag: Implementation + +- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts) +- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/packages/eslint-plugin-svelte/tests/src/rules/no-navigation-without-base.ts) diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts index 8e747b6e4..99233b5f5 100644 --- a/packages/eslint-plugin-svelte/src/rule-types.ts +++ b/packages/eslint-plugin-svelte/src/rule-types.ts @@ -174,6 +174,11 @@ export interface RuleOptions { * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-inspect/ */ 'svelte/no-inspect'?: Linter.RuleEntry<[]> + /** + * disallow using goto() without the base path + * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-navigation-without-base/ + */ + 'svelte/no-navigation-without-base'?: Linter.RuleEntry<[]> /** * disallow use of not function in event handler * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-not-function-handler/ diff --git a/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts b/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts new file mode 100644 index 000000000..7275c8ddb --- /dev/null +++ b/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts @@ -0,0 +1,134 @@ +import type { TSESTree } from '@typescript-eslint/types'; +import { createRule } from '../utils'; +import { ReferenceTracker } from '@eslint-community/eslint-utils'; +import { getSourceCode } from '../utils/compat'; +import { findVariable } from '../utils/ast-utils'; +import type { RuleContext } from '../types'; + +export default createRule('no-navigation-without-base', { + meta: { + docs: { + description: 'disallow using goto() without the base path', + category: 'SvelteKit', + recommended: false + }, + schema: [], + messages: { + isNotPrefixedWithBasePath: + "Found a goto() call with a url that isn't prefixed with the base path." + }, + type: 'suggestion' + }, + create(context) { + return { + Program() { + const referenceTracker = new ReferenceTracker( + getSourceCode(context).scopeManager.globalScope! + ); + const basePathNames = extractBasePathReferences(referenceTracker, context); + for (const gotoCall of extractGotoReferences(referenceTracker)) { + if (gotoCall.arguments.length < 1) { + continue; + } + const path = gotoCall.arguments[0]; + switch (path.type) { + case 'BinaryExpression': + checkBinaryExpression(context, path, basePathNames); + break; + case 'Literal': + checkLiteral(context, path); + break; + case 'TemplateLiteral': + checkTemplateLiteral(context, path, basePathNames); + break; + default: + context.report({ loc: path.loc, messageId: 'isNotPrefixedWithBasePath' }); + } + } + } + }; + } +}); + +function checkBinaryExpression( + context: RuleContext, + path: TSESTree.BinaryExpression, + basePathNames: Set +): void { + if (path.left.type !== 'Identifier' || !basePathNames.has(path.left)) { + context.report({ loc: path.loc, messageId: 'isNotPrefixedWithBasePath' }); + } +} + +function checkTemplateLiteral( + context: RuleContext, + path: TSESTree.TemplateLiteral, + basePathNames: Set +): void { + const startingIdentifier = extractStartingIdentifier(path); + if (startingIdentifier === undefined || !basePathNames.has(startingIdentifier)) { + context.report({ loc: path.loc, messageId: 'isNotPrefixedWithBasePath' }); + } +} + +function checkLiteral(context: RuleContext, path: TSESTree.Literal): void { + const absolutePathRegex = /^(?:[+a-z]+:)?\/\//i; + if (!absolutePathRegex.test(path.value?.toString() ?? '')) { + context.report({ loc: path.loc, messageId: 'isNotPrefixedWithBasePath' }); + } +} + +function extractStartingIdentifier( + templateLiteral: TSESTree.TemplateLiteral +): TSESTree.Identifier | undefined { + const literalParts = [...templateLiteral.expressions, ...templateLiteral.quasis].sort((a, b) => + a.range[0] < b.range[0] ? -1 : 1 + ); + for (const part of literalParts) { + if (part.type === 'TemplateElement' && part.value.raw === '') { + // Skip empty quasi in the begining + continue; + } + if (part.type === 'Identifier') { + return part; + } + return undefined; + } + return undefined; +} + +function extractGotoReferences(referenceTracker: ReferenceTracker): TSESTree.CallExpression[] { + return Array.from( + referenceTracker.iterateEsmReferences({ + '$app/navigation': { + [ReferenceTracker.ESM]: true, + goto: { + [ReferenceTracker.CALL]: true + } + } + }), + ({ node }) => node + ); +} + +function extractBasePathReferences( + referenceTracker: ReferenceTracker, + context: RuleContext +): Set { + const set = new Set(); + for (const { node } of referenceTracker.iterateEsmReferences({ + '$app/paths': { + [ReferenceTracker.ESM]: true, + base: { + [ReferenceTracker.READ]: true + } + } + })) { + const variable = findVariable(context, (node as TSESTree.ImportSpecifier).local); + if (!variable) continue; + for (const reference of variable.references) { + if (reference.identifier.type === 'Identifier') set.add(reference.identifier); + } + } + return set; +} diff --git a/packages/eslint-plugin-svelte/src/utils/rules.ts b/packages/eslint-plugin-svelte/src/utils/rules.ts index 5bb5a080c..276b5e1de 100644 --- a/packages/eslint-plugin-svelte/src/utils/rules.ts +++ b/packages/eslint-plugin-svelte/src/utils/rules.ts @@ -34,6 +34,7 @@ import noImmutableReactiveStatements from '../rules/no-immutable-reactive-statem import noInlineStyles from '../rules/no-inline-styles'; import noInnerDeclarations from '../rules/no-inner-declarations'; import noInspect from '../rules/no-inspect'; +import noNavigationWithoutBase from '../rules/no-navigation-without-base'; import noNotFunctionHandler from '../rules/no-not-function-handler'; import noObjectInTextMustaches from '../rules/no-object-in-text-mustaches'; import noReactiveFunctions from '../rules/no-reactive-functions'; @@ -101,6 +102,7 @@ export const rules = [ noInlineStyles, noInnerDeclarations, noInspect, + noNavigationWithoutBase, noNotFunctionHandler, noObjectInTextMustaches, noReactiveFunctions, diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/aliased-goto01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/aliased-goto01-errors.yaml new file mode 100644 index 000000000..cc35de50a --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/aliased-goto01-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a goto() call with a url that isn't prefixed with the base path. + line: 4 + column: 8 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/aliased-goto01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/aliased-goto01-input.svelte new file mode 100644 index 000000000..00f19edec --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/aliased-goto01-input.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/base-not-prefixed01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/base-not-prefixed01-errors.yaml new file mode 100644 index 000000000..f0a24ef44 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/base-not-prefixed01-errors.yaml @@ -0,0 +1,8 @@ +- message: Found a goto() call with a url that isn't prefixed with the base path. + line: 5 + column: 7 + suggestions: null +- message: Found a goto() call with a url that isn't prefixed with the base path. + line: 6 + column: 7 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/base-not-prefixed01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/base-not-prefixed01-input.svelte new file mode 100644 index 000000000..be68b2e4a --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/base-not-prefixed01-input.svelte @@ -0,0 +1,7 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/no-base01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/no-base01-errors.yaml new file mode 100644 index 000000000..658fcb47d --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/no-base01-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a goto() call with a url that isn't prefixed with the base path. + line: 4 + column: 7 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/no-base01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/no-base01-input.svelte new file mode 100644 index 000000000..6f011fe2d --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/no-base01-input.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/absolute-uri01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/absolute-uri01-input.svelte new file mode 100644 index 000000000..87ee8abf3 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/absolute-uri01-input.svelte @@ -0,0 +1,6 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/base-aliased01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/base-aliased01-input.svelte new file mode 100644 index 000000000..b11e55f92 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/base-aliased01-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/base-prefixed01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/base-prefixed01-input.svelte new file mode 100644 index 000000000..cd7177deb --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/base-prefixed01-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/src/rules/no-navigation-without-base.ts b/packages/eslint-plugin-svelte/tests/src/rules/no-navigation-without-base.ts new file mode 100644 index 000000000..6b00e4d6a --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/src/rules/no-navigation-without-base.ts @@ -0,0 +1,12 @@ +import { RuleTester } from '../../utils/eslint-compat'; +import rule from '../../../src/rules/no-navigation-without-base'; +import { loadTestCases } from '../../utils/utils'; + +const tester = new RuleTester({ + languageOptions: { + ecmaVersion: 2020, + sourceType: 'module' + } +}); + +tester.run('no-navigation-without-base', rule as any, loadTestCases('no-navigation-without-base')); From e6949eafa502f08c012dfe6c7b8f1eeecd84c31c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Fri, 1 Nov 2024 15:08:53 +0100 Subject: [PATCH 2/9] chore: deprecated the no-goto-without-base rule --- README.md | 2 +- docs/rules.md | 8 ++++---- docs/rules/no-goto-without-base.md | 2 ++ packages/eslint-plugin-svelte/src/rule-types.ts | 1 + .../src/rules/no-goto-without-base.ts | 2 ++ 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index dbe925da4..e8ec2b3a7 100644 --- a/README.md +++ b/README.md @@ -473,7 +473,6 @@ These rules relate to SvelteKit and its best Practices. | Rule ID | Description | | |:--------|:------------|:---| -| [svelte/no-goto-without-base](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-goto-without-base/) | disallow using goto() without the base path | | | [svelte/no-navigation-without-base](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-navigation-without-base/) | disallow using goto() without the base path | | ## Experimental @@ -502,6 +501,7 @@ These rules relate to this plugin works: | Rule ID | Replaced by | |:--------|:------------| | [svelte/@typescript-eslint/no-unnecessary-condition](https://sveltejs.github.io/eslint-plugin-svelte/rules/@typescript-eslint/no-unnecessary-condition/) | This rule is no longer needed when using svelte-eslint-parser>=v0.19.0. | +| [svelte/no-goto-without-base](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-goto-without-base/) | [svelte/no-navigation-without-base](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-navigation-without-base/) | diff --git a/docs/rules.md b/docs/rules.md index bd29cca41..eb90c0a55 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -110,7 +110,6 @@ These rules relate to SvelteKit and its best Practices. | Rule ID | Description | | | :------------------------------------------------------------------------- | :------------------------------------------ | :-- | -| [svelte/no-goto-without-base](./rules/no-goto-without-base.md) | disallow using goto() without the base path | | | [svelte/no-navigation-without-base](./rules/no-navigation-without-base.md) | disallow using goto() without the base path | | ## Experimental @@ -136,6 +135,7 @@ These rules relate to this plugin works: - :warning: We're going to remove deprecated rules in the next major release. Please migrate to successor/new rules. - :innocent: We don't fix bugs which are in deprecated rules since we don't have enough resources. -| Rule ID | Replaced by | -| :----------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------- | -| [svelte/@typescript-eslint/no-unnecessary-condition](./rules/@typescript-eslint/no-unnecessary-condition.md) | This rule is no longer needed when using svelte-eslint-parser>=v0.19.0. | +| Rule ID | Replaced by | +| :----------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------- | +| [svelte/@typescript-eslint/no-unnecessary-condition](./rules/@typescript-eslint/no-unnecessary-condition.md) | This rule is no longer needed when using svelte-eslint-parser>=v0.19.0. | +| [svelte/no-goto-without-base](./rules/no-goto-without-base.md) | [svelte/no-navigation-without-base](./rules/no-navigation-without-base.md) | diff --git a/docs/rules/no-goto-without-base.md b/docs/rules/no-goto-without-base.md index ab36738e7..a5f126dd3 100644 --- a/docs/rules/no-goto-without-base.md +++ b/docs/rules/no-goto-without-base.md @@ -10,6 +10,8 @@ since: 'v2.36.0-next.9' > disallow using goto() without the base path +- :warning: This rule was **deprecated** and replaced by [svelte/no-navigation-without-base](no-navigation-without-base.md) rule. + ## :book: Rule Details This rule reports navigation using SvelteKit's `goto()` function without prefixing a relative URL with the base path. If a non-prefixed relative URL is used for navigation, the `goto` function navigates away from the base path, which is usually not what you wanted to do (for external URLs, `window.location = url` should be used instead). diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts index 99233b5f5..da646f5a1 100644 --- a/packages/eslint-plugin-svelte/src/rule-types.ts +++ b/packages/eslint-plugin-svelte/src/rule-types.ts @@ -147,6 +147,7 @@ export interface RuleOptions { /** * disallow using goto() without the base path * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-goto-without-base/ + * @deprecated */ 'svelte/no-goto-without-base'?: Linter.RuleEntry<[]> /** diff --git a/packages/eslint-plugin-svelte/src/rules/no-goto-without-base.ts b/packages/eslint-plugin-svelte/src/rules/no-goto-without-base.ts index 9154dc264..21a32e564 100644 --- a/packages/eslint-plugin-svelte/src/rules/no-goto-without-base.ts +++ b/packages/eslint-plugin-svelte/src/rules/no-goto-without-base.ts @@ -7,6 +7,8 @@ import type { RuleContext } from '../types'; export default createRule('no-goto-without-base', { meta: { + deprecated: true, + replacedBy: ['no-navigation-without-base'], docs: { description: 'disallow using goto() without the base path', category: 'SvelteKit', From f6ba67106c25c2a9f818e78daddec915881d6bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Fri, 1 Nov 2024 19:02:08 +0100 Subject: [PATCH 3/9] test(no-navigation-without-base): added test for pushState, replaceState and links --- README.md | 2 +- docs/rules.md | 6 +++--- docs/rules/no-navigation-without-base.md | 4 ++-- packages/eslint-plugin-svelte/src/rule-types.ts | 2 +- ...oto01-errors.yaml => goto-aliased01-errors.yaml} | 0 ...o01-input.svelte => goto-aliased01-input.svelte} | 0 ...s.yaml => goto-base-not-as-prefix01-errors.yaml} | 0 ...velte => goto-base-not-as-prefix01-input.svelte} | 0 ...ase01-errors.yaml => goto-no-base01-errors.yaml} | 0 ...e01-input.svelte => goto-no-base01-input.svelte} | 0 .../invalid/link-base-not-as-prefix01-errors.yaml | 8 ++++++++ .../invalid/link-base-not-as-prefix01-input.svelte | 6 ++++++ .../invalid/link-no-base01-errors.yaml | 12 ++++++++++++ .../invalid/link-no-base01-input.svelte | 3 +++ .../invalid/pushState-aliased01-errors.yaml | 4 ++++ .../invalid/pushState-aliased01-input.svelte | 5 +++++ .../pushState-base-not-as-prefix01-errors.yaml | 8 ++++++++ .../pushState-base-not-as-prefix01-input.svelte | 7 +++++++ .../invalid/pushState-no-base01-errors.yaml | 4 ++++ .../invalid/pushState-no-base01-input.svelte | 5 +++++ .../invalid/replaceState-aliased01-errors.yaml | 5 +++++ .../invalid/replaceState-aliased01-input.svelte | 5 +++++ .../replaceState-base-not-as-prefix01-errors.yaml | 10 ++++++++++ .../replaceState-base-not-as-prefix01-input.svelte | 7 +++++++ .../invalid/replaceState-no-base01-errors.yaml | 5 +++++ .../invalid/replaceState-no-base01-input.svelte | 5 +++++ .../valid/absolute-uri01-input.svelte | 6 ------ ...nput.svelte => goto-base-aliased01-input.svelte} | 0 ...put.svelte => goto-base-prefixed01-input.svelte} | 0 .../valid/link-absolute-url01-input.svelte | 13 +++++++++++++ .../valid/link-base-aliased01-input.svelte | 6 ++++++ .../valid/link-base-prefixed01-input.svelte | 6 ++++++ .../valid/pushState-base-aliased01-input.svelte | 8 ++++++++ .../valid/pushState-base-prefixed01-input.svelte | 8 ++++++++ .../valid/pushState-empty-url01-input.svelte | 6 ++++++ .../valid/replaceState-base-aliased01-input.svelte | 8 ++++++++ .../valid/replaceState-base-prefixed01-input.svelte | 8 ++++++++ .../valid/replaceState-empty-url01-input.svelte | 6 ++++++ 38 files changed, 175 insertions(+), 13 deletions(-) rename packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/{aliased-goto01-errors.yaml => goto-aliased01-errors.yaml} (100%) rename packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/{aliased-goto01-input.svelte => goto-aliased01-input.svelte} (100%) rename packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/{base-not-prefixed01-errors.yaml => goto-base-not-as-prefix01-errors.yaml} (100%) rename packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/{base-not-prefixed01-input.svelte => goto-base-not-as-prefix01-input.svelte} (100%) rename packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/{no-base01-errors.yaml => goto-no-base01-errors.yaml} (100%) rename packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/{no-base01-input.svelte => goto-no-base01-input.svelte} (100%) create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-base-not-as-prefix01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-base-not-as-prefix01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-aliased01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-aliased01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-base-not-as-prefix01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-base-not-as-prefix01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-aliased01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-aliased01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-base-not-as-prefix01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-base-not-as-prefix01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-errors.yaml create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-input.svelte delete mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/absolute-uri01-input.svelte rename packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/{base-aliased01-input.svelte => goto-base-aliased01-input.svelte} (100%) rename packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/{base-prefixed01-input.svelte => goto-base-prefixed01-input.svelte} (100%) create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-absolute-url01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-base-aliased01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-base-prefixed01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-base-aliased01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-base-prefixed01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-empty-url01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-base-aliased01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-base-prefixed01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-empty-url01-input.svelte diff --git a/README.md b/README.md index e8ec2b3a7..b68009d48 100644 --- a/README.md +++ b/README.md @@ -473,7 +473,7 @@ These rules relate to SvelteKit and its best Practices. | Rule ID | Description | | |:--------|:------------|:---| -| [svelte/no-navigation-without-base](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-navigation-without-base/) | disallow using goto() without the base path | | +| [svelte/no-navigation-without-base](https://sveltejs.github.io/eslint-plugin-svelte/rules/no-navigation-without-base/) | disallow using navigation (links, goto, pushState, replaceState) without the base path | | ## Experimental diff --git a/docs/rules.md b/docs/rules.md index eb90c0a55..ff56134a4 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -108,9 +108,9 @@ These rules extend the rules provided by ESLint itself, or other plugins to work These rules relate to SvelteKit and its best Practices. -| Rule ID | Description | | -| :------------------------------------------------------------------------- | :------------------------------------------ | :-- | -| [svelte/no-navigation-without-base](./rules/no-navigation-without-base.md) | disallow using goto() without the base path | | +| Rule ID | Description | | +| :------------------------------------------------------------------------- | :------------------------------------------------------------------------------------- | :-- | +| [svelte/no-navigation-without-base](./rules/no-navigation-without-base.md) | disallow using navigation (links, goto, pushState, replaceState) without the base path | | ## Experimental diff --git a/docs/rules/no-navigation-without-base.md b/docs/rules/no-navigation-without-base.md index 39c81631b..b2704a247 100644 --- a/docs/rules/no-navigation-without-base.md +++ b/docs/rules/no-navigation-without-base.md @@ -2,13 +2,13 @@ pageClass: 'rule-details' sidebarDepth: 0 title: 'svelte/no-navigation-without-base' -description: 'disallow using goto() without the base path' +description: 'disallow using navigation (links, goto, pushState, replaceState) without the base path' since: 'v2.36.0-next.9' --- # svelte/no-navigation-without-base -> disallow using goto() without the base path +> disallow using navigation (links, goto, pushState, replaceState) without the base path ## :book: Rule Details diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts index da646f5a1..29cabe58c 100644 --- a/packages/eslint-plugin-svelte/src/rule-types.ts +++ b/packages/eslint-plugin-svelte/src/rule-types.ts @@ -176,7 +176,7 @@ export interface RuleOptions { */ 'svelte/no-inspect'?: Linter.RuleEntry<[]> /** - * disallow using goto() without the base path + * disallow using navigation (links, goto, pushState, replaceState) without the base path * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-navigation-without-base/ */ 'svelte/no-navigation-without-base'?: Linter.RuleEntry<[]> diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/aliased-goto01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-aliased01-errors.yaml similarity index 100% rename from packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/aliased-goto01-errors.yaml rename to packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-aliased01-errors.yaml diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/aliased-goto01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-aliased01-input.svelte similarity index 100% rename from packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/aliased-goto01-input.svelte rename to packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-aliased01-input.svelte diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/base-not-prefixed01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-base-not-as-prefix01-errors.yaml similarity index 100% rename from packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/base-not-prefixed01-errors.yaml rename to packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-base-not-as-prefix01-errors.yaml diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/base-not-prefixed01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-base-not-as-prefix01-input.svelte similarity index 100% rename from packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/base-not-prefixed01-input.svelte rename to packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-base-not-as-prefix01-input.svelte diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/no-base01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-no-base01-errors.yaml similarity index 100% rename from packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/no-base01-errors.yaml rename to packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-no-base01-errors.yaml diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/no-base01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-no-base01-input.svelte similarity index 100% rename from packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/no-base01-input.svelte rename to packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-no-base01-input.svelte diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-base-not-as-prefix01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-base-not-as-prefix01-errors.yaml new file mode 100644 index 000000000..087b29b87 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-base-not-as-prefix01-errors.yaml @@ -0,0 +1,8 @@ +- message: Found a link with a url that isn't prefixed with the base path. + line: 5 + column: 9 + suggestions: null +- message: Found a link with a url that isn't prefixed with the base path. + line: 6 + column: 9 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-base-not-as-prefix01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-base-not-as-prefix01-input.svelte new file mode 100644 index 000000000..6244c012c --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-base-not-as-prefix01-input.svelte @@ -0,0 +1,6 @@ + + +Click me! +Click me! diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-errors.yaml new file mode 100644 index 000000000..6b8f7a36e --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-errors.yaml @@ -0,0 +1,12 @@ +- message: Found a link with a url that isn't prefixed with the base path. + line: 1 + column: 10 + suggestions: null +- message: Found a link with a url that isn't prefixed with the base path. + line: 2 + column: 9 + suggestions: null +- message: Found a link with a url that isn't prefixed with the base path. + line: 3 + column: 9 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-input.svelte new file mode 100644 index 000000000..546ecda29 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-input.svelte @@ -0,0 +1,3 @@ +Click me! +Click me! +Click me! diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-aliased01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-aliased01-errors.yaml new file mode 100644 index 000000000..52bd762b5 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-aliased01-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a pushState() call with a url that isn't prefixed with the base path. + line: 4 + column: 8 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-aliased01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-aliased01-input.svelte new file mode 100644 index 000000000..066397bf9 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-aliased01-input.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-base-not-as-prefix01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-base-not-as-prefix01-errors.yaml new file mode 100644 index 000000000..5fc5eff37 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-base-not-as-prefix01-errors.yaml @@ -0,0 +1,8 @@ +- message: Found a pushState() call with a url that isn't prefixed with the base path. + line: 5 + column: 12 + suggestions: null +- message: Found a pushState() call with a url that isn't prefixed with the base path. + line: 6 + column: 12 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-base-not-as-prefix01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-base-not-as-prefix01-input.svelte new file mode 100644 index 000000000..cf7aacafa --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-base-not-as-prefix01-input.svelte @@ -0,0 +1,7 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-errors.yaml new file mode 100644 index 000000000..a814d0c45 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-errors.yaml @@ -0,0 +1,4 @@ +- message: Found a pushState() call with a url that isn't prefixed with the base path. + line: 4 + column: 12 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-input.svelte new file mode 100644 index 000000000..37ea2b65d --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-input.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-aliased01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-aliased01-errors.yaml new file mode 100644 index 000000000..5e60d7000 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-aliased01-errors.yaml @@ -0,0 +1,5 @@ +- message: Found a replaceState() call with a url that isn't prefixed with the + base path. + line: 4 + column: 8 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-aliased01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-aliased01-input.svelte new file mode 100644 index 000000000..de4169926 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-aliased01-input.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-base-not-as-prefix01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-base-not-as-prefix01-errors.yaml new file mode 100644 index 000000000..b8fbf0d99 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-base-not-as-prefix01-errors.yaml @@ -0,0 +1,10 @@ +- message: Found a replaceState() call with a url that isn't prefixed with the + base path. + line: 5 + column: 15 + suggestions: null +- message: Found a replaceState() call with a url that isn't prefixed with the + base path. + line: 6 + column: 15 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-base-not-as-prefix01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-base-not-as-prefix01-input.svelte new file mode 100644 index 000000000..0dd57d44a --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-base-not-as-prefix01-input.svelte @@ -0,0 +1,7 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-errors.yaml new file mode 100644 index 000000000..fdd0c4620 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-errors.yaml @@ -0,0 +1,5 @@ +- message: Found a replaceState() call with a url that isn't prefixed with the + base path. + line: 4 + column: 15 + suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-input.svelte new file mode 100644 index 000000000..5826d76b0 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-input.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/absolute-uri01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/absolute-uri01-input.svelte deleted file mode 100644 index 87ee8abf3..000000000 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/absolute-uri01-input.svelte +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/base-aliased01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/goto-base-aliased01-input.svelte similarity index 100% rename from packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/base-aliased01-input.svelte rename to packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/goto-base-aliased01-input.svelte diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/base-prefixed01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/goto-base-prefixed01-input.svelte similarity index 100% rename from packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/base-prefixed01-input.svelte rename to packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/goto-base-prefixed01-input.svelte diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-absolute-url01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-absolute-url01-input.svelte new file mode 100644 index 000000000..b7896c265 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-absolute-url01-input.svelte @@ -0,0 +1,13 @@ + + +Click me! +Click me! +Click me! +Click me! +Click me! +Click me! +Click me! +Click me! +Click me! diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-base-aliased01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-base-aliased01-input.svelte new file mode 100644 index 000000000..fe192e12f --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-base-aliased01-input.svelte @@ -0,0 +1,6 @@ + + +Click me!; +Click me!; diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-base-prefixed01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-base-prefixed01-input.svelte new file mode 100644 index 000000000..3bdaf021c --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-base-prefixed01-input.svelte @@ -0,0 +1,6 @@ + + +Click me! +Click me! diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-base-aliased01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-base-aliased01-input.svelte new file mode 100644 index 000000000..ca77f79a4 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-base-aliased01-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-base-prefixed01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-base-prefixed01-input.svelte new file mode 100644 index 000000000..66854e812 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-base-prefixed01-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-empty-url01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-empty-url01-input.svelte new file mode 100644 index 000000000..4a8db79d9 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-empty-url01-input.svelte @@ -0,0 +1,6 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-base-aliased01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-base-aliased01-input.svelte new file mode 100644 index 000000000..ad91c1585 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-base-aliased01-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-base-prefixed01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-base-prefixed01-input.svelte new file mode 100644 index 000000000..35b7082c2 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-base-prefixed01-input.svelte @@ -0,0 +1,8 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-empty-url01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-empty-url01-input.svelte new file mode 100644 index 000000000..96dae8e4d --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-empty-url01-input.svelte @@ -0,0 +1,6 @@ + From 154c2dc40de287cedc9b4118d17aa714e8a23bb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Sat, 2 Nov 2024 00:41:29 +0100 Subject: [PATCH 4/9] feat(no-navigation-without-base): added support for pushState and replaceState --- .../src/rules/no-navigation-without-base.ts | 214 ++++++++++++------ 1 file changed, 148 insertions(+), 66 deletions(-) diff --git a/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts b/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts index 7275c8ddb..334d59380 100644 --- a/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts +++ b/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts @@ -8,14 +8,19 @@ import type { RuleContext } from '../types'; export default createRule('no-navigation-without-base', { meta: { docs: { - description: 'disallow using goto() without the base path', + description: + 'disallow using navigation (links, goto, pushState, replaceState) without the base path', category: 'SvelteKit', recommended: false }, schema: [], messages: { - isNotPrefixedWithBasePath: - "Found a goto() call with a url that isn't prefixed with the base path." + gotoNotPrefixed: "Found a goto() call with a url that isn't prefixed with the base path.", + linkNotPrefixed: "Found a link with a url that isn't prefixed with the base path.", + pushStateNotPrefixed: + "Found a pushState() call with a url that isn't prefixed with the base path.", + replaceStateNotPrefixed: + "Found a replaceState() call with a url that isn't prefixed with the base path." }, type: 'suggestion' }, @@ -26,59 +31,153 @@ export default createRule('no-navigation-without-base', { getSourceCode(context).scopeManager.globalScope! ); const basePathNames = extractBasePathReferences(referenceTracker, context); - for (const gotoCall of extractGotoReferences(referenceTracker)) { - if (gotoCall.arguments.length < 1) { - continue; - } - const path = gotoCall.arguments[0]; - switch (path.type) { - case 'BinaryExpression': - checkBinaryExpression(context, path, basePathNames); - break; - case 'Literal': - checkLiteral(context, path); - break; - case 'TemplateLiteral': - checkTemplateLiteral(context, path, basePathNames); - break; - default: - context.report({ loc: path.loc, messageId: 'isNotPrefixedWithBasePath' }); - } + const { + goto: gotoCalls, + pushState: pushStateCalls, + replaceState: replaceStateCalls + } = extractFunctionCallReferences(referenceTracker); + for (const gotoCall of gotoCalls) { + checkGotoCall(context, gotoCall, basePathNames); + } + for (const pushStateCall of pushStateCalls) { + checkShallowNavigationCall(context, pushStateCall, basePathNames, 'pushStateNotPrefixed'); + } + for (const replaceStateCall of replaceStateCalls) { + checkShallowNavigationCall( + context, + replaceStateCall, + basePathNames, + 'replaceStateNotPrefixed' + ); } } }; } }); -function checkBinaryExpression( +// Extract all imports of the base path + +function extractBasePathReferences( + referenceTracker: ReferenceTracker, + context: RuleContext +): Set { + const set = new Set(); + for (const { node } of referenceTracker.iterateEsmReferences({ + '$app/paths': { + [ReferenceTracker.ESM]: true, + base: { + [ReferenceTracker.READ]: true + } + } + })) { + const variable = findVariable(context, (node as TSESTree.ImportSpecifier).local); + if (!variable) continue; + for (const reference of variable.references) { + if (reference.identifier.type === 'Identifier') set.add(reference.identifier); + } + } + return set; +} + +// Extract all references to goto, pushState and replaceState + +function extractFunctionCallReferences(referenceTracker: ReferenceTracker): { + goto: TSESTree.CallExpression[]; + pushState: TSESTree.CallExpression[]; + replaceState: TSESTree.CallExpression[]; +} { + const rawReferences = Array.from( + referenceTracker.iterateEsmReferences({ + '$app/navigation': { + [ReferenceTracker.ESM]: true, + goto: { + [ReferenceTracker.CALL]: true + }, + pushState: { + [ReferenceTracker.CALL]: true + }, + replaceState: { + [ReferenceTracker.CALL]: true + } + } + }) + ); + return { + goto: rawReferences + .filter(({ path }) => path[path.length - 1] === 'goto') + .map(({ node }) => node), + pushState: rawReferences + .filter(({ path }) => path[path.length - 1] === 'pushState') + .map(({ node }) => node), + replaceState: rawReferences + .filter(({ path }) => path[path.length - 1] === 'replaceState') + .map(({ node }) => node) + }; +} + +// Actual function checking + +function checkGotoCall( context: RuleContext, - path: TSESTree.BinaryExpression, + call: TSESTree.CallExpression, basePathNames: Set ): void { - if (path.left.type !== 'Identifier' || !basePathNames.has(path.left)) { - context.report({ loc: path.loc, messageId: 'isNotPrefixedWithBasePath' }); + if (call.arguments.length < 1) { + return; + } + const url = call.arguments[0]; + if (!urlStartsWithBase(url, basePathNames)) { + context.report({ loc: url.loc, messageId: 'gotoNotPrefixed' }); } } -function checkTemplateLiteral( +function checkShallowNavigationCall( context: RuleContext, - path: TSESTree.TemplateLiteral, - basePathNames: Set + call: TSESTree.CallExpression, + basePathNames: Set, + messageId: string ): void { - const startingIdentifier = extractStartingIdentifier(path); - if (startingIdentifier === undefined || !basePathNames.has(startingIdentifier)) { - context.report({ loc: path.loc, messageId: 'isNotPrefixedWithBasePath' }); + if (call.arguments.length < 1) { + return; + } + const url = call.arguments[0]; + if (!urlIsEmpty(url) && !urlStartsWithBase(url, basePathNames)) { + context.report({ loc: url.loc, messageId }); } } -function checkLiteral(context: RuleContext, path: TSESTree.Literal): void { - const absolutePathRegex = /^(?:[+a-z]+:)?\/\//i; - if (!absolutePathRegex.test(path.value?.toString() ?? '')) { - context.report({ loc: path.loc, messageId: 'isNotPrefixedWithBasePath' }); +// Helper functions + +function urlStartsWithBase( + url: TSESTree.CallExpressionArgument, + basePathNames: Set +): boolean { + switch (url.type) { + case 'BinaryExpression': + return binaryExpressionStartsWithBase(url, basePathNames); + case 'TemplateLiteral': + return templateLiteralStartsWithBase(url, basePathNames); + default: + return false; } } -function extractStartingIdentifier( +function binaryExpressionStartsWithBase( + url: TSESTree.BinaryExpression, + basePathNames: Set +): boolean { + return url.left.type === 'Identifier' && basePathNames.has(url.left); +} + +function templateLiteralStartsWithBase( + url: TSESTree.TemplateLiteral, + basePathNames: Set +): boolean { + const startingIdentifier = extractLiteralStartingIdentifier(url); + return startingIdentifier !== undefined && basePathNames.has(startingIdentifier); +} + +function extractLiteralStartingIdentifier( templateLiteral: TSESTree.TemplateLiteral ): TSESTree.Identifier | undefined { const literalParts = [...templateLiteral.expressions, ...templateLiteral.quasis].sort((a, b) => @@ -97,38 +196,21 @@ function extractStartingIdentifier( return undefined; } -function extractGotoReferences(referenceTracker: ReferenceTracker): TSESTree.CallExpression[] { - return Array.from( - referenceTracker.iterateEsmReferences({ - '$app/navigation': { - [ReferenceTracker.ESM]: true, - goto: { - [ReferenceTracker.CALL]: true - } - } - }), - ({ node }) => node +function urlIsEmpty(url: TSESTree.CallExpressionArgument): boolean { + return ( + (url.type === 'Literal' && url.value === '') || + (url.type === 'TemplateLiteral' && + url.expressions.length === 0 && + url.quasis.length === 1 && + url.quasis[0].value.raw === '') ); } -function extractBasePathReferences( - referenceTracker: ReferenceTracker, - context: RuleContext -): Set { - const set = new Set(); - for (const { node } of referenceTracker.iterateEsmReferences({ - '$app/paths': { - [ReferenceTracker.ESM]: true, - base: { - [ReferenceTracker.READ]: true - } - } - })) { - const variable = findVariable(context, (node as TSESTree.ImportSpecifier).local); - if (!variable) continue; - for (const reference of variable.references) { - if (reference.identifier.type === 'Identifier') set.add(reference.identifier); - } +/* +function checkLiteral(context: RuleContext, url: TSESTree.Literal): void { + const absolutePathRegex = /^(?:[+a-z]+:)?\/\//i; + if (!absolutePathRegex.test(url.value?.toString() ?? '')) { + context.report({ loc: url.loc, messageId: 'gotoNotPrefixed' }); } - return set; } +*/ From 4054b8cfd267e7ab28501fccc3f920966fcecd4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Sun, 3 Nov 2024 01:36:49 +0100 Subject: [PATCH 5/9] feat(no-navigation-without-base): added support for links --- .../src/rules/no-navigation-without-base.ts | 63 ++++++++++++++++--- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts b/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts index 334d59380..cd12883d4 100644 --- a/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts +++ b/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts @@ -4,6 +4,7 @@ import { ReferenceTracker } from '@eslint-community/eslint-utils'; import { getSourceCode } from '../utils/compat'; import { findVariable } from '../utils/ast-utils'; import type { RuleContext } from '../types'; +import type { SvelteLiteral } from 'svelte-eslint-parser/lib/ast'; export default createRule('no-navigation-without-base', { meta: { @@ -25,12 +26,13 @@ export default createRule('no-navigation-without-base', { type: 'suggestion' }, create(context) { + let basePathNames: Set = new Set(); return { Program() { const referenceTracker = new ReferenceTracker( getSourceCode(context).scopeManager.globalScope! ); - const basePathNames = extractBasePathReferences(referenceTracker, context); + basePathNames = extractBasePathReferences(referenceTracker, context); const { goto: gotoCalls, pushState: pushStateCalls, @@ -50,6 +52,30 @@ export default createRule('no-navigation-without-base', { 'replaceStateNotPrefixed' ); } + }, + SvelteAttribute(node) { + if ( + node.parent.parent.type !== 'SvelteElement' || + node.parent.parent.kind !== 'html' || + node.parent.parent.name.type !== 'SvelteName' || + node.parent.parent.name.name !== 'a' || + node.key.name !== 'href' + ) { + return; + } + const hrefValue = node.value[0]; + if (hrefValue.type === 'SvelteLiteral') { + if (!urlIsAbsolute(hrefValue)) { + context.report({ loc: hrefValue.loc, messageId: 'linkNotPrefixed' }); + } + return; + } + if ( + !urlStartsWithBase(hrefValue.expression, basePathNames) && + !urlIsAbsolute(hrefValue.expression) + ) { + context.report({ loc: hrefValue.loc, messageId: 'linkNotPrefixed' }); + } } }; } @@ -206,11 +232,34 @@ function urlIsEmpty(url: TSESTree.CallExpressionArgument): boolean { ); } -/* -function checkLiteral(context: RuleContext, url: TSESTree.Literal): void { - const absolutePathRegex = /^(?:[+a-z]+:)?\/\//i; - if (!absolutePathRegex.test(url.value?.toString() ?? '')) { - context.report({ loc: url.loc, messageId: 'gotoNotPrefixed' }); +function urlIsAbsolute(url: SvelteLiteral | TSESTree.Expression): boolean { + switch (url.type) { + case 'BinaryExpression': + return binaryExpressionIsAbsolute(url); + case 'Literal': + return typeof url.value === 'string' && urlValueIsAbsolute(url.value); + case 'SvelteLiteral': + return urlValueIsAbsolute(url.value); + case 'TemplateLiteral': + return templateLiteralIsAbsolute(url); + default: + return false; } } -*/ + +function binaryExpressionIsAbsolute(url: TSESTree.BinaryExpression): boolean { + return ( + (url.left.type !== 'PrivateIdentifier' && urlIsAbsolute(url.left)) || urlIsAbsolute(url.right) + ); +} + +function templateLiteralIsAbsolute(url: TSESTree.TemplateLiteral): boolean { + return ( + url.expressions.some(urlIsAbsolute) || + url.quasis.some((quasi) => urlValueIsAbsolute(quasi.value.raw)) + ); +} + +function urlValueIsAbsolute(url: string): boolean { + return url.includes('://'); +} From 9c8206bedaa645e9c42d4a33d27d08c7d7ca39c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Sun, 3 Nov 2024 18:47:20 +0100 Subject: [PATCH 6/9] feat(no-navigation-without-base): added configuration options --- .../eslint-plugin-svelte/src/rule-types.ts | 9 ++- .../src/rules/no-navigation-without-base.ts | 55 +++++++++++++++---- .../valid/ignoreGoto/_config.json | 7 +++ .../ignoreGoto/goto-ignored01-input.svelte | 5 ++ .../valid/ignoreLinks/_config.json | 7 +++ .../ignoreLinks/link-ignored01-input.svelte | 3 + .../valid/ignorePushState/_config.json | 7 +++ .../pushState-ignored01-input.svelte | 5 ++ .../valid/ignoreReplaceState/_config.json | 7 +++ .../replaceState-ignored01-input.svelte | 5 ++ 10 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreGoto/_config.json create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreGoto/goto-ignored01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreLinks/_config.json create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreLinks/link-ignored01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignorePushState/_config.json create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignorePushState/pushState-ignored01-input.svelte create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreReplaceState/_config.json create mode 100644 packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreReplaceState/replaceState-ignored01-input.svelte diff --git a/packages/eslint-plugin-svelte/src/rule-types.ts b/packages/eslint-plugin-svelte/src/rule-types.ts index 29cabe58c..1c5fd96cc 100644 --- a/packages/eslint-plugin-svelte/src/rule-types.ts +++ b/packages/eslint-plugin-svelte/src/rule-types.ts @@ -179,7 +179,7 @@ export interface RuleOptions { * disallow using navigation (links, goto, pushState, replaceState) without the base path * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-navigation-without-base/ */ - 'svelte/no-navigation-without-base'?: Linter.RuleEntry<[]> + 'svelte/no-navigation-without-base'?: Linter.RuleEntry /** * disallow use of not function in event handler * @see https://sveltejs.github.io/eslint-plugin-svelte/rules/no-not-function-handler/ @@ -443,6 +443,13 @@ type SvelteNoInlineStyles = []|[{ type SvelteNoInnerDeclarations = []|[("functions" | "both")]|[("functions" | "both"), { blockScopedFunctions?: ("allow" | "disallow") }] +// ----- svelte/no-navigation-without-base ----- +type SvelteNoNavigationWithoutBase = []|[{ + ignoreGoto?: boolean + ignoreLinks?: boolean + ignorePushState?: boolean + ignoreReplaceState?: boolean +}] // ----- svelte/no-reactive-reassign ----- type SvelteNoReactiveReassign = []|[{ props?: boolean diff --git a/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts b/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts index cd12883d4..3befcad7c 100644 --- a/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts +++ b/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts @@ -14,7 +14,26 @@ export default createRule('no-navigation-without-base', { category: 'SvelteKit', recommended: false }, - schema: [], + schema: [ + { + type: 'object', + properties: { + ignoreGoto: { + type: 'boolean' + }, + ignoreLinks: { + type: 'boolean' + }, + ignorePushState: { + type: 'boolean' + }, + ignoreReplaceState: { + type: 'boolean' + } + }, + additionalProperties: false + } + ], messages: { gotoNotPrefixed: "Found a goto() call with a url that isn't prefixed with the base path.", linkNotPrefixed: "Found a link with a url that isn't prefixed with the base path.", @@ -38,23 +57,35 @@ export default createRule('no-navigation-without-base', { pushState: pushStateCalls, replaceState: replaceStateCalls } = extractFunctionCallReferences(referenceTracker); - for (const gotoCall of gotoCalls) { - checkGotoCall(context, gotoCall, basePathNames); + if (context.options[0]?.ignoreGoto !== true) { + for (const gotoCall of gotoCalls) { + checkGotoCall(context, gotoCall, basePathNames); + } } - for (const pushStateCall of pushStateCalls) { - checkShallowNavigationCall(context, pushStateCall, basePathNames, 'pushStateNotPrefixed'); + if (context.options[0]?.ignorePushState !== true) { + for (const pushStateCall of pushStateCalls) { + checkShallowNavigationCall( + context, + pushStateCall, + basePathNames, + 'pushStateNotPrefixed' + ); + } } - for (const replaceStateCall of replaceStateCalls) { - checkShallowNavigationCall( - context, - replaceStateCall, - basePathNames, - 'replaceStateNotPrefixed' - ); + if (context.options[0]?.ignoreReplaceState !== true) { + for (const replaceStateCall of replaceStateCalls) { + checkShallowNavigationCall( + context, + replaceStateCall, + basePathNames, + 'replaceStateNotPrefixed' + ); + } } }, SvelteAttribute(node) { if ( + context.options[0]?.ignoreLinks === true || node.parent.parent.type !== 'SvelteElement' || node.parent.parent.kind !== 'html' || node.parent.parent.name.type !== 'SvelteName' || diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreGoto/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreGoto/_config.json new file mode 100644 index 000000000..f21168640 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreGoto/_config.json @@ -0,0 +1,7 @@ +{ + "options": [ + { + "ignoreGoto": true + } + ] +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreGoto/goto-ignored01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreGoto/goto-ignored01-input.svelte new file mode 100644 index 000000000..6f011fe2d --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreGoto/goto-ignored01-input.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreLinks/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreLinks/_config.json new file mode 100644 index 000000000..fd03211ab --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreLinks/_config.json @@ -0,0 +1,7 @@ +{ + "options": [ + { + "ignoreLinks": true + } + ] +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreLinks/link-ignored01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreLinks/link-ignored01-input.svelte new file mode 100644 index 000000000..546ecda29 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreLinks/link-ignored01-input.svelte @@ -0,0 +1,3 @@ +Click me! +Click me! +Click me! diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignorePushState/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignorePushState/_config.json new file mode 100644 index 000000000..7fbdd8b77 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignorePushState/_config.json @@ -0,0 +1,7 @@ +{ + "options": [ + { + "ignorePushState": true + } + ] +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignorePushState/pushState-ignored01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignorePushState/pushState-ignored01-input.svelte new file mode 100644 index 000000000..37ea2b65d --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignorePushState/pushState-ignored01-input.svelte @@ -0,0 +1,5 @@ + diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreReplaceState/_config.json b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreReplaceState/_config.json new file mode 100644 index 000000000..a678a15a8 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreReplaceState/_config.json @@ -0,0 +1,7 @@ +{ + "options": [ + { + "ignoreReplaceState": true + } + ] +} diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreReplaceState/replaceState-ignored01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreReplaceState/replaceState-ignored01-input.svelte new file mode 100644 index 000000000..5826d76b0 --- /dev/null +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/ignoreReplaceState/replaceState-ignored01-input.svelte @@ -0,0 +1,5 @@ + From 86898b21ea9afc9c3b0aaf29eb7f86a801c55b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Sun, 3 Nov 2024 19:49:04 +0100 Subject: [PATCH 7/9] docs(no-navigation-without-base): documented the new rule --- docs/rules/no-navigation-without-base.md | 56 +++++++++++++++++++----- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/docs/rules/no-navigation-without-base.md b/docs/rules/no-navigation-without-base.md index b2704a247..b9a532139 100644 --- a/docs/rules/no-navigation-without-base.md +++ b/docs/rules/no-navigation-without-base.md @@ -12,7 +12,9 @@ since: 'v2.36.0-next.9' ## :book: Rule Details -This rule reports navigation using SvelteKit's `goto()` function without prefixing a relative URL with the base path. If a non-prefixed relative URL is used for navigation, the `goto` function navigates away from the base path, which is usually not what you wanted to do (for external URLs, `window.location = url` should be used instead). +This rule reports navigation using HTML `` tags, SvelteKit's `goto()`, `pushState()` and `replaceState()` functions without prefixing a relative URL with the base path. All four of these may be used for navigation, with `goto()`, `pushState()` and `replaceState()` being intended solely for iternal navigation (i.e. not leaving the site), while `` tags may be used for both internal and external navigation. When using any way of internal navigation, the base path must be prepended, otherwise the site may break. For programmatic navigation to external URLs, using `window.location` is advised. + +This rule checks all 4 navigation options for the presence of the base path, with an exception for `` links to absolute URLs, which are assumed to be used for external navigation and so do not require the base path, and for shallow outing functions with an empty string as the path, which keeps the current URL. @@ -22,37 +24,69 @@ This rule reports navigation using SvelteKit's `goto()` function without prefixi + + +Click me! +Click me! +Click me! + + +Click me! +Click me! ``` ## :wrench: Options -Nothing. +```json +{ + "svelte/no-navigation-without-base": [ + "error", + { + "ignoreGoto": false, + "ignoreLinks": false, + "ignorePushState": false, + "ignoreReplaceState": false + } + ] +} +``` + +- `ignoreGoto` ... Whether to ignore all `goto()` calls. Default `false`. +- `ignoreLinks` ... Whether to ignore all `` tags. Default `false`. +- `ignorePushState` ... Whether to ignore all `pushState()` calls. Default `false`. +- `ignoreReplaceState` ... Whether to ignore all `replaceState()` calls. Default `false`. ## :books: Further Reading -- [`goto()` documentation](https://kit.svelte.dev/docs/modules#$app-navigation-goto) -- [`base` documentation](https://kit.svelte.dev/docs/modules#$app-paths-base) +- [`base` documentation](https://svelte.dev/docs/kit/$app-paths#base) +- [Shallow routing](https://svelte.dev/docs/kit/shallow-routing) +- [`goto()` documentation](https://svelte.dev/docs/kit/$app-navigation#goto) +- [`pushState()` documentation](https://svelte.dev/docs/kit/$app-navigation#pushState) +- [`replaceState()` documentation](https://svelte.dev/docs/kit/$app-navigation#replaceState) ## :rocket: Version From 8a9a43d5a3e29eb87932e1b9b7fa1f9842e93676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Sun, 3 Nov 2024 22:15:40 +0100 Subject: [PATCH 8/9] feat(no-navigation-without-base): added support for urls defined in variables --- .../src/rules/no-navigation-without-base.ts | 73 ++++++++++++++----- .../invalid/goto-no-base01-errors.yaml | 6 +- .../invalid/goto-no-base01-input.svelte | 3 + .../invalid/link-no-base01-errors.yaml | 10 ++- .../invalid/link-no-base01-input.svelte | 4 + .../invalid/pushState-no-base01-errors.yaml | 6 +- .../invalid/pushState-no-base01-input.svelte | 3 + .../replaceState-no-base01-errors.yaml | 7 +- .../replaceState-no-base01-input.svelte | 3 + .../valid/goto-base-prefixed01-input.svelte | 5 ++ .../valid/link-base-prefixed01-input.svelte | 5 ++ .../pushState-base-prefixed01-input.svelte | 5 ++ .../replaceState-base-prefixed01-input.svelte | 5 ++ 13 files changed, 110 insertions(+), 25 deletions(-) diff --git a/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts b/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts index 3befcad7c..429315545 100644 --- a/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts +++ b/packages/eslint-plugin-svelte/src/rules/no-navigation-without-base.ts @@ -96,14 +96,14 @@ export default createRule('no-navigation-without-base', { } const hrefValue = node.value[0]; if (hrefValue.type === 'SvelteLiteral') { - if (!urlIsAbsolute(hrefValue)) { + if (!expressionIsAbsolute(hrefValue)) { context.report({ loc: hrefValue.loc, messageId: 'linkNotPrefixed' }); } return; } if ( - !urlStartsWithBase(hrefValue.expression, basePathNames) && - !urlIsAbsolute(hrefValue.expression) + !expressionStartsWithBase(context, hrefValue.expression, basePathNames) && + !expressionIsAbsolute(hrefValue.expression) ) { context.report({ loc: hrefValue.loc, messageId: 'linkNotPrefixed' }); } @@ -183,7 +183,7 @@ function checkGotoCall( return; } const url = call.arguments[0]; - if (!urlStartsWithBase(url, basePathNames)) { + if (url.type === 'SpreadElement' || !expressionStartsWithBase(context, url, basePathNames)) { context.report({ loc: url.loc, messageId: 'gotoNotPrefixed' }); } } @@ -198,45 +198,79 @@ function checkShallowNavigationCall( return; } const url = call.arguments[0]; - if (!urlIsEmpty(url) && !urlStartsWithBase(url, basePathNames)) { + if ( + url.type === 'SpreadElement' || + (!expressionIsEmpty(url) && !expressionStartsWithBase(context, url, basePathNames)) + ) { context.report({ loc: url.loc, messageId }); } } // Helper functions -function urlStartsWithBase( - url: TSESTree.CallExpressionArgument, +function expressionStartsWithBase( + context: RuleContext, + url: TSESTree.Expression, basePathNames: Set ): boolean { switch (url.type) { case 'BinaryExpression': - return binaryExpressionStartsWithBase(url, basePathNames); + return binaryExpressionStartsWithBase(context, url, basePathNames); + case 'Identifier': + return variableStartsWithBase(context, url, basePathNames); case 'TemplateLiteral': - return templateLiteralStartsWithBase(url, basePathNames); + return templateLiteralStartsWithBase(context, url, basePathNames); default: return false; } } function binaryExpressionStartsWithBase( + context: RuleContext, url: TSESTree.BinaryExpression, basePathNames: Set ): boolean { - return url.left.type === 'Identifier' && basePathNames.has(url.left); + return ( + url.left.type !== 'PrivateIdentifier' && + expressionStartsWithBase(context, url.left, basePathNames) + ); +} + +function variableStartsWithBase( + context: RuleContext, + url: TSESTree.Identifier, + basePathNames: Set +): boolean { + if (basePathNames.has(url)) { + return true; + } + const variable = findVariable(context, url); + if ( + variable === null || + variable.identifiers.length !== 1 || + variable.identifiers[0].parent.type !== 'VariableDeclarator' || + variable.identifiers[0].parent.init === null + ) { + return false; + } + return expressionStartsWithBase(context, variable.identifiers[0].parent.init, basePathNames); } function templateLiteralStartsWithBase( + context: RuleContext, url: TSESTree.TemplateLiteral, basePathNames: Set ): boolean { - const startingIdentifier = extractLiteralStartingIdentifier(url); - return startingIdentifier !== undefined && basePathNames.has(startingIdentifier); + const startingIdentifier = extractLiteralStartingExpression(url); + return ( + startingIdentifier !== undefined && + expressionStartsWithBase(context, startingIdentifier, basePathNames) + ); } -function extractLiteralStartingIdentifier( +function extractLiteralStartingExpression( templateLiteral: TSESTree.TemplateLiteral -): TSESTree.Identifier | undefined { +): TSESTree.Expression | undefined { const literalParts = [...templateLiteral.expressions, ...templateLiteral.quasis].sort((a, b) => a.range[0] < b.range[0] ? -1 : 1 ); @@ -245,7 +279,7 @@ function extractLiteralStartingIdentifier( // Skip empty quasi in the begining continue; } - if (part.type === 'Identifier') { + if (part.type !== 'TemplateElement') { return part; } return undefined; @@ -253,7 +287,7 @@ function extractLiteralStartingIdentifier( return undefined; } -function urlIsEmpty(url: TSESTree.CallExpressionArgument): boolean { +function expressionIsEmpty(url: TSESTree.Expression): boolean { return ( (url.type === 'Literal' && url.value === '') || (url.type === 'TemplateLiteral' && @@ -263,7 +297,7 @@ function urlIsEmpty(url: TSESTree.CallExpressionArgument): boolean { ); } -function urlIsAbsolute(url: SvelteLiteral | TSESTree.Expression): boolean { +function expressionIsAbsolute(url: SvelteLiteral | TSESTree.Expression): boolean { switch (url.type) { case 'BinaryExpression': return binaryExpressionIsAbsolute(url); @@ -280,13 +314,14 @@ function urlIsAbsolute(url: SvelteLiteral | TSESTree.Expression): boolean { function binaryExpressionIsAbsolute(url: TSESTree.BinaryExpression): boolean { return ( - (url.left.type !== 'PrivateIdentifier' && urlIsAbsolute(url.left)) || urlIsAbsolute(url.right) + (url.left.type !== 'PrivateIdentifier' && expressionIsAbsolute(url.left)) || + expressionIsAbsolute(url.right) ); } function templateLiteralIsAbsolute(url: TSESTree.TemplateLiteral): boolean { return ( - url.expressions.some(urlIsAbsolute) || + url.expressions.some(expressionIsAbsolute) || url.quasis.some((quasi) => urlValueIsAbsolute(quasi.value.raw)) ); } diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-no-base01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-no-base01-errors.yaml index 658fcb47d..ed8b9578e 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-no-base01-errors.yaml +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-no-base01-errors.yaml @@ -1,4 +1,8 @@ - message: Found a goto() call with a url that isn't prefixed with the base path. - line: 4 + line: 6 + column: 7 + suggestions: null +- message: Found a goto() call with a url that isn't prefixed with the base path. + line: 7 column: 7 suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-no-base01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-no-base01-input.svelte index 6f011fe2d..7cb58ea20 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-no-base01-input.svelte +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/goto-no-base01-input.svelte @@ -1,5 +1,8 @@ diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-errors.yaml index 6b8f7a36e..e006d4d27 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-errors.yaml +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-errors.yaml @@ -1,12 +1,16 @@ - message: Found a link with a url that isn't prefixed with the base path. - line: 1 + line: 4 column: 10 suggestions: null - message: Found a link with a url that isn't prefixed with the base path. - line: 2 + line: 5 column: 9 suggestions: null - message: Found a link with a url that isn't prefixed with the base path. - line: 3 + line: 6 + column: 9 + suggestions: null +- message: Found a link with a url that isn't prefixed with the base path. + line: 7 column: 9 suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-input.svelte index 546ecda29..0e0f6df25 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-input.svelte +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/link-no-base01-input.svelte @@ -1,3 +1,7 @@ + Click me! Click me! Click me! +Click me! diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-errors.yaml index a814d0c45..e70710ad0 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-errors.yaml +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-errors.yaml @@ -1,4 +1,8 @@ - message: Found a pushState() call with a url that isn't prefixed with the base path. - line: 4 + line: 6 + column: 12 + suggestions: null +- message: Found a pushState() call with a url that isn't prefixed with the base path. + line: 7 column: 12 suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-input.svelte index 37ea2b65d..19fe76cf3 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-input.svelte +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/pushState-no-base01-input.svelte @@ -1,5 +1,8 @@ diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-errors.yaml b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-errors.yaml index fdd0c4620..dd4d0d177 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-errors.yaml +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-errors.yaml @@ -1,5 +1,10 @@ - message: Found a replaceState() call with a url that isn't prefixed with the base path. - line: 4 + line: 6 + column: 15 + suggestions: null +- message: Found a replaceState() call with a url that isn't prefixed with the + base path. + line: 7 column: 15 suggestions: null diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-input.svelte index 5826d76b0..4738c2ccb 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-input.svelte +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/invalid/replaceState-no-base01-input.svelte @@ -1,5 +1,8 @@ diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/goto-base-prefixed01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/goto-base-prefixed01-input.svelte index cd7177deb..22602dfc8 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/goto-base-prefixed01-input.svelte +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/goto-base-prefixed01-input.svelte @@ -2,7 +2,12 @@ import { base } from '$app/paths'; import { goto } from '$app/navigation'; + const value1 = base + '/foo/'; + const value2 = `${base}/foo/`; + // eslint-disable-next-line prefer-template -- Testing both variants goto(base + '/foo/'); goto(`${base}/foo/`); + goto(value1); + goto(value2); diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-base-prefixed01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-base-prefixed01-input.svelte index 3bdaf021c..b86295824 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-base-prefixed01-input.svelte +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/link-base-prefixed01-input.svelte @@ -1,6 +1,11 @@ Click me! Click me! +Click me! +Click me! diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-base-prefixed01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-base-prefixed01-input.svelte index 66854e812..468201793 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-base-prefixed01-input.svelte +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/pushState-base-prefixed01-input.svelte @@ -2,7 +2,12 @@ import { base } from '$app/paths'; import { pushState } from '$app/navigation'; + const value1 = base + '/foo/'; + const value2 = `${base}/foo/`; + // eslint-disable-next-line prefer-template -- Testing both variants pushState(base + '/foo/'); pushState(`${base}/foo/`); + pushState(value1); + pushState(value2); diff --git a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-base-prefixed01-input.svelte b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-base-prefixed01-input.svelte index 35b7082c2..7df67a5d5 100644 --- a/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-base-prefixed01-input.svelte +++ b/packages/eslint-plugin-svelte/tests/fixtures/rules/no-navigation-without-base/valid/replaceState-base-prefixed01-input.svelte @@ -2,7 +2,12 @@ import { base } from '$app/paths'; import { replaceState } from '$app/navigation'; + const value1 = base + '/foo/'; + const value2 = `${base}/foo/`; + // eslint-disable-next-line prefer-template -- Testing both variants replaceState(base + '/foo/'); replaceState(`${base}/foo/`); + replaceState(value1); + replaceState(value2); From daa9785a26879742012357ee87eab8206f640824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20D=C4=9Bdi=C4=8D?= Date: Mon, 4 Nov 2024 01:13:10 +0100 Subject: [PATCH 9/9] chore(no-navigation-without-base): added changesets --- .changeset/cold-starfishes-doubt.md | 5 +++++ .changeset/olive-melons-explain.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/cold-starfishes-doubt.md create mode 100644 .changeset/olive-melons-explain.md diff --git a/.changeset/cold-starfishes-doubt.md b/.changeset/cold-starfishes-doubt.md new file mode 100644 index 000000000..f85c6b39a --- /dev/null +++ b/.changeset/cold-starfishes-doubt.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-svelte': minor +--- + +feat: added the no-navigation-without-base rule diff --git a/.changeset/olive-melons-explain.md b/.changeset/olive-melons-explain.md new file mode 100644 index 000000000..6571a5bbf --- /dev/null +++ b/.changeset/olive-melons-explain.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-svelte': major +--- + +chore: deprecated the no-goto-without-base rule