Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Sep 18, 2023. It is now read-only.

Valid tag name #10

Merged
merged 8 commits into from
Mar 26, 2021
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ JSON ESLint config example:
- [File Name Matches Element](./docs/rules/file-name-matches-element.md)
- [No Constructor](./docs/rules/no-constructor.md)
- [No Customized Built in Elements](./docs/rules/no-customized-built-in-elements.md)
- [One Element Per File](./docs/rules/one-element-per-file.md)
- [One Element Per File](./docs/rules/one-element-per-file.md)
- [Valid Tag Name](./docs/rules/valid-tag-name.md)
36 changes: 36 additions & 0 deletions docs/rules/valid-tag-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Valid Tag Name

To be able to use a Custom Element, it must be defined in the `customElements` registry with a valid tag name. Certain characters are allowed for tag names, and invalid tag-names will throw a `DOMException` during runtime.

## Rule Details

This rule enforces that calls to `customElements.define` or `customElements.whenDefined` express a valid tag name. There are certain mandatory rules a tag name must follow - such as starting with a letter, and including one dash. This rule will check the name follows these rules.

In addition, this rule can be customised to perform additional checks, such as requiring a prefix, or disallowing known prefixes such as `x-`.

👎 Examples of **incorrect** code for this rule:

```js
customElements.define('foobar', ...)
```

👍 Examples of **correct** code for this rule:

```js
customElements.define('foo-bar', ...)
```

### Options

- `onlyAlphanum` is a boolean option (default: `false`). When set to `true` it will only allow tag names to consist of alphanumeric characters from a-z, 0-9. The spec allows for a broad range of additional characters such as some emoji, which will be disallowed if this rule is set to `true`.
- `disallowNamespaces` is a boolean option (default: `false`). When set to `true` it will check the tag does not match an existing well known namespace, such as `x-` or `google-`, etc. Setting this rule to true limits the risk of collision with other third-party element names.
- `suffix` can be set to one or more strings, and elements must use at least one of these suffixes.
- `prefix` can be set to one or more strings, and elements must use at least one of these prefixes.

## When Not To Use It

If you do not want to validate the names passed to `customElements.define` or `customElements.whenDefined` then you can disable this rule.

## Version

