Skip to content

Commit

Permalink
Feature: Add "json-es/use-camelcase" rule
Browse files Browse the repository at this point in the history
Based on the ESLint camelcase rule.
  • Loading branch information
cschuller committed Apr 25, 2024
1 parent 0ea4d43 commit ab2b88c
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 1 deletion.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,23 @@ Based on the recommended rules with stylistic aspects.
|🔧| no-multiple-empty-lines | |
| | sort-keys |Alternative with fix [eslint-plugin-sort-keys-fix] |

## Optional Rules

### use-camelcase
The ESLint camelcase rule does not work with JSON files.

A custom 'use-camelcase' [rule](./rules/use-camelcase.js) is available.
Based on the ESLint camelcase rule with minor adjustments.

__Configuration__
```json
{
"rules": {
"json-es/use-camelcase": ["error", {"allow": ["FOO", "[regex]*"]}]
}
}
```

[ESLint]: https://eslint.org/
[custom parser]: https://eslint.org/docs/developer-guide/working-with-custom-parsers
[eslint-plugin-json]: https://github.com/azeemba/eslint-plugin-json
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"description": "A JSON parser for ESLint.",
"main": "index.js",
"scripts": {
"lint": "eslint lib",
"lint": "npm run lint:lib && npm run lint:rules",
"lint:lib": "eslint lib",
"lint:rules": "eslint rules",
"coverage": "c8 --check-coverage --lines 100 npm test",
"test": "vitest run",
"testui": "vitest --ui",
Expand Down
7 changes: 7 additions & 0 deletions rules/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"env": {
"es6": true,
"node": true
},
"extends": ["eslint:recommended"]
}
2 changes: 2 additions & 0 deletions rules/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const noComments = require('./no-comments');
const useValidJson = require('./use-valid-json');
const useCamelCase = require('./use-camelcase');

module.exports = {
'no-comments': noComments,
'use-camelcase': useCamelCase,
'use-valid-json': useValidJson
}
84 changes: 84 additions & 0 deletions rules/use-camelcase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
'use strict';

//------------------------------------------------------------------------------
// Rule Definition
// Base on eslint camelcase rule.
// https://github.com/eslint/eslint/blob/1579ce05cbb523cb5b04ff77fab06ba1ecd18dce/lib/rules/camelcase.js
//------------------------------------------------------------------------------

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Every property should be camelCase.',
category: 'Possible Errors',
recommended: false
},
schema: [
{
type: "object",
properties: {
allow: {
type: "array",
items: {
type: "string"
},
minItems: 0,
uniqueItems: true
}
},
additionalProperties: false
}
]
},
create: function(context) {
const options = context.options[0] || {};
const allow = options.allow || [];

/**
* Checks if a string contains an underscore and isn't all upper-case
* @param {string} name The string to check.
* @returns {boolean} if the string is underscored
* @private
*/
function isUnderscored(name) {
return name.includes('_') || name.includes('-') || name === name.toUpperCase();
}

/**
*
* Checks if a string match the ignore list
* @param {string} name The string to check.
* @returns {boolean} if the string is ignored
* @private
*/
function isAllowed(name) {
return allow.some(
entry => name === entry || name.match(new RegExp(entry, "u"))
);
}

/**
* Checks if a given name is good or not.
* @param {string} name The name to check.
* @returns {boolean} `true` if the name is good.
* @private
*/
function isGoodName(name) {
return !isUnderscored(name) || isAllowed(name);
}

return {
[["ObjectExpression > Property"]](node) {
const keyName = node.key.value || '';

if (!isGoodName(keyName)) {
context.report({
node,
message: `Identifier '${keyName}' is not in camel case.`
});
}
}
};
}
};
141 changes: 141 additions & 0 deletions test/rules/plugin/use-camelcase.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import {describe, expect, test} from 'vitest'
import {linter} from '../../testSandbox.js';

const config = {
parser: 'eslint-plugin-json-es',
rules: {
'json-es/use-camelcase': ['error']
}
};

describe('use-camelcase', () => {
describe('correct', () => {
test('camelCase', () => {
// Given
const json = JSON.stringify({'camelCase': 'value'});

// When
const messages = linter.verify(json, config, {filename: 'test.json'});

// Then
expect(messages.length).toEqual(0);
});

test('alllowercase', () => {
// Given
const json = JSON.stringify({'alllowercase': 'value'});

// When
const messages = linter.verify(json, config, {filename: 'test.json'});

// Then
expect(messages.length).toEqual(0);
});
});

describe('correct + allow', () => {
test('_privateField', () => {
// Given
const json = JSON.stringify({'_privateField': 'value'});

const allowConfig = structuredClone(config);
allowConfig.rules['json-es/use-camelcase'] = ['error', {allow: ['_privateField']}];

// When
const messages = linter.verify(json, allowConfig, {filename: 'test.json'});

// Then
expect(messages.length).toEqual(0);
});

test('A_CONSTANT', () => {
// Given
const json = JSON.stringify({'A_CONSTANT': 'value'});

const allowConfig = structuredClone(config);
allowConfig.rules['json-es/use-camelcase'] = ['error', {allow: ['[A-Z_]*']}];

// When
const messages = linter.verify(json, allowConfig, {filename: 'test.json'});

// Then
expect(messages.length).toEqual(0);
});
});

describe('incorrect', () => {
test('UPPER_case', () => {
// Given
const json = JSON.stringify({'UPPER_case': 'value'});

// When
const messages = linter.verify(json, config, {filename: 'test.json'});

// Then
const expectedMessage = {
severity: 2,
ruleId: 'json-es/use-camelcase',
message: 'Identifier \'UPPER_case\' is not in camel case.'
};

expect(messages.length).toEqual(1);
expect(messages[0]).toMatchObject(expectedMessage);
});

test('A_CONSTANT', () => {
// Given
const json = JSON.stringify({'A_CONSTANT': 'value'});

// When
const messages = linter.verify(json, config, {filename: 'test.json'});

// Then
const expectedMessage = {
severity: 2,
ruleId: 'json-es/use-camelcase',
message: 'Identifier \'A_CONSTANT\' is not in camel case.'
};

expect(messages.length).toEqual(1);
expect(messages[0]).toMatchObject(expectedMessage);
});

test('kebab-case', () => {
// Given
const json = JSON.stringify({'kebab-case': 'value'});

// When
const messages = linter.verify(json, config, {filename: 'test.json'});

// Then
const expectedMessage = {
severity: 2,
ruleId: 'json-es/use-camelcase',
message: 'Identifier \'kebab-case\' is not in camel case.'
};

expect(messages.length).toEqual(1);
expect(messages[0]).toMatchObject(expectedMessage);
});

test('ALLUPPERCASE', () => {
// Given
const json = JSON.stringify({'ALLUPPERCASE': 'value'});

// When
const messages = linter.verify(json, config, {filename: 'test.json'});

// Then
const expectedMessage = {
severity: 2,
ruleId: 'json-es/use-camelcase',
message: 'Identifier \'ALLUPPERCASE\' is not in camel case.'
};

expect(messages.length).toEqual(1);
expect(messages[0]).toMatchObject(expectedMessage);
});
});
});


0 comments on commit ab2b88c

Please sign in to comment.