From c1f0d97976c12401f19077be242bec20e400941f Mon Sep 17 00:00:00 2001 From: Dylan Piercey Date: Wed, 13 Jul 2022 14:09:36 -0700 Subject: [PATCH] fix: regexp issues in syntax highlighting --- .changeset/olive-mails-clap.md | 2 +- .../service/marko/document-links/extract.ts | 113 ++++++++++++++++++ .../src/service/marko/document-links/index.ts | 16 +++ server/src/service/marko/index.ts | 2 + server/src/service/stylesheet/extract.ts | 3 +- server/src/service/stylesheet/index.ts | 12 +- server/src/utils/resolve-url.ts | 15 +++ 7 files changed, 150 insertions(+), 13 deletions(-) create mode 100644 server/src/service/marko/document-links/extract.ts create mode 100644 server/src/service/marko/document-links/index.ts create mode 100644 server/src/utils/resolve-url.ts diff --git a/.changeset/olive-mails-clap.md b/.changeset/olive-mails-clap.md index 139707b7..5c666976 100644 --- a/.changeset/olive-mails-clap.md +++ b/.changeset/olive-mails-clap.md @@ -3,4 +3,4 @@ "@marko/language-server": patch --- -Implements document links provider (currently for stylesheets). +Implements document links provider. diff --git a/server/src/service/marko/document-links/extract.ts b/server/src/service/marko/document-links/extract.ts new file mode 100644 index 00000000..a8a5779d --- /dev/null +++ b/server/src/service/marko/document-links/extract.ts @@ -0,0 +1,113 @@ +import type { TaglibLookup } from "@marko/babel-utils"; +import { DocumentLink } from "vscode-languageserver"; +import type { TextDocument } from "vscode-languageserver-textdocument"; +import { URI } from "vscode-uri"; +import { + type Node, + type Range, + type parse, + NodeType, +} from "../../../utils/parser"; +import resolveUrl from "../../../utils/resolve-url"; + +const importTagReg = /(['"])<((?:[^\1\\>]+|\\.)*)>?\1/g; +const linkedAttrs: Record> = { + src: new Set([ + "audio", + "embed", + "iframe", + "img", + "input", + "script", + "source", + "track", + "video", + ]), + href: new Set(["a", "area", "link"]), + data: new Set(["object"]), + poster: new Set(["video"]), +}; + +/** + * Iterate over the Marko CST and extract all the file links in the document. + */ +export function extractDocumentLinks( + doc: TextDocument, + parsed: ReturnType, + lookup: TaglibLookup +): DocumentLink[] { + if (URI.parse(doc.uri).scheme === "untitled") { + return []; + } + + const links: DocumentLink[] = []; + const { program } = parsed; + const code = doc.getText(); + const read = (range: Range) => code.slice(range.start, range.end); + const visit = (node: Node.ChildNode) => { + switch (node.type) { + case NodeType.Tag: + if (node.attrs && node.nameText) { + for (const attr of node.attrs) { + if ( + attr.type === NodeType.AttrNamed && + attr.value?.type === NodeType.AttrValue && + /^['"]$/.test(code[attr.value.value.start]) + ) { + const attrName = read(attr.name); + if (linkedAttrs[attrName]?.has(node.nameText)) { + links.push( + DocumentLink.create( + { + start: parsed.positionAt(attr.value.value.start), + end: parsed.positionAt(attr.value.value.end), + }, + resolveUrl(read(attr.value.value).slice(1, -1), doc.uri) + ) + ); + } + } + } + } + if (node.body) { + for (const child of node.body) { + visit(child); + } + } + + break; + } + }; + + for (const item of program.static) { + // check for import statement (this currently only support the tag import shorthand). + if (item.type === NodeType.Statement && code[item.start] === "i") { + importTagReg.lastIndex = 0; + const value = parsed.read(item); + const match = importTagReg.exec(value); + if (match) { + const [{ length }, , tagName] = match; + const tagDef = lookup.getTag(tagName); + const fileForTag = tagDef && (tagDef.template || tagDef.renderer); + + if (fileForTag) { + links.push( + DocumentLink.create( + { + start: parsed.positionAt(item.start + match.index), + end: parsed.positionAt(item.start + match.index + length), + }, + fileForTag + ) + ); + } + } + } + } + + for (const item of program.body) { + visit(item); + } + + return links; +} diff --git a/server/src/service/marko/document-links/index.ts b/server/src/service/marko/document-links/index.ts new file mode 100644 index 00000000..c4a3bd30 --- /dev/null +++ b/server/src/service/marko/document-links/index.ts @@ -0,0 +1,16 @@ +import type { DocumentLink } from "vscode-languageserver"; +import { getCompilerInfo, parse } from "../../../utils/compiler"; +import type { Plugin } from "../../types"; +import { extractDocumentLinks } from "./extract"; + +const cache = new WeakMap, DocumentLink[]>(); + +export const findDocumentLinks: Plugin["findDocumentLinks"] = async (doc) => { + const parsed = parse(doc); + let result = cache.get(parsed); + if (!result) { + result = extractDocumentLinks(doc, parsed, getCompilerInfo(doc).lookup); + cache.set(parsed, result); + } + return result; +}; diff --git a/server/src/service/marko/index.ts b/server/src/service/marko/index.ts index 22d23da7..7373154b 100644 --- a/server/src/service/marko/index.ts +++ b/server/src/service/marko/index.ts @@ -2,11 +2,13 @@ import type { Plugin } from "../types"; import { doComplete } from "./complete"; import { doValidate } from "./validate"; import { findDefinition } from "./definition"; +import { findDocumentLinks } from "./document-links"; import { format } from "./format"; export default { doComplete, doValidate, findDefinition, + findDocumentLinks, format, } as Plugin; diff --git a/server/src/service/stylesheet/extract.ts b/server/src/service/stylesheet/extract.ts index 712c38f9..b8a88e2f 100644 --- a/server/src/service/stylesheet/extract.ts +++ b/server/src/service/stylesheet/extract.ts @@ -8,7 +8,7 @@ import { Node, Range, NodeType } from "../../utils/parser"; export function extractStyleSheets( code: string, program: Node.Program, - lookup: TaglibLookup | null | undefined + lookup: TaglibLookup ) { let placeholderId = 0; const extractorsByExt: Record< @@ -84,7 +84,6 @@ export function extractStyleSheets( if ( name === "#style" || (name === "style" && - lookup && node.nameText && name === "style" && lookup.getTag(node.nameText)?.html) diff --git a/server/src/service/stylesheet/index.ts b/server/src/service/stylesheet/index.ts index 4f29a1db..e6906704 100644 --- a/server/src/service/stylesheet/index.ts +++ b/server/src/service/stylesheet/index.ts @@ -21,7 +21,7 @@ import { getCompilerInfo, parse } from "../../utils/compiler"; import { START_OF_FILE } from "../../utils/utils"; import type { Plugin } from "../types"; import { extractStyleSheets } from "./extract"; -import { displayInformation } from "../../utils/messages"; +import resolveUrl from "../../utils/resolve-url"; interface StyleSheetInfo { virtualDoc: TextDocument; @@ -143,14 +143,7 @@ const StyleSheetService: Partial = { const { service, virtualDoc } = info; for (const link of service.findDocumentLinks(virtualDoc, info.parsed, { - resolveReference(ref, baseUrl) { - const resolved = new URL(ref, new URL(baseUrl, "resolve://")); - if (resolved.protocol === "resolve:") { - // `baseUrl` is a relative URL. - return resolved.pathname + resolved.search + resolved.hash; - } - return resolved.toString(); - }, + resolveReference: resolveUrl, })) { if (link.target && updateRange(doc, info, link.range)) { result.push(link); @@ -159,7 +152,6 @@ const StyleSheetService: Partial = { } if (result.length) { - displayInformation(result.map((it) => it.target).join(",")); return result; } }, diff --git a/server/src/utils/resolve-url.ts b/server/src/utils/resolve-url.ts new file mode 100644 index 00000000..fd866943 --- /dev/null +++ b/server/src/utils/resolve-url.ts @@ -0,0 +1,15 @@ +import { fileURLToPath } from "url"; + +export default function resolveUrl(ref: string, baseUrl: string) { + const resolved = new URL(ref, new URL(baseUrl, "resolve://")); + if (resolved.protocol === "resolve:") { + // `baseUrl` is a relative URL. + return resolved.pathname + resolved.search + resolved.hash; + } + + try { + return fileURLToPath(resolved); + } catch { + return undefined; + } +}