This was added in v0.0.1
3 changes: 2 additions & 1 deletion lib/rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ module.exports = {
'file-name-matches-element': require('./rules/file-name-matches-element'),
'no-constructor': require('./rules/no-constructor'),
'no-customized-built-in-elements': require('./rules/no-customized-built-in-elements'),
'one-element-per-file': require('./rules/one-element-per-file')
'one-element-per-file': require('./rules/one-element-per-file'),
'valid-tag-name': require('./rules/valid-tag-name')
}
66 changes: 66 additions & 0 deletions lib/rules/valid-tag-name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const s = require('../custom-selectors')
const {validTagName, reservedTags, knownNamespaces} = require('../tag-names')
module.exports = {
meta: {
type: 'problem',
docs: {description: '', url: require('../url')(module)}
},
schema: [
{
type: 'object',
properties: {
disallowNamespaces: {type: 'boolean'},
onlyAlphanum: {type: 'boolean'},
prefix: {
oneOf: [{type: 'string'}, {type: 'array', items: {type: 'string'}}]
},
suffix: {
oneOf: [{type: 'string'}, {type: 'array', items: {type: 'string'}}]
}
}
}
],
create(context) {
return {
[s.customElements.define](node) {
const nameArg = node.arguments[0]
let name = nameArg.value
if (nameArg.type === 'TemplateLiteral') {
// Give up on TemplateLiteral expressions
if (nameArg.expressions.length) return
name = nameArg.quasis.map(q => q.value.raw).join('')
}
const {disallowNamespaces, onlyAlphanum} = context.options[0] || {}
let {prefix, suffix} = context.options[0] || {}
suffix = [].concat(suffix || [])
prefix = [].concat(prefix || [])
const namespace = (name.match(/^(.*)-/) || [])[1]
if (!validTagName(name)) {
const hints = []
if (!/^[a-z]/.test(name)) hints.push('Custom Element names must start with a letter')
if (!/-/.test(name)) hints.push('Custom Element names must contain at least one dash (-)')
if (/[A-Z]/.test(name)) hints.push('Custom Element names must not contain capital letters')
context.report(
nameArg,
`${name} is not a valid custom element name${hints.length ? '\n' : ''}${hints.join('\n')}`
)
}
if (reservedTags.has(name)) {
context.report(nameArg, `Custom Elements cannot be given the reserved name "${name}"`)
}
if (onlyAlphanum && !/^[a-z0-9-]+$/.test(name)) {
context.report(nameArg, `Non ASCII Custom Elements have been disallowed`)
}
if (disallowNamespaces && knownNamespaces.has(namespace) && !prefix.includes(namespace)) {
context.report(nameArg, `${namespace} is a common namespace, and has been disallowed`)
}
if (suffix.length && !suffix.some(end => name.endsWith(end))) {
context.report(nameArg, `Custom Element name must end with a suffix: ${suffix}`)
}
if (prefix.length && !prefix.some(start => name.startsWith(start))) {
context.report(nameArg, `Custom Element name must begin with a prefix: ${prefix}`)
}
}
}
}
}
155 changes: 155 additions & 0 deletions lib/tag-names.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
module.exports.knownNamespaces = new Set([
'primer',
'polymer',
'x',
'rdf',
'xml',
'annotation',
'color',
'font',
'missing',

// https://www.webcomponents.org/collections
'ct',
'color',
'currency',
'smart',
'github',
'elementui',
'smart',
'github',
'elementui',
'progressive',
'cpss',
'xgd',
'paper',
'hy',
'polymorph',
'granite',
'plastic',
'vega',
'catalyst',
'Gathr',
'fs',
'cr',
'mqtt',
'simpla',
'iron',
'topseed',
'awesome',
'ts',
'globalization',
'super',
's',
'scary',
'best',
'paper',
'platinum',
'paper',
'layout',
'iron',
'app',
'data',
'gold',
'elements',
'pf',
'paperfire',
'core',
'nodecg',
'titanium',
'lrndesign',
'lrn',
'hax',
'k4ng',
'ros',
'geo',
'ibm',
'google',
'gfs',
'geoloeg',
'ami',
'google',
'medical',
'cordova',
'cth',
'paper',
'gui',
'network',
'dialogs',
'layouts',

// https://github.com/nuxodin/web-namespace-registry/
'lume',
'three',
'fast',
'a11y',
'lrn',
'mdl',
'mui',
'pure',
'uk',
'auro',
'ui5',
's',
'ng',
'amp',
'spectrum',
'sp',
'smart',
'hiq',
'ibm',
'google',
'a',
'tangy',
'bs',
'ion',
'mwc',
'mdc',
'mat',
'mv',
'iron',
'paper',
'gold',
'std',
'aria',
'dile',
'lion'
])

module.exports.reservedTags = new Set([
'annotation-xml',
'color-profile',
'font-face',
'font-face-src',
'font-face-uri',
'font-face-format',
'font-face-name',
'missing-glyph'
])

// https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
const PCENChar = [
'(?:\\-',
'\\.',
'[0-9]',
'_',
'[a-z]',
'\\xB7', // ·
'[\\u00C0-\\u00D6]',
'[\\u00D8-\\u00F6]',
'[\\u00F8-\\u037D]',
'[\\u037F-\\u1FFF]',
'[\\u200C-\\u200D]',
'[\\u203F-\\u2040]',
'[\\u2070-\\u218F]',
'[\\u2C00-\\u2FEF]',
'[\\u3001-\\uD7FF]',
'[\\uF900-\\uFDCF]',
'[\\uFDF0-\\uFFFD]',
'[\\uD800-\\uDB7F]',
'[\\uDC00-\\uDFFF])'
].join('|')
module.exports.PCENChar = PCENChar
const customElementNameRegExp = new RegExp(`^[a-z]${PCENChar}*-${PCENChar}*$`)

module.exports.validTagName = name => customElementNameRegExp.test(name)
Loading