UNPKG

w1-system-font-validator

Version:

VS Code extension for validating W1 System font variables (both fontConfig.json and localFontConfig.json)

224 lines (189 loc) 7.44 kB
import * as fs from "fs"; import * as path from "path"; import * as vscode from "vscode"; import { FontConfig, LocalFontConfig, ProjectFontConfig, WeightInfo } from "../types/fontConfig"; export class ConfigDetector { private configWatcher: vscode.FileSystemWatcher | null = null; constructor() { this.setupConfigWatcher(); } /** * Find project root by looking for project boundary markers */ private findProjectRoot(filePath: string): string | null { let currentDir = path.dirname(filePath); const workspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(filePath)); const workspaceRoot = workspaceFolder?.uri.fsPath; while (currentDir !== path.dirname(currentDir)) { // Stop at workspace boundary if (workspaceRoot && currentDir === workspaceRoot) { const projectMarkers = ["package.json", ".git", "next.config.js", "tsconfig.json"]; for (const marker of projectMarkers) { if (fs.existsSync(path.join(currentDir, marker))) { return currentDir; } } break; } // Check for project boundary markers const projectMarkers = ["package.json", ".git", "next.config.js", "tsconfig.json"]; for (const marker of projectMarkers) { if (fs.existsSync(path.join(currentDir, marker))) { return currentDir; } } currentDir = path.dirname(currentDir); } return null; } /** * Detect and load font configurations for a specific file */ public async detectFontConfig(filePath: string): Promise<ProjectFontConfig> { const projectRoot = this.findProjectRoot(filePath); const result: ProjectFontConfig = { hasFontConfig: false, hasLocalFontConfig: false, }; if (!projectRoot) { // No project root found return result; } // Check for fontConfig.json (package-based fonts) const fontConfigPath = path.join(projectRoot, "fontConfig.json"); if (fs.existsSync(fontConfigPath)) { try { const fontConfigContent = fs.readFileSync(fontConfigPath, "utf8"); const fontConfig = JSON.parse(fontConfigContent) as FontConfig; result.hasFontConfig = true; result.fontConfigPath = fontConfigPath; result.fontConfig = fontConfig; // fontConfig.json loaded successfully } catch (error) { console.error(`[W1 Font Validator] Error loading fontConfig.json:`, error); } } // Check for localFontConfig.json (local file-based fonts) const localFontConfigPath = path.join(projectRoot, "localFontConfig.json"); if (fs.existsSync(localFontConfigPath)) { try { const localFontConfigContent = fs.readFileSync(localFontConfigPath, "utf8"); const localFontConfig = JSON.parse(localFontConfigContent) as LocalFontConfig; result.hasLocalFontConfig = true; result.localFontConfigPath = localFontConfigPath; result.localFontConfig = localFontConfig; // localFontConfig.json loaded successfully } catch (error) { console.error(`[W1 Font Validator] Error loading localFontConfig.json:`, error); } } return result; } /** * Get all valid font-family variables from both configurations */ public getValidFontFamilies(projectConfig: ProjectFontConfig): Set<string> { const validFamilies = new Set<string>(); // Add package-based font families if (projectConfig.fontConfig) { Object.values(projectConfig.fontConfig.roles).forEach((role) => { if (role.variables?.family) { validFamilies.add(role.variables.family); } }); } // Add local font families if (projectConfig.localFontConfig) { if (projectConfig.localFontConfig.detailedRoles) { Object.values(projectConfig.localFontConfig.detailedRoles).forEach((role) => { if (role.variables?.family) { validFamilies.add(role.variables.family); } }); } // Also add from validVariables array (fallback) projectConfig.localFontConfig.validVariables?.forEach((variable) => { if (variable.startsWith("--fontfamily_")) { validFamilies.add(variable); } }); } return validFamilies; } /** * Get available weights for a specific font-family variable */ public getAvailableWeights(projectConfig: ProjectFontConfig, fontFamilyVar: string): string[] { // Check package-based fonts if (projectConfig.fontConfig) { for (const role of Object.values(projectConfig.fontConfig.roles)) { if (role.variables?.family === fontFamilyVar) { // Extract weight keys from the availableWeights object return Object.keys(role.availableWeights || {}).filter((key) => role.availableWeights[key] !== null); } } } // Check local fonts (now uses same object format as package fonts) if (projectConfig.localFontConfig?.detailedRoles) { for (const role of Object.values(projectConfig.localFontConfig.detailedRoles)) { if (role.variables?.family === fontFamilyVar) { // Same object format: {"400": {...}, "400_italic": {...}} const weightsObject = role.availableWeights as Record<string, WeightInfo | null>; return Object.keys(weightsObject || {}).filter((key) => weightsObject[key] !== null); } } } return []; } /** * Check if a font-family variable supports italics */ public supportsItalics(projectConfig: ProjectFontConfig, fontFamilyVar: string): boolean { // Check package-based fonts if (projectConfig.fontConfig) { for (const role of Object.values(projectConfig.fontConfig.roles)) { if (role.variables?.family === fontFamilyVar) { return role.hasItalics || false; } } } // Check local fonts if (projectConfig.localFontConfig?.detailedRoles) { for (const role of Object.values(projectConfig.localFontConfig.detailedRoles)) { if (role.variables?.family === fontFamilyVar) { return role.hasItalics || false; } } } return false; } /** * Setup file system watchers for configuration changes */ private setupConfigWatcher(): void { this.configWatcher = vscode.workspace.createFileSystemWatcher("**/{fontConfig.json,localFontConfig.json}"); this.configWatcher.onDidChange((uri: vscode.Uri) => { // Font config changed, will refresh // Trigger re-validation of affected documents this.onConfigChanged(); }); this.configWatcher.onDidCreate((uri: vscode.Uri) => { // Font config created, will refresh this.onConfigChanged(); }); this.configWatcher.onDidDelete((uri: vscode.Uri) => { // Font config deleted, will refresh this.onConfigChanged(); }); } private onConfigChanged(): void { // Clear any cached configurations and trigger re-validation // This will be implemented in the main validator vscode.commands.executeCommand("w1FontValidator.refreshConfig"); } public dispose(): void { if (this.configWatcher) { this.configWatcher.dispose(); } } }