Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .clang-format-ignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ src/ast_pretty_print.c
src/errors.c
src/include/ast_nodes.h
src/include/ast_pretty_print.h
src/include/css_parser.h
src/include/errors.h
src/visitor.c
8 changes: 8 additions & 0 deletions .github/workflows/build-gems.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ jobs:
with:
bundler-cache: false

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable

- name: Build CSS Parser
run: cd src/css && cargo build --release

- name: bundle install
run: bundle install

Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,26 @@ jobs:
with:
bundler-cache: true

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable

- name: Install Rust WASM target
run: rustup target add wasm32-unknown-emscripten

- name: Build CSS Parser
run: cd src/css && cargo build --release

- name: bundle install
run: bundle install

- name: Render Templates
run: bundle exec rake templates

- name: make all
run: make all

- name: Compile Herb
run: bundle exec rake make

Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ jobs:
with:
bundler-cache: true

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable

- name: Install Rust WASM target
run: rustup target add wasm32-unknown-emscripten

- name: Build CSS Parser
run: cd src/css && cargo build --release

- name: bundle install
run: bundle install

Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/javascript.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ jobs:
with:
bundler-cache: true

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable

- name: Install Rust WASM target
run: rustup target add wasm32-unknown-emscripten

- name: Build CSS Parser
run: cd src/css && cargo build --release

- name: bundle install
run: bundle install

Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,7 @@ docs/docs/public/c-reference

# NPM
**/node_modules/**/*

# Rust (CSS Parser)
src/css/target/
src/include/css_parser.h
35 changes: 24 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ prism_build = $(prism_path)/build
prism_flags = -I$(prism_include)
prism_ldflags = $(prism_build)/libprism.a

css_parser_dir = src/css
css_parser_lib = $(css_parser_dir)/target/release/libherb_css_parser.a
css_parser_flags = -I$(css_parser_dir)

# Enable strict warnings
warning_flags = -Wall -Wextra -Werror -pedantic

Expand All @@ -50,42 +54,44 @@ production_flags = $(warning_flags) -O3 -march=native -flto
shared_library_flags = -fPIC

# Default build mode (change this as needed)
flags = $(warning_flags) $(debug_flags) $(prism_flags) -std=c99
flags = $(warning_flags) $(debug_flags) $(prism_flags) $(css_parser_flags) -std=c99

# Separate test compilation flags
test_flags = $(debug_flags) $(prism_flags) -std=gnu99
test_flags = $(debug_flags) $(prism_flags) $(css_parser_flags) -std=gnu99

# Shared library build (if needed)
shared_flags = $(production_flags) $(shared_library_flags) $(prism_flags)
shared_flags = $(production_flags) $(shared_library_flags) $(prism_flags) $(css_parser_flags)

ifeq ($(os),Linux)
css_parser_ldflags = $(css_parser_lib) -ldl -lpthread -lm
test_cflags = $(test_flags) -I/usr/include/check
test_ldflags = -L/usr/lib/x86_64-linux-gnu -lcheck -lm -lsubunit $(prism_ldflags)
test_ldflags = -L/usr/lib/x86_64-linux-gnu -lcheck -lm -lsubunit $(prism_ldflags) $(css_parser_ldflags)
cc = clang-21
clang_format = clang-format-21
clang_tidy = clang-tidy-21
endif

ifeq ($(os),Darwin)
css_parser_ldflags = $(css_parser_lib) -lresolv -framework Security -framework CoreFoundation
brew_prefix := $(shell brew --prefix check)
test_cflags = $(test_flags) -I$(brew_prefix)/include
test_ldflags = -L$(brew_prefix)/lib -lcheck -lm $(prism_ldflags)
test_ldflags = -L$(brew_prefix)/lib -lcheck -lm $(prism_ldflags) $(css_parser_ldflags)
llvm_path = $(shell brew --prefix llvm@21)
cc = $(llvm_path)/bin/clang
clang_format = $(llvm_path)/bin/clang-format
clang_tidy = $(llvm_path)/bin/clang-tidy
endif

