Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 23 additions & 23 deletions htmlhint-server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions htmlhint-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"dependencies": {
"htmlhint": "^1.5.1",
"strip-json-comments": "3.1.1",
"vscode-languageserver": "3.5.1"
"vscode-languageserver": "9.0.1",
"vscode-languageserver-textdocument": "^1.0.12"
},
"devDependencies": {
"@types/node": "20.17.57",
Expand All @@ -19,4 +20,4 @@
"engines": {
"node": "*"
}
}
}
110 changes: 54 additions & 56 deletions htmlhint-server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,26 @@
/// <reference path="typings-custom/htmlhint.d.ts" />

import * as path from "path";
import * as server from "vscode-languageserver";
import {
createConnection,
TextDocuments,
Diagnostic,
DiagnosticSeverity,
ProposedFeatures,
InitializeParams,
DidChangeConfigurationNotification,
CompletionItem,
CompletionItemKind,
TextDocumentPositionParams,
TextDocumentSyncKind,
InitializeResult,
Connection,
ErrorMessageTracker,
CancellationToken,
ResponseError,
InitializeError,
} from "vscode-languageserver/node";
import { TextDocument } from "vscode-languageserver-textdocument";
import * as htmlhint from "htmlhint";
import fs = require("fs");
let stripJsonComments: any = require("strip-json-comments");
Expand Down Expand Up @@ -80,12 +99,9 @@
/**
* Given an htmlhint.Error type return a VS Code server Diagnostic object
*/
function makeDiagnostic(
problem: htmlhint.Error,
lines: string[],
): server.Diagnostic {
function makeDiagnostic(problem: htmlhint.Error, lines: string[]): Diagnostic {
return {
severity: server.DiagnosticSeverity.Warning,
severity: DiagnosticSeverity.Warning,
message: problem.message,
range: getRange(problem, lines),
code: problem.rule.id,
Expand Down Expand Up @@ -193,21 +209,19 @@
);
}

function getErrorMessage(err: unknown, document: server.TextDocument): string {
function getErrorMessage(err: unknown, document: TextDocument): string {
if (isErrorWithMessage(err)) {
return err.message;
}

return `An unknown error occurred while validating file: ${server.Files.uriToFilePath(
document.uri,
)}`;
return `An unknown error occurred while validating file: ${document.uri}`;
}

function validateAllTextDocuments(
connection: server.IConnection,
documents: server.TextDocument[],
connection: Connection,
documents: TextDocument[],
): void {
let tracker = new server.ErrorMessageTracker();
let tracker = new ErrorMessageTracker();
documents.forEach((document) => {
try {
validateTextDocument(connection, document);
Expand All @@ -219,8 +233,8 @@
}

function validateTextDocument(
connection: server.IConnection,
document: server.TextDocument,
connection: Connection,
document: TextDocument,
): void {
try {
doValidate(connection, document);
Expand All @@ -229,16 +243,16 @@
}
}

let connection: server.IConnection = server.createConnection();
let documents: server.TextDocuments = new server.TextDocuments();
let connection: Connection = createConnection(ProposedFeatures.all);
let documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
documents.listen(connection);

function trace(message: string, verbose?: string): void {
connection.tracer.log(message, verbose);
}

connection.onInitialize(
(params: server.InitializeParams, token: server.CancellationToken) => {
(params: InitializeParams, token: CancellationToken) => {
let rootFolder = params.rootPath;
let initOptions: {
nodePath: string;
Expand All @@ -249,55 +263,34 @@
: undefined
: undefined;

const result = server.Files.resolveModule2(
rootFolder,
"htmlhint",
nodePath,
trace,
).then(
(
value,
):
| server.InitializeResult
| server.ResponseError<server.InitializeError> => {
linter = value.default || value.HTMLHint || value;
//connection.window.showInformationMessage(`onInitialize() - found local htmlhint (version ! ${value.HTMLHint.version})`);

let result: server.InitializeResult = {
capabilities: { textDocumentSync: documents.syncKind },
};
return result;
},
(error) => {
// didn't find htmlhint in project or global, so use embedded version.
linter = htmlhint.default || htmlhint.HTMLHint || htmlhint;
//connection.window.showInformationMessage(`onInitialize() using embedded htmlhint(version ! ${linter.version})`);
let result: server.InitializeResult = {
capabilities: { textDocumentSync: documents.syncKind },
};
return result;
},
);
// Since Files API is no longer available, we'll use embedded htmlhint directly
linter = htmlhint.default || htmlhint.HTMLHint || htmlhint;

return result as Thenable<server.InitializeResult>;
let result: InitializeResult = {
capabilities: {
textDocumentSync: TextDocumentSyncKind.Incremental,
},
};
return result;
},
);

function doValidate(
connection: server.IConnection,
document: server.TextDocument,
): void {
function doValidate(connection: Connection, document: TextDocument): void {
try {
let uri = document.uri;
let fsPath = server.Files.uriToFilePath(uri);
// Convert URI to file path manually since Files API is not available
let fsPath = uri.replace(/^file:\/\//, "");
if (process.platform === "win32" && fsPath.startsWith("/")) {
fsPath = fsPath.substring(1);
}
Comment on lines +281 to +285

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider extracting the platform-specific path conversion to a separate function for better readability and maintainability.

    let uri = document.uri;
    // Convert URI to file path manually since Files API is not available
    let fsPath = uri.replace(/^file:\/\//, "");
    if (process.platform === "win32" && fsPath.startsWith("/")) {
      fsPath = fsPath.substring(1);
    }

let contents = document.getText();
let lines = contents.split("\n");

let config = getConfiguration(fsPath);

let errors: htmlhint.Error[] = linter.verify(contents, config);

let diagnostics: server.Diagnostic[] = [];
let diagnostics: Diagnostic[] = [];
if (errors.length > 0) {
errors.forEach((each) => {
diagnostics.push(makeDiagnostic(each, lines));
Expand Down Expand Up @@ -334,8 +327,13 @@
// The watched .htmlhintrc has changed. Clear out the last loaded config, and revalidate all documents.
connection.onDidChangeWatchedFiles((params) => {
for (let i = 0; i < params.changes.length; i++) {
htmlhintrcOptions[server.Files.uriToFilePath(params.changes[i].uri)] =
undefined;
// Convert URI to file path manually since Files API is not available
let uri = params.changes[i].uri;
let fsPath = uri.replace(/^file:\/\//, "");
if (process.platform === "win32" && fsPath.startsWith("/")) {
fsPath = fsPath.substring(1);
}
htmlhintrcOptions[fsPath] = undefined;
}
validateAllTextDocuments(connection, documents.all());
});
Expand Down
25 changes: 19 additions & 6 deletions htmlhint/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {
LanguageClientOptions,
ServerOptions,
TransportKind,
} from "vscode-languageclient";
} from "vscode-languageclient/node";

export function activate(context: ExtensionContext) {
let client: LanguageClient;

export async function activate(_context: ExtensionContext) {
// We need to go one level up since an extension compile the js code into
// the output folder.
let serverModulePath = path.join(__dirname, "..", "server", "server.js");
Expand Down Expand Up @@ -43,9 +45,20 @@ export function activate(context: ExtensionContext) {
};

// Create the language client and start it
let client = new LanguageClient("HTML-hint", serverOptions, clientOptions);
client = new LanguageClient(
"HTML-hint",
"HTML-hint",
serverOptions,
clientOptions,
);

// Start the client
await client.start();
}

// Start the client and add it to the subscriptions
let disposable = client.start();
context.subscriptions.push(disposable);
export async function deactivate(): Promise<void> {
if (!client) {
return;
}
return client.stop();
}
Loading