Files
carbon-lang/utils/tree_sitter/test_runner.cpp
T
Jon Ross-Perkins 6f6e46ef57 Migrate tree-sitter support to MODULE.bazel (#4783)
The WORKSPACE file is deprecated; support is already off by default, and
it'll be removed in the next major bazel release. Our main dependency is
tree-sitter, and I'm trying to address that here.

We're currently using https://github.com/elliottt/rules_tree_sitter, but
that hasn't been updated in a couple years, meaning it lacks
MODULE.bazel support. In the registry, there's
https://registry.bazel.build/modules/tree-sitter-bazel, but this is only
the *parser* libraries of tree-sitter, not the *generator*. I'm using it
for that much, at least.

For the *generator*, which transforms grammar.js to parser.c/h, I'm just
requiring a non-hermetic invocation (i.e., people who want to work on it
will need to install tree-sitter; see the README.md updates). I tried
running it manually, but parser.c is about 600 KB; pre-commit rejects
files that large and I don't think an exception makes sense to override
for this (it'd probably also grow substantially if the grammar were
updated to cover more syntax). In order to make the non-hermetic call
not break "bazel build //..." for most developers, I'm marking most
targets in the package as manual.

Note, I did look long and hard at using `aspect_rules_js`/`rules_nodejs`
to invoke npm. This took a lot of time, and I have a commit that's
mostly working, except I hit a point where it uses `declare_symlink`
which we disallow for compatibility reasons (commit "Lots of work for
figuring out rule_js uses declare_symlink" on the PR). As a consequence,
I think we can't use the primary supported ways to have hermetic npm
calls.

Also, `treesitter` -> `tree_sitter` because it's generally called
`tree-sitter`, two words. We even had a `treesitter/src/tree_sitter`
directory so it's a bit inconsistent.

As far as bugs here, the parser library breaks bazel queries, e.g. the
error:
```
ERROR: Evaluation of query "somepath(//..., @llvm-project//third-party/unittest:gtest)" failed: preloading transitive closure failed: no such package '@@[unknown repo 'platforms' requested from @@tree-sitter-bazel+]//': The repository '@@[unknown repo 'platforms' requested from @@tree-sitter-bazel+]' could not be resolved: No repository visible as '@platforms' from repository '@@tree-sitter-bazel+'
```

I'm just excluding tree_sitter from queries where I can to work around
the error.
2025-01-13 19:04:10 +00:00

79 lines
2.0 KiB
C++

// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <tree_sitter/api.h>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include "utils/tree_sitter/src/tree_sitter/parser.h"
extern "C" {
auto tree_sitter_carbon() -> TSLanguage*;
}
// Reads a file to string.
static auto ReadFile(std::filesystem::path path) -> std::string {
std::ifstream file(path);
std::stringstream buffer;
buffer << file.rdbuf();
file.close();
return buffer.str();
}
// TODO: use file_test.cpp
auto main(int argc, char** argv) -> int {
if (argc < 2) {
std::cerr << "Usage: test_runner <file>...\n";
return 2;
}
auto* parser = ts_parser_new();
ts_parser_set_language(parser, tree_sitter_carbon());
bool fail_tests = std::getenv("FAIL_TESTS") != nullptr;
std::vector<std::string> incorrect;
for (int i = 1; i < argc; i++) {
std::string file_path = argv[i];
std::string source = ReadFile(file_path);
auto* tree =
ts_parser_parse_string(parser, nullptr, source.data(), source.size());
auto root = ts_tree_root_node(tree);
auto has_error = ts_node_has_error(root);
char* node_debug = ts_node_string(root);
std::cout << file_path << ":\n" << node_debug << "\n";
if (has_error ^ fail_tests) {
incorrect.push_back(file_path);
}
free(node_debug);
ts_tree_delete(tree);
}
ts_parser_delete(parser);
for (const auto& file : incorrect) {
if (fail_tests) {
std::cout << "INCORRECTLY PASSING " << file << "\n";
} else {
std::cout << "FAILED " << file << "\n";
}
}
if (!incorrect.empty()) {
if (fail_tests) {
std::cout << incorrect.size() << " tests incorrectly passing.\n";
} else {
std::cout << incorrect.size() << " tests failing.\n";
}
return 1;
}
}