all: templates prism $(exec) $(lib_name) $(static_lib_name) test wasm
all: templates prism css_parser $(exec) $(lib_name) $(static_lib_name) test wasm

$(exec): $(objects)
$(cc) $(objects) $(flags) $(ldflags) $(prism_ldflags) -o $(exec)
$(exec): $(objects) $(css_parser_lib)
$(cc) $(objects) $(flags) $(ldflags) $(prism_ldflags) $(css_parser_ldflags) -o $(exec)

$(lib_name): $(objects)
$(cc) -shared $(objects) $(shared_flags) $(ldflags) $(prism_ldflags) -o $(lib_name)
$(lib_name): $(objects) $(css_parser_lib)
$(cc) -shared $(objects) $(shared_flags) $(ldflags) $(prism_ldflags) $(css_parser_ldflags) -o $(lib_name)
# cp $(lib_name) $(ruby_extension)

$(static_lib_name): $(objects)
$(static_lib_name): $(objects) $(css_parser_lib)
ar rcs $(static_lib_name) $(objects)

src/%.o: src/%.c templates
Expand All @@ -102,6 +108,7 @@ clean:
rm -rf $(objects) $(test_objects) $(extension_objects) lib/herb/*.bundle tmp
rm -rf $(prism_path)
rake prism:clean
cd $(css_parser_dir) && cargo clean

bundle_install:
bundle install
Expand All @@ -113,6 +120,12 @@ prism: bundle_install
cd $(prism_path) && ruby templates/template.rb && make static && cd -
rake prism:vendor

css_parser: $(css_parser_lib)

$(css_parser_lib):
cd $(css_parser_dir) && cargo build --release
cbindgen --config $(css_parser_dir)/cbindgen.toml --output src/include/css_parser.h $(css_parser_dir)

format:
$(clang_format) -i $(project_and_extension_files)

Expand Down
32 changes: 32 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -712,3 +712,35 @@ nodes:
- name: statements
type: array
kind: Node

- name: CSSDeclarationNode
fields:
- name: property
type: string

- name: value
type: string

- name: CSSRuleNode
fields:
- name: selector
type: string

- name: declarations
type: array
kind: CSSDeclarationNode

- name: CSSStyleNode
fields:
- name: content
type: string

- name: rules
type: array
kind: CSSRuleNode

- name: valid
type: boolean

- name: parse_error
type: string
11 changes: 11 additions & 0 deletions ext/herb/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@

include_path = File.expand_path("../../src/include", __dir__)
prism_path = File.expand_path("../../vendor/prism", __dir__)
css_parser_path = File.expand_path("../../src/css", __dir__)

prism_src_path = "#{prism_path}/src"
prism_include_path = "#{prism_path}/include"

css_parser_lib = "#{css_parser_path}/target/release/libherb_css_parser.a"

$VPATH << "$(srcdir)/../../src"
$VPATH << "$(srcdir)/../../src/util"
$VPATH << prism_src_path
Expand All @@ -23,9 +26,17 @@
$INCFLAGS << " -I#{include_path}"
$INCFLAGS << " -I#{prism_src_path}"
$INCFLAGS << " -I#{prism_src_path}/util"
$INCFLAGS << " -I#{css_parser_path}"

$CFLAGS << " -DPRISM_EXPORT_SYMBOLS=static "

$LDFLAGS << " #{css_parser_lib}"
if RUBY_PLATFORM.match?(/darwin/)
$LDFLAGS << " -lresolv -framework Security -framework CoreFoundation"
elsif RUBY_PLATFORM.match?(/linux/)
$LDFLAGS << " -ldl -lpthread -lm"
end

herb_src_files = Dir.glob("#{$srcdir}/../../src/**/*.c").map { |file| file.delete_prefix("../../../../ext/herb/") }.sort

