-
-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: new rule attribute-names (#191)
This introduces a new rule: `attribute-names`. The rule enforces that all attributes are lowercase if the associated property is not. For example: ```ts class Foo extends LitElement { @Property() camelCase = 'foo'; } ``` This will fail since the default attribute will be `camelCase`, and therefore not lowercase. This should instead be: ```ts @Property({attribute: 'camel-case'}) ``` Though in this rule, for now, the fact it is snake-case is a preference rather than being enforced by the rule.
- Loading branch information
Showing
6 changed files
with
296 additions
and
8 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
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,43 @@ | ||
# Enforces attribute naming conventions | ||
|
||
Attributes are always treated lowercase, but it is common to have camelCase | ||
property names. In these situations, an explicit lowercase attribute should | ||
be supplied. | ||
|
||
Further, camelCase names should ideally be exposed as snake-case attributes. | ||
|
||
## Rule Details | ||
|
||
This rule enforces that all lit properties have equivalent lower case attributes | ||
exposed. | ||
|
||
The following patterns are considered warnings: | ||
|
||
```ts | ||
// Using decorators: | ||
|
||
@property() camelCaseName: string; | ||
|
||
// Using a getter: | ||
|
||
static get properties() { | ||
return { | ||
camelCaseName2: {type: String} | ||
}; | ||
} | ||
``` | ||
|
||
The following patterns are not warnings: | ||
|
||
```ts | ||
@property({attribute: 'camel-case-name'}) | ||
camelCaseName: string; | ||
|
||
@property() | ||
lower: string; | ||
``` | ||
|
||
## When Not To Use It | ||
|
||
If you prefer other naming conventions for attributes, this rule should not | ||
be used. |
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,69 @@ | ||
/** | ||
* @fileoverview Enforces attribute naming conventions | ||
* @author James Garbutt <https://github.com/43081j> | ||
*/ | ||
|
||
import {Rule} from 'eslint'; | ||
import * as ESTree from 'estree'; | ||
import {getPropertyMap, isLitClass} from '../util'; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Rule Definition | ||
//------------------------------------------------------------------------------ | ||
|
||
const rule: Rule.RuleModule = { | ||
meta: { | ||
docs: { | ||
description: 'Enforces attribute naming conventions', | ||
recommended: true, | ||
url: 'https://github.com/43081j/eslint-plugin-lit/blob/master/docs/rules/attribute-names.md' | ||
}, | ||
schema: [], | ||
messages: { | ||
casedAttribute: | ||
'Attributes are case-insensitive and therefore should be ' + | ||
'defined in lower case', | ||
casedPropertyWithoutAttribute: | ||
'Property has non-lowercase casing but no attribute. It should ' + | ||
'instead have an explicit `attribute` set to the lower case ' + | ||
'name (usually snake-case)' | ||
} | ||
}, | ||
|
||
create(context): Rule.RuleListener { | ||
return { | ||
ClassDeclaration: (node: ESTree.Class): void => { | ||
if (isLitClass(node)) { | ||
const propertyMap = getPropertyMap(node); | ||
|
||
for (const [prop, propConfig] of propertyMap.entries()) { | ||
if (!propConfig.attribute) { | ||
continue; | ||
} | ||
|
||
if (!propConfig.attributeName) { | ||
if (prop.toLowerCase() !== prop) { | ||
context.report({ | ||
node: propConfig.key, | ||
messageId: 'casedPropertyWithoutAttribute' | ||
}); | ||
} | ||
} else { | ||
if ( | ||
propConfig.attributeName.toLowerCase() !== | ||
propConfig.attributeName | ||
) { | ||
context.report({ | ||
node: propConfig.expr ?? propConfig.key, | ||
messageId: 'casedAttribute' | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
}; | ||
|
||
export = 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
/** | ||
* @fileoverview Enforces attribute naming conventions | ||
* @author James Garbutt <https://github.com/43081j> | ||
*/ | ||
|
||
import rule = require('../../rules/attribute-names'); | ||
import {RuleTester} from 'eslint'; | ||
|
||
const ruleTester = new RuleTester({ | ||
parserOptions: { | ||
sourceType: 'module', | ||
ecmaVersion: 2015 | ||
} | ||
}); | ||
|
||
const parser = require.resolve('@babel/eslint-parser'); | ||
const parserOptions = { | ||
requireConfigFile: false, | ||
babelOptions: { | ||
plugins: [ | ||
['@babel/plugin-proposal-decorators', {decoratorsBeforeExport: true}] | ||
] | ||
} | ||
}; | ||
|
||
ruleTester.run('attribute-names', rule, { | ||
valid: [ | ||
'class Foo {}', | ||
`class Foo { | ||
static get properties() { | ||
return { | ||
whateverCaseYouWant: {type: String} | ||
}; | ||
} | ||
}`, | ||
`class Foo extends LitElement { | ||
static get properties() { | ||
return { | ||
lowercase: {type: String} | ||
}; | ||
} | ||
}`, | ||
`class Foo extends Litelement { | ||
static get properties() { | ||
return { | ||
camelCase: {type: String, attribute: 'lowercase'} | ||
}; | ||
} | ||
}`, | ||
{ | ||
code: `class Foo extends LitElement { | ||
@property({ type: String }) | ||
lowercase = 'foo'; | ||
}`, | ||
parser, | ||
parserOptions | ||
}, | ||
{ | ||
code: `class Foo extends LitElement { | ||
@property({ type: String, attribute: 'lowercase' }) | ||
camelCase = 'foo'; | ||
}`, | ||
parser, | ||
parserOptions | ||
}, | ||
{ | ||
code: `class Foo extends LitElement { | ||
@property({ type: String, attribute: false }) | ||
camelCase = 'foo'; | ||
}`, | ||
parser, | ||
parserOptions | ||
} | ||
], | ||
|
||
invalid: [ | ||
{ | ||
code: `class Foo extends LitElement { | ||
static get properties() { | ||
return { | ||
camelCase: {type: String} | ||
}; | ||
} | ||
}`, | ||
errors: [ | ||
{ | ||
line: 4, | ||
column: 13, | ||
messageId: 'casedPropertyWithoutAttribute' | ||
} | ||
] | ||
}, | ||
{ | ||
code: `class Foo extends LitElement { | ||
static get properties() { | ||
return { | ||
camelCase: {type: String, attribute: 'stillCamelCase'} | ||
}; | ||
} | ||
}`, | ||
errors: [ | ||
{ | ||
line: 4, | ||
column: 24, | ||
messageId: 'casedAttribute' | ||
} | ||
] | ||
}, | ||
{ | ||
code: `class Foo extends LitElement { | ||
@property({ type: String }) | ||
camelCase = 'foo'; | ||
}`, | ||
parser, | ||
parserOptions, | ||
errors: [ | ||
{ | ||
line: 3, | ||
column: 9, | ||
messageId: 'casedPropertyWithoutAttribute' | ||
} | ||
] | ||
} | ||
] | ||
}); |
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