Skip to content

Commit

Permalink
fix(parser): nested <script> tags are not extracted (#656)
Browse files Browse the repository at this point in the history
Co-authored-by: David Dodlek <[email protected]>
  • Loading branch information
Princesseuh and F0rce authored Sep 26, 2023
1 parent 1e84fac commit 4046fb8
Show file tree
Hide file tree
Showing 5 changed files with 270 additions and 135 deletions.
6 changes: 6 additions & 0 deletions .changeset/cold-llamas-compare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@astrojs/language-server': patch
'astro-vscode': patch
---

Fix intellisense not working in nested script and style tags
137 changes: 87 additions & 50 deletions packages/language-server/src/core/parseCSS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { FileKind, FileRangeCapabilities, VirtualFile } from '@volar/language-co
import * as SourceMap from '@volar/source-map';
import * as muggle from 'muggle-string';
import type ts from 'typescript/lib/tsserverlibrary';
import type { HTMLDocument } from 'vscode-html-languageservice';
import type { HTMLDocument, Node } from 'vscode-html-languageservice';
import type { AttributeNodeWithPosition } from './compilerUtils.js';

export function extractStylesheets(
Expand All @@ -13,36 +13,11 @@ export function extractStylesheets(
htmlDocument: HTMLDocument,
ast: ParseResult['ast']
): VirtualFile[] {
const embeddedCSSFiles: VirtualFile['embeddedFiles'] = [];
for (const [index, root] of htmlDocument.roots.entries()) {
if (root.tag === 'style' && root.startTagEnd !== undefined && root.endTagStart !== undefined) {
const styleText = snapshot.getText(root.startTagEnd, root.endTagStart);
embeddedCSSFiles.push({
fileName: fileName + `.${index}.css`,
kind: FileKind.TextFile,
snapshot: {
getText: (start, end) => styleText.substring(start, end),
getLength: () => styleText.length,
getChangeRange: () => undefined,
},
codegenStacks: [],
mappings: [
{
sourceRange: [root.startTagEnd, root.endTagStart],
generatedRange: [0, styleText.length],
data: FileRangeCapabilities.full,
},
],
capabilities: {
diagnostic: false,
documentSymbol: true,
foldingRange: true,
documentFormatting: false,
},
embeddedFiles: [],
});
}
}
const embeddedCSSFiles: VirtualFile[] = findEmbeddedStyles(
fileName,
snapshot,
htmlDocument.roots
);

const inlineStyles = findInlineStyles(ast);
if (inlineStyles.length > 0) {
Expand Down Expand Up @@ -79,19 +54,84 @@ export function extractStylesheets(
return embeddedCSSFiles;
}

// TODO: Provide completion for classes and IDs
export function collectClassesAndIdsFromDocument(ast: ParseResult['ast']): string[] {
const classesAndIds: string[] = [];
/**
* Find all embedded styles in a document.
* Embedded styles are styles that are defined in `<style>` tags.
*/
function findEmbeddedStyles(
fileName: string,
snapshot: ts.IScriptSnapshot,
roots: Node[]
): VirtualFile[] {
const embeddedCSSFiles: VirtualFile[] = [];
let cssIndex = 0;

getEmbeddedCSSInNodes(roots);

function getEmbeddedCSSInNodes(nodes: Node[]) {
for (const [_, node] of nodes.entries()) {
if (
node.tag === 'style' &&
node.startTagEnd !== undefined &&
node.endTagStart !== undefined
) {
const styleText = snapshot.getText(node.startTagEnd, node.endTagStart);
embeddedCSSFiles.push({
fileName: fileName + `.${cssIndex}.css`,
kind: FileKind.TextFile,
snapshot: {
getText: (start, end) => styleText.substring(start, end),
getLength: () => styleText.length,
getChangeRange: () => undefined,
},
codegenStacks: [],
mappings: [
{
sourceRange: [node.startTagEnd, node.endTagStart],
generatedRange: [0, styleText.length],
data: FileRangeCapabilities.full,
},
],
capabilities: {
diagnostic: false,
documentSymbol: true,
foldingRange: true,
documentFormatting: false,
},
embeddedFiles: [],
});
cssIndex++;
}

if (node.children) getEmbeddedCSSInNodes(node.children);
}
}

return embeddedCSSFiles;
}

/**
* Find all inline styles using the Astro AST
* Inline styles are styles that are defined in the `style` attribute of an element.
* TODO: Merge this with `findEmbeddedCSS`? Unlike scripts, there's no scoping being done here, so merging all of it in
* the same virtual file is possible, though it might make mapping more tricky.
*/
function findInlineStyles(ast: ParseResult['ast']): AttributeNodeWithPosition[] {
const styleAttrs: AttributeNodeWithPosition[] = [];

// `@astrojs/compiler`'s `walk` method is async, so we can't use it here. Arf
function walkDown(parent: ParentNode) {
if (!parent.children) return;

parent.children.forEach((child) => {
if (is.element(child)) {
const classOrIDAttributes = child.attributes
.filter((attr) => attr.kind === 'quoted' && (attr.name === 'class' || attr.name === 'id'))
.flatMap((attr) => attr.value.split(' '));
const styleAttribute = child.attributes.find(
(attr) => attr.name === 'style' && attr.kind === 'quoted'
);

classesAndIds.push(...classOrIDAttributes);
if (styleAttribute && styleAttribute.position) {
styleAttrs.push(styleAttribute as AttributeNodeWithPosition);
}
}

if (is.parent(child)) {
Expand All @@ -102,25 +142,22 @@ export function collectClassesAndIdsFromDocument(ast: ParseResult['ast']): strin

walkDown(ast);

return classesAndIds;
return styleAttrs;
}

export function findInlineStyles(ast: ParseResult['ast']): AttributeNodeWithPosition[] {
const styleAttrs: AttributeNodeWithPosition[] = [];

// `@astrojs/compiler`'s `walk` method is async, so we can't use it here. Arf
// TODO: Provide completion for classes and IDs
export function collectClassesAndIdsFromDocument(ast: ParseResult['ast']): string[] {
const classesAndIds: string[] = [];
function walkDown(parent: ParentNode) {
if (!parent.children) return;

parent.children.forEach((child) => {
if (is.element(child)) {
const styleAttribute = child.attributes.find(
(attr) => attr.name === 'style' && attr.kind === 'quoted'
);
const classOrIDAttributes = child.attributes
.filter((attr) => attr.kind === 'quoted' && (attr.name === 'class' || attr.name === 'id'))
.flatMap((attr) => attr.value.split(' '));

if (styleAttribute && styleAttribute.position) {
styleAttrs.push(styleAttribute as AttributeNodeWithPosition);
}
classesAndIds.push(...classOrIDAttributes);
}

if (is.parent(child)) {
Expand All @@ -131,5 +168,5 @@ export function findInlineStyles(ast: ParseResult['ast']): AttributeNodeWithPosi

walkDown(ast);

return styleAttrs;
return classesAndIds;
}
Loading

0 comments on commit 4046fb8

Please sign in to comment.