diff --git a/.changeset/wet-kangaroos-hide.md b/.changeset/wet-kangaroos-hide.md new file mode 100644 index 00000000..59f9340c --- /dev/null +++ b/.changeset/wet-kangaroos-hide.md @@ -0,0 +1,7 @@ +--- +"@astrojs/language-server": minor +"@astrojs/check": minor +"astro-vscode": minor +--- + +Adds support for SCSS and LESS intellisense inside style tags diff --git a/packages/language-server/package.json b/packages/language-server/package.json index a330e212..a3fc3c02 100644 --- a/packages/language-server/package.json +++ b/packages/language-server/package.json @@ -21,7 +21,7 @@ "test:match": "pnpm run test -g" }, "dependencies": { - "@astrojs/compiler": "^2.9.1", + "@astrojs/compiler": "^2.10.0", "@jridgewell/sourcemap-codec": "^1.4.15", "@volar/kit": "~2.4.0-alpha.15", "@volar/language-core": "~2.4.0-alpha.15", diff --git a/packages/language-server/src/core/index.ts b/packages/language-server/src/core/index.ts index 80938965..ea3b283f 100644 --- a/packages/language-server/src/core/index.ts +++ b/packages/language-server/src/core/index.ts @@ -174,7 +174,7 @@ export class AstroVirtualCode implements VirtualCode { this.htmlDocument = htmlDocument; htmlVirtualCode.embeddedCodes = [ - extractStylesheets(tsx.ranges.styles), + ...extractStylesheets(tsx.ranges.styles), ...extractScriptTags(tsx.ranges.scripts), ]; diff --git a/packages/language-server/src/core/parseCSS.ts b/packages/language-server/src/core/parseCSS.ts index 942c8cf0..a06bf678 100644 --- a/packages/language-server/src/core/parseCSS.ts +++ b/packages/language-server/src/core/parseCSS.ts @@ -3,17 +3,30 @@ import type { CodeInformation, VirtualCode } from '@volar/language-core'; import { Segment, toString } from 'muggle-string'; import { buildMappings } from '../buildMappings.js'; -export function extractStylesheets(styles: TSXExtractedStyle[]): VirtualCode { - return mergeCSSContexts(styles); +const SUPPORTED_LANGUAGES = ['css', 'scss', 'less'] as const; +type SupportedLanguages = (typeof SUPPORTED_LANGUAGES)[number]; + +function isSupportedLanguage(lang: string): lang is SupportedLanguages { + return SUPPORTED_LANGUAGES.includes(lang as SupportedLanguages); } -function mergeCSSContexts(inlineStyles: TSXExtractedStyle[]): VirtualCode { - const codes: Segment[] = []; +export function extractStylesheets(styles: TSXExtractedStyle[]): VirtualCode[] { + return mergeCSSContextsByLanguage(styles); +} + +function mergeCSSContextsByLanguage(inlineStyles: TSXExtractedStyle[]): VirtualCode[] { + const codes: Record[]> = { + css: [], + scss: [], + less: [], + }; for (const cssContext of inlineStyles) { + const currentCode = isSupportedLanguage(cssContext.lang) ? codes[cssContext.lang] : codes.css; + const isStyleAttribute = cssContext.type === 'style-attribute'; - if (isStyleAttribute) codes.push('__ { '); - codes.push([ + if (isStyleAttribute) currentCode.push('__ { '); + currentCode.push([ cssContext.content, undefined, cssContext.position.start, @@ -26,15 +39,26 @@ function mergeCSSContexts(inlineStyles: TSXExtractedStyle[]): VirtualCode { format: false, }, ]); - if (isStyleAttribute) codes.push(' }\n'); + if (isStyleAttribute) currentCode.push(' }\n'); } - const mappings = buildMappings(codes); - const text = toString(codes); + let virtualCodes: VirtualCode[] = []; + for (const lang of SUPPORTED_LANGUAGES) { + if (codes[lang].length) { + virtualCodes.push(createVirtualCodeForLanguage(codes[lang], lang)); + } + } + + return virtualCodes; +} + +function createVirtualCodeForLanguage(code: Segment[], lang: string): VirtualCode { + const mappings = buildMappings(code); + const text = toString(code); return { - id: 'style.css', - languageId: 'css', + id: `style.${lang}`, + languageId: lang, snapshot: { getText: (start, end) => text.substring(start, end), getLength: () => text.length, diff --git a/packages/language-server/src/core/parseJS.ts b/packages/language-server/src/core/parseJS.ts index a84a3ce0..61383ba0 100644 --- a/packages/language-server/src/core/parseJS.ts +++ b/packages/language-server/src/core/parseJS.ts @@ -11,7 +11,13 @@ export function extractScriptTags(scripts: TSXExtractedScript[]): VirtualCode[] .map(moduleScriptToVirtualCode) satisfies VirtualCode[]; const inlineScripts = scripts - .filter((script) => script.type === 'event-attribute' || script.type === 'inline') + .filter( + (script) => + // TODO: Change this at some point so that unknown scripts are not included + // We can't guarantee that they are JavaScript, so we shouldn't treat them as such, even if it might work in some cases + // Perhaps we should make it so that the user has to specify the language of the script if it's not a known type (ex: lang="js"), not sure. + script.type === 'event-attribute' || script.type === 'inline' || script.type === 'unknown' + ) .sort((a, b) => a.position.start - b.position.start); embeddedJSCodes.push(...moduleScripts); @@ -20,6 +26,11 @@ export function extractScriptTags(scripts: TSXExtractedScript[]): VirtualCode[] embeddedJSCodes.push(mergedJSContext); } + const JSONScripts = scripts + .filter((script) => script.type === 'json') + .map(jsonScriptToVirtualCode) satisfies VirtualCode[]; + embeddedJSCodes.push(...JSONScripts); + return embeddedJSCodes; } @@ -58,6 +69,35 @@ function moduleScriptToVirtualCode(script: TSXExtractedScript, index: number): V }; } +function jsonScriptToVirtualCode(script: TSXExtractedScript, index: number): VirtualCode { + return { + id: `${index}.json`, + languageId: 'json', + snapshot: { + getText: (start, end) => script.content.substring(start, end), + getLength: () => script.content.length, + getChangeRange: () => undefined, + }, + mappings: [ + { + sourceOffsets: [script.position.start], + generatedOffsets: [0], + lengths: [script.content.length], + // TODO: Support JSON features + data: { + verification: false, + completion: false, + semantic: false, + navigation: false, + structure: false, + format: false, + }, + }, + ], + embeddedCodes: [], + }; +} + /** * Merge all the inline and non-hoisted scripts into a single `.mjs` file */ diff --git a/packages/language-server/test/css/completions.test.ts b/packages/language-server/test/css/completions.test.ts index 3db2cb01..20e7abd8 100644 --- a/packages/language-server/test/css/completions.test.ts +++ b/packages/language-server/test/css/completions.test.ts @@ -55,4 +55,47 @@ describe('CSS - Completions', () => { expect(completions!.items).to.not.be.empty; expect(completions?.items.map((i) => i.label)).to.include('aliceblue'); }); + + it('Can provide completions inside SCSS blocks', async () => { + const document = await languageServer.openFakeDocument( + ` +`, + 'astro' + ); + const completions = await languageServer.handle.sendCompletionRequest( + document.uri, + Position.create(3, 10) + ); + + const allLabels = completions?.items.map((i) => i.label); + + expect(completions!.items).to.not.be.empty; + expect(allLabels).to.include('$c'); + }); + + it('Can provide completions inside LESS blocks', async () => { + const document = await languageServer.openFakeDocument( + ` +`, + 'astro' + ); + const completions = await languageServer.handle.sendCompletionRequest( + document.uri, + Position.create(3, 10) + ); + + const allLabels = completions?.items.map((i) => i.label); + expect(completions!.items).to.not.be.empty; + expect(allLabels).to.include('@link-color'); + }); }); diff --git a/packages/language-server/test/typescript/scripts.test.ts b/packages/language-server/test/typescript/scripts.test.ts index 62a2ef2d..3969766a 100644 --- a/packages/language-server/test/typescript/scripts.test.ts +++ b/packages/language-server/test/typescript/scripts.test.ts @@ -1,4 +1,4 @@ -import { FullDocumentDiagnosticReport } from '@volar/language-server'; +import { FullDocumentDiagnosticReport, Position } from '@volar/language-server'; import { expect } from 'chai'; import { type LanguageServer, getLanguageServer } from '../server.js'; @@ -31,4 +31,32 @@ describe('TypeScript - Diagnostics', async () => { expect(diagnostics.items).length(0); }); + + it('still supports script tags with unknown types', async () => { + const document = await languageServer.openFakeDocument( + '', + 'astro' + ); + + const hoverInfo = await languageServer.handle.sendHoverRequest( + document.uri, + Position.create(0, 38) + ); + + expect(hoverInfo).to.not.be.undefined; + }); + + it('ignores is:raw script tags', async () => { + const document = await languageServer.openFakeDocument( + '', + 'astro' + ); + + const hoverInfo = await languageServer.handle.sendHoverRequest( + document.uri, + Position.create(0, 38) + ); + + expect(hoverInfo).to.be.null; + }); }); diff --git a/packages/language-server/test/units/parseJS.test.ts b/packages/language-server/test/units/parseJS.test.ts index 029fa70f..ae970ea1 100644 --- a/packages/language-server/test/units/parseJS.test.ts +++ b/packages/language-server/test/units/parseJS.test.ts @@ -11,13 +11,12 @@ describe('parseJS - Can find all the scripts in an Astro file', () => { expect(scriptTags.length).to.equal(2); }); - // TODO: This will be outdated in the future, once we add JSON support - it('Ignore JSON scripts', () => { + it('Includes JSON scripts', () => { const input = ``; const { ranges } = astro2tsx(input, 'file.astro'); const scriptTags = extractScriptTags(ranges.scripts); - expect(scriptTags.length).to.equal(0); + expect(scriptTags.length).to.equal(1); }); it('returns the proper capabilities for inline script tags', async () => { diff --git a/packages/ts-plugin/package.json b/packages/ts-plugin/package.json index 45c78c65..6c06e20b 100644 --- a/packages/ts-plugin/package.json +++ b/packages/ts-plugin/package.json @@ -21,7 +21,7 @@ "dependencies": { "@volar/language-core": "~2.4.0-alpha.15", "@volar/typescript": "~2.4.0-alpha.15", - "@astrojs/compiler": "^2.7.0", + "@astrojs/compiler": "^2.10.0", "@jridgewell/sourcemap-codec": "^1.4.15", "semver": "^7.3.8", "vscode-languageserver-textdocument": "^1.0.11" diff --git a/packages/vscode/package.json b/packages/vscode/package.json index 490325d3..70536cde 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -229,7 +229,7 @@ "vscode-tmgrammar-test": "^0.1.2" }, "dependencies": { - "@astrojs/compiler": "^2.9.1", + "@astrojs/compiler": "^2.10.0", "prettier": "^3.2.5", "prettier-plugin-astro": "^0.14.1" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0d466b5..b1f9abd9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -76,8 +76,8 @@ importers: packages/language-server: dependencies: '@astrojs/compiler': - specifier: ^2.9.1 - version: 2.9.1 + specifier: ^2.10.0 + version: 2.10.0 '@jridgewell/sourcemap-codec': specifier: ^1.4.15 version: 1.4.15 @@ -179,8 +179,8 @@ importers: packages/ts-plugin: dependencies: '@astrojs/compiler': - specifier: ^2.7.0 - version: 2.7.0 + specifier: ^2.10.0 + version: 2.10.0 '@jridgewell/sourcemap-codec': specifier: ^1.4.15 version: 1.4.15 @@ -222,8 +222,8 @@ importers: packages/vscode: dependencies: '@astrojs/compiler': - specifier: ^2.9.1 - version: 2.9.1 + specifier: ^2.10.0 + version: 2.10.0 prettier: specifier: ^3.2.5 version: 3.2.5 @@ -309,12 +309,12 @@ packages: '@jridgewell/trace-mapping': 0.3.18 dev: true - /@astrojs/compiler@2.7.0: - resolution: {integrity: sha512-XpC8MAaWjD1ff6/IfkRq/5k1EFj6zhCNqXRd5J43SVJEBj/Bsmizkm8N0xOYscGcDFQkRgEw6/eKnI5x/1l6aA==} - dev: false + /@astrojs/compiler@2.10.0: + resolution: {integrity: sha512-V8ym+4kYCKmMeSdfDvm/SrlmUlKLkKzfWV3cKyiStEKW/D+YsXaQiBZcSCtuKXq8aQhsDw9HtCTZilhEMBX+BA==} /@astrojs/compiler@2.9.1: resolution: {integrity: sha512-s8Ge2lWHx/s3kl4UoerjL/iPtwdtogNM/BLOaGCwQA6crMOVYpphy5wUkYlKyuh8GAeGYH/5haLAFBsgNy9AQQ==} + dev: true /@astrojs/internal-helpers@0.2.1: resolution: {integrity: sha512-06DD2ZnItMwUnH81LBLco3tWjcZ1lGU9rLCCBaeUCGYe9cI0wKyY2W3kDyoW1I6GmcWgt1fu+D1CTvz+FIKf8A==} @@ -2771,7 +2771,7 @@ packages: engines: {node: '>=18.14.1', npm: '>=6.14.0'} hasBin: true dependencies: - '@astrojs/compiler': 2.9.1 + '@astrojs/compiler': 2.10.0 '@astrojs/internal-helpers': 0.2.1 '@astrojs/markdown-remark': 4.2.1 '@astrojs/telemetry': 3.0.4 @@ -6179,7 +6179,7 @@ packages: resolution: {integrity: sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==} engines: {node: ^14.15.0 || >=16.0.0} dependencies: - '@astrojs/compiler': 2.9.1 + '@astrojs/compiler': 2.10.0 prettier: 3.2.5 sass-formatter: 0.7.6 dev: false