You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 4-6.RSVP
Socket
Book a DemoInstallSign in
Socket

@html-eslint/eslint-plugin

Package Overview
Dependencies
Maintainers
0
Versions
86
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@html-eslint/eslint-plugin - npm Package Compare versions

Comparing version

to
0.27.0

lib/rules/no-extra-spacing-text.js

7

lib/configs/recommended.js

@@ -10,3 +10,8 @@ module.exports = {

"@html-eslint/attrs-newline": "error",
"@html-eslint/element-newline": "error",
"@html-eslint/element-newline": [
"error",
{
inline: [`$inline`],
},
],
"@html-eslint/no-duplicate-id": "error",

@@ -13,0 +18,0 @@ "@html-eslint/indent": "error",

317

lib/rules/element-newline.js

@@ -6,2 +6,13 @@ /**

* @typedef { import("../types").BaseNode } BaseNode
* @typedef { import("../types").CommentNode } CommentNode
* @typedef { import("../types").DoctypeNode } DoctypeNode
* @typedef { import("../types").ScriptTagNode } ScriptTagNode
* @typedef { import("../types").StyleTagNode } StyleTagNode
* @typedef { import("../types").TextNode } TextNode
* @typedef { CommentNode | DoctypeNode | ScriptTagNode | StyleTagNode | TagNode | TextNode } NewlineNode
* @typedef {{
* childFirst: NewlineNode | null;
* childLast: NewlineNode | null;
* shouldBeNewline: boolean;
* }} NodeMeta
*/

@@ -13,6 +24,48 @@

EXPECT_NEW_LINE_AFTER: "expectAfter",
EXPECT_NEW_LINE_AFTER_OPEN: "expectAfterOpen",
EXPECT_NEW_LINE_BEFORE: "expectBefore",
EXPECT_NEW_LINE_BEFORE_CLOSE: "expectBeforeClose",
};
/**
* @type {Object.<string, Array<string>>}
*/
const PRESETS = {
// From https://developer.mozilla.org/en-US/docs/Web/HTML/Element#inline_text_semantics
$inline: `
a
abbr
b
bdi
bdo
br
cite
code
data
dfn
em
i
kbd
mark
q
rp
rt
ruby
s
samp
small
span
strong
sub
sup
time
u
var
wbr
`
.trim()
.split(`\n`),
};
/**
* @type {RuleModule}

@@ -35,2 +88,9 @@ */

properties: {
inline: {
type: "array",
items: {
type: "string",
},
},
skip: {

@@ -47,5 +107,9 @@ type: "array",

[MESSAGE_IDS.EXPECT_NEW_LINE_AFTER]:
"There should be a linebreak after {{tag}}.",
"There should be a linebreak after {{tag}} element.",
[MESSAGE_IDS.EXPECT_NEW_LINE_AFTER_OPEN]:
"There should be a linebreak after {{tag}} open.",
[MESSAGE_IDS.EXPECT_NEW_LINE_BEFORE]:
"There should be a linebreak before {{tag}}.",
"There should be a linebreak before {{tag}} element.",
[MESSAGE_IDS.EXPECT_NEW_LINE_BEFORE_CLOSE]:
"There should be a linebreak before {{tag}} close.",
},

@@ -55,102 +119,201 @@ },

