eslint-plugin-lit
Advanced tools
Comparing version 1.14.0 to 1.15.0
@@ -7,3 +7,3 @@ # Enforces attribute naming conventions | ||
Further, camelCase names should ideally be exposed as snake-case attributes. | ||
Further, camelCase names should ideally be exposed as kebab-case attributes. | ||
@@ -37,2 +37,5 @@ ## Rule Details | ||
@property({attribute: 'camel-case-other-name'}) | ||
camelCaseName: string; | ||
@property() | ||
@@ -42,2 +45,49 @@ lower: string; | ||
## Options | ||
### `convention` | ||
You can specify a `convention` to enforce a particular naming convention | ||
on element attributes. | ||
The available values are: | ||
- `none` (default, no convention is enforced) | ||
- `kebab` | ||
- `snake` | ||
For example for a property named `camelCaseProp`, expected attribute names are: | ||
| Convention | Attribute | | ||
|------------|----------------------| | ||
| none | any lower case value | | ||
| kebab | camel-case-prop | | ||
| snake | camel_case_prop | | ||
The following patterns are considered warnings with `{"convention": "kebab"}` | ||
specified: | ||
```ts | ||
// Should have an attribute set to `camel-case-name` | ||
@property() camelCaseName: string; | ||
// Attribute should match the property name when a convention is set | ||
@property({attribute: 'camel-case-other-name'}) | ||
camelCaseName: string; | ||
``` | ||
The following patterns are not warnings with `{"convention": "kebab"}` | ||
specified: | ||
```ts | ||
@property({attribute: 'camel-case-name'}) | ||
camelCaseName: string; | ||
@property({attribute: false}) | ||
camelCaseName: string; | ||
@property() | ||
lower: string; | ||
``` | ||
## When Not To Use It | ||
@@ -44,0 +94,0 @@ |
@@ -17,3 +17,11 @@ "use strict"; | ||
}, | ||
schema: [], | ||
schema: [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
convention: { type: 'string', enum: ['none', 'kebab', 'snake'] } | ||
}, | ||
additionalProperties: false | ||
} | ||
], | ||
messages: { | ||
@@ -24,9 +32,13 @@ casedAttribute: 'Attributes are case-insensitive and therefore should be ' + | ||
'instead have an explicit `attribute` set to the lower case ' + | ||
'name (usually snake-case)' | ||
'name (usually snake-case)', | ||
casedAttributeConvention: 'Attribute should be property name written in {{convention}} ' + | ||
'as "{{name}}"' | ||
} | ||
}, | ||
create(context) { | ||
var _a, _b; | ||
const convention = (_b = (_a = context.options[0]) === null || _a === void 0 ? void 0 : _a.convention) !== null && _b !== void 0 ? _b : 'none'; | ||
return { | ||
ClassDeclaration: (node) => { | ||
var _a; | ||
var _a, _b; | ||
if ((0, util_1.isLitClass)(node)) { | ||
@@ -54,2 +66,28 @@ const propertyMap = (0, util_1.getPropertyMap)(node); | ||
} | ||
else if (convention !== 'none') { | ||
let conventionName; | ||
let expectedAttributeName; | ||
switch (convention) { | ||
case 'snake': | ||
conventionName = 'snake_case'; | ||
expectedAttributeName = (0, util_1.toSnakeCase)(prop); | ||
break; | ||
case 'kebab': | ||
conventionName = 'kebab-case'; | ||
expectedAttributeName = (0, util_1.toKebabCase)(prop); | ||
break; | ||
} | ||
if (expectedAttributeName && | ||
conventionName && | ||
propConfig.attributeName !== expectedAttributeName) { | ||
context.report({ | ||
node: (_b = propConfig.expr) !== null && _b !== void 0 ? _b : propConfig.key, | ||
messageId: 'casedAttributeConvention', | ||
data: { | ||
convention: conventionName, | ||
name: expectedAttributeName | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
@@ -56,0 +94,0 @@ } |
@@ -6,2 +6,3 @@ "use strict"; | ||
*/ | ||
const util_1 = require("../util"); | ||
const methodNames = ['connectedCallback', 'disconnectedCallback', 'update']; | ||
@@ -39,5 +40,3 @@ //------------------------------------------------------------------------------ | ||
function classEnter(node) { | ||
if (!node.superClass || | ||
node.superClass.type !== 'Identifier' || | ||
node.superClass.name !== 'LitElement') { | ||
if (!(0, util_1.isLitClass)(node)) { | ||
return; | ||
@@ -44,0 +43,0 @@ } |
@@ -41,5 +41,3 @@ "use strict"; | ||
function classEnter(node) { | ||
if (!node.superClass || | ||
node.superClass.type !== 'Identifier' || | ||
node.superClass.name !== 'LitElement') { | ||
if (!(0, util_1.isLitClass)(node)) { | ||
return; | ||
@@ -46,0 +44,0 @@ } |
@@ -6,2 +6,3 @@ "use strict"; | ||
*/ | ||
const util_1 = require("../util"); | ||
//------------------------------------------------------------------------------ | ||
@@ -34,5 +35,3 @@ // Rule Definition | ||
function classEnter(node) { | ||
if (!node.superClass || | ||
node.superClass.type !== 'Identifier' || | ||
node.superClass.name !== 'LitElement') { | ||
if (!(0, util_1.isLitClass)(node)) { | ||
return; | ||
@@ -39,0 +38,0 @@ } |
@@ -32,2 +32,3 @@ "use strict"; | ||
const alwaysQuote = context.options[0] === 'always'; | ||
const quotePattern = /=(["'])?$/; | ||
//---------------------------------------------------------------------- | ||
@@ -48,12 +49,10 @@ // Helpers | ||
const nextQuasi = node.quasi.quasis[i + 1]; | ||
const isAttribute = /=["']?$/.test(previousQuasi.value.raw); | ||
const quoteMatch = previousQuasi.value.raw.match(quotePattern); | ||
// don't care about non-attribute bindings | ||
if (!isAttribute) { | ||
if (!quoteMatch) { | ||
continue; | ||
} | ||
const isQuoted = (previousQuasi.value.raw.endsWith('="') && | ||
nextQuasi.value.raw.startsWith('"')) || | ||
(previousQuasi.value.raw.endsWith("='") && | ||
nextQuasi.value.raw.startsWith("'")); | ||
if (alwaysQuote && !isQuoted) { | ||
const hasStartQuote = quoteMatch[1] !== undefined; | ||
const isQuoted = hasStartQuote && nextQuasi.value.raw.startsWith(quoteMatch[1]); | ||
if (alwaysQuote && !hasStartQuote) { | ||
context.report({ | ||
@@ -60,0 +59,0 @@ node: expression, |
@@ -81,1 +81,15 @@ import * as ESTree from 'estree'; | ||
export declare function templateExpressionToHtml(node: ESTree.TaggedTemplateExpression): string; | ||
/** | ||
* Converts a camelCase string to snake_case string | ||
* | ||
* @param {string} camelCaseStr String to convert | ||
* @return {string} | ||
*/ | ||
export declare function toSnakeCase(camelCaseStr: string): string; | ||
/** | ||
* Converts a camelCase string to kebab-case string | ||
* | ||
* @param {string} camelCaseStr String to convert | ||
* @return {string} | ||
*/ | ||
export declare function toKebabCase(camelCaseStr: string): string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.templateExpressionToHtml = exports.isExpressionPlaceholder = exports.getExpressionPlaceholder = exports.hasLitPropertyDecorator = exports.getPropertyMap = exports.getClassFields = exports.extractPropertyEntry = exports.getIdentifierName = exports.isLitClass = void 0; | ||
exports.toKebabCase = exports.toSnakeCase = exports.templateExpressionToHtml = exports.isExpressionPlaceholder = exports.getExpressionPlaceholder = exports.hasLitPropertyDecorator = exports.getPropertyMap = exports.getClassFields = exports.extractPropertyEntry = exports.getIdentifierName = exports.isLitClass = void 0; | ||
/** | ||
* Returns if given node has a customElement decorator | ||
* @param {ESTree.Class} node | ||
* @return {boolean} | ||
*/ | ||
function hasCustomElementDecorator(node) { | ||
const decoratedNode = node; | ||
if (!decoratedNode.decorators || !Array.isArray(decoratedNode.decorators)) { | ||
return false; | ||
} | ||
for (const decorator of decoratedNode.decorators) { | ||
if (decorator.expression.type === 'CallExpression' && | ||
decorator.expression.callee.type === 'Identifier' && | ||
decorator.expression.callee.name === 'customElement') { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
/** | ||
* Returns if given node has a lit identifier | ||
@@ -34,2 +53,5 @@ * @param {ESTree.Node} node | ||
function isLitClass(clazz) { | ||
if (hasCustomElementDecorator(clazz)) { | ||
return true; | ||
} | ||
if (clazz.superClass) { | ||
@@ -263,1 +285,21 @@ return (hasLitIdentifier(clazz.superClass) || isLitByExpression(clazz.superClass)); | ||
exports.templateExpressionToHtml = templateExpressionToHtml; | ||
/** | ||
* Converts a camelCase string to snake_case string | ||
* | ||
* @param {string} camelCaseStr String to convert | ||
* @return {string} | ||
*/ | ||
function toSnakeCase(camelCaseStr) { | ||
return camelCaseStr.replace(/[A-Z]/g, (m) => '_' + m.toLowerCase()); | ||
} | ||
exports.toSnakeCase = toSnakeCase; | ||
/** | ||
* Converts a camelCase string to kebab-case string | ||
* | ||
* @param {string} camelCaseStr String to convert | ||
* @return {string} | ||
*/ | ||
function toKebabCase(camelCaseStr) { | ||
return camelCaseStr.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase()); | ||
} | ||
exports.toKebabCase = toKebabCase; |
{ | ||
"name": "eslint-plugin-lit", | ||
"version": "1.14.0", | ||
"version": "1.15.0", | ||
"description": "lit-html support for ESLint", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
@@ -39,2 +39,18 @@ <div align="center"> | ||
Or if you're using (flat) config files, add to your `eslint.config.js`: | ||
```ts | ||
import {configs} from 'eslint-plugin-lit'; | ||
export default [ | ||
configs['flat/recommended'], | ||
// or if you want to specify `files`, or other options | ||
{ | ||
...configs['flat/recommended'], | ||
files: ['test/**/*.js'] | ||
} | ||
]; | ||
``` | ||
### Custom Configuration | ||
@@ -41,0 +57,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
141312
2939
137