Compare commits
8 commits
wasm/iswpu
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6739742fb6 | ||
|
|
d251226a3c | ||
|
|
ae8184b8b9 | ||
|
|
470ecf8996 | ||
|
|
0cdb6bef7b | ||
|
|
cd603fa981 | ||
|
|
b12009a746 | ||
|
|
9f9a0bc410 |
22 changed files with 268 additions and 46 deletions
25
.github/scripts/wasm_stdlib.js
vendored
Normal file
25
.github/scripts/wasm_stdlib.js
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
module.exports = async ({ github, context, core }) => {
|
||||||
|
if (context.eventName !== 'pull_request') return;
|
||||||
|
|
||||||
|
const prNumber = context.payload.pull_request.number;
|
||||||
|
const owner = context.repo.owner;
|
||||||
|
const repo = context.repo.repo;
|
||||||
|
|
||||||
|
const { data: files } = await github.rest.pulls.listFiles({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
pull_number: prNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
const changedFiles = files.map(file => file.filename);
|
||||||
|
|
||||||
|
const wasmStdLibSrc = 'crates/language/wasm/';
|
||||||
|
const dirChanged = changedFiles.some(file => file.startsWith(wasmStdLibSrc));
|
||||||
|
|
||||||
|
if (!dirChanged) return;
|
||||||
|
|
||||||
|
const wasmStdLibHeader = 'lib/src/wasm/wasm-stdlib.h';
|
||||||
|
const requiredChanged = changedFiles.includes(wasmStdLibHeader);
|
||||||
|
|
||||||
|
if (!requiredChanged) core.setFailed(`Changes detected in ${wasmStdLibSrc} but ${wasmStdLibHeader} was not modified.`);
|
||||||
|
};
|
||||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
|
@ -44,3 +44,6 @@ jobs:
|
||||||
|
|
||||||
build:
|
build:
|
||||||
uses: ./.github/workflows/build.yml
|
uses: ./.github/workflows/build.yml
|
||||||
|
|
||||||
|
check-wasm-stdlib:
|
||||||
|
uses: ./.github/workflows/wasm_stdlib.yml
|
||||||
|
|
|
||||||
19
.github/workflows/wasm_stdlib.yml
vendored
Normal file
19
.github/workflows/wasm_stdlib.yml
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
name: Check Wasm Stdlib build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Check directory changes
|
||||||
|
uses: actions/github-script@v8
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const scriptPath = `${process.env.GITHUB_WORKSPACE}/.github/scripts/wasm_stdlib.js`;
|
||||||
|
const script = require(scriptPath);
|
||||||
|
return script({ github, context, core });
|
||||||
8
Cargo.lock
generated
8
Cargo.lock
generated
|
|
@ -187,9 +187,9 @@ checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.52"
|
version = "1.2.53"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3"
|
checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"shlex",
|
"shlex",
|
||||||
|
|
@ -664,9 +664,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "find-msvc-tools"
|
||||||
version = "0.1.7"
|
version = "0.1.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41"
|
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ ansi_colours = "1.2.3"
|
||||||
anstyle = "1.0.13"
|
anstyle = "1.0.13"
|
||||||
anyhow = "1.0.100"
|
anyhow = "1.0.100"
|
||||||
bstr = "1.12.0"
|
bstr = "1.12.0"
|
||||||
cc = "1.2.52"
|
cc = "1.2.53"
|
||||||
clap = { version = "4.5.54", features = [
|
clap = { version = "4.5.54", features = [
|
||||||
"cargo",
|
"cargo",
|
||||||
"derive",
|
"derive",
|
||||||
|
|
|
||||||
|
|
@ -953,7 +953,7 @@ fn render_node_range(
|
||||||
|
|
||||||
fn cst_render_node(
|
fn cst_render_node(
|
||||||
opts: &ParseFileOptions,
|
opts: &ParseFileOptions,
|
||||||
cursor: &mut TreeCursor,
|
cursor: &TreeCursor,
|
||||||
source_code: &[u8],
|
source_code: &[u8],
|
||||||
out: &mut impl Write,
|
out: &mut impl Write,
|
||||||
total_width: usize,
|
total_width: usize,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@
|
||||||
--light-scrollbar-track: #f1f1f1;
|
--light-scrollbar-track: #f1f1f1;
|
||||||
--light-scrollbar-thumb: #c1c1c1;
|
--light-scrollbar-thumb: #c1c1c1;
|
||||||
--light-scrollbar-thumb-hover: #a8a8a8;
|
--light-scrollbar-thumb-hover: #a8a8a8;
|
||||||
|
--light-tree-row-bg: #e3f2fd;
|
||||||
|
|
||||||
--dark-bg: #1d1f21;
|
--dark-bg: #1d1f21;
|
||||||
--dark-border: #2d2d2d;
|
--dark-border: #2d2d2d;
|
||||||
--dark-text: #c5c8c6;
|
--dark-text: #c5c8c6;
|
||||||
|
|
@ -28,6 +29,7 @@
|
||||||
--dark-scrollbar-track: #25282c;
|
--dark-scrollbar-track: #25282c;
|
||||||
--dark-scrollbar-thumb: #4a4d51;
|
--dark-scrollbar-thumb: #4a4d51;
|
||||||
--dark-scrollbar-thumb-hover: #5a5d61;
|
--dark-scrollbar-thumb-hover: #5a5d61;
|
||||||
|
--dark-tree-row-bg: #373737;
|
||||||
|
|
||||||
--primary-color: #0550ae;
|
--primary-color: #0550ae;
|
||||||
--primary-color-alpha: rgba(5, 80, 174, 0.1);
|
--primary-color-alpha: rgba(5, 80, 174, 0.1);
|
||||||
|
|
@ -42,6 +44,7 @@
|
||||||
--text-color: var(--dark-text);
|
--text-color: var(--dark-text);
|
||||||
--panel-bg: var(--dark-panel-bg);
|
--panel-bg: var(--dark-panel-bg);
|
||||||
--code-bg: var(--dark-code-bg);
|
--code-bg: var(--dark-code-bg);
|
||||||
|
--tree-row-bg: var(--dark-tree-row-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="light"] {
|
[data-theme="light"] {
|
||||||
|
|
@ -50,6 +53,7 @@
|
||||||
--text-color: var(--light-text);
|
--text-color: var(--light-text);
|
||||||
--panel-bg: white;
|
--panel-bg: white;
|
||||||
--code-bg: white;
|
--code-bg: white;
|
||||||
|
--tree-row-bg: var(--light-tree-row-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Base Styles */
|
/* Base Styles */
|
||||||
|
|
@ -275,7 +279,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#output-container a.highlighted {
|
#output-container a.highlighted {
|
||||||
background-color: #d9d9d9;
|
background-color: #cae2ff;
|
||||||
color: red;
|
color: red;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
|
@ -346,7 +350,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
& #output-container a.highlighted {
|
& #output-container a.highlighted {
|
||||||
background-color: #373b41;
|
background-color: #656669;
|
||||||
color: red;
|
color: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -373,6 +377,9 @@
|
||||||
color: var(--dark-text);
|
color: var(--dark-text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.tree-row:has(.highlighted) {
|
||||||
|
background-color: var(--tree-row-bg);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,7 @@ impl Pattern {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find every matching combination of child patterns and child nodes.
|
// Find every matching combination of child patterns and child nodes.
|
||||||
let mut finished_matches = Vec::<Match>::new();
|
let mut finished_matches = Vec::<Match<'_, 'tree>>::new();
|
||||||
if cursor.goto_first_child() {
|
if cursor.goto_first_child() {
|
||||||
let mut match_states = vec![(0, mat)];
|
let mut match_states = vec![(0, mat)];
|
||||||
loop {
|
loop {
|
||||||
|
|
|
||||||
|
|
@ -215,11 +215,11 @@ fn try_resolve_path(path: &Path) -> rquickjs::Result<PathBuf> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
fn require_from_module<'a>(
|
fn require_from_module<'js>(
|
||||||
ctx: Ctx<'a>,
|
ctx: Ctx<'js>,
|
||||||
module_path: String,
|
module_path: String,
|
||||||
from_module: &str,
|
from_module: &str,
|
||||||
) -> rquickjs::Result<Value<'a>> {
|
) -> rquickjs::Result<Value<'js>> {
|
||||||
let current_module = PathBuf::from(from_module);
|
let current_module = PathBuf::from(from_module);
|
||||||
let current_dir = if current_module.is_file() {
|
let current_dir = if current_module.is_file() {
|
||||||
current_module.parent().unwrap_or(Path::new("."))
|
current_module.parent().unwrap_or(Path::new("."))
|
||||||
|
|
@ -234,13 +234,13 @@ fn require_from_module<'a>(
|
||||||
load_module_from_content(&ctx, &resolved_path, &contents)
|
load_module_from_content(&ctx, &resolved_path, &contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_module_from_content<'a>(
|
fn load_module_from_content<'js>(
|
||||||
ctx: &Ctx<'a>,
|
ctx: &Ctx<'js>,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
contents: &str,
|
contents: &str,
|
||||||
) -> rquickjs::Result<Value<'a>> {
|
) -> rquickjs::Result<Value<'js>> {
|
||||||
if path.extension().is_some_and(|ext| ext == "json") {
|
if path.extension().is_some_and(|ext| ext == "json") {
|
||||||
return ctx.eval::<Value, _>(format!("JSON.parse({contents:?})"));
|
return ctx.eval::<Value<'js>, _>(format!("JSON.parse({contents:?})"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let exports = Object::new(ctx.clone())?;
|
let exports = Object::new(ctx.clone())?;
|
||||||
|
|
@ -256,7 +256,7 @@ fn load_module_from_content<'a>(
|
||||||
let module_path = filename.clone();
|
let module_path = filename.clone();
|
||||||
let require = Function::new(
|
let require = Function::new(
|
||||||
ctx.clone(),
|
ctx.clone(),
|
||||||
move |ctx_inner: Ctx<'a>, target_path: String| -> rquickjs::Result<Value<'a>> {
|
move |ctx_inner: Ctx<'js>, target_path: String| -> rquickjs::Result<Value<'js>> {
|
||||||
require_from_module(ctx_inner, target_path, &module_path)
|
require_from_module(ctx_inner, target_path, &module_path)
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -264,8 +264,8 @@ fn load_module_from_content<'a>(
|
||||||
let wrapper =
|
let wrapper =
|
||||||
format!("(function(exports, require, module, __filename, __dirname) {{ {contents} }})");
|
format!("(function(exports, require, module, __filename, __dirname) {{ {contents} }})");
|
||||||
|
|
||||||
let module_func = ctx.eval::<Function, _>(wrapper)?;
|
let module_func = ctx.eval::<Function<'js>, _>(wrapper)?;
|
||||||
module_func.call::<_, Value>((exports, require, module_obj.clone(), filename, dirname))?;
|
module_func.call::<_, Value<'js>>((exports, require, module_obj.clone(), filename, dirname))?;
|
||||||
|
|
||||||
module_obj.get("exports")
|
module_obj.get("exports")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ struct HighlightIterLayer<'a> {
|
||||||
depth: usize,
|
depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct _QueryCaptures<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> {
|
pub struct _QueryCaptures<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> {
|
||||||
ptr: *mut ffi::TSQueryCursor,
|
ptr: *mut ffi::TSQueryCursor,
|
||||||
query: &'query Query,
|
query: &'query Query,
|
||||||
text_provider: T,
|
text_provider: T,
|
||||||
|
|
@ -225,7 +225,7 @@ impl<'tree> _QueryMatch<'_, 'tree> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> Iterator
|
impl<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> Iterator
|
||||||
for _QueryCaptures<'query, 'tree, T, I>
|
for _QueryCaptures<'query, 'tree, T, I>
|
||||||
{
|
{
|
||||||
type Item = (QueryMatch<'query, 'tree>, usize);
|
type Item = (QueryMatch<'query, 'tree>, usize);
|
||||||
|
|
@ -594,6 +594,7 @@ impl<'a> HighlightIterLayer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
// The `captures` iterator borrows the `Tree` and the `QueryCursor`, which
|
// The `captures` iterator borrows the `Tree` and the `QueryCursor`, which
|
||||||
// prevents them from being moved. But both of these values are really just
|
// prevents them from being moved. But both of these values are really just
|
||||||
// pointers, so it's actually ok to move them.
|
// pointers, so it's actually ok to move them.
|
||||||
|
|
|
||||||
|
|
@ -765,7 +765,7 @@ impl Loader {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_all_language_configurations(&self) -> Vec<(&LanguageConfiguration, &Path)> {
|
pub fn get_all_language_configurations(&self) -> Vec<(&LanguageConfiguration<'static>, &Path)> {
|
||||||
self.language_configurations
|
self.language_configurations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| (c, self.languages_by_id[c.language_id].0.as_ref()))
|
.map(|c| (c, self.languages_by_id[c.language_id].0.as_ref()))
|
||||||
|
|
@ -775,7 +775,7 @@ impl Loader {
|
||||||
pub fn language_configuration_for_scope(
|
pub fn language_configuration_for_scope(
|
||||||
&self,
|
&self,
|
||||||
scope: &str,
|
scope: &str,
|
||||||
) -> LoaderResult<Option<(Language, &LanguageConfiguration)>> {
|
) -> LoaderResult<Option<(Language, &LanguageConfiguration<'static>)>> {
|
||||||
for configuration in &self.language_configurations {
|
for configuration in &self.language_configurations {
|
||||||
if configuration.scope.as_ref().is_some_and(|s| s == scope) {
|
if configuration.scope.as_ref().is_some_and(|s| s == scope) {
|
||||||
let language = self.language_for_id(configuration.language_id)?;
|
let language = self.language_for_id(configuration.language_id)?;
|
||||||
|
|
@ -788,7 +788,7 @@ impl Loader {
|
||||||
pub fn language_configuration_for_first_line_regex(
|
pub fn language_configuration_for_first_line_regex(
|
||||||
&self,
|
&self,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
) -> LoaderResult<Option<(Language, &LanguageConfiguration)>> {
|
) -> LoaderResult<Option<(Language, &LanguageConfiguration<'static>)>> {
|
||||||
self.language_configuration_ids_by_first_line_regex
|
self.language_configuration_ids_by_first_line_regex
|
||||||
.iter()
|
.iter()
|
||||||
.try_fold(None, |_, (regex, ids)| {
|
.try_fold(None, |_, (regex, ids)| {
|
||||||
|
|
@ -817,7 +817,7 @@ impl Loader {
|
||||||
pub fn language_configuration_for_file_name(
|
pub fn language_configuration_for_file_name(
|
||||||
&self,
|
&self,
|
||||||
path: &Path,
|
path: &Path,
|
||||||
) -> LoaderResult<Option<(Language, &LanguageConfiguration)>> {
|
) -> LoaderResult<Option<(Language, &LanguageConfiguration<'static>)>> {
|
||||||
// Find all the language configurations that match this file name
|
// Find all the language configurations that match this file name
|
||||||
// or a suffix of the file name.
|
// or a suffix of the file name.
|
||||||
let configuration_ids = path
|
let configuration_ids = path
|
||||||
|
|
@ -889,7 +889,7 @@ impl Loader {
|
||||||
pub fn language_configuration_for_injection_string(
|
pub fn language_configuration_for_injection_string(
|
||||||
&self,
|
&self,
|
||||||
string: &str,
|
string: &str,
|
||||||
) -> LoaderResult<Option<(Language, &LanguageConfiguration)>> {
|
) -> LoaderResult<Option<(Language, &LanguageConfiguration<'static>)>> {
|
||||||
let mut best_match_length = 0;
|
let mut best_match_length = 0;
|
||||||
let mut best_match_position = None;
|
let mut best_match_position = None;
|
||||||
for (i, configuration) in self.language_configurations.iter().enumerate() {
|
for (i, configuration) in self.language_configurations.iter().enumerate() {
|
||||||
|
|
@ -1305,6 +1305,11 @@ impl Loader {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
warn!(
|
||||||
|
"Failed to run `nm` to verify symbols in {}",
|
||||||
|
library_path.display()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -1534,7 +1539,9 @@ impl Loader {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_language_configuration_in_current_path(&self) -> Option<&LanguageConfiguration> {
|
pub fn get_language_configuration_in_current_path(
|
||||||
|
&self,
|
||||||
|
) -> Option<&LanguageConfiguration<'static>> {
|
||||||
self.language_configuration_in_current_path
|
self.language_configuration_in_current_path
|
||||||
.map(|i| &self.language_configurations[i])
|
.map(|i| &self.language_configurations[i])
|
||||||
}
|
}
|
||||||
|
|
@ -1543,7 +1550,7 @@ impl Loader {
|
||||||
&mut self,
|
&mut self,
|
||||||
parser_path: &Path,
|
parser_path: &Path,
|
||||||
set_current_path_config: bool,
|
set_current_path_config: bool,
|
||||||
) -> LoaderResult<&[LanguageConfiguration]> {
|
) -> LoaderResult<&[LanguageConfiguration<'static>]> {
|
||||||
let initial_language_configuration_count = self.language_configurations.len();
|
let initial_language_configuration_count = self.language_configurations.len();
|
||||||
|
|
||||||
match TreeSitterJSON::from_file(parser_path) {
|
match TreeSitterJSON::from_file(parser_path) {
|
||||||
|
|
|
||||||
|
|
@ -313,6 +313,7 @@ impl TagsContext {
|
||||||
)
|
)
|
||||||
.ok_or(Error::Cancelled)?;
|
.ok_or(Error::Cancelled)?;
|
||||||
|
|
||||||
|
// SAFETY:
|
||||||
// The `matches` iterator borrows the `Tree`, which prevents it from being
|
// The `matches` iterator borrows the `Tree`, which prevents it from being
|
||||||
// moved. But the tree is really just a pointer, so it's actually ok to
|
// moved. But the tree is really just a pointer, so it's actually ok to
|
||||||
// move it.
|
// move it.
|
||||||
|
|
|
||||||
|
|
@ -317,7 +317,7 @@ pub trait Decode {
|
||||||
|
|
||||||
/// A stateful object for walking a syntax [`Tree`] efficiently.
|
/// A stateful object for walking a syntax [`Tree`] efficiently.
|
||||||
#[doc(alias = "TSTreeCursor")]
|
#[doc(alias = "TSTreeCursor")]
|
||||||
pub struct TreeCursor<'cursor>(ffi::TSTreeCursor, PhantomData<&'cursor ()>);
|
pub struct TreeCursor<'tree>(ffi::TSTreeCursor, PhantomData<&'tree ()>);
|
||||||
|
|
||||||
/// A set of patterns that match nodes in a syntax tree.
|
/// A set of patterns that match nodes in a syntax tree.
|
||||||
#[doc(alias = "TSQuery")]
|
#[doc(alias = "TSQuery")]
|
||||||
|
|
@ -392,7 +392,7 @@ pub struct QueryMatch<'cursor, 'tree> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A sequence of [`QueryMatch`]es associated with a given [`QueryCursor`].
|
/// A sequence of [`QueryMatch`]es associated with a given [`QueryCursor`].
|
||||||
pub struct QueryMatches<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> {
|
pub struct QueryMatches<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> {
|
||||||
ptr: *mut ffi::TSQueryCursor,
|
ptr: *mut ffi::TSQueryCursor,
|
||||||
query: &'query Query,
|
query: &'query Query,
|
||||||
text_provider: T,
|
text_provider: T,
|
||||||
|
|
@ -407,7 +407,7 @@ pub struct QueryMatches<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]
|
||||||
///
|
///
|
||||||
/// During iteration, each element contains a [`QueryMatch`] and index. The index can
|
/// During iteration, each element contains a [`QueryMatch`] and index. The index can
|
||||||
/// be used to access the new capture inside of the [`QueryMatch::captures`]'s [`captures`].
|
/// be used to access the new capture inside of the [`QueryMatch::captures`]'s [`captures`].
|
||||||
pub struct QueryCaptures<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> {
|
pub struct QueryCaptures<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> {
|
||||||
ptr: *mut ffi::TSQueryCursor,
|
ptr: *mut ffi::TSQueryCursor,
|
||||||
query: &'query Query,
|
query: &'query Query,
|
||||||
text_provider: T,
|
text_provider: T,
|
||||||
|
|
@ -1581,7 +1581,7 @@ impl<'tree> Node<'tree> {
|
||||||
/// Get the [`Language`] that was used to parse this node's syntax tree.
|
/// Get the [`Language`] that was used to parse this node's syntax tree.
|
||||||
#[doc(alias = "ts_node_language")]
|
#[doc(alias = "ts_node_language")]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn language(&self) -> LanguageRef {
|
pub fn language(&self) -> LanguageRef<'tree> {
|
||||||
LanguageRef(unsafe { ffi::ts_node_language(self.0) }, PhantomData)
|
LanguageRef(unsafe { ffi::ts_node_language(self.0) }, PhantomData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2082,11 +2082,11 @@ impl fmt::Display for Node<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'cursor> TreeCursor<'cursor> {
|
impl<'tree> TreeCursor<'tree> {
|
||||||
/// Get the tree cursor's current [`Node`].
|
/// Get the tree cursor's current [`Node`].
|
||||||
#[doc(alias = "ts_tree_cursor_current_node")]
|
#[doc(alias = "ts_tree_cursor_current_node")]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn node(&self) -> Node<'cursor> {
|
pub fn node(&self) -> Node<'tree> {
|
||||||
Node(
|
Node(
|
||||||
unsafe { ffi::ts_tree_cursor_current_node(&self.0) },
|
unsafe { ffi::ts_tree_cursor_current_node(&self.0) },
|
||||||
PhantomData,
|
PhantomData,
|
||||||
|
|
@ -2227,7 +2227,7 @@ impl<'cursor> TreeCursor<'cursor> {
|
||||||
/// Re-initialize this tree cursor to start at the original node that the
|
/// Re-initialize this tree cursor to start at the original node that the
|
||||||
/// cursor was constructed with.
|
/// cursor was constructed with.
|
||||||
#[doc(alias = "ts_tree_cursor_reset")]
|
#[doc(alias = "ts_tree_cursor_reset")]
|
||||||
pub fn reset(&mut self, node: Node<'cursor>) {
|
pub fn reset(&mut self, node: Node<'tree>) {
|
||||||
unsafe { ffi::ts_tree_cursor_reset(&mut self.0, node.0) };
|
unsafe { ffi::ts_tree_cursor_reset(&mut self.0, node.0) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3404,7 +3404,7 @@ impl QueryProperty {
|
||||||
/// Provide a `StreamingIterator` instead of the traditional `Iterator`, as the
|
/// Provide a `StreamingIterator` instead of the traditional `Iterator`, as the
|
||||||
/// underlying object in the C library gets updated on each iteration. Copies would
|
/// underlying object in the C library gets updated on each iteration. Copies would
|
||||||
/// have their internal state overwritten, leading to Undefined Behavior
|
/// have their internal state overwritten, leading to Undefined Behavior
|
||||||
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIterator
|
impl<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIterator
|
||||||
for QueryMatches<'query, 'tree, T, I>
|
for QueryMatches<'query, 'tree, T, I>
|
||||||
{
|
{
|
||||||
type Item = QueryMatch<'query, 'tree>;
|
type Item = QueryMatch<'query, 'tree>;
|
||||||
|
|
@ -3435,15 +3435,13 @@ impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIterato
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIteratorMut
|
impl<T: TextProvider<I>, I: AsRef<[u8]>> StreamingIteratorMut for QueryMatches<'_, '_, T, I> {
|
||||||
for QueryMatches<'query, 'tree, T, I>
|
|
||||||
{
|
|
||||||
fn get_mut(&mut self) -> Option<&mut Self::Item> {
|
fn get_mut(&mut self) -> Option<&mut Self::Item> {
|
||||||
self.current_match.as_mut()
|
self.current_match.as_mut()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIterator
|
impl<'query, 'tree, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIterator
|
||||||
for QueryCaptures<'query, 'tree, T, I>
|
for QueryCaptures<'query, 'tree, T, I>
|
||||||
{
|
{
|
||||||
type Item = (QueryMatch<'query, 'tree>, usize);
|
type Item = (QueryMatch<'query, 'tree>, usize);
|
||||||
|
|
@ -3480,9 +3478,7 @@ impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIterato
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> StreamingIteratorMut
|
impl<T: TextProvider<I>, I: AsRef<[u8]>> StreamingIteratorMut for QueryCaptures<'_, '_, T, I> {
|
||||||
for QueryCaptures<'query, 'tree, T, I>
|
|
||||||
{
|
|
||||||
fn get_mut(&mut self) -> Option<&mut Self::Item> {
|
fn get_mut(&mut self) -> Option<&mut Self::Item> {
|
||||||
self.current_match.as_mut()
|
self.current_match.as_mut()
|
||||||
}
|
}
|
||||||
|
|
@ -3622,8 +3618,8 @@ impl From<ffi::TSRange> for Range {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&'_ InputEdit> for ffi::TSInputEdit {
|
impl From<&InputEdit> for ffi::TSInputEdit {
|
||||||
fn from(val: &'_ InputEdit) -> Self {
|
fn from(val: &InputEdit) -> Self {
|
||||||
Self {
|
Self {
|
||||||
start_byte: val.start_byte as u32,
|
start_byte: val.start_byte as u32,
|
||||||
old_end_byte: val.old_end_byte as u32,
|
old_end_byte: val.old_end_byte as u32,
|
||||||
|
|
|
||||||
8
lib/binding_web/src/finalization_registry.ts
Normal file
8
lib/binding_web/src/finalization_registry.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
export function newFinalizer<T>(handler: (value: T) => void): FinalizationRegistry<T> | undefined {
|
||||||
|
try {
|
||||||
|
return new FinalizationRegistry(handler);
|
||||||
|
} catch(e) {
|
||||||
|
console.error('Unsupported FinalizationRegistry:', e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
import { C, Internal, assertInternal } from './constants';
|
import { C, Internal, assertInternal } from './constants';
|
||||||
import { Language } from './language';
|
import { Language } from './language';
|
||||||
|
import { newFinalizer } from './finalization_registry';
|
||||||
|
|
||||||
|
const finalizer = newFinalizer((address: number) => {
|
||||||
|
C._ts_lookahead_iterator_delete(address);
|
||||||
|
});
|
||||||
|
|
||||||
export class LookaheadIterator implements Iterable<string> {
|
export class LookaheadIterator implements Iterable<string> {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
|
@ -13,6 +18,7 @@ export class LookaheadIterator implements Iterable<string> {
|
||||||
assertInternal(internal);
|
assertInternal(internal);
|
||||||
this[0] = address;
|
this[0] = address;
|
||||||
this.language = language;
|
this.language = language;
|
||||||
|
finalizer?.register(this, address, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the current symbol of the lookahead iterator. */
|
/** Get the current symbol of the lookahead iterator. */
|
||||||
|
|
@ -27,6 +33,7 @@ export class LookaheadIterator implements Iterable<string> {
|
||||||
|
|
||||||
/** Delete the lookahead iterator, freeing its resources. */
|
/** Delete the lookahead iterator, freeing its resources. */
|
||||||
delete(): void {
|
delete(): void {
|
||||||
|
finalizer?.unregister(this);
|
||||||
C._ts_lookahead_iterator_delete(this[0]);
|
C._ts_lookahead_iterator_delete(this[0]);
|
||||||
this[0] = 0;
|
this[0] = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { Language } from './language';
|
||||||
import { marshalRange, unmarshalRange } from './marshal';
|
import { marshalRange, unmarshalRange } from './marshal';
|
||||||
import { checkModule, initializeBinding } from './bindings';
|
import { checkModule, initializeBinding } from './bindings';
|
||||||
import { Tree } from './tree';
|
import { Tree } from './tree';
|
||||||
|
import { newFinalizer } from './finalization_registry';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for parsing
|
* Options for parsing
|
||||||
|
|
@ -82,6 +83,11 @@ export let LANGUAGE_VERSION: number;
|
||||||
*/
|
*/
|
||||||
export let MIN_COMPATIBLE_VERSION: number;
|
export let MIN_COMPATIBLE_VERSION: number;
|
||||||
|
|
||||||
|
const finalizer = newFinalizer((addresses: number[]) => {
|
||||||
|
C._ts_parser_delete(addresses[0]);
|
||||||
|
C._free(addresses[1]);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A stateful object that is used to produce a {@link Tree} based on some
|
* A stateful object that is used to produce a {@link Tree} based on some
|
||||||
* source code.
|
* source code.
|
||||||
|
|
@ -117,6 +123,7 @@ export class Parser {
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
this.initialize();
|
this.initialize();
|
||||||
|
finalizer?.register(this, [this[0], this[1]], this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
|
@ -131,6 +138,7 @@ export class Parser {
|
||||||
|
|
||||||
/** Delete the parser, freeing its resources. */
|
/** Delete the parser, freeing its resources. */
|
||||||
delete() {
|
delete() {
|
||||||
|
finalizer?.unregister(this);
|
||||||
C._ts_parser_delete(this[0]);
|
C._ts_parser_delete(this[0]);
|
||||||
C._free(this[1]);
|
C._free(this[1]);
|
||||||
this[0] = 0;
|
this[0] = 0;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { Node } from './node';
|
||||||
import { marshalNode, unmarshalCaptures } from './marshal';
|
import { marshalNode, unmarshalCaptures } from './marshal';
|
||||||
import { TRANSFER_BUFFER } from './parser';
|
import { TRANSFER_BUFFER } from './parser';
|
||||||
import { Language } from './language';
|
import { Language } from './language';
|
||||||
|
import { newFinalizer } from './finalization_registry';
|
||||||
|
|
||||||
const PREDICATE_STEP_TYPE_CAPTURE = 1;
|
const PREDICATE_STEP_TYPE_CAPTURE = 1;
|
||||||
|
|
||||||
|
|
@ -506,6 +507,10 @@ function parsePattern(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const finalizer = newFinalizer((address: number) => {
|
||||||
|
C._ts_query_delete(address);
|
||||||
|
});
|
||||||
|
|
||||||
export class Query {
|
export class Query {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
private [0] = 0; // Internal handle for Wasm
|
private [0] = 0; // Internal handle for Wasm
|
||||||
|
|
@ -687,10 +692,12 @@ export class Query {
|
||||||
this.assertedProperties = assertedProperties;
|
this.assertedProperties = assertedProperties;
|
||||||
this.refutedProperties = refutedProperties;
|
this.refutedProperties = refutedProperties;
|
||||||
this.exceededMatchLimit = false;
|
this.exceededMatchLimit = false;
|
||||||
|
finalizer?.register(this, address, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Delete the query, freeing its resources. */
|
/** Delete the query, freeing its resources. */
|
||||||
delete(): void {
|
delete(): void {
|
||||||
|
finalizer?.unregister(this);
|
||||||
C._ts_query_delete(this[0]);
|
C._ts_query_delete(this[0]);
|
||||||
this[0] = 0;
|
this[0] = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { TreeCursor } from './tree_cursor';
|
||||||
import { marshalEdit, marshalPoint, unmarshalNode, unmarshalRange } from './marshal';
|
import { marshalEdit, marshalPoint, unmarshalNode, unmarshalRange } from './marshal';
|
||||||
import { TRANSFER_BUFFER } from './parser';
|
import { TRANSFER_BUFFER } from './parser';
|
||||||
import { Edit } from './edit';
|
import { Edit } from './edit';
|
||||||
|
import { newFinalizer } from './finalization_registry';
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
export function getText(tree: Tree, startIndex: number, endIndex: number, startPosition: Point): string {
|
export function getText(tree: Tree, startIndex: number, endIndex: number, startPosition: Point): string {
|
||||||
|
|
@ -28,6 +29,10 @@ export function getText(tree: Tree, startIndex: number, endIndex: number, startP
|
||||||
return result ?? '';
|
return result ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const finalizer = newFinalizer((address: number) => {
|
||||||
|
C._ts_tree_delete(address);
|
||||||
|
});
|
||||||
|
|
||||||
/** A tree that represents the syntactic structure of a source code file. */
|
/** A tree that represents the syntactic structure of a source code file. */
|
||||||
export class Tree {
|
export class Tree {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
|
@ -45,6 +50,7 @@ export class Tree {
|
||||||
this[0] = address;
|
this[0] = address;
|
||||||
this.language = language;
|
this.language = language;
|
||||||
this.textCallback = textCallback;
|
this.textCallback = textCallback;
|
||||||
|
finalizer?.register(this, address, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a shallow copy of the syntax tree. This is very fast. */
|
/** Create a shallow copy of the syntax tree. This is very fast. */
|
||||||
|
|
@ -55,6 +61,7 @@ export class Tree {
|
||||||
|
|
||||||
/** Delete the syntax tree, freeing its resources. */
|
/** Delete the syntax tree, freeing its resources. */
|
||||||
delete(): void {
|
delete(): void {
|
||||||
|
finalizer?.unregister(this);
|
||||||
C._ts_tree_delete(this[0]);
|
C._ts_tree_delete(this[0]);
|
||||||
this[0] = 0;
|
this[0] = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,11 @@ import { marshalNode, marshalPoint, marshalTreeCursor, unmarshalNode, unmarshalP
|
||||||
import { Node } from './node';
|
import { Node } from './node';
|
||||||
import { TRANSFER_BUFFER } from './parser';
|
import { TRANSFER_BUFFER } from './parser';
|
||||||
import { getText, Tree } from './tree';
|
import { getText, Tree } from './tree';
|
||||||
|
import { newFinalizer } from './finalization_registry';
|
||||||
|
|
||||||
|
const finalizer = newFinalizer((address: number) => {
|
||||||
|
C._ts_tree_cursor_delete_wasm(address);
|
||||||
|
});
|
||||||
|
|
||||||
/** A stateful object for walking a syntax {@link Tree} efficiently. */
|
/** A stateful object for walking a syntax {@link Tree} efficiently. */
|
||||||
export class TreeCursor {
|
export class TreeCursor {
|
||||||
|
|
@ -30,6 +35,7 @@ export class TreeCursor {
|
||||||
assertInternal(internal);
|
assertInternal(internal);
|
||||||
this.tree = tree;
|
this.tree = tree;
|
||||||
unmarshalTreeCursor(this);
|
unmarshalTreeCursor(this);
|
||||||
|
finalizer?.register(this, this.tree[0], this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a deep copy of the tree cursor. This allocates new memory. */
|
/** Creates a deep copy of the tree cursor. This allocates new memory. */
|
||||||
|
|
@ -42,6 +48,7 @@ export class TreeCursor {
|
||||||
|
|
||||||
/** Delete the tree cursor, freeing its resources. */
|
/** Delete the tree cursor, freeing its resources. */
|
||||||
delete(): void {
|
delete(): void {
|
||||||
|
finalizer?.unregister(this);
|
||||||
marshalTreeCursor(this);
|
marshalTreeCursor(this);
|
||||||
C._ts_tree_cursor_delete_wasm(this.tree[0]);
|
C._ts_tree_cursor_delete_wasm(this.tree[0]);
|
||||||
this[0] = this[1] = this[2] = 0;
|
this[0] = this[1] = this[2] = 0;
|
||||||
|
|
|
||||||
74
lib/binding_web/test/memory.test.ts
Normal file
74
lib/binding_web/test/memory.test.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { gc, event, Finalizer } from './memory';
|
||||||
|
|
||||||
|
// hijack finalization registry before import web-tree-sitter
|
||||||
|
globalThis.FinalizationRegistry = Finalizer;
|
||||||
|
|
||||||
|
describe('Memory Management', () => {
|
||||||
|
describe('call .delete()', () => {
|
||||||
|
it('test free memory manually', async () => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
gc();
|
||||||
|
}, 100);
|
||||||
|
let done = 0;
|
||||||
|
event.on('gc', () => {
|
||||||
|
done++;
|
||||||
|
});
|
||||||
|
await (async () => {
|
||||||
|
const { JavaScript } = await (await import('./helper')).default;
|
||||||
|
const { Parser, Query } = await import('../src');
|
||||||
|
const parser = new Parser();
|
||||||
|
parser.setLanguage(JavaScript);
|
||||||
|
const tree = parser.parse('1+1')!;
|
||||||
|
const copyTree = tree.copy();
|
||||||
|
const cursor = tree.walk();
|
||||||
|
const copyCursor = cursor.copy();
|
||||||
|
const lookaheadIterator = JavaScript.lookaheadIterator(cursor.currentNode.nextParseState)!;
|
||||||
|
const query = new Query(JavaScript, '(identifier) @element');
|
||||||
|
parser.delete();
|
||||||
|
tree.delete();
|
||||||
|
copyTree.delete();
|
||||||
|
cursor.delete();
|
||||||
|
copyCursor.delete();
|
||||||
|
lookaheadIterator.delete();
|
||||||
|
query.delete();
|
||||||
|
})();
|
||||||
|
// wait for gc
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
clearInterval(timer);
|
||||||
|
// expect no gc event fired
|
||||||
|
expect(done).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('do not call .delete()', () => {
|
||||||
|
it('test free memory automatically', async () => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
gc();
|
||||||
|
}, 100);
|
||||||
|
let done = 0;
|
||||||
|
const promise = new Promise((resolve) => {
|
||||||
|
event.on('gc', () => {
|
||||||
|
if (++done === 7) {
|
||||||
|
resolve(undefined);
|
||||||
|
clearInterval(timer);
|
||||||
|
}
|
||||||
|
console.log('free memory times: ', done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await (async () => {
|
||||||
|
const { JavaScript } = await (await import('./helper')).default;
|
||||||
|
const { Parser, Query } = await import('../src');
|
||||||
|
const parser = new Parser(); // 1
|
||||||
|
parser.setLanguage(JavaScript);
|
||||||
|
const tree = parser.parse('1+1')!; // 2
|
||||||
|
tree.copy(); // 3
|
||||||
|
const cursor = tree.walk(); // 4
|
||||||
|
cursor.copy(); // 5
|
||||||
|
JavaScript.lookaheadIterator(cursor.currentNode.nextParseState)!; // 6
|
||||||
|
new Query(JavaScript, '(identifier) @element'); // 7
|
||||||
|
})();
|
||||||
|
await promise;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
20
lib/binding_web/test/memory.ts
Normal file
20
lib/binding_web/test/memory.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
import { Session } from 'inspector';
|
||||||
|
|
||||||
|
const session = new Session();
|
||||||
|
session.connect();
|
||||||
|
|
||||||
|
export function gc() {
|
||||||
|
session.post('HeapProfiler.collectGarbage');
|
||||||
|
}
|
||||||
|
|
||||||
|
export const event = new EventEmitter();
|
||||||
|
|
||||||
|
export class Finalizer<T> extends FinalizationRegistry<T> {
|
||||||
|
constructor(handler: (value: T) => void) {
|
||||||
|
super((value) => {
|
||||||
|
handler(value);
|
||||||
|
event.emit('gc');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
25
lib/binding_web/test/memory_unsupported.test.ts
Normal file
25
lib/binding_web/test/memory_unsupported.test.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { describe, it } from 'vitest';
|
||||||
|
|
||||||
|
describe('FinalizationRegistry is unsupported', () => {
|
||||||
|
it('test FinalizationRegistry is unsupported', async () => {
|
||||||
|
// @ts-expect-error: test FinalizationRegistry is not supported
|
||||||
|
globalThis.FinalizationRegistry = undefined;
|
||||||
|
const { JavaScript } = await (await import('./helper')).default;
|
||||||
|
const { Parser, Query } = await import('../src');
|
||||||
|
const parser = new Parser();
|
||||||
|
parser.setLanguage(JavaScript);
|
||||||
|
const tree = parser.parse('1+1')!;
|
||||||
|
const copyTree = tree.copy();
|
||||||
|
const cursor = tree.walk();
|
||||||
|
const copyCursor = cursor.copy();
|
||||||
|
const lookaheadIterator = JavaScript.lookaheadIterator(cursor.currentNode.nextParseState)!;
|
||||||
|
const query = new Query(JavaScript, '(identifier) @element');
|
||||||
|
parser.delete();
|
||||||
|
tree.delete();
|
||||||
|
copyTree.delete();
|
||||||
|
cursor.delete();
|
||||||
|
copyCursor.delete();
|
||||||
|
lookaheadIterator.delete();
|
||||||
|
query.delete();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue