diff --git a/htmlhint-server/src/server.ts b/htmlhint-server/src/server.ts
index 345e24c..f904fe3 100644
--- a/htmlhint-server/src/server.ts
+++ b/htmlhint-server/src/server.ts
@@ -262,6 +262,67 @@ function loadConfigurationFile(configFile: string): HtmlHintConfig | null {
return ruleset;
}
+function isHtmlHintRuleEnabled(value: unknown): boolean {
+ if (value === undefined || value === null) {
+ return false;
+ }
+
+ if (Array.isArray(value)) {
+ if (value.length === 0) {
+ return false;
+ }
+ return isHtmlHintRuleEnabled(value[0]);
+ }
+
+ if (typeof value === "boolean") {
+ return value;
+ }
+
+ if (typeof value === "number") {
+ return value !== 0;
+ }
+
+ if (typeof value === "string") {
+ const normalized = value.trim().toLowerCase();
+ return normalized !== "false" && normalized !== "0" && normalized !== "";
+ }
+
+ if (typeof value === "object") {
+ return true;
+ }
+
+ return false;
+}
+
+function isRuleEnabledForDocument(
+ document: TextDocument,
+ ruleId: string,
+): boolean {
+ try {
+ const parsedUri = URI.parse(document.uri);
+
+ if (parsedUri.scheme !== "file") {
+ trace(
+ `[DEBUG] isRuleEnabledForDocument: Non-file scheme for ${document.uri}, rule ${ruleId} treated as disabled`,
+ );
+ return false;
+ }
+
+ const config = getConfiguration(parsedUri.fsPath);
+ const ruleValue = (config as Record)[ruleId];
+ const enabled = isHtmlHintRuleEnabled(ruleValue);
+ trace(
+ `[DEBUG] isRuleEnabledForDocument: Rule ${ruleId} enabled=${enabled} for ${document.uri}`,
+ );
+ return enabled;
+ } catch (error) {
+ trace(
+ `[DEBUG] isRuleEnabledForDocument: Failed to determine rule ${ruleId} for ${document.uri}: ${error}`,
+ );
+ return false;
+ }
+}
+
function isErrorWithMessage(err: unknown): err is { message: string } {
return (
typeof err === "object" &&
@@ -789,7 +850,9 @@ function createMetaCharsetRequireFix(
// Insert charset meta tag at the beginning of head (right after )
const headStart = headMatch.index! + headMatch[0].indexOf(">") + 1;
const insertPosition = headStart;
- const newText = '\n ';
+ const shouldSelfClose = isRuleEnabledForDocument(document, "tag-self-close");
+ const newText =
+ '\n " : ">");
const edit: TextEdit = {
range: {
@@ -846,19 +909,20 @@ function createMetaViewportRequireFix(
/]*>/i,
);
+ const shouldSelfClose = isRuleEnabledForDocument(document, "tag-self-close");
+ trace(
+ `[DEBUG] createMetaViewportRequireFix: tag-self-close enabled=${shouldSelfClose}`,
+ );
+ const viewportSnippet = `\n " : ">"}`;
+
let insertPosition: number;
- let newText: string;
if (metaCharsetMatch) {
const metaCharsetEnd =
headStart + metaCharsetMatch.index! + metaCharsetMatch[0].length;
insertPosition = metaCharsetEnd;
- newText =
- '\n ';
} else {
insertPosition = headStart;
- newText =
- '\n ';
}
const edit: TextEdit = {
@@ -866,7 +930,7 @@ function createMetaViewportRequireFix(
start: document.positionAt(insertPosition),
end: document.positionAt(insertPosition),
},
- newText: newText,
+ newText: viewportSnippet,
};
const workspaceEdit: WorkspaceEdit = {
@@ -923,24 +987,24 @@ function createMetaDescriptionRequireFix(
);
let insertPosition: number;
- let newText: string;
+ const shouldSelfClose = isRuleEnabledForDocument(document, "tag-self-close");
+ const descriptionSnippet =
+ '\n " : ">");
if (metaViewportMatch) {
// Insert after viewport meta tag
const metaViewportEnd =
headStart + metaViewportMatch.index! + metaViewportMatch[0].length;
insertPosition = metaViewportEnd;
- newText = '\n ';
} else if (metaCharsetMatch) {
// Insert after charset meta tag
const metaCharsetEnd =
headStart + metaCharsetMatch.index! + metaCharsetMatch[0].length;
insertPosition = metaCharsetEnd;
- newText = '\n ';
} else {
// Insert at the beginning of head
insertPosition = headStart;
- newText = '\n ';
}
const edit: TextEdit = {
@@ -948,7 +1012,7 @@ function createMetaDescriptionRequireFix(
start: document.positionAt(insertPosition),
end: document.positionAt(insertPosition),
},
- newText: newText,
+ newText: descriptionSnippet,
};
const workspaceEdit: WorkspaceEdit = {
@@ -1977,7 +2041,10 @@ function createAttrValueNoDuplicationFix(
`[DEBUG] createAttrValueNoDuplicationFix called with diagnostic: ${JSON.stringify(diagnostic)}`,
);
- if (!diagnostic.data || diagnostic.data.ruleId !== "attr-value-no-duplication") {
+ if (
+ !diagnostic.data ||
+ diagnostic.data.ruleId !== "attr-value-no-duplication"
+ ) {
trace(
`[DEBUG] createAttrValueNoDuplicationFix: Invalid diagnostic data or ruleId`,
);
@@ -1990,7 +2057,9 @@ function createAttrValueNoDuplicationFix(
// Use robust tag boundary detection
const tagBoundaries = findTagBoundaries(text, diagnosticOffset);
if (!tagBoundaries) {
- trace(`[DEBUG] createAttrValueNoDuplicationFix: Could not find tag boundaries`);
+ trace(
+ `[DEBUG] createAttrValueNoDuplicationFix: Could not find tag boundaries`,
+ );
return null;
}
@@ -2017,8 +2086,9 @@ function createAttrValueNoDuplicationFix(
if (values.length !== uniqueValues.length) {
// Found duplicates, create an edit to fix them
- const newAttrValue = uniqueValues.join(' ');
- const quote = match[2] !== undefined ? '"' : match[3] !== undefined ? "'" : '';
+ const newAttrValue = uniqueValues.join(" ");
+ const quote =
+ match[2] !== undefined ? '"' : match[3] !== undefined ? "'" : "";
const newFullMatch = quote
? `${attrName}=${quote}${newAttrValue}${quote}`
: `${attrName}=${newAttrValue}`;
diff --git a/htmlhint/CHANGELOG.md b/htmlhint/CHANGELOG.md
index 22c7a77..fe81b23 100644
--- a/htmlhint/CHANGELOG.md
+++ b/htmlhint/CHANGELOG.md
@@ -2,6 +2,10 @@
All notable changes to the "vscode-htmlhint" extension will be documented in this file.
+### v1.15.0 (2025-11-27)
+
+- Smarter autofix for rules which accommodates for `tag-self-close` rule.
+
### v1.14.0 (2025-11-26)
- Add autofix for the `attr-no-duplication` rule
diff --git a/htmlhint/package-lock.json b/htmlhint/package-lock.json
index 06efc1b..c781618 100644
--- a/htmlhint/package-lock.json
+++ b/htmlhint/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "vscode-htmlhint",
- "version": "1.14.0",
+ "version": "1.15.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vscode-htmlhint",
- "version": "1.14.0",
+ "version": "1.15.0",
"bundleDependencies": [
"vscode-languageclient",
"htmlhint",
diff --git a/htmlhint/package.json b/htmlhint/package.json
index 933b451..37593a6 100644
--- a/htmlhint/package.json
+++ b/htmlhint/package.json
@@ -3,7 +3,7 @@
"displayName": "HTMLHint",
"description": "VS Code integration for HTMLHint - A Static Code Analysis Tool for HTML",
"icon": "images/icon.png",
- "version": "1.14.0",
+ "version": "1.15.0",
"publisher": "HTMLHint",
"galleryBanner": {
"color": "#333333",
diff --git a/package-lock.json b/package-lock.json
index 2f536b0..208fdd4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,7 +25,7 @@
"glob": "^13.0.0",
"globals": "^16.5.0",
"mocha": "^11.7.5",
- "prettier": "3.6.2",
+ "prettier": "3.7.1",
"ts-node": "^10.9.2",
"typescript": "5.5.4"
},
@@ -2359,10 +2359,11 @@
}
},
"node_modules/prettier": {
- "version": "3.6.2",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
- "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.1.tgz",
+ "integrity": "sha512-RWKXE4qB3u5Z6yz7omJkjWwmTfLdcbv44jUVHC5NpfXwFGzvpQM798FGv/6WNK879tc+Cn0AAyherCl1KjbyZQ==",
"dev": true,
+ "license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
diff --git a/package.json b/package.json
index bbc4456..c920278 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"version": "1.0.0",
"private": true,
"description": "VS Code extension to support HTMLHint, an HTML linter.",
- "homepage": "https://github.com/htmlhint/vscode-htmlhint#readme",
+ "homepage": "https://htmlhint.com",
"bugs": {
"url": "https://github.com/htmlhint/vscode-htmlhint/issues"
},
@@ -43,7 +43,7 @@
"glob": "^13.0.0",
"globals": "^16.5.0",
"mocha": "^11.7.5",
- "prettier": "3.6.2",
+ "prettier": "3.7.1",
"ts-node": "^10.9.2",
"typescript": "5.5.4"
},