How to create a simple VSCE for CSS Lube
Tags: webdev, javascript, opensource, css
Published: 2023-09-28 12:12:35

CSS Lube is a CSS library that I created. I personally use the term "CSS interpreter", but it can also be seen as a general utility-first library.
{% embed https://dev.to/artxe2/css-lube-highly-optimized-css-interpreter-2nl5 %}

One of the slogans of this library is "It takes only 3 minutes to learn this".
In order to make this claim a reality, I created a vscode extension.
The features required for VSCE were simple autocompletion and hover messages to prevent the need to memorize shortcuts, and I decided to add code highlighting features as a side benefit.
![IntelliSense CSS Lube](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jqtf1m4p4vxxp9l6g3t8.gif)


## VSCE's basic API

First, I looked at the tutorials for VSCE.
[The sample repository](embed https://github.com/microsoft/vscode-extension-samples) I found in the tutorial had many examples, and I copied the source code for autocompletion and code highlighting, and then proceeded with the work by finding additional information through Google search.
I wish the sample code was more helpful, but I was able to learn how to use the VSCE API required for feature implementation with the help of Stack Overflow.
```ts
const hover_message = new vscode.MarkdownString()
hover_message.supportHtml = true
hover_message.appendMarkdown("**" + compile_style(cname) + "**")
```

## How to find the class name using AST

After I learned the basic usage of the VSCE API, I needed a way to find where the parts of the code corresponding to the HTML's className are.
At first, I tried to use regular expressions naively.
```js
const class_name_regex = /(?<=class(?:Name)?=")[^"{]*/g
```
However, since common front-end frameworks use JavaScript code blocks together when writing HTML, it was necessary to support the functionality of the extension for JavaScript strings used in className.
```html
<script>
const name = "world"
</script>

<div className={`c=red
    bg=blue
    ${is_left ? "pr=1" : "pl=1"}`}>
  <span>Hello {name}!</span>
</div>
```
My solution to this was to implement an HTML AST parser and use it, even if support for JSX would be postponed until later.
Looking at [the issues of Svelte](https://github.com/sveltejs/svelte/issues/4701), it seemed that there was no parser that met my requirements, and I created an AST parser for it myself.
The principle is simple.
The starting point of a script block can be identified using `{`.
The starting point of a string can be identified using `'`, `"`, or backtick.
If `}` appears outside of a string, it indicates the end of the script block.
The challenge is to determine when a string ends.
For example, in the case of backticks, I used the following regular expression to find the end point of backticks.
```js
const stop_backtick_regex = /(?<=(?<!\\)(\\\\)*)(?:`|\${)/
```
I was able to determine the end point of an unescaped string using `(?<=(?<!\\)(\\\\)*)`.
Ultimately, the AST was composed using the types below.
```ts
import type ast_syntax_error from "./src/parse_dom/src/AstSyntaxError.js"

export type AstNode =
  Attribute
  | Script
  | String
  | Style
  | Element
  | Text

export type AstSyntaxError = ReturnType<typeof ast_syntax_error>

export type Attribute = {
  type: "Attribute"
  name: string
  value: (
    true
    | Script & { subType: "block" }
    | String & { subType: "single" | "double" }
  )
} & BaseAstNode

type BaseAstNode = {
  end: number
  start: number
}

export type Element = {
  attributes: Attribute[]
  children: AstNode[]
  name: string
  subType: "close" | "closed" | "open"
  type: "Element"
} & BaseAstNode

export type Script = {
  strings: String[]
  subType: "block" | "content" | "template"
  type: "Script"
} & BaseAstNode

export type String = {
  scripts: Script[]
  subType: "backtick" | "double" | "single"
  type: "String"
} & BaseAstNode

export type Style = {
  type: "Style"
} & BaseAstNode

export type Text = {
  type: "Text"
} & BaseAstNode
```
Other than this idea, the code was not very elegant, as it was written in a way that simply wrote and debugged repeatedly until it was completed without a clear design.
Still, if you are curious about the actual code, please refer to the link below.
[dom-eater](https://github.com/Artxe2/dom-eater/tree/main/packages/dom-eater)
[css-lube/intellisense](https://github.com/Artxe2/css-lube/tree/main/intellisense)


## TODO?

Writing Ast test code is very difficult.
I am thinking about how to write test code simply.
![Test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j64o2ajcoce5ljrpedyc.png)

JSX support seems to be more difficult than expected.
It is necessary to support parsing for code that is being written, but even in the case of acorn, it seems that `JSXElement` parsing is not working properly if the `loose`(syntax error allowed) option is enabled.

Thank you.