New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@html-eslint/eslint-plugin

Package Overview
Dependencies
Maintainers
0
Versions
70
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 0.26.0 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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc