on master help sign in
htmlang/CONTRIBUTING.md
167 lines 9.6 kB history raw
1# Contributing
3Thanks for your interest in improving htmlang. This file covers the practical
4bits: how to run the project locally, where the code lives, and what style of
5changes land smoothly.
7## Development
9```
10cargo build # build the CLI + LSP binaries
11cargo test # run all tests (unit + snapshot + integration)
12cargo run -- page.hl # compile a .hl file to page.html
13cargo run -- serve examples # run the dev server at http://127.0.0.1:3000
14```
16CI runs `cargo test`, `cargo fmt --check`, and `cargo clippy -- -D warnings` on
17Linux, macOS, and Windows. Your change should pass all three.
19## Repository layout
21- `crates/htmlang-core/` — parser, AST, code generator. No I/O lives here.
22 - `syntax.rs` — the syntax tree: every line as written, with spans. The
23 compiler evaluates it, the formatter prints it back, and the language
24 server takes definitions, directives and verbatim bodies from it. It
25 also holds `ESCAPES`, the one escape table of every htmlang string,
26 and decides where `{@...}` is an inline element: in every line of text
27 except the text of a `literal` element.
28 - `parser.rs` — evaluates the tree: definitions, data, loops and
29 conditions, and the checks over code that doesn't run. Names live in
30 `Env`, one frame per block, so a definition is visible to the end of
31 its block and a function keeps the frames of its definition.
32 - `diagnostic.rs` — `Diagnostic` and the stable diagnostic codes.
33 - `codegen.rs` — writes the HTML and the CSS. A parent's `Flow` (the
34 direction its CSS sets, with the changes under media and container
35 prefixes) is what its children's `fill` and `shrink` compile against;
36 the rules for a change are keyed on the parent's class in that block.
37 A page's `<body>` is written like an `@el` whose styles are `@page`'s
38 (the reset makes it the column), so the top level of a page is laid
39 out like any column; a fragment's top level has no layout of its own.
40 Every CSS property goes through one generic path (its name, its value
41 as written, with `vocab::with_px`'s pixels); only htmlang's layout
42 words and `line-clamp` have code of their own. Generated classes are
43 `hl-` + 7 base-36 digits of `stable_hash` of the style
44 (`CLASS_DIGITS`), so a style has one name in every file, and
45 `htmlang build` checks no name stands for two styles; an element's
46 defaults (its layout,
47 `ElementKind::css`) go with its class, as `:where(.hl-2k9xq0m)`, never a
48 rule on a global element selector, so `@markdown`/`@raw` HTML and a
49 page embedding a fragment are untouched. The rules are in cascade
50 layers (`LAYERS`): `hl-reset`, `hl-kind` (the defaults), `hl-inside`
51 (element prefixes, `@td:`, each an `@scope` on the class, and
52 functions' scoped `@style`, written when a function is called) and
53 `htmlang` (the elements' own styles and `children:`), so which one
54 wins is the layer's order, not specificity.
55 A style's prefixes are a `Condition`: its at-rule prefixes (sorted, so
56 `dark:md:` is `md:dark:`, and written as nested `@media`/`@container`
57 blocks) and its selector chain (read left to right; an element prefix
58 splits it into the scope's root and the elements it styles). Each
59 class keeps one list of (condition, declarations), and every block and
60 chain is written in one fixed order, never the order of the source.
61 - `ast.rs` — the element kinds; every plain HTML element is one row in
62 `TAGS` with its one `Layout` (column, row, grid, text, native or void,
63 which decides its text lines, `spacing` and its children's layout
64 words), whether its text is shown as written (`literal`: `@code`,
65 `@textarea`, whose indented body is verbatim text, HTML-escaped), the
66 one HTML attribute its first word fills (`arg`: `src`, `action`, ...;
67 `ElementKind::arg` gives `@link`'s and `@image`'s too) and whether its
68 body is foreign text written into the page as it is (`verbatim`:
69 `@script`), and every directive one row in `DIRECTIVES` (its argument
70 and its kind of body; a verbatim directive's line is its one-line body
71 or file, and it takes no attributes). The list of
72 elements is fixed; `HTML_NAMES_WRITTEN_OTHERWISE` maps HTML's `a`, `img`,
73 `span`, `p` and `div` to htmlang's own names for the unknown-element
74 suggestion only.
75 - `vocab.rs` — the attribute vocabulary (htmlang attributes, CSS properties,
76 HTML attributes, `@page`'s own `favicon`), the pixel rule
77 (`LENGTH_PROPERTIES`, the one table of properties whose bare numbers
78 are px, and `with_px`, the one function that applies it) and the
79 prefixes: `PSEUDOS`, CSS's pseudo-classes and pseudo-elements under
80 their CSS names, each marked `:`, `::` or taking an argument, in the
81 order their rules are written; `children:`; element prefixes (`@td:`,
82 any name; the parser checks it is an element with a tag of its own,
83 `ElementKind::own_tag`); and the width, media and container prefixes,
84 with the rank that orders the at-rule blocks. A new pseudo-class is one
85 row in `PSEUDOS` and one word in DESIGN.md's list.
86 - `expr.rs` — the expression language for conditions, computed values and
87 `${...}`. It evaluates only what decides the result (the branch `if()`
88 takes, the side of `and`/`or` that decides) and only reads the rest.
89 - `value.rs` — the value types (text, number, true/false, list,
90 record) and the rules defined once on them: truthiness, comparison,
91 how numbers and lists print, ranges.
92 - `interp.rs` — `$name` and `${...}`: where a name ends and how one slot
93 of text (a text run, an attribute's value, an argument, a path) is
94 filled in, including what quoted text inserts in CSS and in text. The
95 one place variables are interpolated.
96 - `std.hl` — the standard library, written in htmlang and loaded before
97 every file.
98- `crates/htmlang-wasm/` — thin wrapper exposing `compile` to the web playground.
99- `src/` — CLI, dev server and formatter (`fmt.rs`, which prints the syntax
100 tree back with its comments and blank lines).
101- `src/bin/htmlang_lsp/` — language server binary (`htmlang-lsp`). Its
102 completions come from the compiler's tables; hover and completion text live
103 in `docs.rs`.
104- `editors/vscode/` — VS Code extension.
105- `tests/snapshots.rs` — integration / snapshot tests for the compiler.
106 These tests, and `tests/regressions.rs`, read the output with its
107 hashed classes numbered (`hl-a`, `hl-b`, ..., see `tests/common`), so a
108 snapshot changes when a style does, not when a hash does.
109- `tests/regressions.rs` — one test per fixed bug.
110- `tests/docs.rs` — compiles every example in `DESIGN.md` and `README.md`,
111 and every page in `examples/`: any diagnostic fails it.
112- `tests/fmt.rs` — formats every example and snapshot input: the result is
113 stable, compiles to the same HTML and keeps every comment.
114- `examples/` — complete pages: a landing page, a blog, a docs page, and a
115 tour of the whole language (`demo.hl`).
117## Adding a feature
1191. Add a test first. Most language features fit as a new `#[test]` in
120 `tests/snapshots.rs`; prefer integration tests that exercise the full
121 parser-to-HTML pipeline. Pure parser / codegen helpers can live as unit
122 tests alongside the code.
1232. Prefer the smallest mechanism: a component belongs in `std.hl`, an HTML
124 element is a row in `TAGS` (and a line in DESIGN.md's table of layouts
125 and a name in the VS Code grammar's element list, which tests check), and
126 a CSS property needs no code at all. Only
127 thread a feature through the parser and codegen when it needs to be, and
128 describe it in the LSP's `docs.rs`.
1293. Document it in `DESIGN.md` (the examples there are compiled by the tests).
130 If it's user-facing, also update `README.md`, and show it in
131 `examples/demo.hl` if the tour has a section it belongs in.
1324. If it changes the CLI surface, update the `--help` output.
134## Style
136- Prefer enum-based ASTs and pattern matching over stringly-typed dispatch.
137- Keep `@` prefixes and bracket-attribute syntax consistent with existing
138 directives. A new `@foo` is a row in `ast::DIRECTIVES`; the parser, the
139 formatter and the language server read that table, and `tests/editor.rs`
140 fails when the VS Code grammar's word lists drift from it.
141- Every diagnostic has a code from `diagnostic::code`; add one there when no
142 existing code fits, and key tool behavior (quick fixes, tests) on the code
143 rather than on the message. Put the name the diagnostic is about in
144 `subject` and a replacement in `suggestion`.
145- Diagnostics should include `line` and, when practical, `column` and a
146 `source_line` excerpt. Use `Severity::Help` for suggestions, not `Warning`.
147- Check names, not values, and never drop input silently. When something
148 the author wrote can't go into the page, report an error; a warning means
149 it went into the page as written. Check a CSS value only for what is
150 wrong in any CSS (see `css_breakout` in `parser.rs`), not against a list
151 of values the browser may know better.
152- No unwrap() on parsed user input. Use `Result<_, ParseError>` and record a
153 diagnostic so the compiler keeps going.
155## Reporting bugs
157Bug reports are most useful when they include:
159- The exact `.hl` input that reproduces the issue (minimized if possible).
160- The command you ran and the output you saw.
161- The output you expected instead.
162- `htmlang --version` and your OS.
164## License
166By contributing, you agree that your changes will be licensed under the same
167terms as the rest of the project.