prism_main_files = %w[
Expand Down
17 changes: 17 additions & 0 deletions javascript/packages/formatter/src/format-printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ import {
ERBInNode,
XMLDeclarationNode,
CDATANode,
CSSStyleNode,
CSSRuleNode,
CSSDeclarationNode,
Token
} from "@herb-tools/core"

Expand Down Expand Up @@ -1192,6 +1195,20 @@ export class FormatPrinter extends Printer {
if (node.end_node) this.visit(node.end_node)
}

visitCSSStyleNode(node: CSSStyleNode) {
if (node.content) {
this.push(node.content)
}
}

visitCSSRuleNode(_node: CSSRuleNode) {
// CSS rules are contained within CSSStyleNode, not rendered separately
}

visitCSSDeclarationNode(_node: CSSDeclarationNode) {
// CSS declarations are contained within CSSRuleNode, not rendered separately
}

// --- Element Formatting Analysis Helpers ---

/**
Expand Down
3 changes: 3 additions & 0 deletions javascript/packages/printer/scripts/generate-node-tests.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ function generateNodeTest(nodeInfo) {
astType = astType.replace(/^HTML/, 'HTML_')
} else if (astType.startsWith('ERB')) {
astType = astType.replace(/^ERB/, 'ERB_')
} else if (astType.startsWith('CSS')) {
astType = astType.replace(/^CSS/, 'C_S_S_')
}

astType = astType
Expand Down Expand Up @@ -148,6 +150,7 @@ async function main() {
const kebabCase = nodeTypeName
.replace(/^HTML/, 'html-')
.replace(/^ERB/, 'erb-')
.replace(/^CSS/, 'css-')
.replace(/([A-Z])/g, '-$1')
.toLowerCase()
.replace(/^-/, '')
Expand Down
14 changes: 14 additions & 0 deletions javascript/packages/printer/src/identity-printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,20 @@ export class IdentityPrinter extends Printer {
}
}

visitCSSStyleNode(node: Nodes.CSSStyleNode): void {
if (node.content) {
this.write(node.content)
}
}

visitCSSRuleNode(node: Nodes.CSSRuleNode): void {
// CSS rules are contained within CSSStyleNode, not rendered separately
}

visitCSSDeclarationNode(node: Nodes.CSSDeclarationNode): void {
// CSS declarations are contained within CSSRuleNode, not rendered separately
}

visitERBCaseMatchNode(node: Nodes.ERBCaseMatchNode): void {
this.printERBNode(node)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import dedent from "dedent"
import { describe, test, beforeAll } from "vitest"

import { Herb } from "@herb-tools/node-wasm"
import { CSSDeclarationNode } from "@herb-tools/core"

import { expectNodeToPrint, expectPrintRoundTrip, createLocation } from "../helpers/printer-test-helpers.js"

describe("CSSDeclarationNode Printing", () => {
beforeAll(async () => {
await Herb.load()
})

test("CSS declarations are not printed independently", () => {
const node = CSSDeclarationNode.from({
type: "AST_CSS_DECLARATION_NODE",
location: createLocation(),
errors: [],
property: "color",
value: "red"
})

expectNodeToPrint(node, "")
})

test("CSS declarations are part of style tag content", () => {
expectPrintRoundTrip(dedent`
<style>
body {
color: red;
background: white;
}
</style>
`)
})
})
35 changes: 35 additions & 0 deletions javascript/packages/printer/test/nodes/css-rule-node.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import dedent from "dedent"
import { describe, test, beforeAll } from "vitest"

import { Herb } from "@herb-tools/node-wasm"
import { CSSRuleNode } from "@herb-tools/core"

import { expectNodeToPrint, expectPrintRoundTrip, createLocation } from "../helpers/printer-test-helpers.js"

describe("CSSRuleNode Printing", () => {
beforeAll(async () => {
await Herb.load()
})

test("CSS rules are not printed independently", () => {
const node = CSSRuleNode.from({
type: "AST_CSS_RULE_NODE",
location: createLocation(),
errors: [],
selector: "body",
declarations: []
})

expectNodeToPrint(node, "")
})

test("CSS rules are part of style tag content", () => {
expectPrintRoundTrip(dedent`
<style>
body {
color: red;
}
</style>
`)
})
})
Loading
Loading