Skip to content

Commit

Permalink
feat: add config option for foreign elements in `svelte/html-self-clo…
Browse files Browse the repository at this point in the history
…sing` rule (#841)

This PR adds separate configuration option for foreign (SVG and MathML)
elements to `svelte/html-self-closing` rule.

According to HTML spec
(https://html.spec.whatwg.org/multipage/syntax.html#elements-2):
> Raw text, escapable raw text, and normal elements have a start tag to
indicate where they begin, and an end tag to indicate where they end.
The start and end tags of certain normal elements can be omitted, as
described below in the section on optional tags. Those that cannot be
omitted must not be omitted. Void elements only have a start tag; end
tags must not be specified for void elements. Foreign elements must
either have a start tag and an end tag, or a start tag that is marked as
self-closing, in which case they must not have an end tag.

This means that `<div/>` is invalid, while `<path/>` (which is a SVG
element) is OK. This configuration option would allow users to take
advantage of the terser SVG syntax, while still using correct HTML
syntax for normal elements (like `div`).

Closes #837.
  • Loading branch information
jrmajor committed Sep 13, 2024
1 parent d117d7f commit 85053a1
Show file tree
Hide file tree
Showing 18 changed files with 215 additions and 25 deletions.
5 changes: 5 additions & 0 deletions .changeset/rotten-news-swim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'eslint-plugin-svelte': minor
---

feat: add config option for foreign elements in `svelte/html-self-closing` rule
2 changes: 2 additions & 0 deletions docs/rules/html-self-closing.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ config object:
{
"void": "always", // or "never" or "ignore"
"normal": "always", // or "never" or "ignore"
"foreign": "always", // or "never" or "ignore"
"component": "always", // or "never" or "ignore"
"svelte": "always" // or "never" or "ignore"
}
Expand All @@ -86,6 +87,7 @@ presets:
config object:

- `void` (`"always"` in default preset)... Style of HTML void elements
- `foreign` (`"always"` in default preset)... Style of foreign elements (SVG and MathML)
- `component` (`"always"` in default preset)... Style of svelte components
- `svelte` (`"always"` in default preset)... Style of svelte special elements (`<svelte:head>`, `<svelte:self>`)
- `normal` (`"always"` in default preset)... Style of other elements
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-plugin-svelte/src/rule-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ type SvelteHtmlQuotes = []|[{
type SvelteHtmlSelfClosing = []|[({
void?: ("never" | "always" | "ignore")
normal?: ("never" | "always" | "ignore")
foreign?: ("never" | "always" | "ignore")
component?: ("never" | "always" | "ignore")
svelte?: ("never" | "always" | "ignore")
} | ("all" | "html" | "none"))]
Expand Down
12 changes: 10 additions & 2 deletions packages/eslint-plugin-svelte/src/rules/html-self-closing.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import type { AST } from 'svelte-eslint-parser';
import { createRule } from '../utils';
import { getNodeName, isVoidHtmlElement } from '../utils/ast-utils';
import { getNodeName, isVoidHtmlElement, isForeignElement } from '../utils/ast-utils';
import { getSourceCode } from '../utils/compat';

const TYPE_MESSAGES = {
normal: 'HTML elements',
void: 'HTML void elements',
foreign: 'foreign (SVG or MathML) elements',
component: 'Svelte custom components',
svelte: 'Svelte special elements'
};

type ElementTypes = 'normal' | 'void' | 'component' | 'svelte';
type ElementTypes = 'normal' | 'void' | 'foreign' | 'component' | 'svelte';

export default createRule('html-self-closing', {
meta: {
Expand All @@ -37,6 +38,9 @@ export default createRule('html-self-closing', {
normal: {
enum: ['never', 'always', 'ignore']
},
foreign: {
enum: ['never', 'always', 'ignore']
},
component: {
enum: ['never', 'always', 'ignore']
},
Expand All @@ -57,6 +61,7 @@ export default createRule('html-self-closing', {
let options = {
void: 'always',
normal: 'always',
foreign: 'always',
component: 'always',
svelte: 'always'
};
Expand All @@ -67,6 +72,7 @@ export default createRule('html-self-closing', {
options = {
void: 'never',
normal: 'never',
foreign: 'never',
component: 'never',
svelte: 'never'
};
Expand All @@ -75,6 +81,7 @@ export default createRule('html-self-closing', {
options = {
void: 'always',
normal: 'never',
foreign: 'always',
component: 'never',
svelte: 'always'
};
Expand All @@ -101,6 +108,7 @@ export default createRule('html-self-closing', {
if (node.kind === 'component') return 'component';
if (node.kind === 'special') return 'svelte';
if (isVoidHtmlElement(node)) return 'void';
if (isForeignElement(node)) return 'foreign';
return 'normal';
}

Expand Down
9 changes: 8 additions & 1 deletion packages/eslint-plugin-svelte/src/utils/ast-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { TSESTree } from '@typescript-eslint/types';
import type { Scope, Variable } from '@typescript-eslint/scope-manager';
import type { AST as SvAST } from 'svelte-eslint-parser';
import * as eslintUtils from '@eslint-community/eslint-utils';
import voidElements from './void-elements';
import { voidElements, svgElements, mathmlElements } from './element-types';
import { getSourceCode } from './compat';

/**
Expand Down Expand Up @@ -560,6 +560,13 @@ export function isVoidHtmlElement(node: SvAST.SvelteElement): boolean {
return voidElements.includes(getNodeName(node));
}

/**
* Returns true if element is known foreign (SVG or MathML) element
*/
export function isForeignElement(node: SvAST.SvelteElement): boolean {
return svgElements.includes(getNodeName(node)) || mathmlElements.includes(getNodeName(node));
}

/** Checks whether the given identifier node is used as an expression. */
export function isExpressionIdentifier(node: TSESTree.Identifier): boolean {
const parent = node.parent;
Expand Down
140 changes: 140 additions & 0 deletions packages/eslint-plugin-svelte/src/utils/element-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
export const voidElements = [
'area',
'base',
'br',
'col',
'embed',
'hr',
'img',
'input',
'keygen',
'link',
'menuitem',
'meta',
'param',
'source',
'track',
'wbr'
];

export const svgElements = [
'altGlyph',
'altGlyphDef',
'altGlyphItem',
'animate',
'animateColor',
'animateMotion',
'animateTransform',
'circle',
'clipPath',
'color-profile',
'cursor',
'defs',
'desc',
'discard',
'ellipse',
'feBlend',
'feColorMatrix',
'feComponentTransfer',
'feComposite',
'feConvolveMatrix',
'feDiffuseLighting',
'feDisplacementMap',
'feDistantLight',
'feDropShadow',
'feFlood',
'feFuncA',
'feFuncB',
'feFuncG',
'feFuncR',
'feGaussianBlur',
'feImage',
'feMerge',
'feMergeNode',
'feMorphology',
'feOffset',
'fePointLight',
'feSpecularLighting',
'feSpotLight',
'feTile',
'feTurbulence',
'filter',
'font',
'font-face',
'font-face-format',
'font-face-name',
'font-face-src',
'font-face-uri',
'foreignObject',
'g',
'glyph',
'glyphRef',
'hatch',
'hatchpath',
'hkern',
'image',
'line',
'linearGradient',
'marker',
'mask',
'mesh',
'meshgradient',
'meshpatch',
'meshrow',
'metadata',
'missing-glyph',
'mpath',
'path',
'pattern',
'polygon',
'polyline',
'radialGradient',
'rect',
'set',
'solidcolor',
'stop',
'svg',
'switch',
'symbol',
'text',
'textPath',
'tref',
'tspan',
'unknown',
'use',
'view',
'vkern'
];

export const mathmlElements = [
'annotation',
'annotation-xml',
'maction',
'math',
'merror',
'mfrac',
'mi',
'mmultiscripts',
'mn',
'mo',
'mover',
'mpadded',
'mphantom',
'mprescripts',
'mroot',
'mrow',
'ms',
'mspace',
'msqrt',
'mstyle',
'msub',
'msubsup',
'msup',
'mtable',
'mtd',
'mtext',
'mtr',
'munder',
'munderover',
'semantics'
];
20 changes: 0 additions & 20 deletions packages/eslint-plugin-svelte/src/utils/void-elements.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"options": [
{
"foreign": "never"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
- message: Disallow self-closing on foreign (SVG or MathML) elements.
line: 2
column: 12
suggestions: null
- message: Disallow self-closing on foreign (SVG or MathML) elements.
line: 3
column: 13
suggestions: null
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<!-- prettier-ignore -->
<svg><path /></svg>
<math><msup /></math>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<!-- prettier-ignore -->
<svg><path ></path></svg>
<math><msup ></msup></math>
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@
line: 5
column: 18
suggestions: null
- message: Require self-closing on foreign (SVG or MathML) elements.
line: 6
column: 13
suggestions: null
- message: Require self-closing on foreign (SVG or MathML) elements.
line: 7
column: 14
suggestions: null
- message: Require self-closing on Svelte special elements.
line: 8
line: 10
column: 13
suggestions: null
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<div />
<img>
<TestComponent />
<svg><path></path></svg>
<math><msup></msup></math>
</div>
<!-- prettier-ignore -->
<svelte:head></svelte:head>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<div ></div>
<img/>
<TestComponent ></TestComponent>
<svg><path/></svg>
<math><msup/></math>
</div>
<!-- prettier-ignore -->
<svelte:head/>
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@
line: 5
column: 8
suggestions: null
- message: Disallow self-closing on foreign (SVG or MathML) elements.
line: 6
column: 14
suggestions: null
- message: Disallow self-closing on foreign (SVG or MathML) elements.
line: 7
column: 15
suggestions: null
- message: Disallow self-closing on Svelte special elements.
line: 8
line: 10
column: 14
suggestions: null
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<div />
<TestComponent />
<img />
<svg><path /></svg>
<math><msup /></math>
</div>
<!-- prettier-ignore -->
<svelte:head />
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<div ></div>
<TestComponent ></TestComponent>
<img >
<svg><path ></path></svg>
<math><msup ></msup></math>
</div>
<!-- prettier-ignore -->
<svelte:head ></svelte:head>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<div />
<div>hello</div>
<img />
<svg><path /></svg>
<math><msup /></math>
{#if true}
<svelte:self />
{/if}
Expand Down

0 comments on commit 85053a1

Please sign in to comment.