Skip to content
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ The rules with the following star :star: are included in the config.

| Rule ID | Description | Fixable | JSON | JSONC | JSON5 |
|:--------|:------------|:-------:|:----:|:-----:|:-----:|
| [jsonc/key-name-casing](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/key-name-casing.html) | enforce naming convention to property key names | | | | |
| [jsonc/no-bigint-literals](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-bigint-literals.html) | disallow BigInt literals | | :star: | :star: | :star: |
| [jsonc/no-comments](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-comments.html) | disallow comments | | :star: | | |
| [jsonc/no-number-props](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-number-props.html) | disallow number property keys | :wrench: | :star: | :star: | :star: |
Expand Down
1 change: 1 addition & 0 deletions docs/rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The rules with the following star :star: are included in the `plugin:jsonc/recom

| Rule ID | Description | Fixable | JSON | JSONC | JSON5 |
|:--------|:------------|:-------:|:----:|:-----:|:-----:|
| [jsonc/key-name-casing](./key-name-casing.md) | enforce naming convention to property key names | | | | |
| [jsonc/no-bigint-literals](./no-bigint-literals.md) | disallow BigInt literals | | :star: | :star: | :star: |
| [jsonc/no-comments](./no-comments.md) | disallow comments | | :star: | | |
| [jsonc/no-number-props](./no-number-props.md) | disallow number property keys | :wrench: | :star: | :star: | :star: |
Expand Down
60 changes: 60 additions & 0 deletions docs/rules/key-name-casing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
pageClass: "rule-details"
sidebarDepth: 0
title: "jsonc/key-name-casing"
description: "enforce naming convention to property key names"
---
# jsonc/key-name-casing

> enforce naming convention to property key names

## :book: Rule Details

This rule enforces a naming convention to property key names.

<eslint-code-block>

```json5
/* eslint jsonc/key-name-casing: 'error' */
{
/* ✓ GOOD */
"camelCase": "camelCase",

/* ✗ BAD */
"PascalCase": "PascalCase",
"SCREAMING_SNAKE_CASE": "SCREAMING_SNAKE_CASE",
"kebab-case": "kebab-case",
"snake_case": "snake_case"
}
```

</eslint-code-block>

## :wrench: Options

Nothing.

```json5
{
"jsonc/key-name-casing": ["error", {
"camelCase": true,
"PascalCase": false,
"SCREAMING_SNAKE_CASE": false,
"kebab-case": false,
"snake_case": false,
"ignores": []
}]
}
```

- `"camelCase"` ... if `true`, allows camelCase naming. default `true`
- `"PascalCase"` ... if `true`, allows PascalCase naming. default `false`
- `"SCREAMING_SNAKE_CASE"` ... if `true`, allows SCREAMING_SNAKE_CASE naming. default `false`
- `"kebab-case"` ... if `true`, allows kebab-case naming. default `false`
- `"snake_case"` ... if `true`, allows snake_case naming. default `false`
- `"ignores"` ... you can specify the patterns to ignore in the array.

## Implementation

- [Rule source](https://github.com/ota-meshi/eslint-plugin-jsonc/blob/master/lib/rules/key-name-casing.ts)
- [Test source](https://github.com/ota-meshi/eslint-plugin-jsonc/blob/master/tests/lib/rules/key-name-casing.js)
116 changes: 116 additions & 0 deletions lib/rules/key-name-casing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import type { AST } from "jsonc-eslint-parser"
import type { RuleListener } from "../types"
import { createRule } from "../utils"
import type { CasingKind } from "../utils/casing"
import { getChecker, allowedCaseOptions } from "../utils/casing"

type Option = {
[key in CasingKind]?: boolean
} & {
ignores?: string[]
}

export default createRule("key-name-casing", {
meta: {
docs: {
description: "enforce naming convention to property key names",
recommended: null,
extensionRule: false,
},
schema: [
{
type: "object",
properties: {
camelCase: {
type: "boolean",
default: true,
},
// eslint-disable-next-line @typescript-eslint/naming-convention -- option
PascalCase: {
type: "boolean",
default: false,
},
// eslint-disable-next-line @typescript-eslint/naming-convention -- option
SCREAMING_SNAKE_CASE: {
type: "boolean",
default: false,
},
// eslint-disable-next-line @typescript-eslint/naming-convention -- option
"kebab-case": {
type: "boolean",
default: false,
},
// eslint-disable-next-line @typescript-eslint/naming-convention -- option
snake_case: {
type: "boolean",
default: false,
},
ignores: {
type: "array",
items: {
type: "string",
},
uniqueItems: true,
additionalItems: false,
},
},
additionalProperties: false,
},
],
messages: {
doesNotMatchFormat:
"Property name `{{name}}` must match one of the following formats: {{formats}}",
},
type: "layout",
},
create(context) {
if (!context.parserServices.isJSON) {
return {} as RuleListener
}
const sourceCode = context.getSourceCode()
const option: Option = { ...context.options[0] }
if (option.camelCase !== false) {
option.camelCase = true
}
const ignores = option.ignores
? option.ignores.map((ignore) => new RegExp(ignore))
: []
const formats = Object.keys(option)
.filter((key): key is CasingKind =>
allowedCaseOptions.includes(key as CasingKind),
)
.filter((key) => option[key])

const checkers: ((str: string) => boolean)[] = formats.map(getChecker)

/**
* Check whether a given name is a valid.
*/
function isValid(name: string): boolean {
if (ignores.some((regex) => regex.test(name))) {
return true
}
return checkers.length ? checkers.some((c) => c(name)) : true
}

return {
JSONProperty(node: AST.JSONProperty) {
const name =
node.key.type === "JSONLiteral" &&
typeof node.key.value === "string"
? node.key.value
: sourceCode.text.slice(...node.key.range)
if (!isValid(name)) {
context.report({
loc: node.key.loc,
messageId: "doesNotMatchFormat",
data: {
name,
formats: formats.join(", "),
},
})
}
},
}
},
})
Loading