frontend-standards-checker
Version:
A comprehensive frontend standards validation tool with TypeScript support
204 lines (203 loc) • 7.79 kB
JavaScript
;
/**
* Language Server Protocol implementation for Frontend Standards Checker
* Provides real-time validation while coding in VS Code
*/
Object.defineProperty(exports, "__esModule", { value: true });
const node_js_1 = require("vscode-languageserver/node.js");
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
const vscode_uri_1 = require("vscode-uri");
const index_js_1 = require("../index.js");
// Default settings
const defaultSettings = {
maxNumberOfProblems: 1000,
enabled: true,
configPath: './checkFrontendStandards.config.mjs',
includePatterns: ['**/*.{ts,tsx,js,jsx}'],
excludePatterns: ['**/node_modules/**', '**/dist/**', '**/build/**'],
};
let globalSettings = defaultSettings;
let documentSettings = new Map();
// Create a connection for the server, using Node's IPC as a transport.
const connection = (0, node_js_1.createConnection)(node_js_1.ProposedFeatures.all);
// Create a simple text document manager.
const documents = new node_js_1.TextDocuments(vscode_languageserver_textdocument_1.TextDocument);
let hasConfigurationCapability = false;
let hasWorkspaceFolderCapability = false;
// Frontend Standards Checker instance
let frontendChecker = null;
connection.onInitialize((params) => {
const capabilities = params.capabilities;
// Does the client support the `workspace/configuration` request?
hasConfigurationCapability = !!(capabilities.workspace && !!capabilities.workspace.configuration);
hasWorkspaceFolderCapability = !!(capabilities.workspace && !!capabilities.workspace.workspaceFolders);
const result = {
capabilities: {
textDocumentSync: node_js_1.TextDocumentSyncKind.Incremental,
diagnosticProvider: {
interFileDependencies: false,
workspaceDiagnostics: false,
},
},
};
if (hasWorkspaceFolderCapability) {
result.capabilities.workspace = {
workspaceFolders: {
supported: true,
},
};
}
return result;
});
connection.onInitialized(() => {
if (hasConfigurationCapability) {
// Register for all configuration changes.
connection.client.register(node_js_1.DidChangeConfigurationNotification.type, undefined);
}
if (hasWorkspaceFolderCapability) {
connection.workspace.onDidChangeWorkspaceFolders((_event) => {
connection.console.log('Workspace folder change event received.');
});
}
// Initialize Frontend Standards Checker
try {
frontendChecker = new index_js_1.FrontendStandardsChecker();
connection.console.log('Frontend Standards Checker initialized successfully');
}
catch (error) {
connection.console.error(`Failed to initialize Frontend Standards Checker: ${error}`);
}
});
connection.onDidChangeConfiguration((change) => {
if (hasConfigurationCapability) {
// Reset all cached document settings
documentSettings.clear();
}
else {
globalSettings = ((change.settings.frontendStandards || defaultSettings));
}
// Revalidate all open text documents
documents.all().forEach(validateTextDocument);
});
function getDocumentSettings(resource) {
if (!hasConfigurationCapability) {
return Promise.resolve(globalSettings);
}
let result = documentSettings.get(resource);
if (!result) {
result = connection.workspace.getConfiguration({
scopeUri: resource,
section: 'frontendStandards',
});
documentSettings.set(resource, result);
}
return result;
}
// Only keep settings for open documents
documents.onDidClose((e) => {
documentSettings.delete(e.document.uri);
});
// The content of a text document has changed. This event is emitted
// when the text document first opened or when its content has changed.
documents.onDidChangeContent((change) => {
validateTextDocument(change.document);
});
// Diagnostic provider
connection.languages.diagnostics.on(async (params) => {
const document = documents.get(params.textDocument.uri);
if (document !== undefined) {
return {
kind: node_js_1.DocumentDiagnosticReportKind.Full,
items: await validateDocument(document),
};
}
else {
// We don't know the document. We can either try to read it from disk
// or we don't report problems for it.
return {
kind: node_js_1.DocumentDiagnosticReportKind.Full,
items: [],
};
}
});
async function validateTextDocument(textDocument) {
const diagnostics = await validateDocument(textDocument);
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
}
async function validateDocument(textDocument) {
const settings = await getDocumentSettings(textDocument.uri);
if (!settings.enabled || !frontendChecker) {
return [];
}
const uri = vscode_uri_1.URI.parse(textDocument.uri);
const filePath = uri.fsPath;
// Check if file should be validated based on patterns
const shouldValidate = isFileIncluded(filePath, settings.includePatterns || [], settings.excludePatterns || []);
if (!shouldValidate) {
return [];
}
try {
// Validate using Frontend Standards Checker
const validationErrors = await frontendChecker.validateContent(textDocument.getText(), filePath);
const diagnostics = validationErrors
.slice(0, settings.maxNumberOfProblems)
.map((error) => convertToDiagnostic(error, textDocument));
return diagnostics;
}
catch (error) {
connection.console.error(`Error validating document ${filePath}: ${error}`);
return [];
}
}
function convertToDiagnostic(error, textDocument) {
const line = Math.max(0, (error.line || 1) - 1);
const column = Math.max(0, error.column || 0);
// Try to get the actual line length for better range
const lineText = textDocument
.getText({
start: { line, character: 0 },
end: { line: line + 1, character: 0 },
})
.trim();
const endColumn = column + Math.max(1, lineText.length || 1);
return {
severity: getDiagnosticSeverity(error.severity),
range: {
start: { line, character: column },
end: { line, character: endColumn },
},
message: `[${error.rule}] ${error.message}`,
source: 'frontend-standards',
code: error.rule,
};
}
function getDiagnosticSeverity(severity) {
switch (severity.toLowerCase()) {
case 'error':
return node_js_1.DiagnosticSeverity.Error;
case 'warning':
return node_js_1.DiagnosticSeverity.Warning;
case 'info':
return node_js_1.DiagnosticSeverity.Information;
default:
return node_js_1.DiagnosticSeverity.Hint;
}
}
function isFileIncluded(filePath, includePatterns, excludePatterns) {
// Simple pattern matching - you might want to use a proper glob library
const fileName = filePath.split('/').pop() || '';
const isIncluded = includePatterns.some((pattern) => {
const regex = new RegExp(pattern.replace(/\*/g, '.*').replace(/\?/g, '.'));
return regex.test(filePath) || regex.test(fileName);
});
const isExcluded = excludePatterns.some((pattern) => {
const regex = new RegExp(pattern.replace(/\*/g, '.*').replace(/\?/g, '.'));
return regex.test(filePath);
});
return isIncluded && !isExcluded;
}
// Make the text document manager listen on the connection
// for open, change and close text document events
documents.listen(connection);
// Listen on the connection
connection.listen();