-
-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added the
no-goto-without-base
rule (#679)
- Loading branch information
1 parent
d4303f5
commit 4e6c681
Showing
20 changed files
with
295 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"eslint-plugin-svelte": minor | ||
--- | ||
|
||
feat: added the no-goto-without-base rule |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
--- | ||
pageClass: 'rule-details' | ||
sidebarDepth: 0 | ||
title: 'svelte/no-goto-without-base' | ||
description: 'disallow using goto() without the base path' | ||
--- | ||
|
||
# svelte/no-goto-without-base | ||
|
||
> disallow using goto() without the base path | ||
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge> | ||
|
||
## :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). | ||
|
||
<ESLintCodeBlock> | ||
|
||
<!--eslint-skip--> | ||
|
||
```svelte | ||
<script> | ||
/* eslint svelte/no-goto-without-base: "error" */ | ||
import { goto } from '$app/navigation'; | ||
import { base } from '$app/paths'; | ||
import { base as baseAlias } from '$app/paths'; | ||
// ✓ GOOD | ||
goto(base + '/foo/'); | ||
goto(`${base}/foo/`); | ||
goto(baseAlias + '/foo/'); | ||
goto(`${baseAlias}/foo/`); | ||
goto('https://localhost/foo/'); | ||
// ✗ BAD | ||
goto('/foo'); | ||
goto('/foo/' + base); | ||
goto(`/foo/${base}`); | ||
</script> | ||
``` | ||
|
||
</ESLintCodeBlock> | ||
|
||
## :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) | ||
|
||
## :mag: Implementation | ||
|
||
- [Rule source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/src/rules/no-goto-without-base.ts) | ||
- [Test source](https://github.com/sveltejs/eslint-plugin-svelte/blob/main/tests/src/rules/no-goto-without-base.ts) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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-goto-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<TSESTree.Identifier> | ||
): 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<TSESTree.Identifier> | ||
): 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<TSESTree.Identifier> { | ||
const set = new Set<TSESTree.Identifier>(); | ||
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 4 additions & 0 deletions
4
tests/fixtures/rules/no-goto-without-base/invalid/aliased-goto01-errors.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
5 changes: 5 additions & 0 deletions
5
tests/fixtures/rules/no-goto-without-base/invalid/aliased-goto01-input.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<script> | ||
import { goto as alias } from '$app/navigation'; | ||
alias('/foo'); | ||
</script> |
8 changes: 8 additions & 0 deletions
8
tests/fixtures/rules/no-goto-without-base/invalid/base-not-prefixed01-errors.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
- message: Found a goto() call with a url that isn't prefixed with the base path. | ||
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 |
8 changes: 8 additions & 0 deletions
8
tests/fixtures/rules/no-goto-without-base/invalid/base-not-prefixed01-input.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<script> | ||
import { base } from '$app/paths'; | ||
import { goto } from '$app/navigation'; | ||
// eslint-disable-next-line prefer-template -- Testing both variants | ||
goto('/foo/' + base); | ||
goto(`/foo/${base}`); | ||
</script> |
4 changes: 4 additions & 0 deletions
4
tests/fixtures/rules/no-goto-without-base/invalid/no-base01-errors.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
5 changes: 5 additions & 0 deletions
5
tests/fixtures/rules/no-goto-without-base/invalid/no-base01-input.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<script> | ||
import { goto } from '$app/navigation'; | ||
goto('/foo'); | ||
</script> |
6 changes: 6 additions & 0 deletions
6
tests/fixtures/rules/no-goto-without-base/valid/absolute-uri01-input.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<script> | ||
import { goto } from '$app/navigation'; | ||
goto('http://localhost/foo/'); | ||
goto('https://localhost/foo/'); | ||
</script> |
8 changes: 8 additions & 0 deletions
8
tests/fixtures/rules/no-goto-without-base/valid/base-aliased01-input.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<script> | ||
import { base as alias } from '$app/paths'; | ||
import { goto } from '$app/navigation'; | ||
// eslint-disable-next-line prefer-template -- Testing both variants | ||
goto(alias + '/foo/'); | ||
goto(`${alias}/foo/`); | ||
</script> |
8 changes: 8 additions & 0 deletions
8
tests/fixtures/rules/no-goto-without-base/valid/base-prefixed01-input.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<script> | ||
import { base } from '$app/paths'; | ||
import { goto } from '$app/navigation'; | ||
// eslint-disable-next-line prefer-template -- Testing both variants | ||
goto(base + '/foo/'); | ||
goto(`${base}/foo/`); | ||
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { RuleTester } from '../../utils/eslint-compat'; | ||
import rule from '../../../src/rules/no-goto-without-base'; | ||
import { loadTestCases } from '../../utils/utils'; | ||
|
||
const tester = new RuleTester({ | ||
languageOptions: { | ||
ecmaVersion: 2020, | ||
sourceType: 'module' | ||
} | ||
}); | ||
|
||
tester.run('no-goto-without-base', rule as any, loadTestCases('no-goto-without-base')); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters