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" },