@master/css-language-server 2.0.0-rc.9 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,104 @@
1
+ <br>
2
+ <div align="center">
3
+
4
+ <p align="center">
5
+ <a href="https://css.master.co">
6
+ <picture>
7
+ <source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/33840671/201701649-3bb7d698-abec-4d5f-ac30-ccc4d7bafcd4.svg">
8
+ <source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/33840671/201703010-77bf2373-9899-40cc-98f5-30cf9b546941.svg">
9
+ <img alt="Master CSS" src="https://user-images.githubusercontent.com/33840671/201703010-77bf2373-9899-40cc-98f5-30cf9b546941.svg" width="100%">
10
+ </picture>
11
+ </a>
12
+ </p>
13
+ <p align="center">Language Server for Master CSS</p>
14
+
15
+ <p align="center">
16
+ <a aria-label="GitHub release (latest by date including pre-releases)" href="https://github.com/master-co/css/releases">
17
+ <picture>
18
+ <source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/github/v/release/master-co/css?include_prereleases&color=212022&label=&style=for-the-badge&logo=github&logoColor=fff">
19
+ <source media="(prefers-color-scheme: light)" srcset="https://img.shields.io/github/v/release/master-co/css?include_prereleases&color=f6f7f8&label=&style=for-the-badge&logo=github&logoColor=%23000">
20
+ <img alt="GitHub release" src="https://img.shields.io/github/v/release/master-co/css?include_prereleases&color=f6f7f8&label=&style=for-the-badge&logo=github">
21
+ </picture>
22
+ </a>
23
+ <a aria-label="NPM Package" href="https://www.npmjs.com/package/@master/css-language-server">
24
+ <picture>
25
+ <source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/npm/dm/@master/css-language-server?color=212022&label=%20&logo=npm&style=for-the-badge">
26
+ <source media="(prefers-color-scheme: light)" srcset="https://img.shields.io/npm/dm/@master/css-language-server?color=f6f7f8&label=%20&logo=npm&style=for-the-badge">
27
+ <img alt="NPM package ( download / month )" src="https://img.shields.io/npm/dm/@master/css-language-server?color=f6f7f8&label=%20&logo=npm&style=for-the-badge">
28
+ </picture>
29
+ </a>
30
+ <a aria-label="JSDelivr" href="https://www.jsdelivr.com/package/npm/@master/css-language-server">
31
+ <picture>
32
+ <source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/jsdelivr/npm/hm/@master/css-language-server?color=212022&label=%20&logo=jsdelivr&style=for-the-badge">
33
+ <source media="(prefers-color-scheme: light)" srcset="https://img.shields.io/jsdelivr/npm/hm/@master/css-language-server?color=f6f7f8&label=%20&logo=jsdelivr&style=for-the-badge">
34
+ <img alt="JSDelivr hits (npm scoped)" src="https://img.shields.io/jsdelivr/npm/hm/@master/css-language-server?color=f6f7f8&label=%20&logo=jsdelivr&style=for-the-badge">
35
+ </picture>
36
+ </a>
37
+ <a aria-label="Follow @mastercorg" href="https://twitter.com/mastercorg">
38
+ <picture>
39
+ <source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/static/v1?label=%20&message=twitter&color=212022&logo=twitter&style=for-the-badge">
40
+ <source media="(prefers-color-scheme: light)" srcset="https://img.shields.io/static/v1?label=%20&message=twitter&color=f6f7f8&logo=twitter&style=for-the-badge">
41
+ <img alt="Follow @mastercorg" src="https://img.shields.io/static/v1?label=%20&message=twitter&color=f6f7f8&logo=twitter&style=for-the-badge">
42
+ </picture>
43
+ </a>
44
+ <a aria-label="GitHub Actions" href="https://github.com/master-co/css/actions/workflows/release.yml">
45
+ <picture>
46
+ <source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/github/actions/workflow/status/master-co/css/release.yml?branch=main&label=%20&color=212022&logo=githubactions&style=for-the-badge">
47
+ <source media="(prefers-color-scheme: light)" srcset="https://img.shields.io/github/actions/workflow/status/master-co/css/release.yml?branch=main&label=%20&color=f6f7f8&logo=githubactions&style=for-the-badge&logoColor=%23000">
48
+ <img alt="GitHub release workflow" src="https://img.shields.io/github/actions/workflow/status/master-co/css/release.yml?branch=main&label=%20&color=f6f7f8&logo=githubactions&style=for-the-badge&logoColor=%23000">
49
+ </picture>
50
+ </a>
51
+ </p>
52
+
53
+ </div>
54
+
55
+ ## Installation
56
+
57
+ ```bash
58
+ npm install @master/css-language-server
59
+ ```
60
+
61
+ ## Usage
62
+
63
+ ```js
64
+ import { MasterCSSLanguageServer } from '@master/css-language-server'
65
+
66
+ const languageServer = new MasterCSSLanguageServer(connection, customSettings)
67
+ ```
68
+
69
+ `MasterCSSLanguageServer` wraps `@master/css-language-service` with LSP workspace lifecycle, completion, hover, document color, color presentation, semantic token, and directive formatting handlers. Call `dispose()` when the transport shuts down.
70
+
71
+ Diagnostics are intentionally limited to language-server-owned behavior: CSS directive diagnostics and project manifest loading diagnostics. Class syntax and policy rules such as sorting, canonical class preference, conflict detection, invalid classes, and raw-value approval are owned by `@master/eslint-plugin-css`.
72
+
73
+ ## Semantic tokens
74
+
75
+ The language server supports full-document and active-position semantic tokens.
76
+
77
+ | `embeddedSyntaxHighlighting` | Behavior |
78
+ | --- | --- |
79
+ | `always` | Advertises the standard LSP `semanticTokensProvider` and handles `textDocument/semanticTokens/full`. |
80
+ | `active` | Uses the VS Code extension request for active class-context highlighting. |
81
+ | `off` | Disables embedded utility highlighting. |
82
+
83
+ CSS directive syntax is highlighted by the shared TextMate grammar. Server semantic tokens cover Master CSS class-list spans and directive class-list spans such as `@safelist`.
84
+
85
+ ## Formatting
86
+
87
+ The server advertises `documentFormattingProvider` and `documentRangeFormattingProvider` when `formatDirectives` is enabled. Formatting returns directive-only edits for CSS, SCSS, and LESS documents, plus CSS-family `<style>` blocks in Vue, Svelte, and Astro documents.
88
+
89
+ Directive formatting normalizes safe directive spacing and repairs class-list important markers such as `(background-color:transparent) !` to `(background-color:transparent)!`. It does not run the compiler and does not change generated CSS output.
90
+
91
+ ## Settings
92
+
93
+ ```js
94
+ import { defaultLanguageServerSettings } from '@master/css-language-server'
95
+ ```
96
+
97
+ | Setting | Type | Default | Description |
98
+ | --- | --- | --- | --- |
99
+ | `workspaces` | `FastGlobPattern[] \| 'auto'` | `'auto'` | Workspace roots where independent language services are created. The root workspace is always included. |
100
+ | `verbose` | `boolean` | `false` | Print server logs. |
101
+ | `formatDirectives` | `boolean` | `true` | Enables LSP document and range formatting for Master CSS directives. |
102
+ | `embeddedSyntaxHighlighting` | `'active' \| 'always' \| 'off'` | `'active'` | Inherited language-service semantic token mode. |
103
+
104
+ With `workspaces: 'auto'`, the server creates workspaces from CSS files importing `@master/css`, and `package.json` files that declare Master CSS package dependencies.
package/dist/core.d.ts ADDED
@@ -0,0 +1,96 @@
1
+ import { MasterCSSLanguageServerSettings } from "./settings.js";
2
+ import { ClientCapabilities, ColorInformation, ColorPresentation, ColorPresentationParams, CompletionItem, CompletionParams, Connection, DidChangeConfigurationParams, DocumentColorParams, DocumentFormattingParams, DocumentRangeFormattingParams, Hover, HoverParams, InitializeParams, InitializeResult, RemoteConsole, SemanticTokens, SemanticTokensParams, TextDocumentChangeEvent, TextDocumentPositionParams, TextDocuments, TextEdit, WorkspaceFolder } from "vscode-languageserver/node";
3
+ import { TextDocument } from "vscode-languageserver-textdocument";
4
+ import { MasterCSSLanguageService } from "@master/css-language-service";
5
+ import { MasterCSSManifest } from "@master/css-schema/manifest";
6
+ interface MasterCSSResolvedWorkspacePackage {
7
+ readonly name: string;
8
+ readonly entry: string;
9
+ readonly directory: string;
10
+ readonly packageJSON?: string;
11
+ readonly version?: string;
12
+ }
13
+ interface MasterCSSWorkspacePackageResolution {
14
+ readonly workspaceDir: string;
15
+ readonly css?: MasterCSSResolvedWorkspacePackage;
16
+ readonly presetManifest?: MasterCSSResolvedWorkspacePackage;
17
+ readonly languageServer?: MasterCSSResolvedWorkspacePackage;
18
+ readonly errors: readonly {
19
+ readonly name: string;
20
+ readonly message: string;
21
+ }[];
22
+ }
23
+ declare interface MasterCSSWorkspace {
24
+ uri: string;
25
+ openedTextDocuments: TextDocument[];
26
+ languageService?: MasterCSSLanguageService;
27
+ languageServiceSettings: MasterCSSLanguageServerSettings;
28
+ baseManifest?: MasterCSSManifest;
29
+ manifestResolution?: MasterCSSWorkspacePackageResolution;
30
+ manifestSource?: 'workspace' | 'bundled';
31
+ manifestErrors?: unknown[];
32
+ planEntries?: string[];
33
+ designSystemFiles?: readonly string[];
34
+ }
35
+ declare const ACTIVE_SEMANTIC_TOKENS_REQUEST = "masterCSS/renderActiveSemanticTokens";
36
+ declare const DOCUMENT_SEMANTIC_TOKENS_REQUEST = "masterCSS/renderDocumentSemanticTokens";
37
+ declare class MasterCSSLanguageServer implements Disposable {
38
+ connection: Connection;
39
+ customSettings?: MasterCSSLanguageServerSettings | undefined;
40
+ workspaceFolders: WorkspaceFolder[];
41
+ workspaces: Map<string, MasterCSSWorkspace>;
42
+ globalWorkspace: MasterCSSWorkspace;
43
+ documents: TextDocuments<TextDocument>;
44
+ initializing?: Promise<void>;
45
+ clientCapabilities: ClientCapabilities;
46
+ settings?: MasterCSSLanguageServerSettings;
47
+ console: RemoteConsole;
48
+ private disposables;
49
+ private workspaceLanguageServiceInitializations;
50
+ private disposed;
51
+ constructor(connection?: Connection, customSettings?: MasterCSSLanguageServerSettings | undefined);
52
+ start(): void;
53
+ init(): Promise<void>;
54
+ onInitialize(params: InitializeParams): InitializeResult;
55
+ onHover(params: HoverParams): Promise<Hover | undefined>;
56
+ onDefinition(params: TextDocumentPositionParams): Promise<{
57
+ uri: string;
58
+ range: {
59
+ start: import("vscode-languageserver-textdocument").Position;
60
+ end: import("vscode-languageserver-textdocument").Position;
61
+ };
62
+ } | undefined>;
63
+ onCompletion(params: CompletionParams): Promise<CompletionItem[] | undefined>;
64
+ onDocumentColor(params: DocumentColorParams): Promise<ColorInformation[]>;
65
+ onColorPresentation(params: ColorPresentationParams): Promise<ColorPresentation[]>;
66
+ private getWorkspaceDocument;
67
+ onDocumentFormatting(params: DocumentFormattingParams): Promise<TextEdit[]>;
68
+ onDocumentRangeFormatting(params: DocumentRangeFormattingParams): Promise<TextEdit[]>;
69
+ onSemanticTokens(params: SemanticTokensParams): Promise<SemanticTokens>;
70
+ onDocumentSemanticTokens(params: SemanticTokensParams): Promise<SemanticTokens>;
71
+ onActiveSemanticTokens(params: TextDocumentPositionParams): Promise<SemanticTokens>;
72
+ onDidOpen(params: TextDocumentChangeEvent<TextDocument>): Promise<void>;
73
+ onDidChangeContent(params: TextDocumentChangeEvent<TextDocument>): Promise<void>;
74
+ onDidClose(params: TextDocumentChangeEvent<TextDocument>): Promise<void>;
75
+ onDidSave(params: TextDocumentChangeEvent<TextDocument>): Promise<void>;
76
+ onDidChangeConfiguration({ settings: changedSettings }: DidChangeConfigurationParams): Promise<void>;
77
+ private initWorkspaceFolder;
78
+ initWorkspaceLanguageService(workspace: MasterCSSWorkspace): Promise<void>;
79
+ private ensureWorkspaceLanguageService;
80
+ private loadWorkspacePlan;
81
+ private loadWorkspaceBaseManifest;
82
+ destroyLanguageService(workspace: MasterCSSWorkspace): void;
83
+ findClosestWorkspace(textDocumentURI: string): MasterCSSWorkspace;
84
+ private publishDiagnostics;
85
+ private createDesignSystemDiagnostics;
86
+ private createCSSDirectiveDiagnostics;
87
+ private createManifestLoadingDiagnostics;
88
+ private createMasterCSSDiagnostic;
89
+ private createCSSDirectiveDiagnostic;
90
+ private createCSSDirectiveRelatedInformation;
91
+ private createCSSDirectiveRange;
92
+ private refreshSemanticTokens;
93
+ dispose(): void;
94
+ [Symbol.dispose](): void;
95
+ }
96
+ export { ACTIVE_SEMANTIC_TOKENS_REQUEST, DOCUMENT_SEMANTIC_TOKENS_REQUEST, MasterCSSLanguageServer, MasterCSSWorkspace };
package/dist/core.js ADDED
@@ -0,0 +1,605 @@
1
+ import { discoverBuildWorkspaceDirectories } from "./internal/src/workspace-directories.js";
2
+ import { resolveMasterCSSWorkspacePackages } from "./internal/src/workspace.js";
3
+ import { defaultLanguageServerSettings } from "./settings.js";
4
+ import { getCSSDiagnosticSources, getMasterCSSDiagnostics, isCSSDiagnosticDocument, toCSSDirectiveError, toLSPDiagnosticSeverity } from "./diagnostics.js";
5
+ import { DiagnosticSeverity, TextDocumentSyncKind, TextDocuments, createConnection } from "vscode-languageserver/node";
6
+ import { TextDocument } from "vscode-languageserver-textdocument";
7
+ import path from "node:path";
8
+ import { readFile } from "node:fs/promises";
9
+ import { MasterCSSLanguageService } from "@master/css-language-service";
10
+ import { compileManifestSync } from "@master/css-compiler/node";
11
+ import { discoverManifestEntries, loadProjectManifest } from "@master/css-compiler/project";
12
+ import { defu } from "defu";
13
+ import defaultManifestJSON from "@master/css-preset/default-manifest.json" with { type: "json" };
14
+ import { AT_TRIGGER_CHARACTER, INVOKED_TRIGGER_CHARACTERS, QUERY_TRIGGER_CHARACTERS, SELECTOR_TRIGGER_CHARACTERS, VALUE_TRIGGER_CHARACTERS } from "@master/css-language-service/common";
15
+ import { createToolingSession } from "@master/css-tooling";
16
+ import { SEMANTIC_TOKENS_LEGEND } from "@master/css-tooling/language";
17
+ import glob from "fast-glob";
18
+ import { URI } from "vscode-uri";
19
+ // src/core.ts
20
+ const defaultManifest = defaultManifestJSON;
21
+ const ACTIVE_SEMANTIC_TOKENS_REQUEST = "masterCSS/renderActiveSemanticTokens";
22
+ const DOCUMENT_SEMANTIC_TOKENS_REQUEST = "masterCSS/renderDocumentSemanticTokens";
23
+ const SERVER_CAPABILITIES = {
24
+ textDocumentSync: TextDocumentSyncKind.Incremental,
25
+ completionProvider: {
26
+ resolveProvider: false,
27
+ workDoneProgress: false,
28
+ triggerCharacters: [.../* @__PURE__ */ new Set([
29
+ ...INVOKED_TRIGGER_CHARACTERS,
30
+ ...VALUE_TRIGGER_CHARACTERS,
31
+ ...SELECTOR_TRIGGER_CHARACTERS,
32
+ ...QUERY_TRIGGER_CHARACTERS,
33
+ AT_TRIGGER_CHARACTER
34
+ ])]
35
+ },
36
+ colorProvider: true,
37
+ hoverProvider: true,
38
+ definitionProvider: true,
39
+ documentFormattingProvider: true,
40
+ documentRangeFormattingProvider: true,
41
+ semanticTokensProvider: {
42
+ legend: SEMANTIC_TOKENS_LEGEND,
43
+ full: true
44
+ },
45
+ workspace: { workspaceFolders: { supported: true } }
46
+ };
47
+ function getInitializationSettings(initializationOptions) {
48
+ if (!initializationOptions || typeof initializationOptions !== "object") return;
49
+ const options = initializationOptions;
50
+ return options.masterCSS ?? options;
51
+ }
52
+ function isWindowsFilePath(fsPath) {
53
+ return /^[a-z]:[\\/]/i.test(fsPath) || fsPath.startsWith("\\\\") || fsPath.startsWith("//");
54
+ }
55
+ function getFilePathTools(...fsPaths) {
56
+ return fsPaths.some(isWindowsFilePath) ? path.win32 : path;
57
+ }
58
+ function toComparableFilePath(uri) {
59
+ const parsedURI = URI.parse(uri);
60
+ if (parsedURI.scheme !== "file") return;
61
+ const fsPath = parsedURI.fsPath;
62
+ if (!fsPath) return;
63
+ const resolvedPath = getFilePathTools(fsPath).resolve(fsPath);
64
+ return isWindowsFilePath(resolvedPath) ? resolvedPath.toLowerCase() : resolvedPath;
65
+ }
66
+ function containsFilePath(parentPath, childPath) {
67
+ const filePathTools = getFilePathTools(parentPath, childPath);
68
+ const relativePath = filePathTools.relative(parentPath, childPath);
69
+ return relativePath === "" || !!relativePath && relativePath !== ".." && !relativePath.startsWith(`..${filePathTools.sep}`) && !filePathTools.isAbsolute(relativePath);
70
+ }
71
+ var MasterCSSLanguageServer = class {
72
+ connection;
73
+ customSettings;
74
+ workspaceFolders = [];
75
+ workspaces = /* @__PURE__ */ new Map();
76
+ globalWorkspace = {
77
+ uri: "",
78
+ openedTextDocuments: [],
79
+ languageServiceSettings: this.settings,
80
+ baseManifest: defaultManifest,
81
+ manifestSource: "bundled"
82
+ };
83
+ documents;
84
+ initializing;
85
+ clientCapabilities = {};
86
+ settings;
87
+ console;
88
+ disposables = [];
89
+ workspaceLanguageServiceInitializations = /* @__PURE__ */ new Map();
90
+ disposed = false;
91
+ constructor(connection = process.argv.includes("--stdio") ? createConnection(process.stdin, process.stdout) : createConnection(), customSettings) {
92
+ this.connection = connection;
93
+ this.customSettings = customSettings;
94
+ this.documents = new TextDocuments(TextDocument);
95
+ this.settings = defu(this.customSettings, defaultLanguageServerSettings);
96
+ this.globalWorkspace.languageServiceSettings = this.settings;
97
+ this.console = new Proxy(this.connection.console, { get: (target, prop) => {
98
+ if (!this.settings?.verbose) return () => {};
99
+ return this.connection.console[prop];
100
+ } });
101
+ }
102
+ start() {
103
+ this.disposables.push(this.documents.onDidSave(this.onDidSave.bind(this)), this.documents.onDidOpen(this.onDidOpen.bind(this)), this.documents.onDidChangeContent(this.onDidChangeContent.bind(this)), this.documents.onDidClose(this.onDidClose.bind(this)), this.documents.listen(this.connection), this.connection.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)), this.connection.onHover(this.onHover.bind(this)), this.connection.onDefinition(this.onDefinition.bind(this)), this.connection.onCompletion(this.onCompletion.bind(this)), this.connection.onDocumentColor(this.onDocumentColor.bind(this)), this.connection.onColorPresentation(this.onColorPresentation.bind(this)), this.connection.onDocumentFormatting(this.onDocumentFormatting.bind(this)), this.connection.onDocumentRangeFormatting(this.onDocumentRangeFormatting.bind(this)), this.connection.languages.semanticTokens.on(this.onSemanticTokens.bind(this)), this.connection.onRequest(DOCUMENT_SEMANTIC_TOKENS_REQUEST, this.onDocumentSemanticTokens.bind(this)), this.connection.onRequest(ACTIVE_SEMANTIC_TOKENS_REQUEST, this.onActiveSemanticTokens.bind(this)), this.connection.onInitialize(this.onInitialize.bind(this)), this.connection.onInitialized(() => this.init()));
104
+ this.connection.listen();
105
+ }
106
+ init() {
107
+ if (this.initializing) return this.initializing;
108
+ return this.initializing = Promise.all(this.workspaceFolders.map((folder) => this.initWorkspaceFolder(folder.uri))).then(() => void 0).catch((error) => {
109
+ this.initializing = void 0;
110
+ throw error;
111
+ });
112
+ }
113
+ onInitialize(params) {
114
+ this.clientCapabilities = params.capabilities;
115
+ const initializationSettings = getInitializationSettings(params.initializationOptions);
116
+ if (initializationSettings) {
117
+ this.customSettings = defu(initializationSettings, this.customSettings);
118
+ this.settings = defu(this.customSettings, defaultLanguageServerSettings);
119
+ this.globalWorkspace.languageServiceSettings = this.settings;
120
+ }
121
+ if (params.workspaceFolders?.length) this.workspaceFolders = params.workspaceFolders;
122
+ const capabilities = { ...SERVER_CAPABILITIES };
123
+ if (this.settings?.embeddedSyntaxHighlighting !== "always") delete capabilities.semanticTokensProvider;
124
+ if (!this.settings?.formatDirectives) {
125
+ delete capabilities.documentFormattingProvider;
126
+ delete capabilities.documentRangeFormattingProvider;
127
+ }
128
+ return { capabilities };
129
+ }
130
+ async onHover(params) {
131
+ await this.init();
132
+ const workspace = this.findClosestWorkspace(params.textDocument.uri);
133
+ if (workspace?.languageService) {
134
+ const document = this.documents.get(params.textDocument.uri);
135
+ if (document) return workspace.languageService.inspectSyntax(document, params.position);
136
+ }
137
+ }
138
+ async onDefinition(params) {
139
+ await this.init();
140
+ const service = this.findClosestWorkspace(params.textDocument.uri)?.languageService;
141
+ const document = this.documents.get(params.textDocument.uri);
142
+ if (!service || !document) return;
143
+ const position = service.getClassPosition(document, params.position);
144
+ if (!position) return;
145
+ const source = service.session.inspectClassName(position.token).definitionSource;
146
+ if (!source?.file) return;
147
+ const uri = URI.file(source.file).toString();
148
+ const target = this.documents.get(uri) ?? TextDocument.create(uri, "css", 0, await readFile(source.file, "utf8"));
149
+ return {
150
+ uri,
151
+ range: {
152
+ start: target.positionAt(source.range.start),
153
+ end: target.positionAt(source.range.end)
154
+ }
155
+ };
156
+ }
157
+ async onCompletion(params) {
158
+ await this.init();
159
+ const workspace = this.findClosestWorkspace(params.textDocument.uri);
160
+ if (workspace?.languageService) {
161
+ const document = this.documents.get(params.textDocument.uri);
162
+ if (document) return workspace.languageService.suggestSyntax(document, params.position, params.context);
163
+ }
164
+ }
165
+ async onDocumentColor(params) {
166
+ await this.init();
167
+ const workspace = this.findClosestWorkspace(params.textDocument.uri);
168
+ if (workspace?.languageService) {
169
+ const document = this.documents.get(params.textDocument.uri);
170
+ if (document) return await workspace.languageService.renderSyntaxColors(document) ?? [];
171
+ }
172
+ return [];
173
+ }
174
+ async onColorPresentation(params) {
175
+ await this.init();
176
+ const workspace = this.findClosestWorkspace(params.textDocument.uri);
177
+ if (workspace?.languageService) {
178
+ const document = this.documents.get(params.textDocument.uri);
179
+ if (document) return workspace.languageService.editSyntaxColors(document, params.color, params.range) ?? [];
180
+ }
181
+ return [];
182
+ }
183
+ getWorkspaceDocument(uri) {
184
+ const workspace = this.findClosestWorkspace(uri);
185
+ if (!workspace?.languageService) return;
186
+ const document = this.documents.get(uri) ?? workspace.openedTextDocuments.find((document) => document.uri === uri);
187
+ if (!document) return;
188
+ return {
189
+ workspace,
190
+ document
191
+ };
192
+ }
193
+ async onDocumentFormatting(params) {
194
+ await this.init();
195
+ const context = this.getWorkspaceDocument(params.textDocument.uri);
196
+ return context?.workspace.languageService?.formatDirectives(context.document) ?? [];
197
+ }
198
+ async onDocumentRangeFormatting(params) {
199
+ await this.init();
200
+ const context = this.getWorkspaceDocument(params.textDocument.uri);
201
+ return context?.workspace.languageService?.formatDirectives(context.document, params.range) ?? [];
202
+ }
203
+ async onSemanticTokens(params) {
204
+ await this.init();
205
+ const context = this.getWorkspaceDocument(params.textDocument.uri);
206
+ if (context) return context.workspace.languageService?.renderSemanticTokens(context.document) ?? { data: [] };
207
+ return { data: [] };
208
+ }
209
+ async onDocumentSemanticTokens(params) {
210
+ return this.onSemanticTokens(params);
211
+ }
212
+ async onActiveSemanticTokens(params) {
213
+ await this.init();
214
+ const context = this.getWorkspaceDocument(params.textDocument.uri);
215
+ if (context) return context.workspace.languageService?.renderSemanticTokensAtPosition(context.document, params.position) ?? { data: [] };
216
+ return { data: [] };
217
+ }
218
+ async onDidOpen(params) {
219
+ await this.init();
220
+ const workspace = this.findClosestWorkspace(params.document.uri);
221
+ if (!workspace) return;
222
+ const documentIndex = workspace.openedTextDocuments.findIndex(({ uri }) => uri === params.document.uri);
223
+ if (documentIndex < 0) workspace.openedTextDocuments.push(params.document);
224
+ else workspace.openedTextDocuments[documentIndex] = params.document;
225
+ await this.ensureWorkspaceLanguageService(workspace);
226
+ if (!workspace.openedTextDocuments.some(({ uri }) => uri === params.document.uri)) return;
227
+ this.publishDiagnostics(params.document, workspace);
228
+ }
229
+ async onDidChangeContent(params) {
230
+ await this.init();
231
+ const workspace = this.findClosestWorkspace(params.document.uri);
232
+ if (!workspace) return;
233
+ const documentIndex = workspace.openedTextDocuments.findIndex(({ uri }) => uri === params.document.uri);
234
+ if (documentIndex >= 0) workspace.openedTextDocuments[documentIndex] = params.document;
235
+ this.publishDiagnostics(params.document, workspace);
236
+ }
237
+ async onDidClose(params) {
238
+ await this.init();
239
+ const workspace = this.findClosestWorkspace(params.document.uri);
240
+ if (!workspace) return;
241
+ const documentIndex = workspace.openedTextDocuments.findIndex(({ uri }) => uri === params.document.uri);
242
+ if (documentIndex >= 0) workspace.openedTextDocuments.splice(documentIndex, 1);
243
+ this.connection.sendDiagnostics({
244
+ uri: params.document.uri,
245
+ diagnostics: []
246
+ });
247
+ if (!workspace.openedTextDocuments.length) this.destroyLanguageService(workspace);
248
+ }
249
+ async onDidSave(params) {
250
+ await this.init();
251
+ const workspace = this.findClosestWorkspace(params.document.uri);
252
+ if (!workspace) return;
253
+ this.publishDiagnostics(params.document, workspace);
254
+ if (path.basename(URI.parse(params.document.uri).fsPath).endsWith(".css")) {
255
+ this.refreshSemanticTokens();
256
+ this.connection.sendRequest("masterCSS/restart", { title: "Updating Master CSS configuration" });
257
+ }
258
+ }
259
+ async onDidChangeConfiguration({ settings: changedSettings }) {
260
+ await this.init();
261
+ if (changedSettings?.masterCSS) {
262
+ this.connection.sendNotification("masterCSS/globalSettingsChanged", changedSettings.masterCSS);
263
+ this.customSettings = changedSettings.masterCSS;
264
+ this.settings = defu(this.customSettings, defaultLanguageServerSettings);
265
+ this.globalWorkspace.languageServiceSettings = this.settings;
266
+ this.refreshSemanticTokens();
267
+ this.connection.sendRequest("masterCSS/restart", { title: "Updating Master CSS settings" });
268
+ for (const workspace of [this.globalWorkspace, ...this.workspaces.values()]) for (const document of workspace.openedTextDocuments) this.publishDiagnostics(document, workspace);
269
+ }
270
+ }
271
+ async initWorkspaceFolder(workspaceFolderURI) {
272
+ const workspaceFolderCWD = URI.parse(workspaceFolderURI).fsPath;
273
+ let customWorkspaceFolderSettings;
274
+ if (this.clientCapabilities.workspace?.configuration) customWorkspaceFolderSettings = await this.connection.workspace.getConfiguration({
275
+ scopeUri: workspaceFolderURI,
276
+ section: "masterCSS"
277
+ });
278
+ const { workspaces, ...languageServiceSettings } = defu(customWorkspaceFolderSettings, this.customSettings, defaultLanguageServerSettings);
279
+ const resolvedWorkspaceDirectories = /* @__PURE__ */ new Set([workspaceFolderCWD]);
280
+ if (workspaceFolderCWD) this.console.info(`Registered workspace folder ${workspaceFolderURI}`);
281
+ else this.console.info(`Registered global workspace folder`);
282
+ if (workspaces === "auto") {
283
+ const manifestEntries = await discoverManifestEntries({ root: workspaceFolderCWD });
284
+ for (const workspaceDir of await discoverBuildWorkspaceDirectories(workspaceFolderCWD, manifestEntries)) resolvedWorkspaceDirectories.add(workspaceDir);
285
+ } else if (workspaces?.length) (await glob([...workspaces], {
286
+ cwd: workspaceFolderCWD,
287
+ absolute: true,
288
+ onlyDirectories: true,
289
+ ignore: ["**/node_modules/**"]
290
+ })).forEach((workspaceDir) => resolvedWorkspaceDirectories.add(path.resolve(workspaceDir)));
291
+ for (const workspaceDir of resolvedWorkspaceDirectories) {
292
+ const workspaceURI = URI.file(workspaceDir).toString();
293
+ this.console.info(`Added workspace ${workspaceURI}`);
294
+ this.workspaces.set(workspaceURI, {
295
+ uri: workspaceURI,
296
+ openedTextDocuments: [],
297
+ languageServiceSettings,
298
+ planEntries: [...await discoverManifestEntries({ root: workspaceDir })]
299
+ });
300
+ }
301
+ }
302
+ async initWorkspaceLanguageService(workspace) {
303
+ workspace.baseManifest = await this.loadWorkspaceBaseManifest(workspace);
304
+ let workspacePlan;
305
+ workspace.manifestErrors = [];
306
+ workspace.designSystemFiles = [];
307
+ if (workspace !== this.globalWorkspace) {
308
+ try {
309
+ workspacePlan = await this.loadWorkspacePlan(workspace, workspace.baseManifest);
310
+ } catch (e) {
311
+ workspace.manifestErrors = [e];
312
+ this.console.info(`Failed to load manifest from ${workspace.uri}`);
313
+ this.console.error(e instanceof Error ? e.stack : e.toString());
314
+ }
315
+ if (workspacePlan) this.console.info(`Initialized workspace ${workspace.planEntries?.length ? "(with manifest entry)" : "(with manifest)"} ${workspace.uri}`);
316
+ else this.console.info(`Initialized workspace ${workspace.uri}`);
317
+ }
318
+ const manifest = workspacePlan?.manifest ?? workspace.languageServiceSettings.manifest ?? workspace.baseManifest;
319
+ workspace.designSystemFiles = workspacePlan?.sourceGraph?.files.filter((file) => file.projectOwned).map((file) => file.source) ?? [];
320
+ workspace.languageService = new MasterCSSLanguageService({
321
+ ...workspace.languageServiceSettings,
322
+ manifest
323
+ }, { session: await createToolingSession({
324
+ manifest,
325
+ nativeClassNames: workspacePlan?.nativeClassNames
326
+ }) });
327
+ }
328
+ async ensureWorkspaceLanguageService(workspace) {
329
+ if (workspace.languageService) return;
330
+ let initialization = this.workspaceLanguageServiceInitializations.get(workspace);
331
+ if (!initialization) {
332
+ initialization = this.initWorkspaceLanguageService(workspace);
333
+ this.workspaceLanguageServiceInitializations.set(workspace, initialization);
334
+ }
335
+ try {
336
+ await initialization;
337
+ } finally {
338
+ if (this.workspaceLanguageServiceInitializations.get(workspace) === initialization) this.workspaceLanguageServiceInitializations.delete(workspace);
339
+ if ((this.disposed || !workspace.openedTextDocuments.length) && workspace.languageService) this.destroyLanguageService(workspace);
340
+ }
341
+ }
342
+ async loadWorkspacePlan(workspace, baseManifest) {
343
+ const entries = workspace.planEntries ?? [];
344
+ if (!entries.length) return void 0;
345
+ const cwd = workspace.uri ? URI.parse(workspace.uri).fsPath : process.cwd();
346
+ return await loadProjectManifest({
347
+ root: cwd,
348
+ entries,
349
+ baseManifest,
350
+ includeSourceGraph: Boolean(workspace.languageServiceSettings.designSystem)
351
+ });
352
+ }
353
+ async loadWorkspaceBaseManifest(workspace) {
354
+ if (workspace === this.globalWorkspace || !workspace.uri) {
355
+ workspace.manifestSource = "bundled";
356
+ return defaultManifest;
357
+ }
358
+ const cwd = URI.parse(workspace.uri).fsPath;
359
+ let resolution;
360
+ try {
361
+ resolution = resolveMasterCSSWorkspacePackages(cwd);
362
+ workspace.manifestResolution = resolution;
363
+ const presetManifestPackage = resolution.presetManifest;
364
+ if (!presetManifestPackage) throw new Error("Missing workspace package @master/css-preset/default-manifest.json.");
365
+ const manifest = JSON.parse(await readFile(presetManifestPackage.entry, "utf8"));
366
+ workspace.manifestSource = "workspace";
367
+ this.console.info(`Using workspace Master CSS manifest ${presetManifestPackage.version ?? "(unknown version)"} from ${presetManifestPackage.directory}`);
368
+ if (resolution.errors.length) this.console.info(`MasterCSSWorkspace Master CSS manifest optional resolution warnings: ${resolution.errors.map(({ name, message }) => `${name}: ${message}`).join("; ")}`);
369
+ return manifest;
370
+ } catch (error) {
371
+ workspace.manifestSource = "bundled";
372
+ this.console.info(`Using bundled Master CSS manifest for ${workspace.uri}`);
373
+ if (resolution?.errors.length) this.console.info(`MasterCSSWorkspace Master CSS package resolution errors: ${resolution.errors.map(({ name, message }) => `${name}: ${message}`).join("; ")}`);
374
+ this.console.error(error instanceof Error ? error.stack || error.message : String(error));
375
+ return defaultManifest;
376
+ }
377
+ }
378
+ destroyLanguageService(workspace) {
379
+ this.console.info(`Destroyed workspace ${workspace.uri}`);
380
+ workspace.languageService?.dispose();
381
+ delete workspace.languageService;
382
+ }
383
+ findClosestWorkspace(textDocumentURI) {
384
+ const documentPath = toComparableFilePath(textDocumentURI);
385
+ if (!documentPath) {
386
+ this.console.info(`This is an external document ${textDocumentURI} with the global workspace`);
387
+ return this.globalWorkspace;
388
+ }
389
+ let foundWorkspace;
390
+ let foundWorkspacePath = "";
391
+ for (const [uri, workspace] of this.workspaces) {
392
+ if (!uri) continue;
393
+ const workspacePath = toComparableFilePath(uri);
394
+ if (!workspacePath) continue;
395
+ if (containsFilePath(workspacePath, documentPath) && workspacePath.length > foundWorkspacePath.length) {
396
+ foundWorkspace = workspace;
397
+ foundWorkspacePath = workspacePath;
398
+ }
399
+ }
400
+ if (foundWorkspace) return foundWorkspace;
401
+ this.console.info(`This is an external document ${textDocumentURI} with the global workspace`);
402
+ return this.globalWorkspace;
403
+ }
404
+ publishDiagnostics(textDocument, workspace) {
405
+ const diagnostics = [];
406
+ diagnostics.push(...this.createManifestLoadingDiagnostics(textDocument, workspace));
407
+ diagnostics.push(...this.createCSSDirectiveDiagnostics(textDocument, workspace));
408
+ diagnostics.push(...this.createDesignSystemDiagnostics(textDocument, workspace));
409
+ this.connection.sendDiagnostics({
410
+ uri: textDocument.uri,
411
+ diagnostics
412
+ });
413
+ }
414
+ createDesignSystemDiagnostics(document, workspace) {
415
+ const options = workspace.languageServiceSettings.designSystem;
416
+ if (!options || !workspace.languageService || workspace.manifestErrors?.length || document.languageId !== "css") return [];
417
+ const source = path.resolve(URI.parse(document.uri).fsPath);
418
+ if (!workspace.designSystemFiles?.includes(source)) return [];
419
+ try {
420
+ const report = workspace.languageService.session.inspectDesignSystem({
421
+ ...options,
422
+ stylesheets: [{
423
+ source,
424
+ content: document.getText()
425
+ }]
426
+ });
427
+ return report.diagnostics.map((item) => ({
428
+ range: {
429
+ start: document.positionAt(item.range?.start ?? 0),
430
+ end: document.positionAt(item.range?.end ?? 0)
431
+ },
432
+ severity: item.severity === "error" ? DiagnosticSeverity.Error : item.severity === "warning" ? DiagnosticSeverity.Warning : DiagnosticSeverity.Information,
433
+ source: "Master CSS",
434
+ code: item.code,
435
+ message: item.message,
436
+ data: {
437
+ version: report.version,
438
+ coverage: report.coverage,
439
+ ...item.data
440
+ }
441
+ }));
442
+ } catch (error) {
443
+ return [{
444
+ range: {
445
+ start: document.positionAt(0),
446
+ end: document.positionAt(0)
447
+ },
448
+ severity: DiagnosticSeverity.Error,
449
+ source: "Master CSS",
450
+ code: "DESIGN_SYSTEM_INSPECTION_ERROR",
451
+ message: error instanceof Error ? error.message : String(error)
452
+ }];
453
+ }
454
+ }
455
+ createCSSDirectiveDiagnostics(textDocument, workspace) {
456
+ if (!isCSSDiagnosticDocument(textDocument)) return [];
457
+ const diagnostics = [];
458
+ const documentFile = path.resolve(URI.parse(textDocument.uri).fsPath);
459
+ for (const { source, offset } of getCSSDiagnosticSources(textDocument)) try {
460
+ compileManifestSync(source, {
461
+ from: documentFile,
462
+ baseManifest: workspace.baseManifest ?? defaultManifest
463
+ });
464
+ } catch (error) {
465
+ const structuredDiagnostics = getMasterCSSDiagnostics(error);
466
+ if (structuredDiagnostics?.length) {
467
+ for (const structuredDiagnostic of structuredDiagnostics) {
468
+ const diagnostic = this.createMasterCSSDiagnostic(structuredDiagnostic, textDocument, documentFile, source, offset);
469
+ if (diagnostic) diagnostics.push(diagnostic);
470
+ }
471
+ continue;
472
+ }
473
+ const directiveError = toCSSDirectiveError(error);
474
+ if (!directiveError) continue;
475
+ const diagnostic = this.createCSSDirectiveDiagnostic(directiveError, textDocument, documentFile, offset);
476
+ if (diagnostic) diagnostics.push(diagnostic);
477
+ }
478
+ return diagnostics;
479
+ }
480
+ createManifestLoadingDiagnostics(textDocument, workspace) {
481
+ if (!workspace.manifestErrors?.length) return [];
482
+ const documentFile = path.resolve(URI.parse(textDocument.uri).fsPath);
483
+ return workspace.manifestErrors.flatMap((error) => {
484
+ const structuredDiagnostics = getMasterCSSDiagnostics(error);
485
+ if (structuredDiagnostics?.length) return structuredDiagnostics.map((diagnostic) => this.createMasterCSSDiagnostic(diagnostic, textDocument, documentFile, textDocument.getText(), 0, {
486
+ allowExternalSource: true,
487
+ messagePrefix: "Failed to load Master CSS manifest: "
488
+ })).filter((diagnostic) => !!diagnostic);
489
+ const directiveError = toCSSDirectiveError(error);
490
+ const source = directiveError?.source;
491
+ return [{
492
+ range: source && (!source.file || path.resolve(source.file) === documentFile) ? this.createCSSDirectiveRange(source, textDocument, documentFile, 0) : {
493
+ start: {
494
+ line: 0,
495
+ character: 0
496
+ },
497
+ end: {
498
+ line: 0,
499
+ character: 0
500
+ }
501
+ },
502
+ severity: DiagnosticSeverity.Error,
503
+ code: "manifest-loading-error",
504
+ source: "Master CSS",
505
+ message: `Failed to load Master CSS manifest: ${error instanceof Error ? error.message : String(error)}`,
506
+ relatedInformation: directiveError ? this.createCSSDirectiveRelatedInformation(directiveError.related, textDocument, documentFile, 0) : void 0
507
+ }];
508
+ });
509
+ }
510
+ createMasterCSSDiagnostic(diagnostic, textDocument, documentFile, sourceText, sourceOffset, options = {}) {
511
+ const externalSource = !!diagnostic.source && path.resolve(diagnostic.source) !== documentFile;
512
+ if (externalSource && !options.allowExternalSource) return;
513
+ const sourceDocument = TextDocument.create("inmemory://master-css/diagnostic.css", "css", 0, sourceText);
514
+ const range = diagnostic.range && !externalSource ? {
515
+ start: textDocument.positionAt(sourceOffset + sourceDocument.offsetAt(diagnostic.range.start)),
516
+ end: textDocument.positionAt(sourceOffset + sourceDocument.offsetAt(diagnostic.range.end))
517
+ } : {
518
+ start: textDocument.positionAt(sourceOffset),
519
+ end: textDocument.positionAt(sourceOffset)
520
+ };
521
+ const relatedMessages = [...diagnostic.notes ?? [], ...diagnostic.help ? [diagnostic.help] : []];
522
+ return {
523
+ range,
524
+ severity: toLSPDiagnosticSeverity(diagnostic.severity),
525
+ code: diagnostic.code,
526
+ source: "Master CSS",
527
+ message: `${options.messagePrefix ?? ""}${diagnostic.message}`,
528
+ relatedInformation: relatedMessages.length ? relatedMessages.map((message) => ({
529
+ message,
530
+ location: {
531
+ uri: textDocument.uri,
532
+ range
533
+ }
534
+ })) : void 0
535
+ };
536
+ }
537
+ createCSSDirectiveDiagnostic(error, textDocument, documentFile, sourceOffset) {
538
+ if (error.source?.file && path.resolve(error.source.file) !== documentFile) return;
539
+ return {
540
+ range: this.createCSSDirectiveRange(error.source, textDocument, documentFile, sourceOffset),
541
+ severity: DiagnosticSeverity.Error,
542
+ code: error.code,
543
+ source: "Master CSS",
544
+ message: error.message,
545
+ relatedInformation: this.createCSSDirectiveRelatedInformation(error.related, textDocument, documentFile, sourceOffset)
546
+ };
547
+ }
548
+ createCSSDirectiveRelatedInformation(related, textDocument, documentFile, sourceOffset) {
549
+ if (!related?.length) return;
550
+ return related.map(({ message, source }) => ({
551
+ message,
552
+ location: {
553
+ uri: source?.file ? URI.file(source.file).toString() : textDocument.uri,
554
+ range: this.createCSSDirectiveRange(source, textDocument, documentFile, sourceOffset)
555
+ }
556
+ }));
557
+ }
558
+ createCSSDirectiveRange(source, textDocument, documentFile, sourceOffset) {
559
+ if (source?.file && path.resolve(source.file) !== documentFile) return source.loc ? {
560
+ start: {
561
+ line: Math.max(0, source.loc.start.line - 1),
562
+ character: Math.max(0, source.loc.start.column - 1)
563
+ },
564
+ end: {
565
+ line: Math.max(0, source.loc.end.line - 1),
566
+ character: Math.max(0, source.loc.end.column - 1)
567
+ }
568
+ } : {
569
+ start: {
570
+ line: 0,
571
+ character: 0
572
+ },
573
+ end: {
574
+ line: 0,
575
+ character: 0
576
+ }
577
+ };
578
+ const start = Math.max(0, (source?.range.start || 0) + sourceOffset);
579
+ const end = Math.max(start, (source?.range.end || 0) + sourceOffset);
580
+ return {
581
+ start: textDocument.positionAt(start),
582
+ end: textDocument.positionAt(end)
583
+ };
584
+ }
585
+ refreshSemanticTokens() {
586
+ if (this.settings?.embeddedSyntaxHighlighting !== "always") return;
587
+ if (!this.clientCapabilities.workspace?.semanticTokens?.refreshSupport) return;
588
+ this.connection.languages.semanticTokens.refresh();
589
+ }
590
+ dispose() {
591
+ if (this.disposed) return;
592
+ this.disposed = true;
593
+ this.connection.sendNotification("masterCSS/dispose");
594
+ for (const workspace of [this.globalWorkspace, ...this.workspaces.values()]) this.destroyLanguageService(workspace);
595
+ this.disposables.forEach((disposable) => disposable.dispose());
596
+ this.disposables.length = 0;
597
+ this.workspaceLanguageServiceInitializations.clear();
598
+ this.connection.dispose();
599
+ this.initializing = void 0;
600
+ }
601
+ [Symbol.dispose]() {
602
+ this.dispose();
603
+ }
604
+ };
605
+ export { ACTIVE_SEMANTIC_TOKENS_REQUEST, DOCUMENT_SEMANTIC_TOKENS_REQUEST, MasterCSSLanguageServer };
@@ -0,0 +1,78 @@
1
+ import { DiagnosticSeverity } from "vscode-languageserver/node";
2
+ import { CSSDirectiveError } from "@master/css-schema/css-directives";
3
+ import { MasterCSSError } from "@master/css-schema";
4
+ // src/diagnostics.ts
5
+ const CSS_DIAGNOSTIC_LANGUAGE_IDS = /* @__PURE__ */ new Set([
6
+ "css",
7
+ "scss",
8
+ "less"
9
+ ]);
10
+ const SFC_DIAGNOSTIC_LANGUAGE_IDS = /* @__PURE__ */ new Set([
11
+ "vue",
12
+ "svelte",
13
+ "astro"
14
+ ]);
15
+ const STYLE_BLOCK_RE = /<style\b([^>]*)>([\s\S]*?)<\/style>/gi;
16
+ function getMasterCSSDiagnostics(error) {
17
+ if (error instanceof MasterCSSError) return error.diagnostics;
18
+ if (!error || typeof error !== "object" || error.name !== "MasterCSSError") return;
19
+ const diagnostics = error.diagnostics;
20
+ if (!Array.isArray(diagnostics)) return;
21
+ return diagnostics;
22
+ }
23
+ function toLSPDiagnosticSeverity(severity) {
24
+ switch (severity) {
25
+ case "warning": return DiagnosticSeverity.Warning;
26
+ case "information": return DiagnosticSeverity.Information;
27
+ default: return DiagnosticSeverity.Error;
28
+ }
29
+ }
30
+ function isCSSDiagnosticDocument(textDocument) {
31
+ return CSS_DIAGNOSTIC_LANGUAGE_IDS.has(textDocument.languageId) || SFC_DIAGNOSTIC_LANGUAGE_IDS.has(textDocument.languageId);
32
+ }
33
+ function getSFCStyleLanguage(attributes) {
34
+ const match = /\blang\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s>]+))/i.exec(attributes);
35
+ return (match?.[1] || match?.[2] || match?.[3] || "css").toLowerCase();
36
+ }
37
+ function getCSSDiagnosticSources(textDocument) {
38
+ const text = textDocument.getText();
39
+ if (CSS_DIAGNOSTIC_LANGUAGE_IDS.has(textDocument.languageId)) return [{
40
+ source: text,
41
+ offset: 0
42
+ }];
43
+ if (!SFC_DIAGNOSTIC_LANGUAGE_IDS.has(textDocument.languageId)) return [];
44
+ const sources = [];
45
+ STYLE_BLOCK_RE.lastIndex = 0;
46
+ for (const match of text.matchAll(STYLE_BLOCK_RE)) {
47
+ const styleLanguage = getSFCStyleLanguage(match[1]);
48
+ if (!CSS_DIAGNOSTIC_LANGUAGE_IDS.has(styleLanguage)) continue;
49
+ const source = match[2];
50
+ const offset = (match.index || 0) + match[0].indexOf(">") + 1;
51
+ sources.push({
52
+ source,
53
+ offset
54
+ });
55
+ }
56
+ return sources;
57
+ }
58
+ function isCSSDirectiveError(error) {
59
+ return error instanceof CSSDirectiveError || !!error && typeof error === "object" && error.name === "CSSDirectiveError" && typeof error.code === "string";
60
+ }
61
+ function toCSSDirectiveError(error) {
62
+ if (isCSSDirectiveError(error)) return error;
63
+ if (!error || typeof error !== "object") return;
64
+ const diagnostic = error;
65
+ if (typeof diagnostic.code !== "string" || typeof diagnostic.message !== "string") return;
66
+ const range = diagnostic.range;
67
+ if (!range || typeof range !== "object") return;
68
+ const { start, end } = range;
69
+ if (typeof start !== "number" || typeof end !== "number") return;
70
+ return new CSSDirectiveError(diagnostic.code, diagnostic.message, {
71
+ ...typeof diagnostic.source === "string" && diagnostic.source ? { file: diagnostic.source } : {},
72
+ range: {
73
+ start,
74
+ end
75
+ }
76
+ });
77
+ }
78
+ export { getCSSDiagnosticSources, getMasterCSSDiagnostics, isCSSDiagnosticDocument, toCSSDirectiveError, toLSPDiagnosticSeverity };
@@ -0,0 +1,3 @@
1
+ import { MasterCSSLanguageServerSettings, defaultLanguageServerSettings } from "./settings.js";
2
+ import { ACTIVE_SEMANTIC_TOKENS_REQUEST, DOCUMENT_SEMANTIC_TOKENS_REQUEST, MasterCSSLanguageServer, MasterCSSWorkspace } from "./core.js";
3
+ export { ACTIVE_SEMANTIC_TOKENS_REQUEST, DOCUMENT_SEMANTIC_TOKENS_REQUEST, MasterCSSLanguageServer, type MasterCSSLanguageServerSettings, type MasterCSSWorkspace, defaultLanguageServerSettings };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { defaultLanguageServerSettings } from "./settings.js";
2
+ import { ACTIVE_SEMANTIC_TOKENS_REQUEST, DOCUMENT_SEMANTIC_TOKENS_REQUEST, MasterCSSLanguageServer } from "./core.js";
3
+ export { ACTIVE_SEMANTIC_TOKENS_REQUEST, DOCUMENT_SEMANTIC_TOKENS_REQUEST, MasterCSSLanguageServer, defaultLanguageServerSettings };
@@ -0,0 +1,63 @@
1
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
2
+ import { readFile, readdir } from "node:fs/promises";
3
+ import "node:fs";
4
+ // ../internal/src/workspace-directories.ts
5
+ const IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
6
+ "node_modules",
7
+ "dist",
8
+ "out",
9
+ ".master",
10
+ ".next",
11
+ ".nuxt",
12
+ ".svelte-kit"
13
+ ]);
14
+ const MASTER_CSS_PACKAGE_NAME = /^@master\/css(?:$|[.-])/;
15
+ const DEPENDENCY_FIELDS = [
16
+ "dependencies",
17
+ "devDependencies",
18
+ "peerDependencies",
19
+ "optionalDependencies"
20
+ ];
21
+ function hasMasterCSSDependency(packageJSON) {
22
+ return DEPENDENCY_FIELDS.some((field) => {
23
+ const dependencies = packageJSON[field];
24
+ return dependencies && typeof dependencies === "object" && Object.keys(dependencies).some((dependency) => MASTER_CSS_PACKAGE_NAME.test(dependency));
25
+ });
26
+ }
27
+ async function collectPackageDirectories(directory, directories) {
28
+ try {
29
+ for (const dirent of await readdir(directory, { withFileTypes: true })) {
30
+ const file = join(directory, dirent.name);
31
+ if (dirent.isDirectory()) {
32
+ if (!IGNORED_DIRECTORIES.has(dirent.name)) await collectPackageDirectories(file, directories);
33
+ } else if (dirent.isFile() && dirent.name === "package.json") try {
34
+ if (hasMasterCSSDependency(JSON.parse(await readFile(file, "utf8")))) directories.add(dirname(file));
35
+ } catch {}
36
+ }
37
+ } catch {}
38
+ }
39
+ function isSameOrChildPath(parent, child) {
40
+ const path = relative(parent, child);
41
+ return path === "" || path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path);
42
+ }
43
+ function resolveEntryWorkspace(entry, directories) {
44
+ const absoluteEntry = resolve(entry);
45
+ let closest;
46
+ for (const directory of directories) {
47
+ const absoluteDirectory = resolve(directory);
48
+ if (isSameOrChildPath(absoluteDirectory, absoluteEntry) && (!closest || absoluteDirectory.length > closest.length)) closest = absoluteDirectory;
49
+ }
50
+ return closest ?? dirname(absoluteEntry);
51
+ }
52
+ function finalizeWorkspaceDirectories(root, packageDirectories, manifestEntries) {
53
+ const directories = /* @__PURE__ */ new Set([root, ...packageDirectories]);
54
+ for (const entry of manifestEntries) directories.add(resolveEntryWorkspace(entry, packageDirectories));
55
+ return Object.freeze([...directories].sort());
56
+ }
57
+ async function discoverBuildWorkspaceDirectories(rootDirectory, manifestEntries = []) {
58
+ const root = resolve(rootDirectory);
59
+ const packageDirectories = /* @__PURE__ */ new Set();
60
+ await collectPackageDirectories(root, packageDirectories);
61
+ return finalizeWorkspaceDirectories(root, packageDirectories, manifestEntries);
62
+ }
63
+ export { discoverBuildWorkspaceDirectories };
@@ -0,0 +1,70 @@
1
+ import { createRequire } from "node:module";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { existsSync, readFileSync, statSync } from "node:fs";
4
+ // ../internal/src/workspace.ts
5
+ function readPackageJSON(file) {
6
+ try {
7
+ return JSON.parse(readFileSync(file, "utf8"));
8
+ } catch {
9
+ return;
10
+ }
11
+ }
12
+ function findPackageDir(entry, packageName) {
13
+ let current = statSync(entry).isDirectory() ? entry : dirname(entry);
14
+ while (current !== dirname(current)) {
15
+ const packageJSONPath = join(current, "package.json");
16
+ if (existsSync(packageJSONPath)) {
17
+ const packageJSON = readPackageJSON(packageJSONPath);
18
+ if (packageJSON?.name === packageName) return {
19
+ directory: current,
20
+ packageJSONPath,
21
+ version: typeof packageJSON.version === "string" ? packageJSON.version : void 0
22
+ };
23
+ }
24
+ current = dirname(current);
25
+ }
26
+ }
27
+ function resolvePackage(name, resolver) {
28
+ const entry = resolver.resolve(name);
29
+ const packageRoot = findPackageDir(entry, name.startsWith("@master/css-preset/") ? "@master/css-preset" : name.startsWith("@master/css-schema/") ? "@master/css-schema" : name.startsWith("@master/css-language-server/") ? "@master/css-language-server" : name);
30
+ if (!packageRoot) throw new Error(`Unable to find package root for ${name} from ${entry}`);
31
+ return {
32
+ name,
33
+ entry,
34
+ directory: packageRoot.directory,
35
+ packageJSON: packageRoot.packageJSONPath,
36
+ version: packageRoot.version
37
+ };
38
+ }
39
+ function tryResolvePackage(name, resolver, errors) {
40
+ try {
41
+ return resolvePackage(name, resolver);
42
+ } catch (error) {
43
+ errors.push({
44
+ name,
45
+ message: error instanceof Error ? error.message : String(error)
46
+ });
47
+ }
48
+ }
49
+ function createWorkspaceRequire(workspaceDir) {
50
+ return createRequire(join(resolve(workspaceDir), "package.json"));
51
+ }
52
+ function createPackageRequire(resolvedPackage) {
53
+ return createRequire(resolvedPackage.packageJSON || join(resolvedPackage.directory, "package.json"));
54
+ }
55
+ function resolveMasterCSSWorkspacePackages(workspaceDir = process.cwd()) {
56
+ const errors = [];
57
+ const workspaceRequire = createWorkspaceRequire(workspaceDir);
58
+ const css = tryResolvePackage("@master/css", workspaceRequire, errors);
59
+ const result = {
60
+ workspaceDir: resolve(workspaceDir),
61
+ errors,
62
+ ...css ? { css } : {}
63
+ };
64
+ if (css) result.presetManifest = tryResolvePackage("@master/css-preset/default-manifest.json", createPackageRequire(css), errors);
65
+ try {
66
+ result.languageServer = resolvePackage("@master/css-language-server/server", workspaceRequire);
67
+ } catch {}
68
+ return result;
69
+ }
70
+ export { resolveMasterCSSWorkspacePackages };
package/dist/server.d.ts CHANGED
@@ -1 +1 @@
1
- export {};
1
+ export { };
package/dist/server.js ADDED
@@ -0,0 +1,7 @@
1
+ import { MasterCSSLanguageServer } from "./core.js";
2
+ // src/server.ts
3
+ new MasterCSSLanguageServer(void 0, { verbose: true }).start();
4
+ process.on("unhandledRejection", (error) => {
5
+ console.error("Unhandled rejection", error);
6
+ });
7
+ export {};
@@ -0,0 +1,11 @@
1
+ import { MasterCSSLanguageServiceSettings } from "@master/css-language-service";
2
+ import { Pattern } from "fast-glob";
3
+ import { MasterCSSDesignSystemOptions } from "@master/css-tooling/lint";
4
+ declare const defaultLanguageServerSettings: Readonly<MasterCSSLanguageServerSettings>;
5
+ type MasterCSSLanguageServerSettings = MasterCSSLanguageServiceSettings & {
6
+ workspaces?: readonly Pattern[] | 'auto';
7
+ verbose?: boolean;
8
+ /** Opt-in checks for authored CSS in this workspace's managed stylesheet graph. */
9
+ designSystem?: MasterCSSDesignSystemOptions;
10
+ };
11
+ export { MasterCSSLanguageServerSettings, defaultLanguageServerSettings };
@@ -0,0 +1,8 @@
1
+ import { defaultLanguageServiceSettings } from "@master/css-language-service";
2
+ // src/settings.ts
3
+ const defaultLanguageServerSettings = Object.freeze({
4
+ ...defaultLanguageServiceSettings,
5
+ workspaces: "auto",
6
+ verbose: false
7
+ });
8
+ export { defaultLanguageServerSettings };
package/package.json CHANGED
@@ -1 +1,58 @@
1
- {"name":"@master/css-language-server","scripts":{"build":"techor pack --platform node --declare","dev":"pnpm run build --watch","type-check":"tsc --noEmit","lint":"eslint src","test":"jest"},"description":"Master CSS Language Server","author":"Aoyue Design LLC.","license":"MIT","dependencies":{"@master/css":"^2.0.0-rc.9","@master/css-language-service":"^2.0.0-rc.9","explore-config":"^2.5.24","js-beautify":"^1.14.7","minimatch":"^9.0.1","vscode-languageserver":"^7.0.0","vscode-languageserver-textdocument":"^1.0.9"},"main":"./dist/server.bundle.js","types":"./dist/server.d.ts","files":["dist"],"publishConfig":{"access":"public"},"version":"2.0.0-rc.9"}
1
+ {
2
+ "name": "@master/css-language-server",
3
+ "type": "module",
4
+ "engines": {
5
+ "node": "^24"
6
+ },
7
+ "scripts": {
8
+ "build": "bun --bun tsdown --config ../../shared/tsdown-package.config.ts",
9
+ "dev": "bun --bun tsdown --config ../../shared/tsdown-package.config.ts --watch",
10
+ "type-check": "bun --bun tsc -b tsconfig.typecheck.json",
11
+ "lint": "bun --bun eslint",
12
+ "test": "bun --bun vitest run"
13
+ },
14
+ "description": "Master CSS Language Server",
15
+ "author": "Aoyue Design LLC.",
16
+ "license": "MIT",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/master-co/css.git",
20
+ "directory": "packages/language-server"
21
+ },
22
+ "dependencies": {
23
+ "@master/css-compiler": "2.0.0",
24
+ "@master/css-language-service": "2.0.0",
25
+ "@master/css-preset": "2.0.0",
26
+ "@master/css-tooling": "2.0.0",
27
+ "defu": "^6.1.7",
28
+ "fast-glob": "^3.3.3",
29
+ "nanoid": "^6.0.2",
30
+ "vscode-languageserver": "^11.0.0",
31
+ "vscode-languageserver-textdocument": "^1.0.16",
32
+ "vscode-uri": "^3.2.0",
33
+ "@master/css-schema": "2.0.0"
34
+ },
35
+ "sideEffects": false,
36
+ "types": "./dist/index.d.ts",
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/index.d.ts",
40
+ "import": "./dist/index.js",
41
+ "default": "./dist/index.js"
42
+ },
43
+ "./server": {
44
+ "types": "./dist/server.d.ts",
45
+ "import": "./dist/server.js",
46
+ "default": "./dist/server.js"
47
+ }
48
+ },
49
+ "files": [
50
+ "dist"
51
+ ],
52
+ "publishConfig": {
53
+ "access": "public",
54
+ "provenance": true
55
+ },
56
+ "version": "2.0.0",
57
+ "gitHead": "6bf9adc4432b67e62613caea59d3b554711b4d54"
58
+ }
@@ -1 +0,0 @@
1
- "use strict";var W=Object.create;var S=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var H=Object.getOwnPropertyNames;var K=Object.getPrototypeOf,L=Object.prototype.hasOwnProperty;var E=(t,e,n,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of H(e))!L.call(t,i)&&i!==n&&S(t,i,{get:()=>e[i],enumerable:!(s=U(e,i))||s.enumerable});return t};var h=(t,e,n)=>(n=t!=null?W(K(t)):{},E(e||!t||!t.__esModule?S(n,"default",{value:t,enumerable:!0}):n,t));var u=require("vscode-languageserver/node"),w=require("@master/css"),b=require("minimatch"),P=require("vscode-languageserver-textdocument"),k=h(require("path")),T=require("url"),o=require("@master/css-language-service"),v=h(require("explore-config")),a=(0,u.createConnection)(u.ProposedFeatures.all),f=new u.TextDocuments(P.TextDocument),C=!1,D=!1,O=!1,l,g,I;var M=o.settings,p=new Map;a.onDidChangeConfiguration(t=>{C?p.clear():M=t.settings.masterCSS||o.settings,f.all().forEach(y)});async function B(t){if(!C)return Promise.resolve(M);let e=p.get(t);return e||(e=a.workspace.getConfiguration({scopeUri:t,section:"masterCSS"}),p.set(t,e)),e}f.onDidClose(t=>{p.delete(t.document.uri)});f.onDidOpen(t=>{y(t.document)});f.onDidSave(async t=>{k.default.parse(t.document.uri).name==="master.css"&&await A(t.document.uri)});async function y(t){l=await B(t.uri),await A(t.uri)}async function A(t){let e=await a.workspace.getWorkspaceFolders(),n;if(e?.length===1?n=e[0]:n=e?.find(s=>t.includes(s.uri)),n?.uri){let s=(0,T.fileURLToPath)(n.uri.replace("%3A",":"));I=(0,v.default)(l.config||"master.css.*",{cwd:s}),g=new w.MasterCSS(I)}}a.onInitialize(t=>{let e=t.capabilities;C=!!(e.workspace&&e.workspace.configuration),D=!!(e.workspace&&e.workspace.workspaceFolders),O=!!(e.textDocument&&e.textDocument.publishDiagnostics&&e.textDocument.publishDiagnostics.relatedInformation);let n={capabilities:{textDocumentSync:u.TextDocumentSyncKind.Incremental,completionProvider:{resolveProvider:!0,workDoneProgress:!1,triggerCharacters:[":","@","'"]},colorProvider:{},hoverProvider:!0}};return D&&(n.capabilities.workspace={workspaceFolders:{supported:!0}}),n});a.onInitialized(()=>{C&&a.client.register(u.DidChangeConfigurationNotification.type,void 0)});a.onCompletion(t=>{if(l.suggestions==!0&&x(t.textDocument.uri)){let e=t.textDocument.uri,n=f.get(e),s=e.substring(e.lastIndexOf(".")+1),i=t.position;if(n){let c=n.getText(),d=n.offsetAt(i)??0,r=n.offsetAt({line:i.line-100,character:0})??0,F=n.offsetAt({line:i.line+100,character:0})??void 0,R=(0,o.positionCheck)(c.substring(r,F),d,r,l.classMatch).IsMatch,z=n.getText({start:{line:i.line,character:0},end:{line:i.line,character:i.character}}).trim(),m=(0,o.getLastInstance)(z,i,s);if(m.isInstance==!0&&R==!0)return(0,o.getCompletionItem)(m.lastKey,m.triggerKey,m.isStart,m.language,g);if(m.isInstance==!0&&(0,o.checkConfigColorsBlock)(n,t.position)==!0)return(0,o.getConfigColorsCompletionItem)(g)}}return[]});a.onCompletionResolve(t=>t);a.onDocumentColor(async t=>{if(l==null)return[];if(l.previewColors==!0&&x(t.textDocument.uri)){let e=t.textDocument.uri,n=f.get(e);if(n){let s=n.getText()??"";if(typeof n==null)return[];let i=await(0,o.getDocumentColors)(s,g),c=new Set;return i.filter(r=>c.has(r.index.start)?!1:(c.add(r.index.start),!0)).map(r=>({range:{start:n.positionAt(r.index.start),end:n.positionAt(r.index.end)},color:r.color}))}}return[]});a.onColorPresentation(t=>{if(l.previewColors==!0&&x(t.textDocument.uri)){let e=f.get(t.textDocument.uri);if(e){let n=e.getText(),s=["(?<=colors:\\s*{\\s*.*)([^}]*)}"],i=e.offsetAt(t.range.start)??0,c=e.offsetAt({line:t.range.start.line-100,character:0})??0,d=e.offsetAt({line:t.range.start.line+100,character:0})??void 0,r=(0,o.positionCheck)(n.substring(c,d),i,c,s);return(0,o.getColorPresentation)(t,r.IsMatch)}}return[]});a.onHover(t=>{if(l.inspect==!0&&x(t.textDocument.uri)){let e=f.get(t.textDocument.uri),n=t.position;if(e){let s=e.getText(),i=e.offsetAt(n)??0,c=e.offsetAt({line:n.line-100,character:0})??0,d=e.offsetAt({line:n.line+100,character:0})??void 0,r=(0,o.positionCheck)(s.substring(c,d),i,c,l.classMatch);if(r.IsMatch)return(0,o.doHover)(r.instance.instanceString,N(r.instance.index,e),I)}}return null});function x(t){for(let e of l.files.exclude)if((0,b.minimatch)(t,e))return!1;return!0}function N(t,e){return{start:e.positionAt(t.start),end:e.positionAt(t.end)}}f.listen(a);a.listen();