Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add astro/semi rule #105

Merged
merged 2 commits into from
Sep 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/little-kangaroos-invent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eslint-plugin-astro": minor
---

feat: add `astro/semi` rule
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
/tests/fixtures/rules/semi/**/*.astro
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
| [astro/prefer-class-list-directive](https://ota-meshi.github.io/eslint-plugin-astro/rules/prefer-class-list-directive/) | require `class:list` directives instead of `class` with expressions | :wrench: |
| [astro/prefer-object-class-list](https://ota-meshi.github.io/eslint-plugin-astro/rules/prefer-object-class-list/) | require use object instead of ternary expression in `class:list` | :wrench: |
| [astro/prefer-split-class-list](https://ota-meshi.github.io/eslint-plugin-astro/rules/prefer-split-class-list/) | require use split array elements in `class:list` | :wrench: |
| [astro/semi](https://ota-meshi.github.io/eslint-plugin-astro/rules/semi/) | Require or disallow semicolons instead of ASI | :wrench: |

## A11Y Extension Rules

Expand Down
21 changes: 15 additions & 6 deletions docs-build/src/components/eslint/ESLintEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
let messageMap = new Map()
let editor

let lastId = 0

export function setCursorPosition(loc) {
if (editor) {
editor.setCursorPosition(loc)
Expand Down Expand Up @@ -60,6 +62,9 @@
throw new Error()
}
}

const currId = ++lastId

const messages = linter.verify(code, config, options)
const time = Date.now() - start

Expand All @@ -75,12 +80,16 @@
fixedMessages: fixResult.messages,
})

leftMarkers = await Promise.all(
messages.map((m) => messageToMarker(m, messageMap)),
)
rightMarkers = await Promise.all(
fixResult.messages.map((m) => messageToMarker(m)),
)
const marsers = await Promise.all([
Promise.all(messages.map((m) => messageToMarker(m, messageMap))),
Promise.all(fixResult.messages.map((m) => messageToMarker(m))),
])

if (currId !== lastId) {
return
}

;[leftMarkers, rightMarkers] = marsers
}

function applyFix() {
Expand Down
1 change: 1 addition & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ These rules relate to style guidelines, and are therefore quite subjective:
| [astro/prefer-class-list-directive](./rules/prefer-class-list-directive.md) | require `class:list` directives instead of `class` with expressions | :wrench: |
| [astro/prefer-object-class-list](./rules/prefer-object-class-list.md) | require use object instead of ternary expression in `class:list` | :wrench: |
| [astro/prefer-split-class-list](./rules/prefer-split-class-list.md) | require use split array elements in `class:list` | :wrench: |
| [astro/semi](./rules/semi.md) | Require or disallow semicolons instead of ASI | :wrench: |

## A11Y Extension Rules

Expand Down
100 changes: 100 additions & 0 deletions docs/rules/semi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
title: "astro/semi"
description: "Require or disallow semicolons instead of ASI"
---

# astro/semi

> Require or disallow semicolons instead of ASI

- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> **_This rule has not been released yet._** </badge>
- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.

## :book: Rule Details

This rule enforces consistent use of semicolons.

This rule extends the base ESLint's [semi] rule. The [semi] rule does not understand frontmatter fence tokens (`---`), so using the `never` option in the [semi] rule will result in a false negative.
This rule supports [astro-eslint-parser]'s AST and tokens.

[astro-eslint-parser]: https://github.com/ota-meshi/astro-eslint-parser

Default:

<ESLintCodeBlock fix>

<!--eslint-skip-->

```astro
---
/* eslint astro/semi: ["error"] */
/* ✓ GOOD */
fn();
/* ✗ BAD */
fn()
---

{() => {
/* ✓ GOOD */
fn();
/* ✗ BAD */
fn()
}}
```

</ESLintCodeBlock>

with `"never"` option:

<ESLintCodeBlock fix>

<!--eslint-skip-->

```astro
---
/* eslint astro/semi: ["error", "never"] */
/* ✓ GOOD */
fn()
/* ✗ BAD */
fn();
---

{() => {
/* ✓ GOOD */
fn()
/* ✗ BAD */
fn();
}}
```

</ESLintCodeBlock>

## :wrench: Options

```json
{
"semi": "off", // Don't need ESLint's semi, so turn it off.
"astro/semi": [
"error",
"always", // or "never"
{ "omitLastInOneLineBlock": true }
// or { "beforeStatementContinuationChars": "any" | "always" | "never" }
]
}
```

Same as [semi] rule option. See [here](https://eslint.org/docs/rules/semi#options) for details.

## :couple: Related rules

- [semi]

[semi]: https://eslint.org/docs/rules/semi

## :mag: Implementation

- [Rule source](https://github.com/ota-meshi/eslint-plugin-astro/blob/main/src/rules/semi.ts)
- [Test source](https://github.com/ota-meshi/eslint-plugin-astro/blob/main/tests/src/rules/semi.ts)
- [Test fixture sources](https://github.com/ota-meshi/eslint-plugin-astro/tree/main/tests/fixtures/rules/semi)

<sup>Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/semi)</sup>
111 changes: 111 additions & 0 deletions src/rules/semi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import type { AST } from "astro-eslint-parser"
import type { SourceCode } from "../types"
import { createRule } from "../utils"
import { getCoreRule, newProxy } from "../utils/eslint-core"

const coreRule = getCoreRule("semi")
export default createRule("semi", {
meta: {
docs: {
description: coreRule.meta.docs.description,
category: "Stylistic Issues",
recommended: false,
extensionRule: "semi",
},
schema: coreRule.meta.schema,
messages: coreRule.meta.messages,
type: coreRule.meta.type,
fixable: coreRule.meta.fixable,
hasSuggestions: coreRule.meta.hasSuggestions,
},
create(context) {
if (!context.parserServices.isAstro) {
return coreRule.create(context)
}

let sourceCodeWrapper: SourceCode | undefined

return coreRule.create(
newProxy(context, {
getSourceCode() {
if (sourceCodeWrapper) {
return sourceCodeWrapper
}
const sourceCode = context.getSourceCode()

/** Transforms token */
function transformToken(
token: AST.Token | AST.Comment,
): AST.Token | AST.Comment {
return token.value === "---"
? newProxy(token, { value: "___" })
: token
}

return (sourceCodeWrapper = newProxy(sourceCode, {
/* eslint-disable @typescript-eslint/unbound-method -- ignore */
getFirstToken: wrapGetTokenFunction(sourceCode.getFirstToken),
getFirstTokens: wrapGetTokensFunction(sourceCode.getFirstTokens),
getFirstTokenBetween: wrapGetTokenFunction(
sourceCode.getFirstTokenBetween,
),
getFirstTokensBetween: wrapGetTokensFunction(
sourceCode.getFirstTokensBetween,
),
getLastToken: wrapGetTokenFunction(sourceCode.getLastToken),
getLastTokens: wrapGetTokensFunction(sourceCode.getLastTokens),
getLastTokenBetween: wrapGetTokenFunction(
sourceCode.getLastTokenBetween,
),
getLastTokensBetween: wrapGetTokensFunction(
sourceCode.getLastTokensBetween,
),
getTokenBefore: wrapGetTokenFunction(sourceCode.getTokenBefore),
getTokensBefore: wrapGetTokensFunction(sourceCode.getTokensBefore),
getTokenAfter: wrapGetTokenFunction(sourceCode.getTokenAfter),
getTokensAfter: wrapGetTokensFunction(sourceCode.getTokensAfter),
getTokenByRangeStart: wrapGetTokenFunction(
sourceCode.getTokenByRangeStart,
),
getTokens: wrapGetTokensFunction(sourceCode.getTokens),
getTokensBetween: wrapGetTokensFunction(
sourceCode.getTokensBetween,
),
/* eslint-enable @typescript-eslint/unbound-method -- ignore */
}))

/** Wrap token getter function */
function wrapGetTokenFunction<
T extends (
this: SourceCode,
...args: never[]
) => AST.Token | AST.Comment | null,
>(base: T): T {
return function (this: SourceCode, ...args) {
// eslint-disable-next-line no-invalid-this -- is valid
const token = base.apply(this, args)
if (!token) {
return token
}
return transformToken(token)
} as T
}

/** Wrap tokens getter function */
function wrapGetTokensFunction<
T extends (
this: SourceCode,
...args: never[]
) => (AST.Token | AST.Comment)[],
>(base: T): T {
return function (this: SourceCode, ...args) {
// eslint-disable-next-line no-invalid-this -- is valid
const tokens = base.apply(this, args)
return tokens.map(transformToken)
} as T
}
},
}),
)
},
})
Loading