create(context) {
const option = context.options[0] || { skip: [] };
const skipTags = option.skip;
let skipTagCount = 0;
const option = context.options[0] || {};
const skipTags = option.skip || [];
const inlineTags = optionsOrPresets(option.inline || []);
/**
* @param {import("../types").ChildType<TagNode | ProgramNode>[]} siblings
* @param {Array<NewlineNode>} siblings
* @returns {NodeMeta} meta
*/
function checkSiblings(siblings) {
siblings
.filter((node) => node.type !== "Text")
.forEach((current, index, arr) => {
const after = arr[index + 1];
if (after) {
if (isOnTheSameLine(current, after)) {
/**
* @type {NodeMeta}
*/
const meta = {
childFirst: null,
childLast: null,
shouldBeNewline: false,
};
const nodesWithContent = [];
for (
let length = siblings.length, index = 0;
index < length;
index += 1
) {
const node = siblings[index];
if (isEmptyText(node) === false) {
nodesWithContent.push(node);
}
}
for (
let length = nodesWithContent.length, index = 0;
index < length;
index += 1
) {
const node = nodesWithContent[index];
const nodeNext = nodesWithContent[index + 1];
if (meta.childFirst === null) {
meta.childFirst = node;
}
meta.childLast = node;
const nodeShouldBeNewline = shouldBeNewline(node);
if (node.type === `Tag` && skipTags.includes(node.name) === false) {
const nodeMeta = checkSiblings(node.children);
const nodeChildShouldBeNewline = nodeMeta.shouldBeNewline;
if (nodeShouldBeNewline || nodeChildShouldBeNewline) {
meta.shouldBeNewline = true;
}
if (
nodeShouldBeNewline &&
nodeChildShouldBeNewline &&
nodeMeta.childFirst &&
nodeMeta.childLast
) {
if (
node.openEnd.loc.end.line === nodeMeta.childFirst.loc.start.line
) {
if (isNotNewlineStart(nodeMeta.childFirst)) {
context.report({
node: node,
messageId: MESSAGE_IDS.EXPECT_NEW_LINE_AFTER_OPEN,
data: { tag: label(node) },
fix(fixer) {
return fixer.insertTextAfter(node.openEnd, `\n`);
},
});
}
}
if (nodeMeta.childLast.loc.end.line === node.close.loc.start.line) {
if (isNotNewlineEnd(nodeMeta.childLast)) {
context.report({
node: node,
messageId: MESSAGE_IDS.EXPECT_NEW_LINE_BEFORE_CLOSE,
data: { tag: label(node, { isClose: true }) },
fix(fixer) {
return fixer.insertTextBefore(node.close, `\n`);
},
});
}
}
}
}
if (nodeNext && node.loc.end.line === nodeNext.loc.start.line) {
if (nodeShouldBeNewline) {
if (isNotNewlineStart(nodeNext)) {
context.report({
node: current,
node: nodeNext,
messageId: MESSAGE_IDS.EXPECT_NEW_LINE_AFTER,
// @ts-ignore
data: { tag: `<${current.name}>` },
data: { tag: label(node) },
fix(fixer) {
return fixer.insertTextAfter(current, "\n");
return fixer.insertTextAfter(node, `\n`);
},
});
}
} else if (shouldBeNewline(nodeNext)) {
if (isNotNewlineEnd(node)) {
context.report({
node: nodeNext,
messageId: MESSAGE_IDS.EXPECT_NEW_LINE_BEFORE,
data: { tag: label(nodeNext) },
fix(fixer) {
return fixer.insertTextBefore(nodeNext, `\n`);
},
});
}
}
});
}
}
return meta;
}
/**
* @param {TagNode} node
* @param {import("../types").ChildType<TagNode>[]} children
* @param {NewlineNode} node
*/
function checkChild(node, children) {
const targetChildren = children.filter((n) => n.type !== "Text");
const first = targetChildren[0];
const last = targetChildren[targetChildren.length - 1];
if (first) {
if (isOnTheSameLine(node.openEnd, first)) {
context.report({
node: node.openEnd,
messageId: MESSAGE_IDS.EXPECT_NEW_LINE_AFTER,
data: { tag: `<${node.name}>` },
fix(fixer) {
return fixer.insertTextAfter(node.openEnd, "\n");
},
});
}
function isEmptyText(node) {
return node.type === `Text` && node.value.trim().length === 0;
}
/**
* @param {NewlineNode} node
*/
function isNotNewlineEnd(node) {
return node.type !== `Text` || /\n\s*$/.test(node.value) === false;
}
/**
* @param {NewlineNode} node
*/
function isNotNewlineStart(node) {
return node.type !== `Text` || /^\n/.test(node.value) === false;
}
/**
* @param {NewlineNode} node
* @param {{ isClose?: boolean }} options
*/
function label(node, options = {}) {
const isClose = options.isClose || false;
switch (node.type) {
case `Tag`:
if (isClose) {
return `</${node.name}>`;
}
return `<${node.name}>`;
default:
return `<${node.type}>`;
}
}
if (last) {
if (node.close && isOnTheSameLine(node.close, last)) {
context.report({
node: node.close,
messageId: MESSAGE_IDS.EXPECT_NEW_LINE_BEFORE,
data: { tag: `</${node.name}>` },
fix(fixer) {
return fixer.insertTextBefore(node.close, "\n");
},
});
/**
* @param {Array<string>} options
*/
function optionsOrPresets(options) {
const result = [];
for (const option of options) {
if (option in PRESETS) {
const preset = PRESETS[option];
result.push(...preset);
} else {
result.push(option);
}
}
return result;
}
/**
* @param {NewlineNode} node
*/
function shouldBeNewline(node) {
switch (node.type) {
case `Comment`:
return /[\n\r]+/.test(node.value.value.trim());
case `Tag`:
return inlineTags.includes(node.name.toLowerCase()) === false;
case `Text`:
return /[\n\r]+/.test(node.value.trim());
default:
return true;
}
}
return {
Program(node) {
// @ts-ignore
checkSiblings(node.body);
},
Tag(node) {
if (skipTagCount > 0) {
return;
}
if (skipTags.includes(node.name)) {
skipTagCount++;
return;
}
checkSiblings(node.children);
checkChild(node, node.children);
},
/**
* @param {TagNode} node
* @returns
*/
"Tag:exit"(node) {
if (skipTags.includes(node.name)) {
skipTagCount--;
return;
}
},
};
},
};
/**
* @param {BaseNode} nodeBefore
* @param {BaseNode} nodeAfter
* @returns
*/
function isOnTheSameLine(nodeBefore, nodeAfter) {
if (nodeBefore && nodeAfter) {
return nodeBefore.loc.end.line === nodeAfter.loc.start.line;
}
return false;
}

@@ -9,2 +9,3 @@ const requireLang = require("./require-lang");

const noExtraSpacingAttrs = require("./no-extra-spacing-attrs");
const noExtraSpacingText = require("./no-extra-spacing-text");
const attrsNewline = require("./attrs-newline");

@@ -50,2 +51,3 @@ const elementNewLine = require("./element-newline");

"no-extra-spacing-attrs": noExtraSpacingAttrs,
"no-extra-spacing-text": noExtraSpacingText,
"attrs-newline": attrsNewline,

@@ -52,0 +54,0 @@ "element-newline": elementNewLine,

@@ -24,2 +24,3 @@ /**

EXTRA_BEFORE_CLOSE: "unexpectedBeforeClose",
EXTRA_IN_ASSIGNMENT: "unexpectedInAssignment",
MISSING_BEFORE: "missingBefore",

@@ -51,2 +52,5 @@ MISSING_BEFORE_SELF_CLOSE: "missingBeforeSelfClose",

properties: {
disallowInAssignment: {
type: "boolean",
},
disallowMissing: {

@@ -69,2 +73,4 @@ type: "boolean",

[MESSAGE_IDS.EXTRA_BEFORE_CLOSE]: "Unexpected space before closing",
[MESSAGE_IDS.EXTRA_IN_ASSIGNMENT]:
"Unexpected space in attribute assignment",
[MESSAGE_IDS.MISSING_BEFORE_SELF_CLOSE]:

@@ -88,2 +94,4 @@ "Missing space before self closing",

const disallowTabs = !!(context.options[0] || {}).disallowTabs;
const disallowInAssignment = !!(context.options[0] || [])
.disallowInAssignment;

@@ -111,3 +119,6 @@ const sourceCode = context.getSourceCode().text;

fix(fixer) {
return fixer.removeRange([current.range[1] + 1, after.range[0]]);
return fixer.replaceTextRange(
[current.range[1], after.range[0]],
` `
);
},

@@ -130,3 +141,3 @@ });

return fixer.replaceTextRange(
[current.range[1], current.range[1] + 1],
[current.range[1], after.range[0]],
` `

@@ -193,2 +204,21 @@ );

checkExtraSpaceBefore(node.openStart, node.attributes[0]);
for (const attr of node.attributes) {
if (attr.startWrapper && attr.value) {
if (
disallowInAssignment &&
attr.startWrapper.loc.start.column - attr.key.loc.end.column > 1
) {
const start = attr.key.range[1];
const end = attr.startWrapper.range[0];
context.report({
node: attr,
messageId: MESSAGE_IDS.EXTRA_IN_ASSIGNMENT,
fix(fixer) {
return fixer.replaceTextRange([start, end], `=`);
},
});
}
}
}
}

@@ -195,0 +225,0 @@

@@ -270,1 +270,9 @@ import ESTree from "estree";

: never;
export type ContentNode =
| CommentNode
| DoctypeNode
| ScriptTagNode
| StyleTagNode
| TagNode
| TextNode;
{
"name": "@html-eslint/eslint-plugin",
"version": "0.26.0",
"version": "0.27.0",
"description": "ESLint plugin for html",

@@ -48,3 +48,3 @@ "author": "yeonjuan",

"devDependencies": {
"@html-eslint/parser": "^0.26.0",
"@html-eslint/parser": "^0.27.0",
"@types/eslint": "^8.56.2",

@@ -55,3 +55,3 @@ "@types/estree": "^0.0.47",

},
"gitHead": "34d55c3b5be5a29cc416063b4b4375cb89b3a519"
"gitHead": "a7c09dfb3090bb779d6fe62fda814d4d7ca07d4a"
}

@@ -9,3 +9,5 @@ export const rules: {

"@html-eslint/attrs-newline": string;
"@html-eslint/element-newline": string;
"@html-eslint/element-newline": (string | {
inline: string[];
})[];
"@html-eslint/no-duplicate-id": string;

@@ -12,0 +14,0 @@ "@html-eslint/indent": string;

@@ -7,2 +7,13 @@ declare const _exports: RuleModule;

export type BaseNode = import("../types").BaseNode;
export type CommentNode = import("../types").CommentNode;
export type DoctypeNode = import("../types").DoctypeNode;
export type ScriptTagNode = import("../types").ScriptTagNode;
export type StyleTagNode = import("../types").StyleTagNode;
export type TextNode = import("../types").TextNode;
export type NewlineNode = CommentNode | DoctypeNode | ScriptTagNode | StyleTagNode | TagNode | TextNode;
export type NodeMeta = {
childFirst: NewlineNode | null;
childLast: NewlineNode | null;
shouldBeNewline: boolean;
};
//# sourceMappingURL=element-newline.d.ts.map

@@ -10,2 +10,3 @@ declare const _exports: {

"no-extra-spacing-attrs": import("../types").RuleModule;
"no-extra-spacing-text": import("../types").RuleModule;
"attrs-newline": import("../types").RuleModule;

@@ -12,0 +13,0 @@ "element-newline": import("../types").RuleModule;

@@ -16,3 +16,3 @@ export type TagNode = import("../../types").TagNode;

*/
declare function findAttr(node: import("../../types").TagNode | import("../../types").ScriptTagNode | import("../../types").StyleTagNode, key: string): import("../../types").AttributeNode | undefined;
declare function findAttr(node: import("../../types").ScriptTagNode | import("../../types").TagNode | import("../../types").StyleTagNode, key: string): import("../../types").AttributeNode | undefined;
/**

@@ -19,0 +19,0 @@ * Checks whether a node's all tokens are on the same line or not.

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet