Skip to content

Commit

Permalink
fixing more crashes!
Browse files Browse the repository at this point in the history
  • Loading branch information
kristoff-it committed Jul 15, 2024
1 parent 303969a commit 98f3b8c
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 44 deletions.
34 changes: 25 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# SuperHTML
Actually functional HTML Language Server (and Templating Language Library).
HTML Language Server and Templating Language Library

**NOTE: SuperHTML is still incomplete, some features are missing and looking of somebody to implement them :^)**
*And there's also bugs, waiting for people to report them as Issues :^)*

## HTML Language Server
The Super CLI Tool offers syntax checking and autoformatting features for HTML files.
The Super CLI Tool offers **syntax checking** and **autoformatting** features for HTML files.

The tool can be used either directly (for example by running it on save), or through a LSP client implementation.

```
$ super
Expand Down Expand Up @@ -36,7 +36,7 @@ As an example, HTML allows for closing some tags implicitly. For example the fol
</ul>
```

This will still be reported as an error by SuperHTML because otherwise the following snippet would have to be considered correct (while it's much probably a typo):
This will still be reported as an error by SuperHTML because otherwise the following snippet would have to be considered correct (while it's most probably a typo):

```html
<h1>Title<h1>
Expand Down Expand Up @@ -115,11 +115,27 @@ After:

### Editor support
#### VSCode
1. Download a prebuilt version of `super` from the Releases section (or build it yourself).
2. Put `super` in your `PATH`.
1. Download a prebuilt version of `superhtml` from the Releases section (or build it yourself).
2. Put `superhtml` in your `PATH`.
3. Install the [Super HTML VSCode extension](https://marketplace.visualstudio.com/items?itemName=LorisCro.super).

#### Flow
#### Helix
Add to your `.config/helix/languages.toml`:
```toml
[language-server.superhtml-lsp]
command = "superhtml"
args = ["lsp"]

[[language]]
name = "html"
scope = "source.html"
roots = []
file-types = ["html"]
language-servers = [ "superhtml-lsp" ]
```
See https://helix-editor.com for more information on how to add new language servers.

#### [Flow](https://github.com/neurocyte/flow)
Already defaults to using SuperHTML, just add the executable to your `PATH`.

#### Other editors
Expand All @@ -128,7 +144,7 @@ Follow your editor specific intructions on how to define a new Language Server f
*(Also feel free to contribute more specific intructions to this readme / add files under the `editors/` subdirectory).*

## Templating Language Library
SuperHTML is not only an LSP but also an HTML templating language. More on that soon.
SuperHTML is also a HTML templating language. More on that soon.

## Contributing
SuperHTML tracks the latest Zig release (0.13.0 at the moment of writing).
Expand Down
14 changes: 7 additions & 7 deletions src/cli/lsp.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const lsp = @import("lsp");
const types = lsp.types;
Expand Down Expand Up @@ -276,16 +277,13 @@ pub const Handler = struct {
log.debug("format request!!", .{});

const doc = self.files.getPtr(request.textDocument.uri) orelse return null;

if (doc.html.errors.len != 0) {
return null;
}

log.debug("format!!", .{});

var buf = std.ArrayList(u8).init(self.gpa);
errdefer buf.deinit();

var buf = std.ArrayList(u8).init(arena);
try doc.html.render(doc.src, buf.writer());

const edits = try lsp.diff.edits(
Expand All @@ -295,9 +293,11 @@ pub const Handler = struct {
.@"utf-8",
);

self.gpa.free(doc.src);
doc.src = try buf.toOwnedSlice();
try doc.reparse(self.gpa);
if (builtin.mode == .Debug) {
if (std.mem.eql(u8, buf.items, doc.src)) {
std.debug.assert(edits.items.len == 0);
}
}

return edits.items;
}
Expand Down
15 changes: 12 additions & 3 deletions src/fuzz.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,33 @@ pub const std_options = .{ .log_level = .err };

pub fn main() !void {
var gpa_impl: std.heap.GeneralPurposeAllocator(.{}) = .{};

// this will check for leaks and crash the program if it finds any
defer std.debug.assert(gpa_impl.deinit() == .ok);
const gpa = gpa_impl.allocator();

// Read the data from stdin
const stdin = std.io.getStdIn();
const data = try stdin.readToEndAlloc(gpa, std.math.maxInt(usize));
defer gpa.free(data);
const src = try stdin.readToEndAlloc(gpa, std.math.maxInt(usize));
defer gpa.free(src);

const ast = try super.html.Ast.init(gpa, data, .html);
const ast = try super.html.Ast.init(gpa, src, .html);
defer ast.deinit(gpa);

if (ast.errors.len == 0) {
try ast.render(src, std.io.null_writer);
}
}

test "afl++ fuzz cases" {
const cases: []const []const u8 = &.{
@embedFile("fuzz/2.html"),
@embedFile("fuzz/3.html"),
@embedFile("fuzz/12.html"),
@embedFile("fuzz/round2/2.html"),
@embedFile("fuzz/round2/3.html"),
@embedFile("fuzz/round3/2.html"),
@embedFile("fuzz/77.html"),
};

for (cases) |c| {
Expand Down
5 changes: 5 additions & 0 deletions src/fuzz/77.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
tl></s</>
;sphmlK
<pYn.


Binary file added src/fuzz/round2/2.html
Binary file not shown.
Binary file added src/fuzz/round2/3.html
Binary file not shown.
Binary file added src/fuzz/round3/2.html
Binary file not shown.
9 changes: 6 additions & 3 deletions src/html/Ast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -574,10 +574,10 @@ pub fn render(ast: Ast, src: []const u8, w: anytype) !void {
current,
});

switch (current.kind) {
if (pre == 0) switch (current.kind) {
else => {},
.element => indentation -= 1,
}
};

const open_was_vertical = std.ascii.isWhitespace(src[current.open.end]);

Expand Down Expand Up @@ -748,7 +748,9 @@ pub fn render(ast: Ast, src: []const u8, w: anytype) !void {
current = ast.nodes[current.first_child_idx];
}
},
.element_void => {
.element_void,
.element_self_closing,
=> {
if (current.next_idx != 0) {
current = ast.nodes[current.next_idx];
} else {
Expand All @@ -760,6 +762,7 @@ pub fn render(ast: Ast, src: []const u8, w: anytype) !void {
},
.exit => {
std.debug.assert(current.kind != .element_void);
std.debug.assert(current.kind != .element_self_closing);
last_rbracket = current.close.end;
if (current.close.start != 0) {
const name = blk: {
Expand Down
47 changes: 25 additions & 22 deletions src/html/Tokenizer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1702,7 +1702,9 @@ fn next2(self: *Tokenizer, src: []const u8) ?struct {
// Set the temporary buffer to the empty string. Emit a U+003C LESS-THAN SIGN character token. Reconsume in the script data double escape start state.
'a'...'z', 'A'...'Z' => {
self.idx -= 1;
@panic("TODO");
self.state = .{
.script_data_double_escape_start = state,
};
},
// Anything else
// Emit a U+003C LESS-THAN SIGN character token. Reconsume in the script data escaped state.
Expand All @@ -1722,7 +1724,11 @@ fn next2(self: *Tokenizer, src: []const u8) ?struct {
// Create a new end tag token, set its tag name to the empty string. Reconsume in the script data escaped end tag name state.
'a'...'z', 'A'...'Z' => {
self.idx -= 1;
@panic("TODO");
var new = state;
new.name_start = self.idx;
self.state = .{
.script_data_escaped_end_tag_name = new,
};
},
// Anything else
// Emit a U+003C LESS-THAN SIGN character token and a U+002F SOLIDUS character token. Reconsume in the script data escaped state.
Expand Down Expand Up @@ -2348,12 +2354,14 @@ fn next2(self: *Tokenizer, src: []const u8) ?struct {
},
// U+003D EQUALS SIGN (=)
// Switch to the before attribute value state.
'=' => self.state = .{
.before_attribute_value = .{
.tag = state.tag,
.name = state.name,
.equal_sign = self.idx - 1,
},
'=' => {
self.state = .{
.before_attribute_value = .{
.tag = state.tag,
.name = state.name,
.equal_sign = self.idx - 1,
},
};
},
// U+003E GREATER-THAN SIGN (>)
// Switch to the data state. Emit the current tag token.
Expand Down Expand Up @@ -2383,8 +2391,9 @@ fn next2(self: *Tokenizer, src: []const u8) ?struct {
// Anything else
// Start a new attribute in the current tag token. Set that attribute name and value to the empty string. Reconsume in the attribute name state.
else => {
self.idx -= 1;
std.debug.assert(state.name.len() != 0);

self.idx -= 1;
var tag = state.tag;
tag.attr_count += 1;

Expand Down Expand Up @@ -3513,28 +3522,22 @@ fn next2(self: *Tokenizer, src: []const u8) ?struct {
// https://html.spec.whatwg.org/multipage/parsing.html#comment-less-than-sign-bang-dash-state
.comment_less_than_sign_bang_dash => |comment_start| {
if (!self.consume(src)) {
self.idx -= 1;
self.state = .{ .comment_end_dash = comment_start };
}
switch (self.current) {
// U+002D HYPHEN-MINUS (-)
// Switch to the comment less-than sign bang dash dash state.
'-' => switch (self.state) {
else => unreachable,
.comment_less_than_sign_bang => {
self.state = .{
.comment_less_than_sign_bang_dash = comment_start,
};
},
.comment_less_than_sign_bang_dash => {
self.state = .{
.comment_less_than_sign_bang_dash_dash = comment_start,
};
},
'-' => self.state = .{
.comment_less_than_sign_bang_dash_dash = comment_start,
},

// Anything else
// Reconsume in the comment end dash state.
else => self.state = .{ .comment_end_dash = comment_start },
else => {
self.idx -= 1;
self.state = .{ .comment_end_dash = comment_start };
},
}
},
// https://html.spec.whatwg.org/multipage/parsing.html#comment-less-than-sign-bang-dash-dash-state
Expand Down

0 comments on commit 98f3b8c

Please sign in to comment.