diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..951f358
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,73 @@
+name: CI
+
+on:
+ push:
+ branches: ["main"]
+ pull_request:
+ branches: ["main"]
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: pnpm/action-setup@v3
+ with:
+ version: 8
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20.x
+ cache: pnpm
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm lint
+
+ test:
+ strategy:
+ fail-fast: false
+ matrix:
+ node-version: [18.x, 20.x, 22.x]
+ os: [ubuntu-latest, windows-latest]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: pnpm/action-setup@v3
+ with:
+ version: 8
+ - uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: pnpm
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm test:coverage
+ - name: Upload coverage artifact
+ if: matrix.os == 'ubuntu-latest' && matrix.node-version == '20.x'
+ uses: actions/upload-artifact@v4
+ with:
+ name: coverage-report
+ path: coverage/
+ retention-days: 7
+
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: pnpm/action-setup@v3
+ with:
+ version: 8
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20.x
+ cache: pnpm
+ - run: pnpm install --frozen-lockfile
+ - run: pnpm build
+
+ compatibility-summary:
+ runs-on: ubuntu-latest
+ needs: test
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20.x
+ - name: Generate compatibility summary
+ run: node scripts/compat-summary.mjs >> $GITHUB_STEP_SUMMARY
diff --git a/.github/workflows/run_test.yml b/.github/workflows/run_test.yml
deleted file mode 100644
index ed557f0..0000000
--- a/.github/workflows/run_test.yml
+++ /dev/null
@@ -1,44 +0,0 @@
-# Github Action to run pnpm test on every push and pull request
-name: Run Test
-on:
- push:
- branches: ["main"]
- pull_request:
- branches: ["main"]
-
-jobs:
- unit-test:
- strategy:
- matrix:
- node-version: [16.x, 18.x, 20.x]
- os: [ubuntu-latest, windows-latest]
- runs-on: ${{ matrix.os }}
- steps:
- - uses: actions/checkout@v2
-
- - name: Setup Node.js with ${{ matrix.node-version }}
- uses: actions/setup-node@v3
- with:
- node-version: ${{ matrix.node-version }}
-
- - uses: pnpm/action-setup@v2
- name: Install pnpm
- with:
- version: 8
- run_install: true
-
- - name: Get pnpm store directory
- shell: bash
- run: |
- echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
-
- - uses: actions/cache@v3
- name: Setup pnpm cache
- with:
- path: ${{ env.STORE_PATH }}
- key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
- restore-keys: |
- ${{ runner.os }}-pnpm-store-
-
- - name: Run unit tests
- run: pnpm test
diff --git a/.gitignore b/.gitignore
index c168127..4c6a366 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,7 @@ lerna-debug.log*
dist
node_modules
+coverage
.DS_Store
.env
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..fbc7b6f
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,93 @@
+# Changelog
+
+## [Unreleased]
+
+### Added — Phase 0 (nền tảng)
+- Vitest 1.6 + v8 coverage reporting, baseline ≥88% statements
+- GitHub Actions CI: lint + test matrix (Node 18/20/22 × Ubuntu/Windows) + build + compat summary
+- 14 Vietnamese keyword aliases: `biến`, `nhường`, `đây`, `rỗng`, `tĩnh`, `công khai`, `riêng tư`, `bảo vệ`, `lấy`, `gán`, `vô cực`, `của`, `lặp`, `là kiểu`
+- `CONTRIBUTING.md` với template thêm parser node + Unicode boundary rules
+- `scripts/compat-summary.mjs` — dashboard đọc `compatibility.md` + `coverage-summary.json`
+- `docs/compatibility.md` + `docs/roadmap.md`
+
+### Added — Phase 1 (cú pháp P1)
+- Arrow function (single param, paren, multi, empty; expr + block body)
+- Default parameters (`AssignmentPattern`)
+- Rest parameters (`RestElement`) + validation vị trí
+- Spread (`...`) trong array / object / call
+- Destructuring object: shorthand, rename, default, rest, nested
+- Destructuring array: hole, default, rest, nested, mixed
+- Template literal interpolation `${...}` với nested braces
+- `for...of` / `for...in` / `for await ... of`
+- Object shorthand `{a}` + computed key `{[k]: v}`
+
+### Added — Phase 2 (cú pháp P2)
+- Private field `#x` (`ClassPrivateProperty`)
+- Getter/setter trong class + object literal (`lấy` / `gán`)
+- Yield expression + `yield*` (tiếng Việt: `nhường`)
+- Optional chaining edge cases: `?.prop`, `?.[k]`, chained
+- Compound assignment đầy đủ (16 operators): `+=`, `-=`, `*=`, `/=`, `%=`, `**=`, `&=`, `|=`, `^=`, `<<=`, `>>=`, `>>>=`, `||=`, `&&=`, `??=`
+- Numeric literals nâng cao: hex `0x`, octal `0o`, binary `0b`, BigInt `123n`, separator `1_000`
+- Regex literal `/x/gi` với context-aware tokenization
+- String escape sequences: `\n`, `\t`, `\x41`, `\u{1F600}`, v.v.
+- Labeled break/continue + fix bug `ContinueStatement` ăn nhầm `BREAK` keyword
+- Ternary `c ? a : b` (`ConditionalExpression`)
+- `NewExpression` với args + `SpreadElement`
+- `TaggedTemplateExpression`
+- `SuperExpression` (call + member)
+- `InfinityIdentifier`
+- Dynamic import `sử dụng("path")`
+- Optional catch binding `bắt lỗi {}`
+- Access modifiers trong class (`công khai`/`riêng tư`/`bảo vệ`)
+- Static method/property with `tĩnh` keyword
+- Generator method `*foo()`
+- Method detection trong class body (transition ClassProperty → ClassMethod khi thấy `(`)
+
+### Added — Phase 3 (mangling)
+- Identifier mangling mới: `xin chào` → `xin_chào` (Unicode trực tiếp, thay vì codepoint `_xin_ch224o`)
+
+### Added — Phase 4 (error quality)
+- `VietScriptError` class với `file:line:col` + snippet + caret + hint
+- `Parser.filename` support
+
+### Added — Phase 5 (CLI + runtime)
+- `@vietscript/cli` package với commands: `run`, `build`, `watch`, `check`
+- Source maps qua `@babel/generator`
+- Node ESM loader: `node --import '@vietscript/cli/register' file.vjs`
+- Bun plugin: `@vietscript/cli/bun-plugin`
+- Binary: `vietscript` / `vjs`
+
+### Added — Phase 6 (plugins)
+- Fix Vite plugin: emit JS (trước đây trả về JSON AST)
+- Fix Webpack plugin: same fix + source maps via `this.callback`
+
+### Added — Phase 7 (stdlib)
+- `@vietscript/stdlib` — wrappers tiếng Việt cho JS globals
+ - Console: `ghi`, `ghi lỗi`, `ghi cảnh báo`
+ - Ép kiểu: `tự số`, `tự chuỗi`, `tự bool`, `kiểu`, `độ dài`
+ - Math: `tròn`, `làm tròn xuống`, `làm tròn lên`, `giá trị tuyệt đối`, `nhỏ nhất`, `lớn nhất`, `ngẫu nhiên`
+ - Async: `đợi`
+ - JSON: `phân tích JSON`, `JSON sang chuỗi`
+ - Fetch: `tải văn bản`, `tải JSON`
+
+### Fixed
+- Tokenizer: specs order bị sai (prefix conflict `+` vs `+=`) → reorder longest-first
+- Tokenizer: trailing non-ASCII char boundary (`xoá`, `chờ`, `khi mà`, `riêng tư`, `bảo vệ`) dùng negative lookahead thay vì `\b`
+- Tokenizer: multi-word identifier nuốt embedded keyword (e.g. `x của arr` thành 1 identifier)
+- `UnaryExpression`: dùng Keyword enum thay vì string literals
+- `BinaryExpression`: support `instanceof`, `in` keyword operators + bitwise ops
+- `ContinueStatement`: eat `CONTINUE` keyword thay vì `BREAK`
+- `ExpressionStatement`: unwrap `LabeledStatement` khi Expression trả về statement-like
+- `StatementList`: handle leading `;` as `EmptyStatement` (không infinite loop)
+- `UndefinedIdentifier`: name = `"undefined"` lowercase (trước là `"Undefined"`)
+- `ClassDeclaration`: support `extends` keyword (ngoài dạng paren)
+- Docs: xóa JSON-based dummy output từ Vite/Webpack plugin
+
+### Stats
+- Tests: 70 → 298 (100% pass)
+- Coverage: 79.85% → 92.44% statements / 71.23% → 85.88% branches / 84.89% → 96.25% functions
+- Compatibility matrix: 40.8% → 98.5% complete (135/137 features)
+
+## [0.0.1] - Initial
+
+Initial parser skeleton, basic syntax support, playground.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c3f750e..39625fc 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,15 +1,148 @@
-# Welcome to FORMKL contributing guide
+# Đóng góp cho VietScript
-Thank you for investing your time in contributing to our project!
+Cảm ơn bạn quan tâm đến dự án! Tài liệu này hướng dẫn cách đóng góp code, đặc biệt là thêm cú pháp mới vào parser.
-Read our [Code of Conduct](CODE_OF_CONDUCT.md) to keep our community approachable and respectable.
+Tham khảo [Code of Conduct](CODE_OF_CONDUCT.md) và [Roadmap](docs/roadmap.md).
-Please make sure to read the [Contributing Guide](https://vietscript.org/learning/contribution-guide.html) before making a pull request.
+## Thiết lập môi trường
-Thank you to all the people who already contributed to Vue!
+```bash
+pnpm install
+pnpm test # chạy test
+pnpm test:coverage # chạy test kèm coverage report
+pnpm lint # lint với eslint
+pnpm build # build tất cả package
+```
-
-
-
+## Kiến trúc tổng quan
-Made with [contrib.rocks](https://contrib.rocks).
+```
+Source .vjs → Tokenizer → Parser → Babel AST → @babel/generator → JavaScript
+```
+
+- `packages/parser/src/tokenizer.ts` — chia source thành token dựa trên `constants/specs.ts`.
+- `packages/parser/src/parser.ts` — điều phối, delegate cho từng node class.
+- `packages/parser/src/nodes/**` — mỗi AST node một file class, tự đọc token và build AST.
+- Codegen dùng `@babel/generator` lib — không tự viết printer.
+
+## Quy trình thêm cú pháp mới
+
+### 1. Thêm keyword (nếu cần)
+
+Nếu cú pháp cần keyword mới:
+
+1. Thêm hằng vào `packages/shared/parser/keyword.enum.ts`.
+2. Thêm regex tokenize vào `packages/parser/src/constants/specs.ts`. **Lưu ý Unicode:**
+ - Nếu từ tiếng Việt **kết thúc bằng chữ ASCII** (a-z), dùng `\b` cuối: `/^\b(english|ti\u1EBFng vi\u1EC7t_)\b/`.
+ - Nếu **kết thúc bằng chữ Unicode tiếng Việt** (ư, ệ, ộ...), dùng negative lookahead: `/^(english\b|ti\u1EBFng vi\u1EC7t(?![A-Za-z\u00C0-\u1EF9]))/`.
+ - Nếu **bắt đầu bằng chữ Unicode** (đ, â...), bỏ `\b` đầu — chỉ giữ `^` anchor.
+3. Thêm test tokenize vào `packages/parser/src/__test__/vietnamese-keywords.test.ts`.
+
+### 2. Tạo node class
+
+Mỗi AST node là 1 file trong `packages/parser/src/nodes//.ts`. Template:
+
+```ts
+import { Parser } from "@parser/parser";
+import { Keyword } from "@vietscript/shared";
+
+export class MyStatement {
+ type = "MyStatement";
+
+ // field khai báo theo chuẩn Babel AST
+ foo: SomeNode;
+
+ constructor(parser: Parser) {
+ parser.eat(Keyword.MY_KEYWORD);
+ parser.eat("(");
+ this.foo = new SomeNode(parser);
+ parser.eat(")");
+ }
+}
+```
+
+Nguyên tắc:
+- **Shape phải khớp Babel AST** — nếu không `@babel/generator` sẽ sinh sai. Tra cứu [babel AST spec](https://github.com/babel/babel/blob/main/packages/babel-parser/ast/spec.md).
+- Dùng `parser.eat(tokenType)` để consume token kỳ vọng, throw nếu không khớp.
+- Không tự log hoặc thêm comment giải thích những gì code đã rõ — chỉ comment nếu có invariant ẩn.
+
+### 3. Wire vào chỗ cần gọi
+
+Edit file cha (ví dụ `Statement.ts` nếu là statement mới) để dispatch sang node mới dựa vào `parser.lookahead.type`.
+
+### 4. Viết test (**bắt buộc 3 nhóm**)
+
+Mỗi feature mới phải có đủ 3 loại test trước khi merge:
+
+**a) Parser positive** — parse đúng, AST đúng shape.
+
+```ts
+// packages/parser/src/nodes/.../__test__/my-statement.test.ts
+import { MyStatement } from "@parser/nodes/.../MyStatement";
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("my-statement.test", () => {
+ it("should parse normally", () => {
+ const result = parser.parse(`từ khoá của tôi (x)`, MyStatement);
+ expect(toPlainObject(result)).toStrictEqual({
+ type: "MyStatement",
+ foo: { /* expected shape */ },
+ });
+ });
+});
+```
+
+**b) Parser negative** — cú pháp sai throw error rõ ràng.
+
+```ts
+it("should throw on missing paren", () => {
+ expect(() => parser.parse(`từ khoá của tôi`, MyStatement))
+ .toThrowError(/expected/i);
+});
+```
+
+**c) Codegen** — AST sinh ra JS string chuẩn, dùng snapshot.
+
+```ts
+// packages/parser/src/nodes/.../__test__/generator/generator-my-statement.test.ts
+import generate from "@babel/generator";
+import { MyStatement } from "@parser/nodes/.../MyStatement";
+import parser from "../../../../setup-test";
+
+describe("generator-my-statement.test", () => {
+ it("should generate javascript", () => {
+ const ast = parser.parse(`từ khoá của tôi (x)`, MyStatement);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+});
+```
+
+### 5. Cập nhật compatibility matrix
+
+Mở [docs/compatibility.md](docs/compatibility.md), tìm dòng feature tương ứng, đổi ❌ → 🟡 → ✅ theo trạng thái thực tế của PR.
+
+### 6. Cập nhật docs
+
+Nếu cú pháp cần hướng dẫn sử dụng, thêm/cập nhật trang tương ứng trong `docs/`.
+
+## Quy ước code
+
+- **Không thêm comment kể lể** "thêm cho task X", "PR #123", "lấy cảm hứng từ Y". Code nên tự giải thích qua tên biến.
+- **Comment chỉ khi có lý do ẩn**: workaround bug cụ thể, invariant không nhìn thấy trong code.
+- **Không thêm error handling cho scenario không thể xảy ra** — tin internal API, chỉ validate boundary.
+- **Ưu tiên sửa file có sẵn** hơn tạo file mới.
+- **Chạy `pnpm lint` trước khi commit**.
+
+## Quy trình PR
+
+1. Fork, tạo branch từ `main`: `git checkout -b feat/arrow-function`.
+2. Commit theo [conventional commits](https://www.conventionalcommits.org/): `feat(parser): add arrow function`, `fix(tokenizer): ...`, `docs: ...`, `test: ...`.
+3. PR phải pass CI (lint + test + coverage + build).
+4. Mỗi feature trong compatibility matrix = 1 PR riêng. Không bó nhiều feature trong 1 PR.
+5. Cập nhật [compatibility.md](docs/compatibility.md) trong cùng PR.
+
+## Thắc mắc
+
+Mở issue tại https://github.com/imrim12/vietscript/issues với label `question`.
diff --git a/README.md b/README.md
index b7ba261..a7208bf 100644
--- a/README.md
+++ b/README.md
@@ -1,51 +1,111 @@
-# VietScript - Vietnamese Programming Language - Ngôn ngữ lập trình tiếng Việt
+# VietScript
-VietScript - Ngôn ngữ lập trình tiếng Việt, là một ngôn ngữ lập trình mã nguồn mở được viết hoàn toàn bằng TypeScript và hỗ trợ cú pháp tiếng Việt với dấu cách. VietScript được thiết kế để giúp cho người Việt mới bắt đầu học lập trình có thể dễ dàng học và sử dụng ngôn ngữ lập trình.
+**Ngôn ngữ lập trình tiếng Việt** — transpile ra JavaScript, chạy trên browser, Node, Bun.
-## Bắt đầu sử dụng
+```vietscript
+hằng số tên = "Thế giới"
+console.log(`Xin chào, ${tên}!`)
-Mời bạn xem tài liệu tại [vietscript.org](https://vietscript.org)!
-
-Xem demo tại: [sandbox.vietscript.org](https://sandbox.vietscript.org)
+hàm cộng(a, b = 0) {
+ trả về a + b
+}
+hằng số mảng = [1, 2, 3, 4, 5]
+hằng số tổng = mảng.reduce((acc, x) => acc + x, 0)
+console.log("Tổng:", tổng)
+```
-## Cài đặt và thử nghiệm
-Vietscript được phát triển theo phương pháp TDD (Test Driven Development), các trường hợp sử dụng trong ngôn ngữ được viết thành các test case và được đặt trong các thư mục `__test__`. Để chạy các test case, bạn cần cài đặt NodeJS và PNPM, sau đó các chạy lệnh sau:
+## Bắt đầu
```bash
-# Cài đặt PNPM
-npm install -g pnpm
-
-# Cài đặt các thư viện
pnpm install
-
-# Chạy unit test
-pnpm test
+pnpm -r build
+node packages/cli/bin/vietscript.mjs run examples/hello/hello.vjs
```
-## Lý thuyết
-VietScript là ngôn ngữ thông dịch, nó được chuyển đổi thành mã nguồn JavaScript và chạy trên trình duyệt. JavaScript cũng từng là ngôn ngữ thông dịch (bây giờ thì là biên dịch tức thời - JIT compiled), nó không được chuyển đổi ngay lập tức thành mã máy và sẽ được thông dịch từng dòng một trước khi nó được thực thi.
+### CLI
-Cú pháp của VietScript giống JavaScript 96%, chỉ khác là nó hỗ trợ tiếng Việt :D
+```bash
+vietscript run # Chạy file .vjs
+vietscript build [outDir] # Build thư mục .vjs → JS
+vietscript watch [outDir] # Watch + auto-rebuild
+vietscript check # Kiểm tra cú pháp không chạy
+```
-## Tiến độ
-Dự án được viết bằng Typescript thuần và không sử dụng thư viện ngoài, trừ các công cụ hỗ trợ đóng gói và kiểm thử. Hiện tại dự án đang trong giai đoạn phát triển và chưa thể sử dụng được. Dự án rất cần sự đóng góp từ cộng đồng để hoàn thiện :D
+## Cú pháp
+
+VietScript giữ ngữ nghĩa JavaScript 100% — chỉ thay keyword tiếng Anh bằng tiếng Việt. Code giống JS mà bạn đã quen.
+
+| JS | VietScript |
+|---|---|
+| `var/let/const` | `khai báo` / `biến` / `hằng số` |
+| `function` | `hàm` |
+| `if/else` | `nếu` / `không thì` |
+| `for (of/in)` | `lặp (của/trong)` |
+| `while` | `khi mà` |
+| `class/extends/this` | `lớp` / `kế thừa` / `đây` |
+| `try/catch/throw` | `thử` / `bắt lỗi` / `báo lỗi` |
+| `return` | `trả về` |
+| `async/await` | `bất đồng bộ` / `chờ` |
+| `import/export/from` | `sử dụng` / `cho phép` / `từ` |
+| `true/false/null/undefined` | `đúng` / `sai` / `rỗng` / `không xác định` |
+| `typeof/instanceof` | `kiểu của` / `là kiểu` |
+| `get/set/static` | `lấy` / `gán` / `tĩnh` |
+| `public/private/protected` | `công khai` / `riêng tư` / `bảo vệ` |
+
+Đầy đủ danh sách tại [docs/compatibility.md](docs/compatibility.md).
+
+## Feature đã hỗ trợ
+
+| | |
+|---|---|
+| ✅ | Mọi declaration (var/let/const) + destructuring object/array |
+| ✅ | Mọi loại function (arrow, async, generator, async generator, default params, rest) |
+| ✅ | Class đầy đủ (kế thừa, private field `#x`, getter/setter, static, method) |
+| ✅ | Control flow đầy đủ (if, switch, for/of/in, while, do-while, labeled break/continue) |
+| ✅ | try/catch/finally/throw |
+| ✅ | ES6 modules (import/export/default/namespace/as) |
+| ✅ | Template literal với interpolation `` `${x}` `` |
+| ✅ | Spread/Rest `...` (array, object, call, params) |
+| ✅ | Optional chaining `?.`, nullish `??`, logical assignment `\|\|=` etc. |
+| ✅ | Regex literal `/abc/gi`, template, hex/oct/bin/BigInt/numeric separator |
+| ✅ | Comment `//` và `/* */` |
+| ✅ | Escape sequence đầy đủ (`\n`, `\x41`, `\u{1F600}`, v.v.) |
+| ✅ | Error messages tiếng Việt có file:line:col + snippet |
+| ✅ | Source maps (debug stack trace trỏ về file `.vjs`) |
+
+**Kiểm tra chi tiết:** [docs/compatibility.md](docs/compatibility.md) — 70.9% ✅ complete, 24.6% 🟡 partial, 4.5% ❌ missing.
+
+**Lộ trình:** [docs/roadmap.md](docs/roadmap.md).
+
+## Dự án cấu trúc
+```
+packages/
+├── parser/ Parser + AST + Babel-compatible
+├── shared/ Types + enum chung
+├── cli/ CLI `vietscript`
+└── plugins/
+ ├── vite/ Vite plugin
+ └── webpack/ Webpack loader
+```
-## Đóng góp
+## Phát triển
-Mọi người có thể đọc [hướng dẫn cách đóng góp dự án](https://vietscript.org/learning/contribution-guide.html) trước khi tạo một pull request.
+```bash
+pnpm install
+pnpm test # 249 test
+pnpm test:coverage # coverage report (≥88% statements)
+pnpm lint
+pnpm build # build tất cả package
+```
-Cảm ơn tất cả mọi người đã đóng góp cho dự án VietScript - Ngôn ngữ lập trình tiếng Việt tại đây!
+Hướng dẫn thêm parser node mới: [CONTRIBUTING.md](CONTRIBUTING.md).
-
-
-
+## Playground
-Tạo ra bởi [contrib.rocks](https://contrib.rocks).
+Demo live: [sandbox.vietscript.org](https://sandbox.vietscript.org)
## License
-[MIT](https://opensource.org/licenses/MIT)
-
-Copyright (c) 2022-present, Nguyen Huu Nguyen Y
+[MIT](https://opensource.org/licenses/MIT) — Copyright (c) 2022-present, Nguyen Huu Nguyen Y
diff --git a/dist-hello/hello.js b/dist-hello/hello.js
new file mode 100644
index 0000000..d7634d2
--- /dev/null
+++ b/dist-hello/hello.js
@@ -0,0 +1,3 @@
+const x = 42;
+console.log(x);
+//# sourceMappingURL=hello.js.map
diff --git a/dist-hello/hello.js.map b/dist-hello/hello.js.map
new file mode 100644
index 0000000..0a62a96
--- /dev/null
+++ b/dist-hello/hello.js.map
@@ -0,0 +1 @@
+{"version":3,"names":[],"sources":[],"sourcesContent":[],"mappings":"","ignoreList":[]}
\ No newline at end of file
diff --git a/dist-test/hello.js b/dist-test/hello.js
new file mode 100644
index 0000000..c1af9f2
--- /dev/null
+++ b/dist-test/hello.js
@@ -0,0 +1,11 @@
+const tên = "Thế giới";
+console.log(`Xin chào, ${tên}!`);
+function cộng(a, b = 0) {
+ return a + b;
+}
+console.log("1 + 2 =", cộng(1, 2));
+console.log("5 + 0 =", cộng(5));
+const mảng = [1, 2, 3, 4, 5];
+const tổng = mảng.reduce((acc, x) => acc + x, 0);
+console.log("Tổng:", tổng);
+//# sourceMappingURL=hello.js.map
diff --git a/dist-test/hello.js.map b/dist-test/hello.js.map
new file mode 100644
index 0000000..0a62a96
--- /dev/null
+++ b/dist-test/hello.js.map
@@ -0,0 +1 @@
+{"version":3,"names":[],"sources":[],"sourcesContent":[],"mappings":"","ignoreList":[]}
\ No newline at end of file
diff --git a/dist-test/stdlib-test.js b/dist-test/stdlib-test.js
new file mode 100644
index 0000000..9358ad9
--- /dev/null
+++ b/dist-test/stdlib-test.js
@@ -0,0 +1,8 @@
+import { ghi, tròn, ngẫu_nhiên, độ_dài } from "@vietscript/stdlib";
+ghi("Xin chào từ stdlib!");
+ghi("Làm tròn 3.7:", tròn(3.7));
+const số_ngẫu = ngẫu_nhiên(1, 100);
+ghi("Số ngẫu nhiên 1-100:", tròn(số_ngẫu));
+const mảng = [1, 2, 3, 4, 5];
+ghi("Độ dài mảng:", độ_dài(mảng));
+//# sourceMappingURL=stdlib-test.js.map
diff --git a/dist-test/stdlib-test.js.map b/dist-test/stdlib-test.js.map
new file mode 100644
index 0000000..0a62a96
--- /dev/null
+++ b/dist-test/stdlib-test.js.map
@@ -0,0 +1 @@
+{"version":3,"names":[],"sources":[],"sourcesContent":[],"mappings":"","ignoreList":[]}
\ No newline at end of file
diff --git a/docs/basics/class.md b/docs/basics/class.md
new file mode 100644
index 0000000..a4b671c
--- /dev/null
+++ b/docs/basics/class.md
@@ -0,0 +1,144 @@
+# Lớp (Class)
+
+## Khai báo class
+
+```vietscript
+lớp Con Mèo {
+ số chân = 4
+ tên: string
+
+ khởi tạo(tên) {
+ đây.tên = tên
+ }
+
+ kêu() {
+ trả về `${đây.tên} meo!`
+ }
+}
+```
+
+| VietScript | JS |
+|---|---|
+| `lớp` | `class` |
+| `khởi tạo` | `constructor` |
+| `đây` | `this` |
+| `kế thừa` | `extends` |
+| `khởi tạo cha` | `super` |
+
+## Kế thừa
+
+```vietscript
+lớp Động Vật {
+ khởi tạo(loại) { đây.loại = loại }
+}
+
+lớp Con Mèo kế thừa Động Vật {
+ khởi tạo(tên) {
+ khởi tạo cha("mèo")
+ đây.tên = tên
+ }
+
+ mô tả() {
+ trả về `${đây.loại}: ${đây.tên}`
+ }
+}
+
+// Syntax khác cho kế thừa:
+lớp Con Chó (Động Vật) {
+ // ...
+}
+```
+
+## Static method / property
+
+```vietscript
+lớp Math Helper {
+ tĩnh PI = 3.14159
+
+ tĩnh bình phương(x) {
+ trả về x * x
+ }
+}
+
+console.log(Math Helper.PI)
+console.log(Math Helper.bình phương(5))
+```
+
+## Getter / setter
+
+```vietscript
+lớp Hình Tròn {
+ khởi tạo(bán kính) {
+ đây._bk = bán kính
+ }
+
+ lấy diện tích() {
+ trả về Math.PI * đây._bk ** 2
+ }
+
+ gán bán kính(giá trị) {
+ đây._bk = giá trị
+ }
+}
+```
+
+## Access modifier
+
+```vietscript
+lớp Tài Khoản {
+ công khai tên: string
+ riêng tư số dư: number
+ bảo vệ ghi log: hàm
+
+ khởi tạo(tên, sốDư) {
+ đây.tên = tên
+ đây.số dư = sốDư
+ }
+}
+```
+
+**Lưu ý:** TypeScript semantics. Ở runtime JS, `riêng tư` không thực sự private — dùng `#field` cho real private.
+
+## Private field `#`
+
+```vietscript
+lớp Đếm {
+ #số lượng = 0
+
+ tăng() {
+ đây.#số lượng++
+ }
+
+ lấy giá trị() {
+ trả về đây.#số lượng
+ }
+}
+```
+
+## Generator method
+
+```vietscript
+lớp Iter {
+ *từ đầu đến cuối(từ, đến) {
+ lặp (biến i = từ; i <= đến; i++) {
+ nhường i
+ }
+ }
+}
+
+hằng số iter = new Iter()
+lặp (biến x của iter.từ đầu đến cuối(1, 3)) {
+ console.log(x) // 1, 2, 3
+}
+```
+
+## Async method
+
+```vietscript
+lớp Dịch Vụ {
+ bất đồng bộ lấy dữ liệu(url) {
+ hằng số res = chờ fetch(url)
+ trả về chờ res.json()
+ }
+}
+```
diff --git a/docs/basics/do-while.md b/docs/basics/do-while.md
deleted file mode 100644
index 9e75c80..0000000
--- a/docs/basics/do-while.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# Câu lệnh khi mà
-
-Câu lệnh lặp khi mà trong VieLang cho phép bạn lặp lại một khối mã chừng nào điều kiện vẫn còn đúng. Đây là một công cụ mạnh mẽ để thực thi mã lặp đi lặp lại dựa trên một điều kiện nhất định.
-
-
-## Cú pháp
-Cú pháp cho câu lệnh lặp khi mà như sau:
-
-```css
-khi mà (điều_kiện) {
- // Khối mã sẽ được lặp lại chừng nào điều kiện còn đúng
-}
-
-```
-
-
-## Tổng kết
-
-Câu lệnh lặp khi mà trong VieLang là một công cụ hữu ích để lặp lại một khối mã chừng nào điều kiện còn đúng. Bằng cách sử dụng câu lệnh này, bạn có thể dễ dàng thực thi các hành động lặp đi lặp lại một cách hiệu quả.
\ No newline at end of file
diff --git a/docs/basics/for.md b/docs/basics/for.md
index 851ff81..dec1f2b 100644
--- a/docs/basics/for.md
+++ b/docs/basics/for.md
@@ -1,28 +1,99 @@
-# Câu lệnh lặp
+# Vòng lặp
-Câu lệnh lặp `lặp` trong VieLang cho phép bạn thực thi một khối mã lặp đi lặp lại trong một khoảng thời gian xác định. Đây là một công cụ hữu ích để lặp qua một phạm vi giá trị cụ thể hoặc để thực thi mã một số lần nhất định.
+## for truyền thống
+```vietscript
+lặp (biến i = 0; i < 10; i++) {
+ console.log(i)
+}
+```
+
+## for ... of
+```vietscript
+hằng số mảng = [1, 2, 3]
+lặp (biến x của mảng) {
+ console.log(x)
+}
+```
-## Cú pháp
-Cú pháp cho câu lệnh lặp như sau:
+## for ... in
-```css
-lặp (khởi_tạo ; điều_kiện ; cập_nhật) {
- // Khối mã sẽ được lặp lại dựa trên các điều kiện xác định
+```vietscript
+hằng số đối tượng = { a: 1, b: 2 }
+lặp (biến khóa trong đối tượng) {
+ console.log(khóa, đối tượng[khóa])
}
+```
+## for await ... of
+
+```vietscript
+bất đồng bộ hàm chạy() {
+ lặp chờ (biến x của luồng bất đồng bộ) {
+ console.log(x)
+ }
+}
```
-### Ví dụ
-Dưới đây là một ví dụ về cách sử dụng câu lệnh lặp trong VieLang:
-```css
-lặp (khai báo i = 0; i < 5; i++) {
- in ra (i);
+## while
+
+```vietscript
+biến i = 0
+khi mà (i < 5) {
+ console.log(i)
+ i++
}
```
+## do ... while
+
+```vietscript
+biến i = 0
+thực hiện {
+ console.log(i)
+ i++
+} khi mà (i < 5)
+```
+
+## break / continue
+
+```vietscript
+lặp (biến i của mảng) {
+ nếu (i === 0) tiếp tục // skip
+ nếu (i > 100) phá vòng lặp // exit
+ console.log(i)
+}
+```
+
+## Labeled break / continue
+
+```vietscript
+ngoài: lặp (biến i = 0; i < 10; i++) {
+ lặp (biến j = 0; j < 10; j++) {
+ nếu (i * j > 20) phá vòng lặp ngoài
+ }
+}
+```
+
+## Destructuring trong for...of
+
+```vietscript
+hằng số cặp = [[1, "a"], [2, "b"]]
+lặp (hằng số [số, chữ] của cặp) {
+ console.log(số, chữ)
+}
+```
-## Tổng kết
+## Tham chiếu
-Câu lệnh lặp `lặp` trong VieLang là một công cụ mạnh mẽ và linh hoạt để lặp qua một phạm vi giá trị hoặc thực thi mã một số lần nhất định. Bằng cách sử dụng câu lệnh này, bạn có thể dễ dàng kiểm soát luồng thực thi của chương trình và thực hiện các hành động lặp đi lặp lại một cách hiệu quả.
\ No newline at end of file
+| VietScript | JS |
+|---|---|
+| `lặp` | `for` |
+| `khi mà` | `while` |
+| `thực hiện` | `do` |
+| `của` | `of` |
+| `trong` | `in` |
+| `chờ` | `await` |
+| `phá vòng lặp` | `break` |
+| `tiếp tục` | `continue` |
diff --git a/docs/basics/function.md b/docs/basics/function.md
index ef71552..73a33b7 100644
--- a/docs/basics/function.md
+++ b/docs/basics/function.md
@@ -1,45 +1,88 @@
-# Định nghĩa và thực thi hàm
+# Hàm
-Hàm VieLang là một khối mã được thiết kế để thực hiện một tác vụ cụ thể. Một hàm Vielang được thực thi khi gọi nó (call it)
+## Khai báo hàm
-## Cú pháp khai báo hàm
-
-Hàm `Vielang` được xác định bằng từ khóa `hàm`, theo sau là tên, theo sau là dấu ngoặc đơn `()`.
+```vietscript
+hàm tính tuổi(năm sinh) {
+ trả về 2024 - năm sinh
+}
-Tên hàm có thể chứa các chữ cái tiếng việt có dấu cách (giống với tên biến)
+hàm chào(tên = "bạn") {
+ console.log(`Xin chào, ${tên}!`)
+}
+```
-Các dấu ngoặc đơn có thể bao gồm các tên tham số được phân tách bằng dấu phẩy: (tham số một, tham số hai, ...)
+## Arrow function
-Mã được hàm thực thi được đặt bên trong dấu ngoặc nhọn: {}
+```vietscript
+hằng số cộng = (a, b) => a + b
+hằng số bình phương = x => x * x
+hằng số xử lý = (dữ liệu) => {
+ trả về dữ liệu.lọc(x => x > 0)
+}
+```
-```js
-hàm tính tuổi(năm sinh) {
- // code to be executed
+## Rest & default parameters
+```vietscript
+hàm tổng(khởi đầu = 0, ...số) {
+ trả về số.reduce((a, b) => a + b, khởi đầu)
}
```
-## Gọi hàm
+## Destructuring parameters
-Hàm sẽ được thực thi khi gọi hàm bằng cú pháp `()`
+```vietscript
+hàm in người({ tên, tuổi = 18 }) {
+ console.log(`${tên}, ${tuổi} tuổi`)
+}
-```js
-hàm tính tuổi(năm sinh) {
- // code to be executed
+hàm đầu cuối([đầu, ...đuôi]) {
+ trả về { đầu, đuôi }
+}
+```
+## Async / await
+
+```vietscript
+bất đồng bộ hàm lấy dữ liệu(url) {
+ hằng số phản hồi = chờ fetch(url)
+ trả về chờ phản hồi.json()
}
+```
+
+## Generator
-tính tuổi()
+```vietscript
+hàm* đếm(từ đầu = 0) {
+ biến i = từ đầu
+ khi mà (đúng) {
+ nhường i++
+ }
+}
```
-## Hàm trả về
+## Async generator
-Sử dụng từ khoá `trả về` để trả về 1 giá trị của hàm
+```vietscript
+bất đồng bộ hàm* đọc từng dòng(file) {
+ // ...
+ nhường dòng
+}
+```
-```js
-hàm tính tuổi(năm sinh) {
- khai báo tuổi = 2024 - năm sinh
- trả về tuổi
+## Hàm dưới dạng giá trị
+
+```vietscript
+hằng số ops = {
+ cộng: (a, b) => a + b,
+ trừ: (a, b) => a - b,
}
-```
\ No newline at end of file
+console.log(ops.cộng(1, 2)) // 3
+```
+
+## Xem thêm
+
+- [Destructuring](../compatibility.md#1-khai-báo-biến)
+- [Class](../compatibility.md#5-lớp)
diff --git a/docs/basics/if.md b/docs/basics/if.md
index 9e3d619..53b8072 100644
--- a/docs/basics/if.md
+++ b/docs/basics/if.md
@@ -1,55 +1,52 @@
# Câu lệnh điều kiện
-Câu lệnh điều kiện trong VieLang cho phép bạn thực thi mã dựa trên việc một điều kiện là đúng hay sai. Câu lệnh điều kiện chính được sử dụng trong VieLang là câu lệnh nếu.
-
-## Cú pháp
-Cú pháp cho câu lệnh nếu như sau:
-
-```js
-nếu (điều_kiện) {
- // Khối mã sẽ được thực thi nếu điều kiện đúng
+## if / else / else-if
+
+```vietscript
+nếu (tuổi >= 18) {
+ console.log("Đủ tuổi")
+} không thì nếu (tuổi >= 16) {
+ console.log("Sắp đủ tuổi")
+} không thì {
+ console.log("Chưa đủ tuổi")
}
```
-**Ví dụ**
-Dưới đây là một ví dụ về cách sử dụng câu lệnh nếu trong VieLang:
+| VietScript | JS |
+|---|---|
+| `nếu` | `if` |
+| `không thì` | `else` |
+| `không thì nếu` | `else if` |
-```js
+## Ternary `?:`
-nếu (a > 2) {
- // Thực hiện hành động khi a lớn hơn 2
-}
+```vietscript
+hằng số nhóm = tuổi >= 18 ? "người lớn" : "trẻ em"
```
-Trong ví dụ trên, nếu giá trị của a lớn hơn 2, khối mã trong dấu ngoặc nhọn {} sẽ được thực thi.
+## switch / case / default
-## Giải thích
-`nếu`: Từ khóa bắt đầu một câu lệnh điều kiện.
-(điều_kiện): Một biểu thức được kiểm tra. Nếu biểu thức này đánh giá là đúng, khối mã bên trong sẽ được thực thi.
-{}: Dấu ngoặc nhọn bao quanh khối mã sẽ được thực thi nếu điều kiện là đúng.
-## Câu lệnh nếu kết hợp với "ngược lại"
-Bạn có thể kết hợp câu lệnh nếu với câu lệnh khác để xử lý trường hợp khi điều kiện không đúng.
+```vietscript
+duyệt (loại) {
+ trường hợp 1:
+ trường hợp 2:
+ console.log("Một hoặc hai")
+ phá vòng lặp
-Cú pháp
+ trường hợp 3:
+ console.log("Ba")
+ phá vòng lặp
-```js
-nếu (a>2){
- // Thực thi code khi a lớn hơn 2
-} ngược lại {
- // Thực thi các trường hợp khác
+ mặc định:
+ console.log("Khác")
}
-
```
-## Câu lệnh nếu lồng nhau
-Bạn cũng có thể sử dụng các câu lệnh nếu lồng nhau để kiểm tra nhiều điều kiện.
-
-Ví dụ:
-```js
-nếu (a > 2) {
- nếu (b > 5) {
- // Thực hiện hành động khi a lớn hơn 2 và b lớn hơn 5
- }
-}
+| VietScript | JS |
+|---|---|
+| `duyệt` | `switch` |
+| `trường hợp` | `case` |
+| `mặc định` | `default` |
+| `phá vòng lặp` | `break` |
-```
\ No newline at end of file
+Fall-through (nhiều `case` liên tiếp không có `break`) giống JS.
diff --git a/docs/basics/module.md b/docs/basics/module.md
new file mode 100644
index 0000000..52b0668
--- /dev/null
+++ b/docs/basics/module.md
@@ -0,0 +1,65 @@
+# Module (Import / Export)
+
+## Import
+
+```vietscript
+// Side-effect import
+sử dụng "./setup"
+
+// Default import
+sử dụng mặc định từ "./module"
+
+// Named imports
+sử dụng { a, b, c } từ "./module"
+
+// Rename
+sử dụng { a như là alias } từ "./module"
+
+// Namespace
+sử dụng * như là Mod từ "./module"
+
+// Mix
+sử dụng mặc định, { a, b } từ "./module"
+```
+
+| VietScript | JS |
+|---|---|
+| `sử dụng` | `import` |
+| `từ` | `from` |
+| `như là` | `as` |
+
+## Export
+
+```vietscript
+// Named export
+cho phép hằng số PI = 3.14
+cho phép hàm cộng(a, b) { trả về a + b }
+cho phép lớp Con Mèo {}
+
+// Named export (listed)
+hằng số x = 1
+hằng số y = 2
+cho phép { x, y }
+
+// Default export
+cho phép mặc định hàm {}
+cho phép mặc định lớp App {}
+
+// Re-export
+cho phép * từ "./khác"
+cho phép { a, b } từ "./khác"
+```
+
+| VietScript | JS |
+|---|---|
+| `cho phép` | `export` |
+| `cho phép mặc định` | `export default` |
+
+## Dynamic import
+
+```vietscript
+bất đồng bộ hàm chạy() {
+ hằng số mod = chờ sử dụng("./có thể thay đổi")
+ mod.mặc định()
+}
+```
diff --git a/docs/basics/operators.md b/docs/basics/operators.md
index 4536ecf..e735ed4 100644
--- a/docs/basics/operators.md
+++ b/docs/basics/operators.md
@@ -1,62 +1,109 @@
# Toán tử
-Vielang hỗ trợ các kiểu toán tử sau: - Toán tử số học - Toán tử gán - Toán tử so sánh - Toán tử logic
+Giống hoàn toàn JavaScript. VietScript không thêm / bớt toán tử nào.
-## Toán tử số học
+## Số học
-Ví dụ:
+| Toán tử | Ý nghĩa |
+|---|---|
+| `+` | Cộng |
+| `-` | Trừ |
+| `*` | Nhân |
+| `/` | Chia |
+| `%` | Chia lấy dư |
+| `**` | Luỹ thừa |
+| `++` | Tăng 1 đơn vị (prefix/postfix) |
+| `--` | Giảm 1 đơn vị (prefix/postfix) |
-```js
- khai báo a = 5;
- khai báo x = 12 * a
+```vietscript
+biến x = 10
+x++
+x = 2 ** 8 // 256
```
-| Toán tử | Thông tin |
-| ------- | ------------- |
-| + | Cộng |
-| - | Trừ |
-| \* | Nhân |
-| / | Chia |
-| % | Chia lấy dư |
-| \*\* | Luỹ thừa |
-| ++ | Tăng 1 đơn vị |
-| -- | Giảm 1 dơn vị |
-
-## Toán tử gán
-
-```js
-khai báo x = 10;
-x += 5;
+## Gán (Assignment)
+
+| Toán tử | Tương đương |
+|---|---|
+| `=` | gán |
+| `+=` / `-=` / `*=` / `/=` / `%=` / `**=` | arithmetic assign |
+| `&=` / `\|=` / `^=` | bitwise assign |
+| `<<=` / `>>=` / `>>>=` | shift assign |
+| `\|\|=` / `&&=` / `??=` | logical assign |
+
+```vietscript
+biến x = 10
+x += 5 // 15
+x ??= 0 // x bằng null/undefined thì gán 0
+x \|\|= "mặc định"
+```
+
+## So sánh
+
+| Toán tử | Ý nghĩa |
+|---|---|
+| `==` / `===` | bằng (loose/strict) |
+| `!=` / `!==` | khác (loose/strict) |
+| `<` / `>` / `<=` / `>=` | so sánh thứ tự |
+
+## Logic
+
+| Toán tử | Ý nghĩa |
+|---|---|
+| `&&` | và |
+| `\|\|` | hoặc |
+| `!` | phủ định |
+| `??` | nullish coalescing |
+
+```vietscript
+hằng số tên = input ?? "mặc định" // null/undefined → "mặc định"
+hằng số ok = x && y
+```
+
+## Bitwise
+
+| Toán tử | Ý nghĩa |
+|---|---|
+| `&` / `\|` / `^` | AND / OR / XOR |
+| `~` | NOT |
+| `<<` / `>>` / `>>>` | Shift left / right arithmetic / right logical |
+
+## Ternary
+
+```vietscript
+hằng số nhóm = tuổi >= 18 ? "người lớn" : "trẻ em"
+```
+
+## Keyword-based operators
+
+| Toán tử | VietScript |
+|---|---|
+| `typeof` | `kiểu của` |
+| `instanceof` | `là kiểu` |
+| `in` | `trong` |
+| `delete` | `xoá` |
+| `void` | `void` |
+| `new` | `new` |
+
+```vietscript
+nếu (kiểu của x === "number") { ... }
+nếu (con mèo là kiểu Động Vật) { ... }
+nếu ("prop" trong đối tượng) { ... }
+xoá đối tượng.prop
```
-| Toán tử | Ví dụ | Tương đương với |
-| ------- | --------- | --------------- |
-| = | x = y | x = y |
-| += | x += y | x = x + y |
-| -= | x -= y | x = x - y |
-| \*= | x \*= y | x = x \* y |
-| /= | x /= y | x = x / y |
-| %= | x %= y | x = x % y |
-| \*\*= | x \*\*= y | x = x \*\* y |
-
-## Toán tử so sánh
-
-| Toán tử | Thông tin |
-| ------- | ---------------------------- |
-| == | bằng giá trị |
-| === | bằng giá trị và kiểu dữ liệu |
-| != | khác giá trị |
-| !== | khác giá trị và kiểu dữ liệu |
-| \> | lớn hơn |
-| \< | nhỏ hơn |
-| \>= | lớn hơn hoặc bằng |
-| \<= | nhỏ hơn hoặc bằng |
-| \? | Toán tử bậc 3 |
-
-## Toán tử logic
-
-| Toán tử | Thông tin |
-| ------- | --------- |
-| \&\& | và |
-| \|\| | hoặc |
-| ! | phủ định |
+## Spread `...`
+
+```vietscript
+hằng số kết hợp = [...a, ...b]
+hằng số trộn = { ...mặc định, ...tùy chỉnh }
+f(...args)
+```
+
+## Optional chaining `?.`
+
+```vietscript
+hằng số tên = người?.thông tin?.tên
+hằng số đầu tiên = mảng?.[0]
+hằng số gọi = đối tượng?.phương thức?.()
+```
diff --git a/docs/basics/switch-case.md b/docs/basics/switch-case.md
deleted file mode 100644
index 2d84ad0..0000000
--- a/docs/basics/switch-case.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# Câu lệnh duyệt điều kiện
-
-Câu lệnh duyệt trong VieLang cho phép bạn kiểm tra giá trị của một biến và thực thi các khối mã khác nhau dựa trên giá trị đó. Đây là một công cụ mạnh mẽ để thay thế cho các câu lệnh nếu...khác phức tạp khi bạn cần kiểm tra nhiều giá trị khác nhau của cùng một biến.
-
-
-## Cú pháp
-
-```css
-duyệt (biến) {
- trường hợp giá_trị_1:
- // Khối mã sẽ được thực thi nếu biến có giá trị là giá_trị_1
- trường hợp giá_trị_2:
- // Khối mã sẽ được thực thi nếu biến có giá trị là giá_trị_2
- ...
- mặc định:
- // Khối mã sẽ được thực thi nếu biến không khớp với bất kỳ giá trị nào ở trên
-}
-```
-
-### Ví dụ
-Dưới đây là một ví dụ về cách sử dụng câu lệnh duyệt trong VieLang:
-
-
-```css
-duyệt (tuổi_tác) {
- trường hợp 20:
- in ra ("bạn 20 tuổi");
- trường hợp 12:
- in ra ("bạn 12 tuổi");
- mặc định:
- in ra ("mặc định ở");
-}
-
-```
-
-
-## Tổng kết
-Câu lệnh duyệt trong VieLang là một công cụ mạnh mẽ để kiểm tra giá trị của một biến và thực thi các khối mã khác nhau dựa trên giá trị đó. Bằng cách sử dụng câu lệnh này, bạn có thể đơn giản hóa mã nguồn khi cần kiểm tra nhiều giá trị khác nhau của cùng một biến.
\ No newline at end of file
diff --git a/docs/basics/variables.md b/docs/basics/variables.md
index cdb13e7..701d256 100644
--- a/docs/basics/variables.md
+++ b/docs/basics/variables.md
@@ -1,62 +1,111 @@
+# Biến và kiểu dữ liệu
-# Biến - Kiểu dữ liệu
+## 3 cách khai báo biến
-Trang này sẽ nói về cú pháp của ***VieLang***, cách khai báo biến và các kiểu dữ liệu
+| VietScript | JS |
+|---|---|
+| `khai báo x = 1` | `var x = 1` |
+| `biến x = 1` | `let x = 1` |
+| `hằng số x = 1` | `const x = 1` |
-## Tổng quan
-
-Cú pháp VieLang dựa trên ngôn ngữ lập trình `Javascript`, do đó dễ dàng tiếp cận cho người đã từng lập trình hay kể cả chưa biết gì về lập trình. Vì ngôn ngữ hỗ trợ `tiếng Việt`, một ngôn ngữ mẹ đẻ của người Việt
-
-So sánh
-
-- Javascript:
-
-```js
- let name = 'Viên';
+```vietscript
+khai báo tuổi = 22 // var
+biến số lần = 0 // let
+hằng số PI = 3.14 // const
```
-- Vielang:
+## Tên biến
-```js
- khai báo tên = 'Viên' // Hỗ trợ tiếng việt cho cả tên biến
+Tên biến hỗ trợ chữ Latin, chữ Việt có dấu, và dấu cách giữa các từ:
+```vietscript
+hằng số tên = "Viên"
+hằng số tuổi hiện tại = 22
+hằng số Con Mèo Xinh = {}
```
-Thử ngay tại [https://vietscript.dev/](https://vietscript.dev/).
-
-## Khai báo
+Khi transpile sang JS, dấu cách sẽ được thay bằng `_`:
-VieLang có 2 cách để khai báo biến:
+```
+tuổi hiện tại → tuổi_hiện_tại
+Con Mèo Xinh → Con_Mèo_Xinh
+```
+JS hỗ trợ Unicode identifier, nên tên biến tiếng Việt có dấu hoàn toàn hợp lệ.
-`khai báo`:
- - Sử dụng từ khoá `khai báo` để khai báo một giá trị
+## Không được dùng keyword làm tên biến
-`hằng số`:
- - Sử dụng từ khoá `hằng số` để khai báo một giá trị chỉ đọc, không được gán lại
+Các từ khóa tiếng Việt không thể dùng làm tên biến:
-## Biến
+```vietscript
+✗ khai báo lớp = 1 // `lớp` là keyword CLASS
+✗ khai báo của = "a" // `của` là keyword OF
+✓ khai báo cái lớp = 1 // OK, "cái lớp" là identifier (không bắt đầu bằng keyword)
+```
-- Tên biến trong **VieLang** được khai báo bằng chữ, hỗ trợ tiếng Việt và dấu cách
+## Destructuring
-- Ví dụ
+```vietscript
+hằng số { tên, tuổi } = người
+hằng số [đầu, ...còn lại] = mảng
+hằng số { a: đổi tên, b = 0, c: { d } } = phức tạp
+```
-```js
+## Kiểu dữ liệu nguyên thủy
+
+| VietScript | JS |
+|---|---|
+| `đúng` / `sai` | `true` / `false` |
+| `rỗng` | `null` |
+| `không xác định` | `undefined` |
+| `NaN` | `NaN` |
+| `vô cực` | `Infinity` |
+| Số (`42`, `3.14`, `0xff`, `1_000`, `10n`) | number / bigint |
+| Chuỗi (`"..."`, `'...'`, `` `...` ``) | string |
+
+### Số
+
+```vietscript
+hằng số nguyên = 42
+hằng số thập phân = 3.14
+hằng số khoa học = 1.5e10
+hằng số hex = 0xff
+hằng số octal = 0o17
+hằng số binary = 0b1010
+hằng số phân cách = 1_000_000
+hằng số big = 12345n // BigInt
+```
-khai báo tên = "Viên"
-khai báo tuổi = 22
+### Chuỗi
+```vietscript
+hằng số đơn = 'hello'
+hằng số kép = "world"
+hằng số template = `Xin chào ${tên}!`
+hằng số nhiều dòng = `dòng 1
+dòng 2`
```
-## Kiểu dữ liệu
+Template literal hỗ trợ interpolation `${biểu thức}` và nested backtick.
+
+## Mảng & object
+
+```vietscript
+hằng số mảng = [1, 2, 3, ...khác]
+hằng số đối tượng = {
+ tên: "Tú",
+ tuổi: 25,
+ ...mặc định,
+ [khóa động]: giá trị,
+ phương thức() { trả về 1 },
+ lấy prop() { trả về 2 },
+ gán prop(v) {},
+}
+```
- - Có 5 kiểu dữ liệu nguyên thuỷ:
-
- - Boolean: `đúng`, `sai`
- - null: `vô giá trị`
- - undefined: `không xác định`
- - Số: số nguyên hoặc số thập phân, Ví dụ: `20`, `3.14`
- - Chuỗi: một chuỗi kí tự đại diện cho văn bản, Ví dụ: `'vienhuynh'`
+## Xem thêm
- - Mảng
- - Đối tượng (object)
\ No newline at end of file
+- [Toán tử](operators.md)
+- [Hàm](function.md)
+- [Điều khiển luồng](../compatibility.md#3-điều-kiện)
+- [Destructuring đầy đủ](../compatibility.md#1-khai-báo-biến)
diff --git a/docs/compatibility.md b/docs/compatibility.md
new file mode 100644
index 0000000..61de92d
--- /dev/null
+++ b/docs/compatibility.md
@@ -0,0 +1,316 @@
+# Ma trận tương thích cú pháp
+
+Đây là **nguồn sự thật duy nhất** về cú pháp nào VietScript đã hỗ trợ, đang làm dở, hay chưa có. Mọi thay đổi parser/codegen đều phải cập nhật trang này.
+
+## Kiến trúc pipeline
+
+```
+Source .vjs → Tokenizer → Parser → Babel-compatible AST → @babel/generator → JavaScript
+```
+
+Vì AST theo chuẩn Babel, codegen dùng sẵn `@babel/generator` — mọi cú pháp JS mà Babel hỗ trợ thì codegen "miễn phí". Công việc hoàn thiện chủ yếu nằm ở **parser** (tokenize + build đúng shape AST) và **test**.
+
+## Quy ước ký hiệu
+
+| Ký hiệu | Ý nghĩa |
+|---|---|
+| ✅ | Hoàn thiện: parser + codegen + test positive + test codegen đều có |
+| 🟡 | Một phần: parse được nhưng thiếu test, thiếu edge case, hoặc codegen output chưa đúng |
+| ❌ | Chưa hỗ trợ: parse sẽ throw hoặc sinh AST sai |
+| — | Không áp dụng |
+
+## Ưu tiên
+
+- **P1** — Cốt lõi JS hiện đại, không có thì ngôn ngữ "thiếu"
+- **P2** — Dang dở, cần hoàn thiện để ổn định
+- **P3** — Niche, có thể hoãn sau
+
+---
+
+## 1. Khai báo biến
+
+| Feature | JS | VietScript | Parser | Codegen | Test | Ưu tiên | Ghi chú |
+|---|---|---|:---:|:---:|:---:|:---:|---|
+| `var` | `var x = 1` | `khai báo x = 1` | ✅ | ✅ | ✅ | — | |
+| `let` | `let x = 1` | `let x = 1` | ✅ | ✅ | ✅ | — | Chưa có từ tiếng Việt |
+| `const` | `const x = 1` | `hằng số x = 1` | ✅ | ✅ | ✅ | — | |
+| Multi-declarator | `let a = 1, b` | `let a = 1, b` | ✅ | ✅ | ✅ | — | |
+| Destructuring object | `const {a,b} = o` | `hằng số {a,b} = o` | ✅ | ✅ | ✅ | — | |
+| Destructuring array | `const [a,b] = arr` | `hằng số [a,b] = arr` | ✅ | ✅ | ✅ | — | Hỗ trợ hole, default, rest, nested |
+| Destructuring default | `const {a=1} = o` | `hằng số {a=1} = o` | ✅ | ✅ | ✅ | — | |
+| Destructuring rename | `const {a:b} = o` | `hằng số {a:b} = o` | ✅ | ✅ | ✅ | — | |
+| Destructuring nested | `const {a:{b}} = o` | `hằng số {a:{b}} = o` | ✅ | ✅ | ✅ | — | |
+| Destructuring rest | `const {a,...rest} = o` | `hằng số {a,...rest} = o` | ✅ | ✅ | ✅ | — | |
+| Destructuring params | `f({a,b})` | `hàm f({a,b})` | ✅ | ✅ | ✅ | — | |
+
+## 2. Hàm
+
+| Feature | JS | VietScript | Parser | Codegen | Test | Ưu tiên | Ghi chú |
+|---|---|---|:---:|:---:|:---:|:---:|---|
+| Function declaration | `function f(){}` | `hàm f(){}` | ✅ | ✅ | ✅ | — | |
+| Function expression | `const f = function(){}` | `khai báo f = hàm(){}` | ✅ | ✅ | ✅ | — | |
+| Arrow function | `() => x` | `() => x` | ✅ | ✅ | ✅ | — | |
+| Arrow với body | `() => { return x }` | `() => { trả về x }` | ✅ | ✅ | ✅ | — | |
+| Default params | `f(a = 1)` | `f(a = 1)` | ✅ | ✅ | ✅ | — | |
+| Rest params | `f(...args)` | `f(...args)` | ✅ | ✅ | ✅ | — | Chỉ cho phép ở vị trí cuối |
+| Destructuring params | `f({a,b})` | `hàm f({a,b})` | ✅ | ✅ | ✅ | — | Covered trong object/array pattern tests |
+| Async function | `async function f(){}` | `bất đồng bộ hàm f(){}` | ✅ | ✅ | ✅ | — | |
+| Generator | `function* g(){}` | `hàm* g(){}` | ✅ | ✅ | ✅ | — | |
+| Async generator | `async function* g(){}` | `bất đồng bộ hàm* g(){}` | ✅ | ✅ | ✅ | — | |
+| `return` | `return x` | `trả về x` | ✅ | ✅ | ✅ | — | |
+| `yield` | `yield x` | `nhường x` | ✅ | ✅ | ✅ | — | |
+| `yield*` | `yield* g()` | `nhường* g()` | ✅ | ✅ | ✅ | — | |
+| `await` | `await p` | `chờ p` | ✅ | ✅ | ✅ | — | |
+
+## 3. Điều kiện
+
+| Feature | JS | VietScript | Parser | Codegen | Test | Ưu tiên | Ghi chú |
+|---|---|---|:---:|:---:|:---:|:---:|---|
+| if | `if (c) {}` | `nếu (c) {}` | ✅ | ✅ | ✅ | — | |
+| if-else | `if (c) {} else {}` | `nếu (c) {} không thì {}` | ✅ | ✅ | ✅ | — | |
+| else-if chain | `else if (c)` | `không thì nếu (c)` | ✅ | ✅ | ✅ | — | |
+| Ternary | `c ? a : b` | `c ? a : b` | ✅ | ✅ | ✅ | — | ConditionalExpression AST |
+| switch | `switch(x) {}` | `duyệt (x) {}` | ✅ | ✅ | ✅ | — | |
+| case | `case 1:` | `trường hợp 1:` | ✅ | ✅ | ✅ | — | |
+| default case | `default:` | `mặc định:` | ✅ | ✅ | ✅ | — | |
+| case fall-through | multiple `case:` | multiple `trường hợp:` | ✅ | ✅ | ✅ | — | |
+
+## 4. Vòng lặp
+
+| Feature | JS | VietScript | Parser | Codegen | Test | Ưu tiên | Ghi chú |
+|---|---|---|:---:|:---:|:---:|:---:|---|
+| for truyền thống | `for (i=0; i> >>>` | giống JS | ✅ | ✅ | ✅ | — | |
+| Assignment ops | `= += -= ...` | giống JS | ✅ | ✅ | ✅ | — | |
+| Logical assignment | `\|\|= &&= ??=` | giống JS | ✅ | ✅ | ✅ | — | |
+| Update | `++ --` | giống JS | ✅ | ✅ | ✅ | — | Cả prefix + postfix |
+| `typeof` | `typeof x` | `kiểu của x` | ✅ | ✅ | ✅ | — | |
+| `instanceof` | `x instanceof A` | `x là kiểu A` | ✅ | ✅ | ✅ | — | |
+| `in` | `k in o` | `k trong o` | ✅ | ✅ | ✅ | — | Phân biệt với for...in qua context |
+| `delete` | `delete o.x` | `xoá o.x` | ✅ | ✅ | ✅ | — | |
+| `void` | `void 0` | `void 0` | ✅ | ✅ | ✅ | — | |
+| Member (dot) | `o.x` | `o.x` | ✅ | ✅ | ✅ | — | |
+| Member (bracket) | `o["x"]` | `o["x"]` | ✅ | ✅ | ✅ | — | |
+| Optional chaining | `o?.x` | `o?.x` | ✅ | ✅ | ✅ | — | Hỗ trợ `?.b`, `?.[k]`, chained |
+| Call | `f(a, b)` | `f(a, b)` | ✅ | ✅ | ✅ | — | |
+| New | `new A()` | `new A()` | ✅ | ✅ | ✅ | — | NewExpression với args + SpreadElement |
+| this | `this` | `đây` | ✅ | ✅ | ✅ | — | |
+| super | `super.x` | `khởi tạo cha.x` | ✅ | ✅ | ✅ | — | |
+| Array literal | `[1,2,3]` | `[1,2,3]` | ✅ | ✅ | ✅ | — | |
+| Object literal | `{a:1}` | `{a:1}` | ✅ | ✅ | ✅ | — | |
+| Shorthand property | `{a}` | `{a}` | ✅ | ✅ | ✅ | — | |
+| Computed key | `{[k]:1}` | `{[k]:1}` | ✅ | ✅ | ✅ | — | |
+| Object method | `{foo(){}}` | `{foo(){}}` | ✅ | ✅ | ✅ | — | |
+| Object getter/setter | `{get x(){}}` | `{lấy x(){}}` | ✅ | ✅ | ✅ | — | |
+| Spread array | `[...a]` | `[...a]` | ✅ | ✅ | ✅ | — | |
+| Spread object | `{...o}` | `{...o}` | ✅ | ✅ | ✅ | — | |
+| Spread call | `f(...args)` | `f(...args)` | ✅ | ✅ | ✅ | — | |
+
+## 9. Giá trị (Literals)
+
+| Feature | JS | VietScript | Parser | Codegen | Test | Ưu tiên | Ghi chú |
+|---|---|---|:---:|:---:|:---:|:---:|---|
+| Số nguyên | `42` | `42` | ✅ | ✅ | ✅ | — | |
+| Số thập phân | `3.14` | `3.14` | ✅ | ✅ | ✅ | — | |
+| Scientific | `1e5` | `1e5` | ✅ | ✅ | ✅ | — | |
+| Hex/Octal/Binary | `0xff 0o7 0b10` | giống JS | ✅ | ✅ | ✅ | — | |
+| BigInt | `10n` | `10n` | ✅ | ✅ | ✅ | — | Hỗ trợ cả hex BigInt `0xffn` |
+| Numeric separator | `1_000_000` | `1_000_000` | ✅ | ✅ | ✅ | — | |
+| String `"..."` | `"hi"` | `"hi"` | ✅ | ✅ | ✅ | — | |
+| String `'...'` | `'hi'` | `'hi'` | ✅ | ✅ | ✅ | — | |
+| Escape sequences | `"\n \t \u00e1"` | giống JS | ✅ | ✅ | ✅ | — | Hỗ trợ `\n`, `\t`, `\r`, `\b`, `\f`, `\v`, `\0`, `\xFF`, `\uFFFF`, `\u{1F600}` |
+| Template literal | `` `hi` `` | `` `hi` `` | ✅ | ✅ | ✅ | — | |
+| Template interpolation | `` `${x}` `` | `` `${x}` `` | ✅ | ✅ | ✅ | — | Hỗ trợ nested braces, escape |
+| Tagged template | `` tag`x${y}` `` | `` tag`x${y}` `` | ✅ | ✅ | ✅ | — | |
+| Boolean | `true false` | `đúng sai` | ✅ | ✅ | ✅ | — | |
+| null | `null` | `null` | ✅ | ✅ | ✅ | — | Chưa có từ tiếng Việt |
+| undefined | `undefined` | `không xác định` | ✅ | ✅ | ✅ | — | |
+| NaN | `NaN` | `NaN` | ✅ | ✅ | ✅ | — | |
+| Infinity | `Infinity` | `vô cực` | ✅ | ✅ | ✅ | — | |
+| Regex literal | `/x/g` | `/x/g` | ✅ | ✅ | ✅ | — | Context-sensitive tokenizer |
+
+## 10. Câu lệnh khác
+
+| Feature | JS | VietScript | Parser | Codegen | Test | Ưu tiên | Ghi chú |
+|---|---|---|:---:|:---:|:---:|:---:|---|
+| Block | `{ ... }` | `{ ... }` | ✅ | ✅ | ✅ | — | |
+| Expression statement | `x;` | `x;` | ✅ | ✅ | ✅ | — | |
+| Empty statement | `;` | `;` | ✅ | ✅ | ✅ | — | |
+| Debugger | `debugger` | `debugger` | ✅ | ✅ | ✅ | — | |
+| with | `with(o){}` | `with(o){}` | ✅ | ✅ | ✅ | — | Deprecated trong strict mode |
+| Labeled | `label: stmt` | `label: stmt` | ✅ | ✅ | ✅ | — | |
+
+## 11. Comment
+
+| Feature | JS | VietScript | Parser | Codegen | Test | Ưu tiên | Ghi chú |
+|---|---|---|:---:|:---:|:---:|:---:|---|
+| Single-line | `// ...` | — | ✅ | — | ✅ | — | Bỏ qua trong tokenizer |
+| Multi-line | `/* ... */` | — | ✅ | — | ✅ | — | |
+| JSDoc | `/** ... */` | — | ❌ | — | ❌ | P3 | Có thể hoãn |
+
+## 12. Tính năng chuyên biệt VietScript
+
+| Feature | Trạng thái | Ghi chú |
+|---|:---:|---|
+| Identifier có dấu cách tiếng Việt | ✅ | `số chân`, `Con Mèo` — hoạt động |
+| Identifier mangling (Babel output) | ✅ | Output dùng Unicode trực tiếp: `xin chào` → `xin_chào` |
+| Source map | ✅ | Sinh qua `@babel/generator`, CLI run + build hỗ trợ |
+| Custom element `` | ❌ | Post-v1 (dùng plugin Vite/Webpack) |
+| CLI | ✅ | `vietscript run/build/watch/check` |
+| Vite plugin | ✅ | Source maps + transform `.vjs` |
+| Webpack loader | ✅ | Source maps qua `this.callback` |
+| Node ESM loader | ✅ | `node --import '@vietscript/cli/register' file.vjs` |
+| Bun plugin | ✅ | `@vietscript/cli/bun-plugin` |
+| Stdlib | ✅ | `@vietscript/stdlib` — wrappers tiếng Việt cho JS globals |
+
+---
+
+## Bảng tổng hợp ưu tiên
+
+### P1 — Phải có (Phase 1)
+
+Những cú pháp này quyết định cảm giác "dùng được" của ngôn ngữ:
+
+1. Arrow function (`=>`)
+2. Destructuring (object + array, cả declaration lẫn param)
+3. Spread/Rest (`...` trong array/object/call/param)
+4. Template literal interpolation (`${...}`)
+5. `for...of` / `for...in`
+6. Default parameters (`f(a = 1)`)
+
+### P2 — Hoàn thiện phần dang dở (Phase 2)
+
+Cần cho ổn định, giảm surprise:
+
+- Private field `#x`, getter/setter
+- Optional chaining edge case (`?.()`, `?.[]`)
+- Async generator, test đầy đủ cho yield/await
+- Logical assignment (`||=`, `&&=`, `??=`)
+- Hex/octal/binary numeric literal
+- Regex literal
+- Shorthand property, computed key object
+- Escape sequences trong string
+- Comment handling (xác minh + test)
+- Labeled break/continue
+- Hoàn thiện codegen test cho mọi node hiện có
+
+### P3 — Niche, có thể hoãn
+
+- Dynamic import, import assertion
+- BigInt, tagged template
+- Infinity literal explicit
+- `void` operator
+- `debugger`, `with` statement
+- JSDoc
+
+---
+
+## Keyword tiếng Việt
+
+Tất cả keyword JS đã có bản tiếng Việt tương đương (đã thêm trong Phase 0). Giữ alias tiếng Anh để không break code cũ.
+
+| JS | Tiếng Việt |
+|---|---|
+| `var` | `khai báo` |
+| `let` | `biến` |
+| `const` | `hằng số` |
+| `function` | `hàm` |
+| `return` | `trả về` |
+| `if/else` | `nếu` / `không thì` |
+| `for` | `lặp` |
+| `while` | `khi mà` |
+| `do` | `thực hiện` |
+| `break` | `phá vòng lặp` |
+| `continue` | `tiếp tục` |
+| `switch/case/default` | `duyệt` / `trường hợp` / `mặc định` |
+| `try/catch/finally/throw` | `thử` / `bắt lỗi` / `cuối cùng` / `báo lỗi` |
+| `class/extends/super/constructor` | `lớp` / `kế thừa` / `khởi tạo cha` / `khởi tạo` |
+| `import/export/from/as` | `sử dụng` / `cho phép` / `từ` / `như là` |
+| `async/await` | `bất đồng bộ` / `chờ` |
+| `yield` | `nhường` |
+| `this` | `đây` |
+| `null` | `rỗng` |
+| `undefined` | `không xác định` |
+| `Infinity` | `vô cực` |
+| `instanceof` | `là kiểu` |
+| `typeof` | `kiểu của` |
+| `in` (operator / for...in) | `trong` |
+| `of` (for...of) | `của` |
+| `delete` | `xoá` |
+| `static` | `tĩnh` |
+| `public/private/protected` | `công khai` / `riêng tư` / `bảo vệ` |
+| `get/set` | `lấy` / `gán` |
+| `true/false` | `đúng` / `sai` |
+
+---
+
+## Cập nhật matrix
+
+Khi thêm/hoàn thiện 1 feature:
+
+1. Đổi ký hiệu trong bảng tương ứng (❌ → 🟡 → ✅).
+2. Nếu feature ảnh hưởng sang node khác (ví dụ destructuring ảnh hưởng `VariableDeclaration` lẫn `ParameterList`), cập nhật tất cả dòng liên quan.
+3. Nếu thay đổi keyword tiếng Việt, cập nhật cả bảng "Từ tiếng Việt còn thiếu" và [keyword.enum.ts](../packages/shared/parser/keyword.enum.ts).
+4. Cập nhật docs trong `docs/basics/` nếu có.
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 7ba7d96..5f5b5cb 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -1,19 +1,121 @@
-# Bắt đầu ngay
+# Bắt đầu nhanh
-## Sử dụng trình duyệt của VietScript
-Playground của VietScript là một môi trường trực tuyến nơi bạn có thể viết, thử nghiệm và chạy mã VietScript trực tiếp trên trình duyệt mà không cần cài đặt thêm bất kỳ phần mềm nào.
-
-
-## Cài đặt và thử nghiệm
-VietScript được phát triển theo phương pháp TDD (Test Driven Development), các trường hợp sử dụng trong ngôn ngữ được viết thành các test case và được đặt trong các thư mục `__test__`. Để chạy các test case, bạn cần cài đặt NodeJS và PNPM, sau đó các chạy lệnh sau:
+## Cài đặt
```bash
-# Cài đặt PNPM
npm install -g pnpm
-# Cài đặt các thư viện
+git clone https://github.com/imrim12/vietscript.git
+cd vietscript
pnpm install
+pnpm -r build
+```
+
+## Chạy file `.vjs` đầu tiên
+
+Tạo file `hello.vjs`:
+
+```vietscript
+hằng số tên = "Thế giới"
+console.log(`Xin chào, ${tên}!`)
+```
+
+Chạy bằng CLI:
+
+```bash
+node packages/cli/bin/vietscript.mjs run hello.vjs
+```
+
+Output:
+
+```
+Xin chào, Thế giới!
+```
+
+## Các lệnh CLI
+
+```bash
+vietscript run # Chạy file .vjs
+vietscript build [outDir] # Build thư mục .vjs → JS (outDir mặc định: dist)
+vietscript watch [outDir] # Build + watch thay đổi
+vietscript check # Parse không chạy, chỉ kiểm tra cú pháp
+```
+
+## Chạy với Node loader (không cần CLI)
+
+```bash
+node --import '@vietscript/cli/register' hello.vjs
+```
+
+Node sẽ tự động transpile file `.vjs` trước khi import.
+
+## Chạy với Bun
+
+```ts
+// plugin-preload.ts
+import plugin from "@vietscript/cli/bun-plugin";
+Bun.plugin(plugin);
+
+await import("./hello.vjs");
+```
+
+```bash
+bun --preload ./plugin-preload.ts ./hello.vjs
+```
+
+## Vite / Webpack
+
+### Vite
+
+```ts
+// vite.config.ts
+import vietscript from "@vietscript/plugin-vite";
+
+export default {
+ plugins: [vietscript()],
+};
+```
+
+Trong file code bạn: `import "./app.vjs"` bình thường.
+
+### Webpack
+
+```js
+// webpack.config.js
+module.exports = {
+ module: {
+ rules: [
+ {
+ test: /\.vjs$/,
+ loader: "@vietscript/plugin-webpack",
+ },
+ ],
+ },
+};
+```
+
+## Stdlib tiếng Việt
+
+Gói `@vietscript/stdlib` cung cấp wrappers tiếng Việt cho JS globals:
+
+```vietscript
+sử dụng { ghi, tròn, ngẫu nhiên } từ "@vietscript/stdlib"
+
+ghi("Xin chào!")
+ghi("Pi =", tròn(3.14 * 100) / 100)
+ghi("Số may mắn:", tròn(ngẫu nhiên(1, 100)))
+```
+
+Chi tiết tại [stdlib.md](stdlib.md).
+
+## Phát triển parser
+
+Dự án phát triển theo TDD. Mỗi cú pháp mới có 3 loại test: parser positive, parser negative, codegen snapshot.
+
+```bash
+pnpm test # Chạy toàn bộ test
+pnpm test:coverage # Test + coverage report
+pnpm build # Build tất cả package
+```
-# Chạy unit test
-pnpm test
-```
\ No newline at end of file
+Hướng dẫn thêm parser node: [CONTRIBUTING.md](../CONTRIBUTING.md).
diff --git a/docs/migration-from-js.md b/docs/migration-from-js.md
new file mode 100644
index 0000000..3180c9e
--- /dev/null
+++ b/docs/migration-from-js.md
@@ -0,0 +1,159 @@
+# Chuyển từ JavaScript sang VietScript
+
+VietScript giữ nguyên ngữ nghĩa JavaScript 100% — chỉ thay từ khóa tiếng Anh bằng tiếng Việt. Ngoài keyword, mọi cú pháp giống hệt.
+
+## Bảng chuyển đổi keyword
+
+| JavaScript | VietScript |
+|---|---|
+| `var` | `khai báo` |
+| `let` | `biến` |
+| `const` | `hằng số` |
+| `function` | `hàm` |
+| `return` | `trả về` |
+| `if` / `else` | `nếu` / `không thì` |
+| `for` | `lặp` |
+| `while` | `khi mà` |
+| `do` | `thực hiện` |
+| `of` | `của` |
+| `in` | `trong` |
+| `break` | `phá vòng lặp` |
+| `continue` | `tiếp tục` |
+| `switch` / `case` / `default` | `duyệt` / `trường hợp` / `mặc định` |
+| `try` / `catch` / `finally` / `throw` | `thử` / `bắt lỗi` / `cuối cùng` / `báo lỗi` |
+| `class` / `extends` | `lớp` / `kế thừa` |
+| `super` / `constructor` | `khởi tạo cha` / `khởi tạo` |
+| `this` | `đây` |
+| `new` | `new` (giữ) |
+| `async` / `await` | `bất đồng bộ` / `chờ` |
+| `yield` | `nhường` |
+| `import` / `export` / `from` / `as` | `sử dụng` / `cho phép` / `từ` / `như là` |
+| `true` / `false` | `đúng` / `sai` |
+| `null` | `rỗng` |
+| `undefined` | `không xác định` |
+| `NaN` | `NaN` (giữ) |
+| `Infinity` | `vô cực` |
+| `typeof` | `kiểu của` |
+| `instanceof` | `là kiểu` |
+| `delete` | `xoá` |
+| `void` | `void` (giữ) |
+| `debugger` | `debugger` (giữ) |
+| `get` / `set` | `lấy` / `gán` |
+| `static` | `tĩnh` |
+| `public` / `private` / `protected` | `công khai` / `riêng tư` / `bảo vệ` |
+
+## Ví dụ chuyển đổi
+
+### JavaScript
+
+```js
+class Animal {
+ #soundCount = 0;
+
+ constructor(name, type = "cat") {
+ this.name = name;
+ this.type = type;
+ }
+
+ makeSound() {
+ this.#soundCount++;
+ return `${this.name} (${this.type}): sound ${this.#soundCount}`;
+ }
+
+ get soundCount() {
+ return this.#soundCount;
+ }
+
+ static create(name) {
+ return new Animal(name);
+ }
+}
+
+const cat = Animal.create("Mimi");
+console.log(cat.makeSound());
+console.log(cat.soundCount);
+
+const numbers = [1, 2, 3];
+const doubled = numbers.map(x => x * 2);
+
+for (const [i, val] of doubled.entries()) {
+ console.log(i, val);
+}
+
+try {
+ throw new Error("demo");
+} catch (e) {
+ console.error(e.message);
+}
+
+const config = { ...defaults, debug: true };
+const { debug } = config;
+const value = obj?.prop ?? "default";
+```
+
+### VietScript
+
+```vietscript
+lớp Động Vật {
+ #số lần kêu = 0
+
+ khởi tạo(tên, loại = "mèo") {
+ đây.tên = tên
+ đây.loại = loại
+ }
+
+ tạo tiếng() {
+ đây.#số lần kêu++
+ trả về `${đây.tên} (${đây.loại}): tiếng số ${đây.#số lần kêu}`
+ }
+
+ lấy số lần kêu() {
+ trả về đây.#số lần kêu
+ }
+
+ tĩnh tạo(tên) {
+ trả về new Động Vật(tên)
+ }
+}
+
+hằng số mèo = Động Vật.tạo("Mimi")
+console.log(mèo.tạo tiếng())
+console.log(mèo.số lần kêu)
+
+hằng số số = [1, 2, 3]
+hằng số gấp đôi = số.map(x => x * 2)
+
+lặp (hằng số [i, giá trị] của gấp đôi.entries()) {
+ console.log(i, giá trị)
+}
+
+thử {
+ báo lỗi new Error("demo")
+} bắt lỗi (e) {
+ console.error(e.message)
+}
+
+hằng số cấu hình = { ...mặc định, debug: đúng }
+hằng số { debug } = cấu hình
+hằng số giá trị = đối tượng?.prop ?? "default"
+```
+
+## Điểm khác biệt cần lưu ý
+
+1. **Tên biến có thể chứa dấu cách**:
+ - VietScript: `hằng số con mèo = ...`
+ - Khi sang JS: `con_mèo`
+
+2. **Keyword không thể làm tên biến**, kể cả ở giữa:
+ - ✗ `hằng số cái của tôi = 1` (`của` là keyword OF)
+ - ✓ `hằng số tôi sở hữu = 1`
+
+3. **Identifier mangling**: dấu cách → `_`, Unicode giữ nguyên:
+ - `xin chào` → `xin_chào`
+ - `Con Mèo Vui` → `Con_Mèo_Vui`
+
+4. **Dùng object JS gốc bình thường**: `console`, `Math`, `JSON`, `fetch`, `Array`, v.v. đều dùng như JS. Hoặc import từ `@vietscript/stdlib` nếu muốn API tiếng Việt.
+
+## Chuyển đổi ngược (JS → VietScript)
+
+Chưa có tool tự động. Post-v1 sẽ thêm.
diff --git a/docs/roadmap.md b/docs/roadmap.md
new file mode 100644
index 0000000..8703d8d
--- /dev/null
+++ b/docs/roadmap.md
@@ -0,0 +1,571 @@
+# Roadmap hoàn thiện VietScript
+
+Kế hoạch đầy đủ đưa dự án từ trạng thái hiện tại (parser ~70%, không có CLI, chưa chạy được trong project thật) đến **v1.0 ổn định**, dùng được trên browser / Node / Bun.
+
+Tham chiếu trạng thái: [compatibility.md](./compatibility.md).
+
+---
+
+## 0. Nguyên tắc & quyết định cố định
+
+Những quyết định này đã chốt trước khi bắt đầu để không phải quay lại bàn trong quá trình thực thi.
+
+### Target & scope
+
+- **Target runtime:** JavaScript (browser + Node + Bun). Không tự viết runtime.
+- **Codegen:** dùng `@babel/generator` đã có sẵn. Không thay thế.
+- **AST:** giữ chuẩn Babel-compatible.
+- **Không làm trong scope v1.0:** type system, LSP, formatter, REPL tương tác, tối ưu bytecode. Để dành post-v1.
+- **File extension:** `.vjs` (đã dùng). Không đổi.
+
+### Keyword tiếng Việt — quyết định
+
+Tất cả keyword JS phải có bản tiếng Việt. Lần này mình chốt:
+
+| JS | Tiếng Việt mới | Giữ alias tiếng Anh? |
+|---|---|---|
+| `let` | `biến` | Có (backward compat) |
+| `yield` | `nhường` | Có |
+| `this` | `đây` | Có |
+| `null` | `rỗng` | Có |
+| `instanceof` | `là kiểu` | Có |
+| `static` | `tĩnh` | Có |
+| `public` | `công khai` | Có |
+| `private` | `riêng tư` | Có |
+| `protected` | `bảo vệ` | Có |
+| `get` | `lấy` | Có |
+| `set` | `gán` | Có |
+| `of` (for...of) | `của` | — |
+| `in` (for...in) | `trong` | — |
+| `Infinity` | `vô cực` | Có |
+| `async` generator `*` | giữ `*` symbol | — |
+
+Alias tiếng Anh giữ lại vì một số chưa có bản tiếng Việt sạch, và để tránh phá code đã có. Khuyến khích dùng tiếng Việt trong docs/example.
+
+### Identifier mangling — quyết định
+
+Hiện tại: `xin chào` → `_xin_ch224o` (codepoint encoding, xấu, khó debug).
+
+**Đổi sang:** giữ nguyên Unicode (JS hỗ trợ Unicode identifier từ ES2015), chỉ thay space bằng `_`:
+
+```
+xin chào → xin_chào
+Con Mèo → Con_Mèo
+số chân → số_chân
+```
+
+- Ưu: output đọc được, dễ debug, 1-1 mapping.
+- Nhược: nếu user có sẵn `xin_chào` (có underscore) và `xin chào` (có space) thì collision. Xử lý: detect collision tại parse time → throw error rõ ràng. Chấp nhận constraint này, không cố tự resolve.
+- Fallback cho ký tự thật sự không hợp lệ JS (rất hiếm với tiếng Việt): encode `\u{xxxx}`.
+
+### Test policy
+
+Mọi feature mới đều phải có 3 nhóm test **trước khi merge**:
+1. **Parser positive** — parse đúng, AST đúng shape.
+2. **Parser negative** — cú pháp sai → throw lỗi tiếng Việt với file:line.
+3. **Codegen** — AST → JS string chuẩn, dùng snapshot.
+
+Coverage target: **≥85% statements** trên `packages/parser/src` khi đóng v1.0.
+
+### Scope boundary cứng
+
+Không làm trong v1.0, **dù tiện tay cỡ nào**:
+- TypeScript type annotations
+- JSX
+- Decorators
+- Pattern matching (proposal)
+- Pipeline operator
+
+Các thứ này đáng làm nhưng sẽ phình scope. Ghi vào post-v1 backlog.
+
+---
+
+## 1. Cấu trúc roadmap
+
+9 giai đoạn, thứ tự tuyến tính (mỗi phase phụ thuộc phase trước):
+
+| Phase | Nội dung | Ước lượng effort |
+|---|---|---|
+| 0 | Thiết lập nền & keyword migration | 3-4 ngày |
+| 1 | Cú pháp P1 (arrow, destructuring, spread, template, for-of/in, default param) | 2-3 tuần |
+| 2 | Cú pháp P2 (private, getter/setter, regex, logical assign, ...) | 2 tuần |
+| 3 | Identifier mangling mới + collision detection | 3-5 ngày |
+| 4 | Source map + error messages chất lượng | 1 tuần |
+| 5 | CLI (`vietscript run/build/watch`) + Node loader | 1-2 tuần |
+| 6 | Plugin Vite/Webpack chạy thật + Bun support | 1 tuần |
+| 7 | Stdlib mỏng (wrappers tiếng Việt cho JS globals) | 3-5 ngày |
+| 8 | Docs tái cấu trúc + 1 sample app chạy thật | 1 tuần |
+| 9 | Release v1.0 (npm publish + announcement) | 2-3 ngày |
+
+**Tổng:** ~10-14 tuần làm part-time (hobby). Chạy tuyến tính, không parallel — vì một mình làm.
+
+---
+
+## 2. Phase 0 — Thiết lập nền (3-4 ngày)
+
+Mục tiêu: có đủ tooling và quyết định để mọi phase sau chạy mượt.
+
+### 0.1 Coverage reporter
+- Thêm `@vitest/coverage-v8` vào devDeps.
+- Cập nhật [vitest.config.ts](../vitest.config.ts) bật `coverage: { reporter: ['text','html','json-summary'], include: ['packages/*/src/**'], exclude: ['**/__test__/**'] }`.
+- Script: `pnpm test:coverage`.
+- Ghi baseline coverage vào [compatibility.md](./compatibility.md).
+
+### 0.2 CI (GitHub Actions)
+- Tạo `.github/workflows/ci.yml`.
+- Chạy: `pnpm install`, `pnpm lint`, `pnpm test:coverage`, `pnpm build`.
+- Upload coverage report.
+- Enforce: PR phải pass trước khi merge.
+
+### 0.3 Thêm keyword tiếng Việt (non-breaking)
+- Thêm các keyword mới ở [keyword.enum.ts](../packages/shared/parser/keyword.enum.ts).
+- Thêm mapping ở [specs.ts](../packages/parser/src/constants/specs.ts).
+- Tokenizer phải accept **cả** tên mới và alias tiếng Anh (không break code cũ).
+- Test: mỗi keyword mới có 1 test parse minimal.
+
+### 0.4 Convention code mới
+- Ghi `CONTRIBUTING.md` phần "thêm parser node": pattern chuẩn để implement `Parse()` + `Node` class + test 3 nhóm.
+- Template file để copy khi thêm node mới.
+
+### 0.5 Build compatibility matrix dashboard
+- Tự động sinh % hoàn thiện từ [compatibility.md](./compatibility.md) (parse markdown table, đếm ✅/🟡/❌).
+- Hiển thị trong CI summary.
+
+**Acceptance:**
+- [x] `pnpm test:coverage` chạy được, ra số.
+- [x] CI xanh trên PR mẫu.
+- [x] 11 keyword mới có trong enum + tokenize được.
+- [x] `CONTRIBUTING.md` có hướng dẫn thêm node.
+
+---
+
+## 3. Phase 1 — Cú pháp P1 (2-3 tuần)
+
+Mục tiêu: xóa bỏ cảm giác "ngôn ngữ thiếu". Mỗi feature = 1 PR.
+
+Thứ tự đề xuất (ít phụ thuộc → nhiều phụ thuộc):
+
+### 1.1 Arrow function
+- File mới: `nodes/expressions/ArrowFunctionExpression.ts`.
+- Parse: `(params) => expr`, `(params) => { ... }`, `x => x`, `async () => ...`.
+- AST shape: Babel `ArrowFunctionExpression` với `params`, `body`, `async`, `expression` flag.
+- Tính: precedence đặt thấp để không va đụng ternary.
+
+### 1.2 Default parameters
+- Mở rộng `ParameterList`: sau identifier, nếu gặp `=` → wrap `AssignmentPattern`.
+- Áp dụng cho function, method, arrow, constructor.
+
+### 1.3 Rest parameters
+- Trong `ParameterList`: nếu bắt đầu bằng `...` → `RestElement`.
+- Chỉ cho phép ở vị trí cuối.
+- Error tiếng Việt nếu sai vị trí.
+
+### 1.4 Spread operator
+- Trong call args: `f(...a)` → `SpreadElement`.
+- Trong array: `[...a]` → `SpreadElement`.
+- Trong object: `{...o}` → `SpreadElement` (Babel shape khác object vs array).
+
+### 1.5 Destructuring — object
+- File mới: `nodes/patterns/ObjectPattern.ts`.
+- Parse trong: variable declarator, function param, assignment target.
+- Hỗ trợ: shorthand `{a}`, rename `{a: b}`, default `{a = 1}`, rest `{a, ...b}`, nested `{a: {b}}`.
+
+### 1.6 Destructuring — array
+- File mới: `nodes/patterns/ArrayPattern.ts`.
+- Hỗ trợ: `[a, b]`, hole `[, b]`, default `[a = 1]`, rest `[a, ...b]`, nested `[a, [b]]`.
+
+### 1.7 Template literal interpolation
+- Sửa tokenizer: backtick string tách thành `TemplateLiteral` với `quasis` + `expressions`.
+- Parse recursive: `${...}` chứa expression (có thể chứa template literal khác).
+- Escape: `\``, `\${`.
+
+### 1.8 `for...of` / `for...in`
+- Sửa [ForStatement.ts](../packages/parser/src/nodes/statements/breakable/iteration/ForStatement.ts): sau `(decl`, nếu gặp keyword `của` → `ForOfStatement`, `trong` → `ForInStatement`.
+- Xóa throw "not implemented".
+- Hỗ trợ `for await (... của ...)`.
+
+### 1.9 Object shorthand + computed key
+- Shorthand: `{a}` parse thành `Property { key: a, value: a, shorthand: true }`.
+- Computed: `{[expr]: value}` → `Property { computed: true, key: expr }`.
+
+**Acceptance mỗi feature:**
+- Parser positive test (≥3 case bao gồm edge).
+- Parser negative test (cú pháp sai → error tiếng Việt).
+- Codegen snapshot test.
+- Cập nhật [compatibility.md](./compatibility.md) từ ❌ → ✅.
+
+**Acceptance Phase 1:**
+- Coverage tăng ít nhất +10% so với baseline.
+- Demo: viết lại `sandbox/src/index.vjs` dùng arrow + destructuring + template → parse + codegen ra JS chạy được với `node --input-type=module -e "..."`.
+
+---
+
+## 4. Phase 2 — Cú pháp P2 (2 tuần)
+
+Hoàn thiện phần dang dở.
+
+### 2.1 Private field `#x`
+- Tokenizer: `#identifier` thành token riêng.
+- Parse trong class body: property + method.
+- Chỉ cho truy cập trong class.
+
+### 2.2 Getter/setter
+- Object literal: `{get x() {}, set x(v) {}}` → `Property { kind: 'get'|'set' }`.
+- Class body: `lấy x() {}`, `gán x(v) {}` (dùng keyword tiếng Việt đã thêm Phase 0).
+
+### 2.3 Async generator
+- Verify: `bất đồng bộ hàm* name() {}` parse đúng với `async: true, generator: true`.
+- Test: `for await (v của asyncGen())`.
+
+### 2.4 Optional chaining edge cases
+- `obj?.()` — optional call.
+- `obj?.[x]` — optional computed member.
+- Test chain: `a?.b?.c`.
+
+### 2.5 Logical assignment
+- Thêm tokens nếu thiếu: `||=`, `&&=`, `??=`.
+- Parse như `AssignmentExpression` với operator tương ứng.
+
+### 2.6 Numeric literals nâng cao
+- Regex tokenizer: hex `0x[0-9a-fA-F]+`, octal `0o[0-7]+`, binary `0b[01]+`.
+- BigInt `123n` (suffix `n`).
+- Numeric separator `1_000_000`.
+
+### 2.7 Regex literal
+- Tokenizer: `/pattern/flags`, phân biệt với division bằng context (sau operator/keyword = regex, sau expression = division).
+- AST: `RegExpLiteral { pattern, flags }`.
+
+### 2.8 Escape sequences trong string
+- `\n`, `\t`, `\r`, `\\`, `\"`, `\'`, `\0`, `\xFF`, `\u00E1`, `\u{1F600}`.
+- Test đầy đủ.
+
+### 2.9 Comment handling
+- Verify tokenizer skip `//` và `/* */`.
+- Preserve comment trong AST (optional, cho future codegen giữ comment).
+- Test: comment ở đủ vị trí (sau statement, giữa expression, đầu file).
+
+### 2.10 Labeled break/continue
+- `nhãn: lặp (...) {}` + `phá vòng lặp nhãn` / `tiếp tục nhãn`.
+- Test với nested loop.
+
+### 2.11 Hoàn thiện codegen test cho node đã có
+- Rà soát: node nào có parser test nhưng chưa có codegen test → bổ sung.
+- Đặc biệt: ForStatement, WhileStatement, DoWhileStatement, TryStatement, ThrowStatement, BreakStatement, ContinueStatement, LabelledStatement, WithStatement.
+
+**Acceptance:**
+- [compatibility.md](./compatibility.md) không còn 🟡 ở cột P2.
+- Coverage ≥85% statements.
+
+---
+
+## 5. Phase 3 — Identifier mangling (3-5 ngày)
+
+### 3.1 Thay cơ chế
+- Xóa logic codepoint encoding hiện tại.
+- Quy tắc mới:
+ 1. Replace ` ` (space) → `_`.
+ 2. Giữ nguyên Unicode char.
+ 3. Nếu ký tự không hợp lệ JS identifier → encode `\u{xxxx}`.
+ 4. Nếu sau mangling trùng với identifier khác trong cùng scope → throw `VietScriptError: tên "xin chào" trùng với "xin_chào" sau khi chuyển đổi`.
+
+### 3.2 Collision detection
+- Trong mỗi scope (function, block, module), trước khi codegen: build set mangled names, detect trùng.
+- Trả error với **cả 2 vị trí** nguồn.
+
+### 3.3 Reserved word handling
+- Nếu identifier sau mangling trùng JS reserved (`if`, `class`, ...) → throw error. User phải đặt tên khác.
+- Lý do: an toàn hơn là auto-rename.
+
+### 3.4 Test
+- Mọi identifier tiếng Việt trong test suite cũ phải giữ nguyên behavior (update snapshot output).
+- Thêm test: collision, reserved word, multi-word với dấu.
+
+### 3.5 Update docs + example
+- [docs/basics/variables.md](./basics/variables.md): thêm section "cách tên biến được dịch sang JS".
+
+**Acceptance:**
+- Output JS của sandbox đọc được, không còn `_ch224o`.
+- Tất cả snapshot test cập nhật và pass.
+- 3 test collision/reserved word mới.
+
+---
+
+## 6. Phase 4 — Source map + error quality (1 tuần)
+
+Quan trọng cho DX khi chạy thật — stack trace phải trỏ về file `.vjs` gốc.
+
+### 4.1 Source map generation
+- Parser phải track `loc: { line, column }` trên mọi AST node (verify có sẵn, bổ sung nếu thiếu).
+- `@babel/generator` có option `sourceMaps: true` — sử dụng.
+- Output: `file.vjs.map` v3 source map spec.
+
+### 4.2 Error messages
+Chuẩn hóa format error tiếng Việt:
+```
+VietScriptError:
+ tại file.vjs:12:5
+ 10 | hàm tính(a, b) {
+ 11 | khai báo tổng = a + b
+ > 12 | trả về tổng +
+ | ^
+ 13 | }
+ gợi ý: thiếu biểu thức sau dấu '+'
+```
+- Class `VietScriptError` với `file`, `line`, `column`, `snippet`, `hint`.
+- Catch tại mọi `throw new Error(...)` trong parser → replace với `VietScriptError`.
+- Rà hết parser để add `hint` cho lỗi phổ biến.
+
+### 4.3 Runtime error remapping
+- Khi chạy JS output có source map, Node 22+ / Bun tự đọc source map cho stack trace.
+- Test: `throw` trong `.vjs`, stack trace phải hiện `file.vjs:line`.
+
+**Acceptance:**
+- `node --enable-source-maps output.js` → error trỏ về `.vjs`.
+- Syntax error trong `.vjs` in ra format đẹp có snippet + hint.
+- Test: ≥10 error case khác nhau.
+
+---
+
+## 7. Phase 5 — CLI + Node loader (1-2 tuần)
+
+Đây là thứ biến dự án từ "demo" thành "dùng được".
+
+### 7.1 Package mới: `packages/cli`
+- Binary name: `vietscript` (ngắn: `vjs`).
+- Sub-command:
+ - `vietscript run file.vjs` — parse + codegen + chạy ngay qua Node.
+ - `vietscript build src/` — build cả thư mục, output JS + source map.
+ - `vietscript watch src/` — watch mode, rebuild on change.
+ - `vietscript check file.vjs` — parse only, báo lỗi không chạy.
+- Stack: `commander` cho parsing args, `chokidar` cho watch.
+
+### 7.2 Node.js ESM loader
+- File: `packages/cli/loader.mjs`.
+- Dùng Node loader hooks API (Node 20+).
+- Khi `import "./file.vjs"`: loader parse → codegen → trả JS.
+- Cho phép: `node --loader @vietscript/cli/loader ./main.vjs` chạy thẳng `.vjs`.
+
+### 7.3 Bun support
+- Bun có `Bun.plugin({ loader })` API.
+- File: `packages/cli/bun-plugin.ts`.
+- Usage: `bun run --preload @vietscript/cli/bun-plugin main.vjs`.
+
+### 7.4 `vietscript init`
+- Tạo `vietscript.config.json` với default (entry, outDir, target).
+- Tạo `src/main.vjs` mẫu.
+
+### 7.5 Error handling
+- CLI catch `VietScriptError`, in đẹp.
+- Exit code != 0 nếu có lỗi (cho CI).
+
+**Acceptance:**
+- `npm i -g @vietscript/cli && vietscript run examples/hello.vjs` in ra "Xin chào".
+- Watch mode tự rebuild khi sửa file.
+- `node --loader @vietscript/cli/loader main.vjs` chạy OK.
+- `bun main.vjs` (sau preload) chạy OK.
+
+---
+
+## 8. Phase 6 — Plugin Vite/Webpack + sample project (1 tuần)
+
+Verify plugin đã có chạy với project thật, không chỉ unit test.
+
+### 8.1 Plugin Vite
+- Review [packages/plugins/vite](../packages/plugins/vite).
+- Test với Vite 5 + React app.
+- HMR: sửa `.vjs` → browser reload đúng.
+
+### 8.2 Plugin Webpack
+- Review [packages/plugins/webpack](../packages/plugins/webpack).
+- Test với Webpack 5 + vanilla JS.
+
+### 8.3 Sample app
+- Thư mục mới: `examples/todo-app/`.
+- Nội dung: app todo nhỏ, viết 100% bằng VietScript, dùng Vite + React.
+- Deploy lên Vercel/Netlify — có URL live trong README.
+
+### 8.4 Sample Node server
+- Thư mục mới: `examples/node-server/`.
+- Express app viết bằng VietScript, chạy qua `vietscript run` hoặc loader.
+
+**Acceptance:**
+- 2 sample project chạy thật, có URL / screenshot trong README.
+- README có badge "dùng trong production" (trong app demo của chính dự án).
+
+---
+
+## 9. Phase 7 — Stdlib mỏng (3-5 ngày)
+
+Ngôn ngữ có "hồn" khi có thể viết **hoàn toàn tiếng Việt**. Stdlib là tập hàm wrap JS globals.
+
+### 9.1 Package mới: `packages/stdlib`
+- Auto-import trong CLI run mode (`import * from '@vietscript/stdlib'` tự động).
+
+### 9.2 Nội dung (minimal)
+```
+in(...args) → console.log
+đọc(prompt) → prompt (browser) / readline (Node)
+tự_số(s) → Number(s)
+tự_chuỗi(x) → String(x)
+loại_của(x) → typeof x
+độ_dài(x) → x.length
+```
+
+### 9.3 Array methods alias
+- Không wrap hết, chỉ thêm alias cho method phổ biến:
+ - `mảng.thêm()` → `Array.prototype.push`
+ - `mảng.bớt()` → `Array.prototype.pop`
+ - `mảng.với_mỗi()` → `forEach`
+ - `mảng.lọc()` → `filter`
+ - `mảng.ánh_xạ()` → `map`
+
+Implement qua prototype extension (polyfill) hoặc wrapper function. Chọn **wrapper function** để không pollute global.
+
+### 9.4 Giới hạn scope
+- Không làm: Promise wrapper, Date wrapper, Math wrapper. Để dành post-v1.
+- Không làm: I/O framework riêng.
+
+**Acceptance:**
+- Viết được full sample: "hello world" 100% tiếng Việt, không có identifier tiếng Anh nào.
+- Docs `docs/stdlib.md` liệt kê hết API.
+
+---
+
+## 10. Phase 8 — Docs & examples (1 tuần)
+
+### 10.1 Tái cấu trúc docs
+Hiện tại docs ở [docs/basics/](./basics/) rời rạc. Cấu trúc mới:
+```
+docs/
+├── index.md (landing)
+├── introduction.md (giữ)
+├── getting-started.md (cập nhật: cài CLI, chạy file đầu tiên)
+├── language/ (ngôn ngữ)
+│ ├── variables.md
+│ ├── operators.md
+│ ├── control-flow.md (if + switch + ternary)
+│ ├── loops.md (for + while + for...of/in)
+│ ├── functions.md (thường + arrow + async + generator)
+│ ├── classes.md
+│ ├── modules.md (import/export)
+│ ├── error-handling.md (try/catch/throw)
+│ ├── destructuring.md
+│ └── template-literals.md
+├── stdlib.md
+├── cli.md
+├── plugins.md (Vite + Webpack + Bun)
+├── compatibility.md (giữ, đã có)
+├── differences-from-js.md (mới — liệt kê minh bạch cái nào khác JS)
+└── roadmap.md (file này)
+```
+
+### 10.2 Mỗi trang phải có
+- Giới thiệu 1 đoạn.
+- Cú pháp VietScript + **JS tương ứng** đặt cạnh nhau (block code 2 cột hoặc 2 block liền kề).
+- ≥2 ví dụ chạy được.
+- Link đến playground với ví dụ đó pre-fill.
+
+### 10.3 Migration guide
+- `docs/migration-from-js.md`: đoạn JS → VietScript tương đương (10-15 ví dụ).
+
+### 10.4 Playground
+- Update playground load các example mới trong `examples/`.
+- Nút "Copy as JS" xem ngay output.
+
+**Acceptance:**
+- Mọi mục trong [compatibility.md](./compatibility.md) có ít nhất 1 link đến trang docs giải thích.
+- 10+ ví dụ runnable trong playground.
+
+---
+
+## 11. Phase 9 — Release v1.0 (2-3 ngày)
+
+### 11.1 Version + changelog
+- Set version `1.0.0` cho tất cả package.
+- `CHANGELOG.md`: liệt kê mọi thay đổi từ trạng thái hiện tại.
+
+### 11.2 NPM publish
+- Publish public: `@vietscript/parser`, `@vietscript/shared`, `@vietscript/cli`, `@vietscript/stdlib`, `@vietscript/vite-plugin`, `@vietscript/webpack-plugin`.
+- Test: install vào project trống, chạy được.
+
+### 11.3 README ở root
+- Rewrite: "VietScript là ngôn ngữ tiếng Việt transpile ra JS. Chạy trên browser/Node/Bun."
+- Quick start 5 dòng.
+- Link docs + playground + sample apps.
+
+### 11.4 Announcement
+- Bài viết ngắn (vn + en) về dự án.
+- Đăng: Twitter/X, Reddit r/programming, dev.to, HackerNews (nếu muốn).
+- Nhắm mạnh cộng đồng dev Việt trước.
+
+### 11.5 Post-release
+- Tạo issue template, PR template trên GitHub.
+- Monitor issue đầu tiên trong 2 tuần, fix bug.
+
+**Acceptance:**
+- Package có trên npm, tải về chạy được.
+- README có badge version, CI status, coverage.
+- Sample app có URL live.
+
+---
+
+## 12. Post-v1 backlog (không làm trong scope này)
+
+Ghi lại để không quên, nhưng không đụng trước khi xong v1:
+
+- **TypeScript annotation** — syntax có sẵn trong specs nhưng parser chưa handle. Cần decide: transpile strip types, hay generate .d.ts?
+- **LSP server** — autocomplete + go-to-definition trong VS Code.
+- **Formatter** (`vietscript fmt`).
+- **REPL** (`vietscript repl`).
+- **Decorators**.
+- **Pattern matching** (proposal stage).
+- **Native compilation** via AssemblyScript / TinyGo (thử nghiệm).
+- **Translator** JS → VietScript tự động (ngược chiều hiện tại).
+- **Stdlib mở rộng**: Promise, Date, Math, Fetch wrapper.
+- **Mobile** — React Native example.
+
+---
+
+## 13. Rủi ro & đối phó
+
+| Rủi ro | Ảnh hưởng | Đối phó |
+|---|---|---|
+| Destructuring + pattern phức tạp hơn dự kiến, lan sang các node khác | Phase 1 kéo dài | Làm `ObjectPattern`/`ArrayPattern` trước, isolated test, rồi mới wire vào VariableDeclaration và ParameterList |
+| Tokenizer regex xung đột khi thêm regex literal | Parse lỗi khắp nơi | Context-aware tokenizer: track "expect expression" state. Test regression cho mọi file test cũ |
+| Keyword tiếng Việt mới xung đột với identifier có sẵn | Break code user | Giữ alias tiếng Anh. Thêm flag `--strict-vietnamese` bật ở v1.x sau |
+| Source map cần `loc` chi tiết mà hiện tại AST không track | Phase 4 rework parser | Audit từng node ở cuối Phase 2, bổ sung `loc` trước khi vào Phase 4 |
+| `@babel/generator` output không khớp syntax VietScript cần | Phải tự viết printer | Kiểm tra ngay đầu Phase 1 bằng test codegen cho mọi feature mới. Nếu có case không hỗ trợ, fork hoặc tự viết printer cho node đó |
+| Scope phình | Không release được | Backlog post-v1 là cứng. Nếu thấy muốn làm, ghi vào backlog và đi tiếp |
+| Solo developer burnout | Dự án stall | Scope nhỏ mỗi phase, release từng phase lên branch, có cảm giác tiến triển |
+
+---
+
+## 14. Tracking
+
+- Mỗi phase = 1 GitHub Milestone.
+- Mỗi task trong phase = 1 Issue.
+- Mỗi feature P1/P2 = 1 PR.
+- [compatibility.md](./compatibility.md) update sau mỗi PR merge.
+- Cuối mỗi phase: cập nhật `CHANGELOG.md` (unreleased section).
+
+---
+
+## 15. Tóm lược số liệu mục tiêu v1.0
+
+- **Cú pháp JS cover:** 95%+ (xem compatibility matrix).
+- **Coverage:** ≥85% statements.
+- **CLI:** chạy được trên Node 20+, Bun 1.1+.
+- **Runtime:** chạy trên Chrome/Firefox/Safari/Edge (≥2024), Node 20+, Bun 1.1+.
+- **Sample apps:** ≥2 (todo web, node server) có URL live.
+- **Docs:** 100% mục trong matrix có trang giải thích.
+- **NPM:** 6 package publish với version 1.0.0.
+
+---
+
+## 16. Làm gì ngay sau khi bạn approve
+
+1. Tạo GitHub milestone cho Phase 0-9.
+2. Tạo issue cho Phase 0 (5 task con).
+3. Bắt đầu với Phase 0.1 (coverage reporter) — có số đo để theo dõi tiến độ.
+
+Không làm gì khác trước khi bạn review roadmap này.
diff --git a/docs/stdlib.md b/docs/stdlib.md
new file mode 100644
index 0000000..1270747
--- /dev/null
+++ b/docs/stdlib.md
@@ -0,0 +1,90 @@
+# Stdlib
+
+`@vietscript/stdlib` cung cấp một tập wrappers tiếng Việt cho JavaScript globals. Mục tiêu: viết code 100% tiếng Việt mà không cần nhắc đến `console`, `Math`, `JSON`, `fetch`, v.v.
+
+## Sử dụng
+
+```vietscript
+sử dụng { ghi, tròn, ngẫu nhiên } từ "@vietscript/stdlib"
+
+ghi("Xin chào!")
+```
+
+## API
+
+### Console I/O
+
+| Hàm | Tương đương JS |
+|---|---|
+| `ghi(...args)` | `console.log(...args)` |
+| `ghi lỗi(...args)` | `console.error(...args)` |
+| `ghi cảnh báo(...args)` | `console.warn(...args)` |
+
+### Ép kiểu
+
+| Hàm | Tương đương JS |
+|---|---|
+| `tự số(x)` | `Number(x)` |
+| `tự chuỗi(x)` | `String(x)` |
+| `tự bool(x)` | `Boolean(x)` |
+| `kiểu(x)` | `typeof x` |
+| `độ dài(x)` | `x.length` |
+
+### Math
+
+| Hàm | Tương đương JS |
+|---|---|
+| `tròn(x)` | `Math.round(x)` |
+| `làm tròn xuống(x)` | `Math.floor(x)` |
+| `làm tròn lên(x)` | `Math.ceil(x)` |
+| `giá trị tuyệt đối(x)` | `Math.abs(x)` |
+| `nhỏ nhất(...values)` | `Math.min(...values)` |
+| `lớn nhất(...values)` | `Math.max(...values)` |
+| `ngẫu nhiên(min = 0, max = 1)` | `Math.random() * (max - min) + min` |
+
+### Async
+
+| Hàm | Tương đương JS |
+|---|---|
+| `đợi(ms)` | `new Promise(r => setTimeout(r, ms))` |
+
+### JSON
+
+| Hàm | Tương đương JS |
+|---|---|
+| `phân tích JSON(text)` | `JSON.parse(text)` |
+| `JSON sang chuỗi(value, space?)` | `JSON.stringify(value, null, space)` |
+
+### Fetch
+
+| Hàm | Tương đương JS |
+|---|---|
+| `tải văn bản(url)` | `fetch(url).then(r => r.text())` |
+| `tải JSON(url)` | `fetch(url).then(r => r.json())` |
+
+## Ví dụ
+
+```vietscript
+sử dụng { ghi, tải JSON, ngẫu nhiên, tròn } từ "@vietscript/stdlib"
+
+bất đồng bộ hàm chạy() {
+ hằng số dữ liệu = chờ tải JSON("https://api.example.com/data")
+ ghi("Số bản ghi:", dữ liệu.length)
+
+ hằng số chỉ số = tròn(ngẫu nhiên(0, dữ liệu.length))
+ ghi("Mẫu ngẫu nhiên:", dữ liệu[chỉ số])
+}
+
+chạy()
+```
+
+## Lưu ý về đặt tên
+
+Vì VietScript identifier tách bằng dấu cách (không phải gạch dưới), khi `sử dụng` stdlib, viết tên hàm với dấu cách:
+
+```vietscript
+✓ sử dụng { ghi lỗi } từ "@vietscript/stdlib"
+✗ sử dụng { ghi_lỗi } từ "@vietscript/stdlib"
+```
+
+Parser tự động chuyển `ghi lỗi` → `ghi_lỗi` khi output JS, khớp với tên export thực của stdlib.
diff --git a/examples/broken.vjs.example b/examples/broken.vjs.example
new file mode 100644
index 0000000..13bf670
--- /dev/null
+++ b/examples/broken.vjs.example
@@ -0,0 +1,4 @@
+hằng số tổng = 10
+nếu (tổng >) {
+ console.log("error")
+}
diff --git a/examples/hello/full-features.vjs b/examples/hello/full-features.vjs
new file mode 100644
index 0000000..4ef98d9
--- /dev/null
+++ b/examples/hello/full-features.vjs
@@ -0,0 +1,52 @@
+sử dụng { ghi } từ "@vietscript/stdlib"
+
+lớp Con Mèo {
+ #số lần kêu = 0
+
+ khởi tạo(tên, màu lông = "đen") {
+ đây.tên = tên
+ đây.màu lông = màu lông
+ }
+
+ kêu() {
+ đây.#số lần kêu++
+ trả về `${đây.tên} (${đây.màu lông}): meo meo lần thứ ${đây.#số lần kêu}`
+ }
+
+ lấy đã kêu() {
+ trả về đây.#số lần kêu
+ }
+}
+
+hằng số mèo1 = new Con Mèo("Mimi", "trắng")
+hằng số mèo2 = new Con Mèo("Luna")
+
+ghi(mèo1.kêu())
+ghi(mèo1.kêu())
+ghi(mèo2.kêu())
+ghi(`${mèo1.tên} đã kêu: ${mèo1.đã kêu} lần`)
+
+// Destructuring + spread
+hằng số { tên, màu lông } = mèo1
+hằng số sao chép = { ...mèo1, tên: "Bản sao" }
+ghi(`${tên} có màu ${màu lông}`)
+
+// Optional chaining + nullish coalescing
+hằng số tuỳ chọn = rỗng
+hằng số giá trị = tuỳ chọn?.tên ?? "không có"
+ghi("Tuỳ chọn:", giá trị)
+
+// Try-catch-finally
+thử {
+ báo lỗi new Error("Lỗi demo")
+} bắt lỗi (lỗi) {
+ ghi("Bắt được:", lỗi.message)
+} cuối cùng {
+ ghi("Luôn chạy")
+}
+
+// For-of with destructuring
+hằng số cặp = [[1, "một"], [2, "hai"], [3, "ba"]]
+lặp (hằng số [n, chữ] của cặp) {
+ ghi(`${n} = ${chữ}`)
+}
diff --git a/examples/hello/hello.vjs b/examples/hello/hello.vjs
new file mode 100644
index 0000000..cbc7e97
--- /dev/null
+++ b/examples/hello/hello.vjs
@@ -0,0 +1,13 @@
+hằng số tên = "Thế giới"
+console.log(`Xin chào, ${tên}!`)
+
+hàm cộng(a, b = 0) {
+ trả về a + b
+}
+
+console.log("1 + 2 =", cộng(1, 2))
+console.log("5 + 0 =", cộng(5))
+
+hằng số mảng = [1, 2, 3, 4, 5]
+hằng số tổng = mảng.reduce((acc, x) => acc + x, 0)
+console.log("Tổng:", tổng)
diff --git a/examples/hello/stdlib-test.vjs b/examples/hello/stdlib-test.vjs
new file mode 100644
index 0000000..4e08b32
--- /dev/null
+++ b/examples/hello/stdlib-test.vjs
@@ -0,0 +1,10 @@
+sử dụng { ghi, tròn, ngẫu nhiên, độ dài } từ "@vietscript/stdlib"
+
+ghi("Xin chào từ stdlib!")
+ghi("Làm tròn 3.7:", tròn(3.7))
+
+hằng số số ngẫu = ngẫu nhiên(1, 100)
+ghi("Số ngẫu nhiên 1-100:", tròn(số ngẫu))
+
+hằng số mảng = [1, 2, 3, 4, 5]
+ghi("Độ dài mảng:", độ dài(mảng))
diff --git a/package.json b/package.json
index 6adbcb0..d3049ed 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,8 @@
"build": "nx run-many --target=build --all",
"typecheck": "nx run-many --target=typecheck --all",
"test": "vitest run",
+ "test:coverage": "vitest run --coverage",
+ "coverage:summary": "node scripts/compat-summary.mjs",
"dev": "vitest --ui",
"dev:docs": "vitepress dev docs",
"serve:docs": "vitepress serve docs",
@@ -40,7 +42,9 @@
"sandbox"
],
"dependencies": {
- "@vietscript/parser": "workspace:*"
+ "@vietscript/cli": "workspace:*",
+ "@vietscript/parser": "workspace:*",
+ "@vietscript/stdlib": "workspace:*"
},
"devDependencies": {
"@babel/generator": "^7.22.10",
@@ -57,7 +61,8 @@
"@typescript-eslint/parser": "^5.38.0",
"@vitejs/plugin-vue": "^3.1.2",
"@vitejs/plugin-vue-jsx": "^2.0.1",
- "@vitest/ui": "^0.24.5",
+ "@vitest/coverage-v8": "^1.6.0",
+ "@vitest/ui": "^1.6.0",
"@vue/test-utils": "^2.2.1",
"eslint": "^8.23.1",
"eslint-plugin-import": "^2.26.0",
@@ -72,7 +77,7 @@
"ts-node": "^10.9.1",
"typescript": "^4.9.4",
"vitepress": "1.0.0-alpha.49",
- "vitest": "^0.24.5",
+ "vitest": "^1.6.0",
"vue": "^3.2.41",
"vue-tsc": "^1.0.9"
}
diff --git a/packages/cli/bin/vietscript.mjs b/packages/cli/bin/vietscript.mjs
new file mode 100644
index 0000000..8645d44
--- /dev/null
+++ b/packages/cli/bin/vietscript.mjs
@@ -0,0 +1,4 @@
+#!/usr/bin/env node
+import { main } from "../dist/index.js";
+
+await main(process.argv);
diff --git a/packages/cli/package.json b/packages/cli/package.json
new file mode 100644
index 0000000..7872610
--- /dev/null
+++ b/packages/cli/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "@vietscript/cli",
+ "version": "1.0.0-beta.1",
+ "description": "VietScript CLI — run and build .vjs files.",
+ "type": "module",
+ "bin": {
+ "vietscript": "./bin/vietscript.mjs",
+ "vjs": "./bin/vietscript.mjs"
+ },
+ "exports": {
+ ".": {
+ "import": "./dist/index.js",
+ "default": "./dist/index.js"
+ },
+ "./loader": {
+ "import": "./dist/loader.js",
+ "default": "./dist/loader.js"
+ },
+ "./register": {
+ "import": "./dist/register.js",
+ "default": "./dist/register.js"
+ },
+ "./bun-plugin": {
+ "import": "./dist/bun-plugin.js",
+ "default": "./dist/bun-plugin.js"
+ }
+ },
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "files": [
+ "bin",
+ "dist"
+ ],
+ "scripts": {
+ "build": "tsc",
+ "dev": "tsc --watch",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@babel/generator": "^7.22.10",
+ "@vietscript/parser": "workspace:*",
+ "chokidar": "^3.6.0"
+ },
+ "devDependencies": {
+ "@types/node": "^18.14.1",
+ "typescript": "^4.9.4"
+ }
+}
diff --git a/packages/cli/src/bun-plugin.ts b/packages/cli/src/bun-plugin.ts
new file mode 100644
index 0000000..6c98354
--- /dev/null
+++ b/packages/cli/src/bun-plugin.ts
@@ -0,0 +1,19 @@
+import { readFileSync } from "node:fs";
+
+import { compile } from "./compile.js";
+
+export default {
+ name: "vietscript",
+ setup(build: {
+ onLoad: (
+ opts: { filter: RegExp },
+ callback: (args: { path: string }) => { contents: string; loader: string },
+ ) => void;
+ }) {
+ build.onLoad({ filter: /\.vjs$/ }, (args) => {
+ const source = readFileSync(args.path, "utf8");
+ const { code } = compile(source, args.path);
+ return { contents: code, loader: "js" };
+ });
+ },
+};
diff --git a/packages/cli/src/commands/build.ts b/packages/cli/src/commands/build.ts
new file mode 100644
index 0000000..737b39d
--- /dev/null
+++ b/packages/cli/src/commands/build.ts
@@ -0,0 +1,48 @@
+import { readFileSync, writeFileSync, mkdirSync, statSync, readdirSync } from "node:fs";
+import { resolve, relative, dirname, join } from "node:path";
+
+import { compile } from "../compile.js";
+
+function walk(dir: string, acc: string[] = []): string[] {
+ for (const entry of readdirSync(dir)) {
+ const full = join(dir, entry);
+ const stat = statSync(full);
+ if (stat.isDirectory()) {
+ walk(full, acc);
+ } else if (entry.endsWith(".vjs")) {
+ acc.push(full);
+ }
+ }
+ return acc;
+}
+
+export function buildCommand(srcDir: string, outDir: string) {
+ const absSrc = resolve(process.cwd(), srcDir);
+ const absOut = resolve(process.cwd(), outDir);
+
+ const files = walk(absSrc);
+
+ if (files.length === 0) {
+ console.error(`Không tìm thấy file .vjs nào trong ${absSrc}`);
+ return;
+ }
+
+ for (const file of files) {
+ const source = readFileSync(file, "utf8");
+ const { code, map } = compile(source, file);
+
+ const relPath = relative(absSrc, file).replace(/\.vjs$/, ".js");
+ const outPath = join(absOut, relPath);
+ mkdirSync(dirname(outPath), { recursive: true });
+
+ const codeWithMap = `${code}\n//# sourceMappingURL=${relPath}.map\n`;
+ writeFileSync(outPath, codeWithMap, "utf8");
+ if (map) {
+ writeFileSync(outPath + ".map", JSON.stringify(map), "utf8");
+ }
+
+ console.log(` ${relative(process.cwd(), file)} → ${relative(process.cwd(), outPath)}`);
+ }
+
+ console.log(`\nĐã build ${files.length} file.`);
+}
diff --git a/packages/cli/src/commands/check.ts b/packages/cli/src/commands/check.ts
new file mode 100644
index 0000000..096b352
--- /dev/null
+++ b/packages/cli/src/commands/check.ts
@@ -0,0 +1,24 @@
+import { readFileSync } from "node:fs";
+import { resolve } from "node:path";
+
+import { VietScriptError } from "@vietscript/parser";
+
+import { compile } from "../compile.js";
+
+export function checkCommand(filepath: string) {
+ const abs = resolve(process.cwd(), filepath);
+ const source = readFileSync(abs, "utf8");
+ try {
+ compile(source, abs);
+ console.log(`✓ ${filepath} parse OK`);
+ } catch (error) {
+ if (error instanceof VietScriptError) {
+ console.error(`✗ ${filepath}`);
+ console.error(error.format());
+ } else {
+ const err = error as Error;
+ console.error(`✗ ${filepath}: ${err.message}`);
+ }
+ process.exit(1);
+ }
+}
diff --git a/packages/cli/src/commands/run.ts b/packages/cli/src/commands/run.ts
new file mode 100644
index 0000000..61f2c6a
--- /dev/null
+++ b/packages/cli/src/commands/run.ts
@@ -0,0 +1,29 @@
+import { readFileSync, writeFileSync, unlinkSync } from "node:fs";
+import { resolve, dirname, basename } from "node:path";
+import { pathToFileURL } from "node:url";
+
+import { compile } from "../compile.js";
+
+export async function runCommand(filepath: string) {
+ const abs = resolve(process.cwd(), filepath);
+ const source = readFileSync(abs, "utf8");
+ const { code, map } = compile(source, abs);
+
+ const dir = dirname(abs);
+ const name = basename(abs, ".vjs");
+ const outFile = resolve(dir, `.${name}.vjs.tmp.mjs`);
+ const mapFile = outFile + ".map";
+
+ const codeWithMap = `${code}\n//# sourceMappingURL=${basename(mapFile)}\n`;
+ writeFileSync(outFile, codeWithMap, "utf8");
+ if (map) writeFileSync(mapFile, JSON.stringify(map), "utf8");
+
+ try {
+ await import(pathToFileURL(outFile).href);
+ } finally {
+ try {
+ unlinkSync(outFile);
+ if (map) unlinkSync(mapFile);
+ } catch {}
+ }
+}
diff --git a/packages/cli/src/commands/watch.ts b/packages/cli/src/commands/watch.ts
new file mode 100644
index 0000000..50fc3bb
--- /dev/null
+++ b/packages/cli/src/commands/watch.ts
@@ -0,0 +1,34 @@
+import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
+import { resolve, relative, dirname, join } from "node:path";
+
+import chokidar from "chokidar";
+
+import { compile } from "../compile.js";
+
+export function watchCommand(srcDir: string, outDir: string) {
+ const absSrc = resolve(process.cwd(), srcDir);
+ const absOut = resolve(process.cwd(), outDir);
+
+ function buildFile(file: string) {
+ try {
+ const source = readFileSync(file, "utf8");
+ const { code, map } = compile(source, file);
+ const relPath = relative(absSrc, file).replace(/\.vjs$/, ".js");
+ const outPath = join(absOut, relPath);
+ mkdirSync(dirname(outPath), { recursive: true });
+ const codeWithMap = `${code}\n//# sourceMappingURL=${relPath}.map\n`;
+ writeFileSync(outPath, codeWithMap, "utf8");
+ if (map) writeFileSync(outPath + ".map", JSON.stringify(map), "utf8");
+ console.log(` ✓ ${relative(process.cwd(), file)}`);
+ } catch (error) {
+ const err = error as Error;
+ console.error(` ✗ ${relative(process.cwd(), file)}: ${err.message}`);
+ }
+ }
+
+ const watcher = chokidar.watch(`${absSrc}/**/*.vjs`, { persistent: true });
+ watcher.on("add", buildFile);
+ watcher.on("change", buildFile);
+
+ console.log(`Watching ${absSrc}...`);
+}
diff --git a/packages/cli/src/compile.ts b/packages/cli/src/compile.ts
new file mode 100644
index 0000000..4179b00
--- /dev/null
+++ b/packages/cli/src/compile.ts
@@ -0,0 +1,25 @@
+import { Parser, VietScriptError } from "@vietscript/parser";
+import generateDefault from "@babel/generator";
+
+const generate = (generateDefault as any).default ?? generateDefault;
+
+export interface CompileResult {
+ code: string;
+ map?: any;
+}
+
+export function compile(source: string, filename?: string): CompileResult {
+ const parser = new Parser();
+ parser.filename = filename;
+ const ast = parser.parse(source);
+ const result = generate(ast, {
+ sourceMaps: true,
+ sourceFileName: filename,
+ });
+ return {
+ code: result.code,
+ map: result.map,
+ };
+}
+
+export { VietScriptError };
diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts
new file mode 100644
index 0000000..ec39b69
--- /dev/null
+++ b/packages/cli/src/index.ts
@@ -0,0 +1,80 @@
+import { VietScriptError } from "@vietscript/parser";
+
+import { runCommand } from "./commands/run.js";
+import { buildCommand } from "./commands/build.js";
+import { watchCommand } from "./commands/watch.js";
+import { checkCommand } from "./commands/check.js";
+
+const USAGE = `VietScript CLI
+
+Cách dùng:
+ vietscript run Chạy file .vjs
+ vietscript build [outDir] Build thư mục .vjs → JS (mặc định outDir: dist)
+ vietscript watch [outDir] Build + watch thay đổi
+ vietscript check Parse không chạy, chỉ kiểm tra cú pháp
+ vietscript --help Hiển thị hướng dẫn
+`;
+
+function printError(error: unknown): void {
+ if (error instanceof VietScriptError) {
+ console.error(error.format());
+ return;
+ }
+ const err = error as Error;
+ console.error(`Lỗi: ${err.message}`);
+}
+
+export async function main(argv: string[]) {
+ const args = argv.slice(2);
+ const command = args[0];
+
+ if (!command || command === "--help" || command === "-h") {
+ console.log(USAGE);
+ return;
+ }
+
+ try {
+ switch (command) {
+ case "run": {
+ if (!args[1]) {
+ console.error("Thiếu tham số file. Cách dùng: vietscript run ");
+ process.exit(1);
+ }
+ await runCommand(args[1]);
+ break;
+ }
+ case "build": {
+ if (!args[1]) {
+ console.error("Thiếu tham số thư mục. Cách dùng: vietscript build [outDir]");
+ process.exit(1);
+ }
+ buildCommand(args[1], args[2] ?? "dist");
+ break;
+ }
+ case "watch": {
+ if (!args[1]) {
+ console.error("Thiếu tham số thư mục. Cách dùng: vietscript watch [outDir]");
+ process.exit(1);
+ }
+ watchCommand(args[1], args[2] ?? "dist");
+ break;
+ }
+ case "check": {
+ if (!args[1]) {
+ console.error("Thiếu tham số file. Cách dùng: vietscript check ");
+ process.exit(1);
+ }
+ checkCommand(args[1]);
+ break;
+ }
+ default: {
+ console.error(`Command không hợp lệ: "${command}"\n`);
+ console.log(USAGE);
+ process.exit(1);
+ }
+ }
+ } catch (error) {
+ printError(error);
+ process.exit(1);
+ }
+}
diff --git a/packages/cli/src/loader.ts b/packages/cli/src/loader.ts
new file mode 100644
index 0000000..f5c94ff
--- /dev/null
+++ b/packages/cli/src/loader.ts
@@ -0,0 +1,31 @@
+import { readFileSync } from "node:fs";
+import { fileURLToPath } from "node:url";
+
+import { compile } from "./compile.js";
+
+export async function load(
+ url: string,
+ context: { format?: string | null; importAssertions?: Record },
+ nextLoad: (url: string, context: unknown) => Promise<{ source?: string | null; format?: string | null }>,
+): Promise<{ format: string; source: string; shortCircuit?: boolean }> {
+ if (url.endsWith(".vjs")) {
+ const filepath = fileURLToPath(url);
+ const source = readFileSync(filepath, "utf8");
+ const { code } = compile(source, filepath);
+ return {
+ format: "module",
+ source: code,
+ shortCircuit: true,
+ };
+ }
+ return nextLoad(url, context) as Promise<{ format: string; source: string }>;
+}
+
+export async function resolve(
+ specifier: string,
+ context: { parentURL?: string },
+ nextResolve: (specifier: string, context: unknown) => Promise<{ url: string; format?: string | null }>,
+): Promise<{ url: string; format?: string | null; shortCircuit?: boolean }> {
+ const resolved = await nextResolve(specifier, context);
+ return resolved;
+}
diff --git a/packages/cli/src/register.ts b/packages/cli/src/register.ts
new file mode 100644
index 0000000..969a66d
--- /dev/null
+++ b/packages/cli/src/register.ts
@@ -0,0 +1,8 @@
+import * as nodeModule from "node:module";
+
+const register = (nodeModule as any).register as (
+ specifier: string,
+ parentURL: string | URL,
+) => void;
+
+register("./loader.js", import.meta.url);
diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json
new file mode 100644
index 0000000..e7a99e7
--- /dev/null
+++ b/packages/cli/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "declaration": true,
+ "esModuleInterop": true,
+ "strict": true,
+ "skipLibCheck": true,
+ "resolveJsonModule": true
+ },
+ "include": ["src/**/*"]
+}
diff --git a/packages/parser/package.json b/packages/parser/package.json
index 7ca0236..659a73a 100644
--- a/packages/parser/package.json
+++ b/packages/parser/package.json
@@ -1,6 +1,6 @@
{
"name": "@vietscript/parser",
- "version": "0.0.1",
+ "version": "1.0.0-beta.1",
"description": "Vietnamese programming language - Ngôn ngữ lập trình tiếng Việt.",
"type": "module",
"exports": {
diff --git a/packages/parser/public/index.js b/packages/parser/public/index.js
index 8a10d8e..21e8f06 100644
--- a/packages/parser/public/index.js
+++ b/packages/parser/public/index.js
@@ -1 +1,8 @@
-(function(b,e){typeof exports=="object"&&typeof module<"u"?e(exports):typeof define=="function"&&define.amd?define(["exports"],e):(b=typeof globalThis<"u"?globalThis:b||self,e(b["@vietscript/parser"]={}))})(this,function(b){"use strict";var gt=Object.defineProperty;var Ot=(b,e,k)=>e in b?gt(b,e,{enumerable:!0,configurable:!0,writable:!0,value:k}):b[e]=k;var s=(b,e,k)=>(Ot(b,typeof e!="symbol"?e+"":e,k),k);var e=(n=>(n.VAR="Var",n.CONST="Const",n.LET="Let",n.IF="If",n.ELSE="Else",n.FOR="For",n.WHILE="While",n.DO="Do",n.BREAK="Break",n.CONTINUE="Continue",n.RETURN="Return",n.SWITCH="Switch",n.CASE="Case",n.DEFAULT="Default",n.TRY="Try",n.CATCH="Catch",n.FINALLY="Finally",n.THROW="Throw",n.FUNCTION="Function",n.CLASS="Class",n.INTERFACE="Interface",n.ENUM="Enum",n.TYPE="Type",n.IMPORT="Import",n.EXPORT="Export",n.FROM="From",n.AS="As",n.IN="In",n.OF="Of",n.IS="Is",n.NEW="New",n.THIS="This",n.SUPER="Super",n.TRUE="True",n.FALSE="False",n.NULL="Null",n.VOID="Void",n.ANY="Any",n.NUMBER="Number",n.STRING="String",n.BOOLEAN="Boolean",n.OBJECT="Object",n.ARRAY="Array",n.NAN="NaN",n.INFINITY="Infinity",n.UNDEFINED="Undefined",n.STATIC="Static",n.PUBLIC="Public",n.PRIVATE="Private",n.PROTECTED="Protected",n.ABSTRACT="Abstract",n.EXTENDS="Extends",n.IMPLEMENTS="Implements",n.GET="Get",n.SET="Set",n.CONSTRUCTOR="Constructor",n.YIELD="Yield",n.AWAIT="Await",n.ASYNC="Async",n.DELETE="Delete",n.TYPEOF="Typeof",n.INSTANCEOF="Instanceof",n.WITH="With",n.DEBUGGER="Debugger",n.IDENTIFIER="Identifier",n))(e||{});const k="\\u00C0\\u00C1\\u00C2\\u00C3\\u00C8\\u00C9\\u00CA\\u00CC\\u00CD\\u00D2\\u00D3\\u00D4\\u00D5\\u00D9\\u00DA\\u0102\\u0110\\u0128\\u0168\\u01A0\\u00E0\\u00E1\\u00E2\\u00E3\\u00E8\\u00E9\\u00EA\\u00EC\\u00ED\\u00F2\\u00F3\\u00F4\\u00F5\\u00F9\\u00FA\\u0103\\u0111\\u0129\\u0169\\u01A1\\u01AF\\u0102\\u1EA0\\u1EA2\\u1EA4\\u1EA6\\u1EA8\\u1EAA\\u1EAC\\u1EAE\\u1EB0\\u1EB2\\u1EB4\\u1EB6\\u1EB8\\u1EBA\\u1EBC\\u1EC0{2}\\u1EC2\\u01B0\\u0103\\u1EA1\\u1EA3\\u1EA5\\u1EA7\\u1EA9\\u1EAB\\u1EAD\\u1EAF\\u1EB1\\u1EB3\\u1EB5\\u1EB7\\u1EB9\\u1EBB\\u1EBD\\u1EC1{2}\\u1EC3\\u1EC4\\u1EC6\\u1EC8\\u1ECA\\u1ECC\\u1ECE\\u1ED0\\u1ED2\\u1ED4\\u1ED6\\u1ED8\\u1EDA\\u1EDC\\u1EDE\\u1EE0\\u1EE2\\u1EE4\\u1EE6\\u1EE8\\u1EEA\\u1EC5\\u1EC7\\u1EC9\\u1ECB\\u1ECD\\u1ECF\\u1ED1\\u1ED3\\u1ED5\\u1ED7\\u1ED9\\u1EDB\\u1EDD\\u1EDF\\u1EE1\\u1EE3\\u1EE5\\u1EE7\\u1EE9\\u1EEB\\u1EEC\\u1EEE\\u1EF0\\u1EF2\\u1EF4\\u00DD\\u1EF6\\u1EF8\\u1EED\\u1EEF\\u1EF1\\u1EF3\\u1EF5\\u1EF7\\u1EF9",F=[new RegExp(`^[a-zA-Z${k}]+(\\s[a-zA-Z${k}]+)*`),e.IDENTIFIER],R=[[/^\s+/,null],[/^\/\/.*/,null],[/^\/\*[\S\s]*?\*\//,null],[/^\[/,"["],[/^]/,"]"],[/^\(/,"("],[/^\)/,")"],[/^{/,"{"],[/^}/,"}"],[/^;/,";"],[/^,/,","],[/^===/,"==="],[/^==/,"=="],[/^!==/,"!=="],[/^!=/,"!="],[/^=/,"="],[/^\?/,"?"],[/^\?\.?/,"?."],[/^:/,":"],[/^\.{3}/,"..."],[/^\./,"."],[/^\+\+/,"++"],[/^\+/,"+"],[/^--/,"--"],[/^-/,"-"],[/^~/,"~"],[/^!/,"!"],[/^\*/,"*"],[/^\//,"/"],[/^%/,"%"],[/^\*\*/,"**"],[/^\?\?/,"??"],[/^#/,"#"],[/^>>>=/,">>>="],[/^>>>/,">>>"],[/^>>/,">>"],[/^>>=/,">>="],[/^<<=/,"<<="],[/^<,"<<"],[/^<=/,"<="],[/^>=/,">="],[/^,"<"],[/^>/,">"],[/^&&/,"&&"],[/^\|\|/,"||"],[/^&/,"&"],[/^\^/,"^"],[/^\|/,"|"],[/^\*=/,"*="],[/^\//,"/="],[/^%=/,"%="],[/^\+=/,"+="],[/^-=/,"-="],[/^&=/,"&="],[/^\^=/,"^="],[/^\|=/,"|="],[/^\*\*=/,"**="],[/^=>/,"=>"],[/^\b(var|khai b\u00E1o)\b/,e.VAR],[/^\b(break|ph\u00E1 v\u00F2ng l\u1EB7p)\b/,e.BREAK],[/^\b(do|th\u1EF1c hi\u1EC7n)\b/,e.DO],[/^\binstanceof\b/,e.INSTANCEOF],[/^\b(typeof|ki\u1EC3u c\u1EE7a)\b/,e.TYPEOF],[/^\b(switch|duy\u1EC7t)\b/,e.SWITCH],[/^\b(case|tr\u01B0\u1EDDng h\u1EE3p)\b/,e.CASE],[/^\b(if|n\u1EBFu)\b/,e.IF],[/^\b(else|kh\u00F4ng th\u00EC)/,e.ELSE],[/^\bnew\b/,e.NEW],[/^\b(catch|b\u1EAFt l\u1ED7i)\b/,e.CATCH],[/^\b(finally|cu\u1ED1i c\u00F9ng)\b/,e.FINALLY],[/^\b(return|tr\u1EA3 v\u1EC1)/,e.RETURN],[/^\bvoid\b/,e.VOID],[/^\b(continue|ti\u1EBFp t\u1EE5c)\b/,e.CONTINUE],[/^\bfor\b/,e.FOR],[/^\b(while|khi m\u00E0)\b/,e.WHILE],[/^\bdebugger\b/,e.DEBUGGER],[/^\b(function|h\u00E0m)\b/,e.FUNCTION],[/^\bthis\b/,e.THIS],[/^\bwith\b/,e.WITH],[/^\b(default|m\u1EB7c \u0111\u1ECBnh)\b/,e.DEFAULT],[/^\b(throw|b\u00E1o l\u1ED7i)\b/,e.THROW],[/^\b(delete|xo\u00E1)\b/,e.DELETE],[/^\b(in|trong)\b/,e.IN],[/^\b(try|th\u1EED)/,e.TRY],[/^\bas\b/,e.AS],[/^\b(from|t\u1EEB)\b/,e.FROM],[/^const|h\u1EB1ng s\u1ED1/,e.CONST],[/^\b(class|l\u1EDBp)\b/,e.CLASS],[/^\b(super|kh\u1EDFi t\u1EA1o cha)\b/,e.SUPER],[/^\b(constructor|kh\u1EDFi t\u1EA1o)\b/,e.CONSTRUCTOR],[/^\benum\b/,e.ENUM],[/^\b(extends|k\u1EBF th\u1EEBa)\b/,e.EXTENDS],[/^\b(export|xu\u1EA5t)\b/,e.EXPORT],[/^\b(import|nh\u1EADp)\b/,e.IMPORT],[/^\b(async|b\u1EA5t \u0111\u1ED3ng b\u1ED9)/,e.ASYNC],[/^\b(await|ch\u1EDD)\b/,e.AWAIT],[/^\byield\b/,e.YIELD],[/^\bimplements\b/,e.IMPLEMENTS],[/^\blet\b/,e.LET],[/^\bprivate\b/,e.PRIVATE],[/^\bpublic\b/,e.PUBLIC],[/^\binterface\b/,e.INTERFACE],[/^\bprotected\b/,e.PROTECTED],[/^\bstatic\b/,e.STATIC],[/^(\d+(\.|)(\d+|)([Ee]([+-]|)\d+|))/,e.NUMBER],[/^"[^"]*"/,e.STRING],[/^'[^']*'/,e.STRING],[/^`[^`]*`/,e.STRING],[/^\bnull\b/,e.NULL],[/^\bNaN\b/,e.NAN],[/^\b(undefined|kh\u00F4ng x\u00E1c \u0111\u1ECBnh)\b/,e.UNDEFINED],[/(true|false|\u0111\u00FAng|sai)\b/,e.BOOLEAN],F];class N{constructor(t){s(this,"parser");s(this,"cursor");this.parser=t,this.cursor=0}isEOF(){return this.cursor===this.parser.syntax.length}hasMoreTokens(){return this.cursor0&&t.eat(","),i.push(new h(t));t.eat(")");const c=new d(t);this.id=null,this.expression=!1,this.params=i,this.body=c}}class at{constructor(t,a){s(this,"type","BinaryExpression");s(this,"left");s(this,"operator");s(this,"right");var i,c,o,l;switch(this.left=a??(((i=t.lookahead)==null?void 0:i.type)===e.IDENTIFIER?new h(t):new u(t)),(c=t.lookahead)==null?void 0:c.type){case"+":case"-":case"*":case"/":case"%":case"**":case"^":case">":case">>":case">>>":case"<":case"<<":case"<<<":case">=":case"<=":case"==":case"===":{this.operator=String(t.eat((o=t.lookahead)==null?void 0:o.type).value);break}}this.right=((l=t.lookahead)==null?void 0:l.type)===e.IDENTIFIER?new h(t):new u(t)}}class m{constructor(t,a){s(this,"type","AssignmentExpression");s(this,"left");s(this,"operator");s(this,"right");var i,c;this.left=a??(((i=t.lookahead)==null?void 0:i.type)===e.IDENTIFIER?new h(t):new u(t)),this.operator=String(t.eat("=").value),this.right=((c=t.lookahead)==null?void 0:c.type)===e.IDENTIFIER?new h(t):new u(t)}}class nt{constructor(t){s(this,"type","UpdateExpression");s(this,"operator");s(this,"argument");s(this,"prefix");var a,i,c;((a=t.lookahead)==null?void 0:a.type)==="++"||((i=t.lookahead)==null?void 0:i.type)==="--"?(this.prefix=!0,this.operator=String(t.eat(t.lookahead.type).value),this.argument=new h(t)):((c=t.lookahead)==null?void 0:c.type)===e.IDENTIFIER&&(this.argument=new h(t),this.prefix=!1,this.operator=String(t.eat(t.lookahead.type).value))}}class it{constructor(t){s(this,"type","UnaryExpression");s(this,"operator");s(this,"prefix");s(this,"argument");var a,i;switch((a=t.lookahead)==null?void 0:a.type){case"delete":case"void":case"typeof":case"+":case"-":case"~":case"!":{this.operator=String(t.eat((i=t.lookahead)==null?void 0:i.type).value),this.prefix=!0,this.argument=new u(t);break}}}}class ct{constructor(t,a){s(this,"type","LogicalExpression");s(this,"left");s(this,"operator");s(this,"right");var i,c,o,l;switch(this.left=a??(((i=t.lookahead)==null?void 0:i.type)===e.IDENTIFIER?new h(t):new u(t)),(c=t.lookahead)==null?void 0:c.type){case"??":case"||":case"&&":{this.operator=String(t.eat((o=t.lookahead)==null?void 0:o.type).value);break}}this.right=((l=t.lookahead)==null?void 0:l.type)===e.IDENTIFIER?new h(t):new u(t)}}class ot{constructor(t){s(this,"type","AwaitExpression");s(this,"argument");t.eat(e.AWAIT),this.argument=new u(t)}}class lt{constructor(t){s(this,"type","ThisExpression");t.eat(e.THIS)}}class C{constructor(t,a){s(this,"type","MemberExpression");s(this,"object");s(this,"property",null);s(this,"computed",!1);s(this,"optional",!1);var i,c,o;this.object=a;do switch((i=t.lookahead)==null?void 0:i.type){case"[":{t.eat("["),this.object=this.property?{...this}:{...this.object},this.property=new u(t),t.eat("]"),this.computed=!0;break}case".":{t.eat("."),this.object=this.property?{...this}:{...this.object},this.property=new h(t),this.computed=!1;break}}while(((c=t.lookahead)==null?void 0:c.type)==="."||((o=t.lookahead)==null?void 0:o.type)==="[")}}class ut{constructor(t){s(this,"type","ObjectProperty");s(this,"method",!1);s(this,"computed",!1);s(this,"key");s(this,"value");var a,i;((a=t.lookahead)==null?void 0:a.type)==="["?(t.eat("["),this.key=new u(t),this.computed=!0,t.eat("]")):this.key=new h(t),((i=t.lookahead)==null?void 0:i.type)===":"?(t.eat(":"),this.value=new u(t)):this.value=null}}class ht{constructor(t){s(this,"type","ObjectMethod");s(this,"method",!0);s(this,"key");s(this,"computed",!1);s(this,"kind","method");s(this,"generator",!1);s(this,"async",!1);s(this,"params",[]);s(this,"body");var i,c;switch((i=t.lookahead)==null?void 0:i.type){case e.GET:{t.eat(e.GET),this.kind="get",this.method=!1;break}case e.SET:{t.eat(e.SET),this.kind="set",this.method=!1;break}case e.ASYNC:{t.eat(e.ASYNC),this.async=!0;break}}((c=t.lookahead)==null?void 0:c.type)==="["?(t.eat("["),this.key=new u(t),this.computed=!0,t.eat("]")):this.key=new h(t);const a=new S(t,!0);this.generator=a.generator,this.params=a.params,this.body=a.body}}class Et{constructor(t,a="}"){s(this,"properties",[]);var i,c,o;for(;((i=t.lookahead)==null?void 0:i.type)!==a;){switch((c=t.lookahead)==null?void 0:c.type){case e.GET:case e.SET:case e.ASYNC:{this.properties.push(new ht(t));break}default:this.properties.push(new ut(t))}((o=t.lookahead)==null?void 0:o.type)!==a&&t.eat(",")}}}class bt{constructor(t){s(this,"type","ObjectLiteral");s(this,"properties",[]);t.eat("{"),this.properties=new Et(t).properties,t.eat("}")}}class dt{constructor(t){s(this,"type","ObjectExpression");s(this,"properties",[]);this.properties=new bt(t).properties}}class kt{constructor(t,a){s(this,"type","CallExpression");s(this,"callee");s(this,"arguments",[]);s(this,"optional",!1);var i;for(this.callee=a,t.eat("(");((i=t.lookahead)==null?void 0:i.type)!==")";)this.arguments.length>0&&t.eat(","),this.arguments.push(new u(t));t.eat(")")}}class u{constructor(t){var a,i,c,o;switch((a=t.lookahead)==null?void 0:a.type){case e.ASYNC:case e.FUNCTION:{Object.assign(this,new S(t));break}case"[":{Object.assign(this,new st(t));break}case"{":{Object.assign(this,new dt(t));break}case e.NUMBER:case e.STRING:case e.BOOLEAN:case e.NAN:case e.NULL:case e.UNDEFINED:{Object.assign(this,new v(t));break}case"++":case"--":{Object.assign(this,new nt(t));break}case"delete":case"void":case"typeof":case"+":case"-":case"~":case"!":{Object.assign(this,new it(t));break}case e.AWAIT:{Object.assign(this,new ot(t));break}case e.THIS:{Object.assign(this,new lt(t)),((i=t.lookahead)==null?void 0:i.type)==="."&&Object.assign(this,new C(t,this));break}case e.IDENTIFIER:{const l=new h(t);switch((c=t.lookahead)==null?void 0:c.type){case"+":case"-":case"*":case"/":case"%":case"**":case"^":case">":case">>":case">>>":case"<":case"<<":case"<<<":case">=":case"<=":case"==":case"===":{Object.assign(this,new at(t,l));break}case"=":{Object.assign(this,new m(t,l));break}case"??":case"||":case"&&":{Object.assign(this,new ct(t,l));break}case":":{Object.assign(this,new K(t,l));break}case"[":case".":{const E=new C(t,l);switch((o=t.lookahead)==null?void 0:o.type){case"=":{Object.assign(this,new m(t,E));break}case"(":{Object.assign(this,new kt(t,E));break}default:{Object.assign(this,E);break}}break}default:{Object.assign(this,l);break}}break}}}}class yt{constructor(t,a=!1){s(this,"type","ClassMethod");s(this,"async",!1);s(this,"static",!1);s(this,"computed",!1);s(this,"key");s(this,"kind","method");s(this,"value");var i,c;switch(this.static=a,(i=t.lookahead)==null?void 0:i.type){case e.GET:{t.eat(e.GET),this.kind="get";break}case e.SET:{t.eat(e.SET),this.kind="set";break}case e.ASYNC:{t.eat(e.ASYNC),this.async=!0;break}}((c=t.lookahead)==null?void 0:c.type)==="["?(t.eat("["),this.key=new u(t),this.computed=!0,t.eat("]")):this.key=new h(t),this.value=new S(t,!0)}}class It{constructor(t){s(this,"type","ClassProperty");s(this,"static",!1);s(this,"computed",!1);s(this,"key");s(this,"value");var a,i,c,o;switch(((a=t.lookahead)==null?void 0:a.type)===e.STATIC&&(t.eat(e.STATIC),this.static=!0),(i=t.lookahead)==null?void 0:i.type){case e.GET:case e.SET:case e.ASYNC:{Object.assign(this,new yt(t));break}default:((c=t.lookahead)==null?void 0:c.type)==="["?(t.eat("["),this.key=new u(t),this.computed=!0,t.eat("]")):this.key=new h(t),((o=t.lookahead)==null?void 0:o.type)==="="?(t.eat("="),this.value=new u(t)):this.value=null}}}class Tt{constructor(t){s(this,"type","ClassElementList");s(this,"properties",[]);var a;for(;((a=t.lookahead)==null?void 0:a.type)!=="}";)this.properties.push(new It(t))}}class Nt{constructor(t){s(this,"type","ClassBody");s(this,"body",[]);t.eat("{"),this.body=new Tt(t).properties,t.eat("}")}}class ft{constructor(t){s(this,"type","ClassDeclaration");s(this,"id");s(this,"superClass",null);s(this,"body");var a;t.eat(e.CLASS),this.id=new h(t),((a=t.lookahead)==null?void 0:a.type)==="("&&(t.eat("("),this.superClass=new h(t),t.eat(")")),this.body=new Nt(t)}}class St{constructor(t,a=")"){s(this,"parameters",[]);var i,c;for(;((i=t.lookahead)==null?void 0:i.type)!==a;)this.parameters.push(new h(t)),((c=t.lookahead)==null?void 0:c.type)!==a&&t.eat(",")}}class wt{constructor(t){s(this,"type","FunctionDeclaration");s(this,"id");s(this,"expression");s(this,"generator");s(this,"async");s(this,"params");s(this,"body");var E,I;let a=!1,i=!1;((E=t.lookahead)==null?void 0:E.type)===e.ASYNC&&(t.eat(e.ASYNC),a=!0),t.eat(e.FUNCTION),((I=t.lookahead)==null?void 0:I.type)==="*"&&(t.eat("*"),i=!0);const c=String(new h(t).name);t.eat("(");const o=new St(t,")").parameters;t.eat(")");const l=new d(t);this.id={type:e.IDENTIFIER,name:c},this.expression=!1,this.generator=i,this.async=a,this.params=o,this.body=l}}class At{constructor(t){var a;switch((a=t.lookahead)==null?void 0:a.type){case e.VAR:case e.LET:case e.CONST:{Object.assign(this,new w(t));break}case e.ASYNC:case e.FUNCTION:{Object.assign(this,new wt(t));break}case e.CLASS:{Object.assign(this,new ft(t));break}}}}class mt{constructor(t){var a;switch((a=t.lookahead)==null?void 0:a.type){case e.VAR:case e.LET:case e.CONST:case e.ASYNC:case e.FUNCTION:case e.CLASS:{Object.assign(this,new At(t));break}case e.IF:case e.DO:case e.WHILE:case e.FOR:case e.SWITCH:case e.CONTINUE:case e.BREAK:case e.RETURN:case e.WITH:case e.IDENTIFIER:case e.THROW:case e.TRY:case e.DEBUGGER:{Object.assign(this,new y(t));break}}}}class T{constructor(t,a){s(this,"body");var c,o,l;const i=[];for(;t.tokenizer.isEOF()===!1&&!(a!=null&&a.includes(String((c=t.lookahead)==null?void 0:c.type)));){const E=new mt(t);for(E!==void 0&&i.push(E),((o=t.lookahead)==null?void 0:o.type)===";"&&t.eat(";");((l=t.lookahead)==null?void 0:l.type)===";";)i.push(new L(t))}this.body=i}}class Ct{constructor(t){s(this,"type","Program");s(this,"body");this.body=new T(t).body}}class g{constructor(){s(this,"syntax");s(this,"tokenizer");s(this,"lookahead");this.syntax="",this.tokenizer=new N(this),this.lookahead=null}parse(t){return this.lookahead=null,this.syntax="",this.syntax=t,this.tokenizer=new N(this),this.lookahead=this.tokenizer.getNextToken(),new Ct(this)}eat(t){const a=this.lookahead;if(a===null)throw new SyntaxError(`Unexpected end of input, expected: "${t}"`);if(a.type!==t)switch(t){case e.IDENTIFIER:throw new SyntaxError(`Unexpected token: "${a.value}", cannot use keyword "${a.value}" for the beginning of the identifer`);default:throw new SyntaxError(`Unexpected token: "${a.value}", expected: "${t}"`)}return this.lookahead=this.tokenizer.getNextToken(t===e.IDENTIFIER),a}}const O=new g;typeof window<"u"&&(customElements.define("vi-script",class extends HTMLElement{constructor(){super()}connectedCallback(){setTimeout(()=>{var n;(n=this.childNodes.item(0).textContent)==null||n.trim(),this.innerHTML=""})}}),window.VietScript={parser:O}),b.Parser=g,b.Tokenizer=N,b.default=O,Object.defineProperties(b,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
+(function(w,e){typeof exports=="object"&&typeof module<"u"?e(exports):typeof define=="function"&&define.amd?define(["exports"],e):(w=typeof globalThis<"u"?globalThis:w||self,e(w["@vietscript/parser"]={}))})(this,function(w){"use strict";var Ie=Object.defineProperty;var Ne=(w,e,N)=>e in w?Ie(w,e,{enumerable:!0,configurable:!0,writable:!0,value:N}):w[e]=N;var a=(w,e,N)=>(Ne(w,typeof e!="symbol"?e+"":e,N),N);var e=(i=>(i.VAR="Var",i.CONST="Const",i.LET="Let",i.IF="If",i.ELSE="Else",i.FOR="For",i.WHILE="While",i.DO="Do",i.BREAK="Break",i.CONTINUE="Continue",i.RETURN="Return",i.SWITCH="Switch",i.CASE="Case",i.DEFAULT="Default",i.TRY="Try",i.CATCH="Catch",i.FINALLY="Finally",i.THROW="Throw",i.FUNCTION="Function",i.CLASS="Class",i.INTERFACE="Interface",i.ENUM="Enum",i.TYPE="Type",i.IMPORT="Import",i.EXPORT="Export",i.FROM="From",i.AS="As",i.IN="In",i.OF="Of",i.IS="Is",i.NEW="New",i.THIS="This",i.SUPER="Super",i.TRUE="True",i.FALSE="False",i.NULL="Null",i.VOID="Void",i.ANY="Any",i.NUMBER="Number",i.STRING="String",i.BOOLEAN="Boolean",i.OBJECT="Object",i.ARRAY="Array",i.NAN="NaN",i.INFINITY="Infinity",i.UNDEFINED="Undefined",i.STATIC="Static",i.PUBLIC="Public",i.PRIVATE="Private",i.PROTECTED="Protected",i.ABSTRACT="Abstract",i.EXTENDS="Extends",i.IMPLEMENTS="Implements",i.GET="Get",i.SET="Set",i.CONSTRUCTOR="Constructor",i.YIELD="Yield",i.AWAIT="Await",i.ASYNC="Async",i.DELETE="Delete",i.TYPEOF="Typeof",i.INSTANCEOF="Instanceof",i.WITH="With",i.DEBUGGER="Debugger",i.IDENTIFIER="Identifier",i))(e||{});const N=[/^[A-Za-z\u00C0-\u1EF9][A-Za-z0-9\u00C0-\u1EF9]*(\s[A-Za-z\u00C0-\u1EF9][A-Za-z0-9\u00C0-\u1EF9]*)*/,e.IDENTIFIER],$=[[/^\s+/,null],[/^\/\/.*/,null],[/^\/\*[\S\s]*?\*\//,null],[/^\[/,"["],[/^]/,"]"],[/^\(/,"("],[/^\)/,")"],[/^{/,"{"],[/^}/,"}"],[/^;/,";"],[/^,/,","],[/^:/,":"],[/^\.{3}/,"..."],[/^\.[\d_]+([eE][+-]?\d[\d_]*)?n?/,e.NUMBER],[/^\./,"."],[/^#/,"#"],[/^>>>=/,">>>="],[/^>>>/,">>>"],[/^>>=/,">>="],[/^>>/,">>"],[/^<<=/,"<<="],[/^<,"<<"],[/^<=/,"<="],[/^>=/,">="],[/^,"<"],[/^>/,">"],[/^===/,"==="],[/^!==/,"!=="],[/^==/,"=="],[/^!=/,"!="],[/^=>/,"=>"],[/^\*\*=/,"**="],[/^\*\*/,"**"],[/^\*=/,"*="],[/^\*/,"*"],[/^\+\+/,"++"],[/^\+=/,"+="],[/^\+/,"+"],[/^--/,"--"],[/^-=/,"-="],[/^-/,"-"],[/^\/=/,"/="],[/^\//,"/"],[/^%=/,"%="],[/^%/,"%"],[/^&&=/,"&&="],[/^&&/,"&&"],[/^&=/,"&="],[/^&/,"&"],[/^\|\|=/,"||="],[/^\|\|/,"||"],[/^\|=/,"|="],[/^\|/,"|"],[/^\^=/,"^="],[/^\^/,"^"],[/^~/,"~"],[/^!/,"!"],[/^\?\?=/,"??="],[/^\?\?/,"??"],[/^\?\./,"?."],[/^\?/,"?"],[/^=/,"="],[/^\b(var|khai b\u00E1o)\b/,e.VAR],[/^\b(break|ph\u00E1 v\u00F2ng l\u1EB7p)\b/,e.BREAK],[/^\b(do|th\u1EF1c hi\u1EC7n)\b/,e.DO],[/^\b(instanceof|l\u00E0 ki\u1EC3u)\b/,e.INSTANCEOF],[/^\b(typeof|ki\u1EC3u c\u1EE7a)\b/,e.TYPEOF],[/^\b(switch|duy\u1EC7t)\b/,e.SWITCH],[/^\b(case|tr\u01B0\u1EDDng h\u1EE3p)\b/,e.CASE],[/^\b(if|n\u1EBFu)\b/,e.IF],[/^\b(else|kh\u00F4ng th\u00EC)/,e.ELSE],[/^\bnew\b/,e.NEW],[/^\b(catch|b\u1EAFt l\u1ED7i)\b/,e.CATCH],[/^\b(finally|cu\u1ED1i c\u00F9ng)\b/,e.FINALLY],[/^\b(return|tr\u1EA3 v\u1EC1)/,e.RETURN],[/^\bvoid\b/,e.VOID],[/^\b(continue|ti\u1EBFp t\u1EE5c)\b/,e.CONTINUE],[/^\b(for|l\u1EB7p)\b/,e.FOR],[/^(while\b|khi m\u00E0(?![A-Za-z\u00C0-\u1EF9]))/,e.WHILE],[/^\bdebugger\b/,e.DEBUGGER],[/^\b(function|h\u00E0m)\b/,e.FUNCTION],[/^(this\b|\u0111\u00E2y\b)/,e.THIS],[/^\bwith\b/,e.WITH],[/^\b(default|m\u1EB7c \u0111\u1ECBnh)\b/,e.DEFAULT],[/^\b(throw|b\u00E1o l\u1ED7i)\b/,e.THROW],[/^(delete\b|xo\u00E1(?![A-Za-z\u00C0-\u1EF9]))/,e.DELETE],[/^\b(in|trong)\b/,e.IN],[/^\b(of|c\u1EE7a)\b/,e.OF],[/^\b(try|th\u1EED)/,e.TRY],[new RegExp("^\\b(as|như là)"),e.AS],[new RegExp("^\\b(from|từ)"),e.FROM],[/^const|h\u1EB1ng s\u1ED1/,e.CONST],[/^\b(class|l\u1EDBp)\b/,e.CLASS],[/^\b(super|kh\u1EDFi t\u1EA1o cha)\b/,e.SUPER],[/^\b(constructor|kh\u1EDFi t\u1EA1o)\b/,e.CONSTRUCTOR],[/^\b(extends|k\u1EBF th\u1EEBa)\b/,e.EXTENDS],[/^\b(export|cho ph\u00E9p)\b/,e.EXPORT],[/^\b(import|s\u1EED d\u1EE5ng)\b/,e.IMPORT],[/^\b(async|b\u1EA5t \u0111\u1ED3ng b\u1ED9)/,e.ASYNC],[/^(await\b|ch\u1EDD(?![A-Za-z\u00C0-\u1EF9]))/,e.AWAIT],[/^\b(yield|nh\u01B0\u1EDDng)\b/,e.YIELD],[/^\b(let|bi\u1EBFn)\b/,e.LET],[/^(private\b|ri\u00EAng t\u01B0(?![A-Za-z\u00C0-\u1EF9]))/,e.PRIVATE],[/^\b(public|c\u00F4ng khai)\b/,e.PUBLIC],[/^(protected\b|b\u1EA3o v\u1EC7(?![A-Za-z\u00C0-\u1EF9]))/,e.PROTECTED],[/^\b(static|t\u0129nh)\b/,e.STATIC],[/^\b(get|l\u1EA5y)\b/,e.GET],[/^\b(set|g\u00E1n)\b/,e.SET],[/^0[xX][0-9a-fA-F][0-9a-fA-F_]*n?/,e.NUMBER],[/^0[oO][0-7][0-7_]*n?/,e.NUMBER],[/^0[bB][01][01_]*n?/,e.NUMBER],[/^(\d[\d_]*(\.[\d_]*)?|\.[\d_]+)([eE][+-]?\d[\d_]*)?n?/,e.NUMBER],[/^"(?:\\[\s\S]|[^"\\])*"/,e.STRING],[/^'(?:\\[\s\S]|[^'\\])*'/,e.STRING],[/^\b(null|r\u1ED7ng)\b/,e.NULL],[/^\bNaN\b/,e.NAN],[/^\b(Infinity|v\u00F4 c\u1EF1c)\b/,e.INFINITY],[/^\b(undefined|kh\u00F4ng x\u00E1c \u0111\u1ECBnh)\b/,e.UNDEFINED],[/(true|false|\u0111\u00FAng|sai)\b/,e.BOOLEAN],N],it=new Set(["(","[","{",",",";",":","=","!","?","+","-","*","/","%","&&","||","??","=>","==","===","!=","!==","<",">","<=",">=","&","|","^","~","<<",">>",">>>","+=","-=","*=","/=","%=","**=","&=","|=","^=","<<=",">>=",">>>=","&&=","||=","??=","...",e.RETURN,e.YIELD,e.AWAIT,e.TYPEOF,e.VOID,e.DELETE,e.NEW,e.THROW,e.IN,e.OF,e.INSTANCEOF,e.CASE,e.DEFAULT]);class D{constructor(t){a(this,"parser");a(this,"cursor");a(this,"lastTokenType",null);this.parser=t,this.cursor=0}rollback(t){return this.parser.lookahead&&(this.parser.lookahead.end-=t),this.cursor-=t,this.cursor}isEOF(){return this.cursor===this.parser.syntax.length}hasMoreTokens(){return this.cursor0;){const l=t[n];if(l==="\\"){n+=2;continue}if(l==='"'||l==="'"){const h=l;for(n++;n0;)t[n]==="{"?h++:t[n]==="}"&&h--,n++;continue}if(t[n]==="`"){n++;break}n++}continue}l==="{"?c++:l==="}"&&c--,n++}continue}n++}throw new SyntaxError(`Template literal không đóng, bắt đầu tại vị trí ${s}`)}match(t,s){const n=s.split(";"),o=t.exec(n[0].concat(";"));return o&&o.index===0?(this.cursor+=o[0].length,o[0]):null}}function G(i,t){let s=1,n=1;for(let o=0;o=0&&c.push(` ${String(o).padStart(l)} | ${n[o-1]}`),c.push(`> ${String(s.line).padStart(l)} | ${n[o]??""}`),c.push(` ${" ".repeat(l)} | ${" ".repeat(Math.max(0,s.column-1))}^`),o+1{if(s[0]==="x")return String.fromCharCode(parseInt(s.slice(1),16));if(s[0]==="u"){const n=s[1]==="{"?s.slice(2,-1):s.slice(1);return String.fromCodePoint(parseInt(n,16))}switch(s){case"n":return`
+`;case"r":return"\r";case"t":return" ";case"b":return"\b";case"f":return"\f";case"v":return"\v";case"0":return"\0";case"\\":return"\\";case"'":return"'";case'"':return'"';default:return s}})}class L{constructor(t){a(this,"type","StringLiteral");a(this,"value");a(this,"extra");a(this,"start");a(this,"end");const s=t.eat(e.STRING);this.start=s.start,this.end=s.end;const o=String(s.value).slice(1,-1),c=ft(o);this.value=c,this.extra={rawValue:c,raw:JSON.stringify(c)}}}function yt(i){return i.replace(/\\(.)/g,(t,s)=>{switch(s){case"n":return`
+`;case"r":return"\r";case"t":return" ";case"\\":return"\\";case"`":return"`";case"$":return"$";default:return s}})}function bt(i){const t=[],s=[];let n="",o=0;for(;o0;){const h=i[o];if(h==="\\"){l+=h+i[o+1],o+=2;continue}if(h==='"'||h==="'"){const E=h;for(l+=h,o++;o"),this.params=s,this.async=n,((o=t.lookahead)==null?void 0:o.type)==="{"?(this.body=new b(t),this.expression=!1):(this.body=new u(t),this.expression=!0)}}class U{constructor(t,s=!1){a(this,"type","FunctionExpression");a(this,"id");a(this,"expression",!1);a(this,"generator",!1);a(this,"async",!1);a(this,"params",[]);a(this,"body");var n,o;((n=t.lookahead)==null?void 0:n.type)===e.ASYNC&&(t.eat(e.ASYNC),this.async=!0),s||t.eat(e.FUNCTION),((o=t.lookahead)==null?void 0:o.type)==="*"&&(t.eat("*"),this.generator=!0),t.eat("("),this.params=new Y(t,")").parameters,t.eat(")"),this.body=new b(t),this.id=null}}const J={[e.INSTANCEOF]:"instanceof",[e.IN]:"in"},Yt=new Set(["+","-","*","/","%","**","&","|","^",">",">>",">>>","<","<<",">=","<=","==","===","!=","!=="]);class Wt{constructor(t,s){a(this,"type","BinaryExpression");a(this,"left");a(this,"operator");a(this,"right");var o,c,l;this.left=s??(((o=t.lookahead)==null?void 0:o.type)===e.IDENTIFIER?new d(t):new u(t));const n=(c=t.lookahead)==null?void 0:c.type;J[n]?(t.eat(n),this.operator=J[n]):Yt.has(n)&&(this.operator=String(t.eat(n).value)),this.right=((l=t.lookahead)==null?void 0:l.type)===e.IDENTIFIER?new d(t):new u(t)}}const Mt=new Set(["=","+=","-=","*=","/=","%=","**=","&=","|=","^=","<<=",">>=",">>>=","&&=","||=","??="]);class W{constructor(t,s){a(this,"type","AssignmentExpression");a(this,"left");a(this,"operator");a(this,"right");var o,c,l;this.left=s??(((o=t.lookahead)==null?void 0:o.type)===e.IDENTIFIER?new d(t):new u(t));const n=String((c=t.lookahead)==null?void 0:c.type);if(!Mt.has(n))throw new SyntaxError(`Expected assignment operator, got: "${n}"`);this.operator=String(t.eat(n).value),this.right=((l=t.lookahead)==null?void 0:l.type)===e.IDENTIFIER?new d(t):new u(t)}}class Ht{constructor(t){a(this,"type","UpdateExpression");a(this,"operator");a(this,"argument");a(this,"prefix");var s,n,o;((s=t.lookahead)==null?void 0:s.type)==="++"||((n=t.lookahead)==null?void 0:n.type)==="--"?(this.prefix=!0,this.operator=String(t.eat(t.lookahead.type).value),this.argument=new d(t)):((o=t.lookahead)==null?void 0:o.type)===e.IDENTIFIER&&(this.argument=new d(t),this.prefix=!1,this.operator=String(t.eat(t.lookahead.type).value))}}const Q={[e.DELETE]:"delete",[e.VOID]:"void",[e.TYPEOF]:"typeof"};class $t{constructor(t){a(this,"type","UnaryExpression");a(this,"operator");a(this,"prefix");a(this,"argument");var n;const s=(n=t.lookahead)==null?void 0:n.type;Q[s]?(t.eat(s),this.operator=Q[s]):this.operator=String(t.eat(s).value),this.prefix=!0,this.argument=new u(t)}}class K{constructor(t,s){a(this,"type","LogicalExpression");a(this,"left");a(this,"operator");a(this,"right");var n,o,c,l;switch(this.left=s??(((n=t.lookahead)==null?void 0:n.type)===e.IDENTIFIER?new d(t):new u(t)),(o=t.lookahead)==null?void 0:o.type){case"??":case"||":case"&&":{this.operator=String(t.eat((c=t.lookahead)==null?void 0:c.type).value);break}}this.right=((l=t.lookahead)==null?void 0:l.type)===e.IDENTIFIER?new d(t):new u(t)}}class Gt{constructor(t){a(this,"type","AwaitExpression");a(this,"argument");t.eat(e.AWAIT),this.argument=new u(t)}}const Vt=i=>{var t;switch((t=i.lookahead)==null?void 0:t.type){case e.ASYNC:case e.FUNCTION:case"[":case"{":case e.NUMBER:case e.STRING:case e.BOOLEAN:case e.NAN:case e.NULL:case e.UNDEFINED:case"++":case"--":case"delete":case"void":case"typeof":case"+":case"-":case"~":case"!":case e.AWAIT:case e.THIS:case e.IDENTIFIER:return!0;default:return!1}};class zt{constructor(t){a(this,"type","YieldExpression");a(this,"delegate",!1);a(this,"argument",null);var s;t.eat(e.YIELD),((s=t.lookahead)==null?void 0:s.type)==="*"?(t.eat("*"),this.delegate=!0,this.argument=new u(t)):Vt(t)&&(this.argument=new u(t))}}class _t{constructor(t){a(this,"type","ThisExpression");t.eat(e.THIS)}}class M{constructor(t){a(this,"type","PrivateName");a(this,"id");t.eat("#"),this.id=new d(t)}}class v{constructor(t,s){a(this,"type","MemberExpression");a(this,"object");a(this,"property",null);a(this,"computed",!1);a(this,"optional",!1);var n,o,c,l,h,E;this.object=s;do switch((n=t.lookahead)==null?void 0:n.type){case"[":{t.eat("["),this.object=this.property?{...this}:{...this.object},this.property=new u(t),t.eat("]"),this.computed=!0,this.optional=!1;break}case".":{t.eat("."),this.object=this.property?{...this}:{...this.object},((o=t.lookahead)==null?void 0:o.type)==="#"?this.property=new M(t):this.property=new d(t),this.computed=!1,this.optional=!1;break}case"?.":{t.eat("?."),this.object=this.property?{...this}:{...this.object},this.type="OptionalMemberExpression",this.optional=!0,((c=t.lookahead)==null?void 0:c.type)==="["?(t.eat("["),this.property=new u(t),t.eat("]"),this.computed=!0):(this.property=new d(t),this.computed=!1);break}}while(((l=t.lookahead)==null?void 0:l.type)==="."||((h=t.lookahead)==null?void 0:h.type)==="["||((E=t.lookahead)==null?void 0:E.type)==="?.")}}class Xt{constructor(t){a(this,"type","ObjectProperty");a(this,"method",!1);a(this,"computed",!1);a(this,"shorthand",!1);a(this,"key");a(this,"value");var s,n;if(((s=t.lookahead)==null?void 0:s.type)==="["?(t.eat("["),this.key=new u(t),this.computed=!0,t.eat("]")):this.key=new d(t),((n=t.lookahead)==null?void 0:n.type)===":")t.eat(":"),this.value=new u(t);else if(!this.computed)this.value=this.key,this.shorthand=!0;else throw new SyntaxError("Computed property key phải có giá trị theo sau")}}class Zt{constructor(t){a(this,"type","ObjectMethod");a(this,"method",!0);a(this,"key");a(this,"computed",!1);a(this,"kind","method");a(this,"generator",!1);a(this,"async",!1);a(this,"params",[]);a(this,"body");var n,o;switch((n=t.lookahead)==null?void 0:n.type){case e.GET:{t.eat(e.GET),this.kind="get",this.method=!1;break}case e.SET:{t.eat(e.SET),this.kind="set",this.method=!1;break}case e.ASYNC:{t.eat(e.ASYNC),this.async=!0;break}}((o=t.lookahead)==null?void 0:o.type)==="["?(t.eat("["),this.key=new u(t),this.computed=!0,t.eat("]")):this.key=new d(t);const s=new U(t,!0);this.generator=s.generator,this.params=s.params,this.body=s.body}}class qt{constructor(t,s="}"){a(this,"properties",[]);var n,o,c,l;for(;((n=t.lookahead)==null?void 0:n.type)!==s;){if(((o=t.lookahead)==null?void 0:o.type)==="...")this.properties.push(new j(t));else switch((c=t.lookahead)==null?void 0:c.type){case e.GET:case e.SET:case e.ASYNC:{this.properties.push(new Zt(t));break}default:this.properties.push(new Xt(t))}((l=t.lookahead)==null?void 0:l.type)!==s&&t.eat(",")}}}class Jt{constructor(t){a(this,"type","ObjectLiteral");a(this,"properties",[]);t.eat("{"),this.properties=new qt(t).properties,t.eat("}")}}class Qt{constructor(t){a(this,"type","ObjectExpression");a(this,"properties",[]);this.properties=new Jt(t).properties}}class O{constructor(t,s){a(this,"type","CallExpression");a(this,"callee");a(this,"arguments",[]);a(this,"optional",!1);var n,o;for(this.callee=s,t.eat("(");((n=t.lookahead)==null?void 0:n.type)!==")";)this.arguments.length>0&&t.eat(","),((o=t.lookahead)==null?void 0:o.type)==="..."?this.arguments.push(new j(t)):this.arguments.push(new u(t));t.eat(")")}}class Kt{constructor(t){a(this,"type","NewExpression");a(this,"callee");a(this,"arguments",[]);var s,n,o;if(t.eat(e.NEW),this.callee=new d(t),((s=t.lookahead)==null?void 0:s.type)==="("){for(t.eat("(");((n=t.lookahead)==null?void 0:n.type)!==")";)this.arguments.length>0&&t.eat(","),((o=t.lookahead)==null?void 0:o.type)==="..."?this.arguments.push(new j(t)):this.arguments.push(new u(t));t.eat(")")}}}class te{constructor(t,s){a(this,"type","ConditionalExpression");a(this,"test");a(this,"consequent");a(this,"alternate");this.test=s,t.eat("?"),t.ternaryDepth++;try{this.consequent=new u(t)}finally{t.ternaryDepth--}t.eat(":"),this.alternate=new u(t)}}class ee{constructor(t,s){a(this,"type","TaggedTemplateExpression");a(this,"tag");a(this,"quasi");this.tag=s,this.quasi=new z(t)}}class u{applyPostfix(t){var s;for(;;){const n=(s=t.lookahead)==null?void 0:s.type;if(n==="."||n==="["||n==="?."){const o={...this};for(const c of Object.keys(this))delete this[c];Object.assign(this,new v(t,o));continue}if(n==="("){const o={...this};for(const c of Object.keys(this))delete this[c];Object.assign(this,new O(t,o));continue}if(n==="??"||n==="||"||n==="&&"){const o={...this};for(const c of Object.keys(this))delete this[c];Object.assign(this,new K(t,o));continue}break}}constructor(t){var s,n,o,c,l,h,E,y,g,k,T,x,R,C,F,p;switch((s=t.lookahead)==null?void 0:s.type){case e.ASYNC:case e.FUNCTION:{Object.assign(this,new U(t));break}case"[":{Object.assign(this,new Bt(t)),this.applyPostfix(t);break}case"(":{t.eat("(");const f=new Y(t,")").parameters;t.eat(")"),Object.assign(this,new q(t,f));break}case"{":{Object.assign(this,new Qt(t)),this.applyPostfix(t);break}case e.NUMBER:case e.STRING:case"TemplateLiteral":case"RegExpLiteral":case e.BOOLEAN:case e.NAN:case e.INFINITY:case e.NULL:case e.UNDEFINED:{Object.assign(this,new wt(t));break}case"++":case"--":{Object.assign(this,new Ht(t));break}case e.DELETE:case e.VOID:case e.TYPEOF:case"+":case"-":case"~":case"!":{Object.assign(this,new $t(t));break}case e.AWAIT:{Object.assign(this,new Gt(t));break}case e.YIELD:{Object.assign(this,new zt(t));break}case e.NEW:{Object.assign(this,new Kt(t));break}case e.IMPORT:{if(t.eat(e.IMPORT),((n=t.lookahead)==null?void 0:n.type)==="("){const f={type:"Import"};Object.assign(this,new O(t,f))}break}case e.SUPER:{t.eat(e.SUPER);const f={type:"Super"};((o=t.lookahead)==null?void 0:o.type)==="("?Object.assign(this,new O(t,f)):((c=t.lookahead)==null?void 0:c.type)==="."?Object.assign(this,new v(t,f)):Object.assign(this,f);break}case e.THIS:{if(Object.assign(this,new _t(t)),((l=t.lookahead)==null?void 0:l.type)==="."||((h=t.lookahead)==null?void 0:h.type)==="["||((E=t.lookahead)==null?void 0:E.type)==="?."){const f=new v(t,{...this});Object.assign(this,f)}switch((y=t.lookahead)==null?void 0:y.type){case"=":case"+=":case"-=":case"*=":case"/=":case"%=":case"**=":case"&=":case"|=":case"^=":case"<<=":case">>=":case">>>=":case"&&=":case"||=":case"??=":{Object.assign(this,new W(t,{...this}));break}case"(":{Object.assign(this,new O(t,{...this}));break}case"++":case"--":{const f=(g=t.lookahead)==null?void 0:g.value;t.eat(f);const I={...this};for(const me of Object.keys(this))delete this[me];Object.assign(this,{type:"UpdateExpression",operator:f,argument:I,prefix:!1});break}}break}case e.IDENTIFIER:{const f=new d(t);if(((k=t.lookahead)==null?void 0:k.type)==="=>"){Object.assign(this,new q(t,[f]));break}if(((T=t.lookahead)==null?void 0:T.type)==="TemplateLiteral"){Object.assign(this,new ee(t,f));break}if(((x=t.lookahead)==null?void 0:x.type)==="++"||((R=t.lookahead)==null?void 0:R.type)==="--"){const I=(C=t.lookahead)==null?void 0:C.value;t.eat(I),Object.assign(this,{type:"UpdateExpression",operator:I,argument:f,prefix:!1});break}switch((F=t.lookahead)==null?void 0:F.type){case"+":case"-":case"*":case"/":case"%":case"**":case"&":case"|":case"^":case">":case">>":case">>>":case"<":case"<<":case">=":case"<=":case"==":case"===":case"!=":case"!==":case e.INSTANCEOF:case e.IN:{Object.assign(this,new Wt(t,f));break}case"=":case"+=":case"-=":case"*=":case"/=":case"%=":case"**=":case"&=":case"|=":case"^=":case"<<=":case">>=":case">>>=":case"&&=":case"||=":case"??=":{Object.assign(this,new W(t,f));break}case"??":case"||":case"&&":{Object.assign(this,new K(t,f));break}case"?":{Object.assign(this,new te(t,f));break}case":":{t.ternaryDepth>0?Object.assign(this,f):Object.assign(this,new Ut(t,f));break}case"(":{Object.assign(this,new O(t,f)),this.applyPostfix(t);break}case"[":case".":case"?.":{const I=new v(t,f);switch((p=t.lookahead)==null?void 0:p.type){case"=":case"+=":case"-=":case"*=":case"/=":case"%=":case"**=":case"&=":case"|=":case"^=":case"<<=":case">>=":case">>>=":case"&&=":case"||=":case"??=":{Object.assign(this,new W(t,I));break}case"(":{Object.assign(this,new O(t,I));break}default:{Object.assign(this,I),this.applyPostfix(t);break}}break}default:{Object.assign(this,f);break}}break}}}}class tt{constructor(t,s=!1){a(this,"type","ClassMethod");a(this,"static",!1);a(this,"key");a(this,"computed",!1);a(this,"kind","method");a(this,"id",null);a(this,"generator",!1);a(this,"async",!1);a(this,"params",[]);a(this,"body");var o,c,l;switch(this.static=s,(o=t.lookahead)==null?void 0:o.type){case e.GET:{t.eat(e.GET),this.kind="get";break}case e.SET:{t.eat(e.SET),this.kind="set";break}case e.ASYNC:{t.eat(e.ASYNC),this.async=!0;break}}((c=t.lookahead)==null?void 0:c.type)==="["?(t.eat("["),this.key=new u(t),this.computed=!0,t.eat("]")):((l=t.lookahead)==null?void 0:l.type)==="#"?(this.key=new M(t),this.type="ClassPrivateMethod"):this.key=new d(t);const n=new U(t,!0);this.id=n.id,this.generator=n.generator,this.params=n.params,this.body=n.body}}const et={[e.PUBLIC]:"public",[e.PRIVATE]:"private",[e.PROTECTED]:"protected"};class se{constructor(t){a(this,"type","ClassProperty");a(this,"static",!1);a(this,"computed",!1);a(this,"accessibility");a(this,"key");a(this,"value");var n,o,c,l,h,E,y,g;const s=(n=t.lookahead)==null?void 0:n.type;switch(et[s]&&(t.eat(s),this.accessibility=et[s]),((o=t.lookahead)==null?void 0:o.type)===e.STATIC&&(t.eat(e.STATIC),this.static=!0),(c=t.lookahead)==null?void 0:c.type){case e.GET:case e.SET:case e.ASYNC:{Object.assign(this,new tt(t,this.static));break}case"*":{t.eat("*"),Object.assign(this,new tt(t,this.static)),this.generator=!0;break}default:{if(((l=t.lookahead)==null?void 0:l.type)==="[")t.eat("["),this.key=new u(t),this.computed=!0,t.eat("]");else if(((h=t.lookahead)==null?void 0:h.type)==="#")this.key=new M(t),this.type="ClassPrivateProperty";else if(((E=t.lookahead)==null?void 0:E.type)===e.CONSTRUCTOR){const k=t.eat(e.CONSTRUCTOR);this.key={type:"Identifier",name:"constructor",start:k.start,end:k.end}}else this.key=new d(t);if(((y=t.lookahead)==null?void 0:y.type)==="("){const k=new U(t,!0);this.type=this.type==="ClassPrivateProperty"?"ClassPrivateMethod":"ClassMethod",this.kind="method",this.generator=k.generator,this.async=k.async,this.params=k.params,this.body=k.body,this.id=null}else((g=t.lookahead)==null?void 0:g.type)==="="?(t.eat("="),this.value=new u(t)):this.value=null}}}}class ae{constructor(t){a(this,"type","ClassElementList");a(this,"properties",[]);var s;for(;((s=t.lookahead)==null?void 0:s.type)!=="}";)this.properties.push(new se(t))}}class ne{constructor(t){a(this,"type","ClassBody");a(this,"body",[]);t.eat("{"),this.body=new ae(t).properties,t.eat("}")}}class ie{constructor(t){a(this,"type","ClassDeclaration");a(this,"id");a(this,"superClass",null);a(this,"body");var s,n;t.eat(e.CLASS),this.id=new d(t),((s=t.lookahead)==null?void 0:s.type)==="("?(t.eat("("),this.superClass=new d(t),t.eat(")")):((n=t.lookahead)==null?void 0:n.type)===e.EXTENDS&&(t.eat(e.EXTENDS),this.superClass=new d(t)),this.body=new ne(t)}}class oe{constructor(t){a(this,"type","FunctionDeclaration");a(this,"id");a(this,"expression");a(this,"generator");a(this,"async");a(this,"params");a(this,"body");var h,E;let s=!1,n=!1;((h=t.lookahead)==null?void 0:h.type)===e.ASYNC&&(t.eat(e.ASYNC),s=!0),t.eat(e.FUNCTION),((E=t.lookahead)==null?void 0:E.type)==="*"&&(t.eat("*"),n=!0);const o=String(new d(t).name);t.eat("(");const c=new Y(t,")").parameters;t.eat(")");const l=new b(t);this.id={type:e.IDENTIFIER,name:o},this.expression=!1,this.generator=n,this.async=s,this.params=c,this.body=l}}class ce{constructor(t){a(this,"type","ExportAllDeclaration");a(this,"source");t.eat("*"),t.eat(e.FROM),this.source=new L(t)}}class le{constructor(t){a(this,"type","ExportSpecifier");a(this,"local");a(this,"exported");var s;this.local=new d(t),((s=t.lookahead)==null?void 0:s.type)===":"?(t.eat(":"),this.exported=new d(t)):this.exported=this.local}}class he{constructor(t){a(this,"specifiers",[]);var s,n;for(;((s=t.lookahead)==null?void 0:s.type)!=="}";)this.specifiers.push(new le(t)),((n=t.lookahead)==null?void 0:n.type)===","&&t.eat(",")}}class ue{constructor(t){a(this,"type","ExportNamedDeclaration");a(this,"specifiers",[]);a(this,"source");var s;t.eat("{"),this.specifiers.push(...new he(t).specifiers),t.eat("}"),((s=t.lookahead)==null?void 0:s.type)===e.FROM&&(t.eat(e.FROM),this.source=new L(t))}}class Ee{constructor(t){a(this,"type","ExportDefaultDeclaration");a(this,"declaration");t.eat(e.DEFAULT),this.declaration=new u(t)}}class de{constructor(t){var s,n;switch(((s=t.lookahead)==null?void 0:s.type)===e.EXPORT&&t.eat(e.EXPORT),(n=t.lookahead)==null?void 0:n.type){case"*":{Object.assign(this,new ce(t));break}case"{":{Object.assign(this,new ue(t));break}case e.DEFAULT:{Object.assign(this,new Ee(t));break}}}}class fe{constructor(t){a(this,"type","ImportNamespaceSpecifier");a(this,"local");var s,n;if(t.eat("*"),t.eat(e.AS),typeof((s=t.lookahead)==null?void 0:s.value)=="string"){const o=(n=/\s+từ$/g.exec(t.lookahead.value))==null?void 0:n[0];o&&(t.tokenizer.rollback(o.length),t.lookahead.value=t.lookahead.value.replace(/\s+từ$/g,""))}this.local=new d(t)}}class st{constructor(t){a(this,"type","ImportDefaultSpecifier");a(this,"local");var s,n;if(typeof((s=t.lookahead)==null?void 0:s.value)=="string"){const o=(n=/\s+từ$/g.exec(t.lookahead.value))==null?void 0:n[0];o&&(t.tokenizer.rollback(o.length),t.lookahead.value=t.lookahead.value.replace(/\s+từ$/g,""))}this.local=new d(t)}}class ye{constructor(t){a(this,"type","ImportSpecifier");a(this,"imported");a(this,"local");var s;this.imported=new d(t),((s=t.lookahead)==null?void 0:s.type)===":"?(t.eat(":"),this.local=new d(t)):this.local=this.imported}}class at{constructor(t){a(this,"specifiers",[]);var s,n;for(;((s=t.lookahead)==null?void 0:s.type)!=="}";)this.specifiers.push(new ye(t)),((n=t.lookahead)==null?void 0:n.type)===","&&t.eat(",")}}class be{constructor(t){a(this,"specifiers",[]);var s,n,o;switch((s=t.lookahead)==null?void 0:s.type){case"*":{this.specifiers=[new fe(t)];break}case"{":{t.eat("{"),this.specifiers=new at(t).specifiers,t.eat("}"),((n=t.lookahead)==null?void 0:n.type)===","&&(t.eat(","),t.eat("{"),this.specifiers.push(new st(t)),t.eat("}"));break}case e.IDENTIFIER:{this.specifiers=[new st(t)],((o=t.lookahead)==null?void 0:o.type)===","&&(t.eat(","),t.eat("{"),this.specifiers.push(...new at(t).specifiers),t.eat("}"));break}}}}class ke{constructor(t){a(this,"type","ImportDeclaration");a(this,"specifiers",[]);a(this,"source");a(this,"assertions",[]);a(this,"importType");var s,n,o,c;if(t.eat(e.IMPORT),((s=t.lookahead)==null?void 0:s.type)==="("){t.eat("(");const l=new u(t);t.eat(")"),Object.assign(this,{type:"ExpressionStatement",expression:{type:"CallExpression",callee:{type:"Import"},arguments:[l]}});return}(((n=t.lookahead)==null?void 0:n.type)===e.IDENTIFIER||((o=t.lookahead)==null?void 0:o.type)==="*"||((c=t.lookahead)==null?void 0:c.type)==="{")&&(this.importType="value",this.specifiers=new be(t).specifiers,t.eat(e.FROM)),this.source=new L(t)}}class we{constructor(t){var s;switch((s=t.lookahead)==null?void 0:s.type){case e.VAR:case e.LET:case e.CONST:{Object.assign(this,new X(t));break}case e.ASYNC:case e.FUNCTION:{Object.assign(this,new oe(t));break}case e.CLASS:{Object.assign(this,new ie(t));break}case e.EXPORT:{Object.assign(this,new de(t));break}case e.IMPORT:{Object.assign(this,new ke(t));break}}}}class ge{constructor(t){var s;switch((s=t.lookahead)==null?void 0:s.type){case e.VAR:case e.LET:case e.CONST:case e.ASYNC:case e.FUNCTION:case e.CLASS:case e.IMPORT:case e.EXPORT:{Object.assign(this,new we(t));break}case e.IF:case e.DO:case e.WHILE:case e.FOR:case e.SWITCH:case e.CONTINUE:case e.BREAK:case e.RETURN:case e.WITH:case e.IDENTIFIER:case e.THROW:case e.TRY:case e.DEBUGGER:case e.YIELD:case e.AWAIT:case e.THIS:case e.NUMBER:case e.STRING:case e.BOOLEAN:case e.NULL:case e.NAN:case e.INFINITY:case e.UNDEFINED:case e.SUPER:case e.NEW:{Object.assign(this,new m(t));break}}}}class P{constructor(t,s){a(this,"body");var o,c,l,h;const n=[];for(;t.tokenizer.isEOF()===!1&&!(s!=null&&s.includes(String((o=t.lookahead)==null?void 0:o.type)));){if(((c=t.lookahead)==null?void 0:c.type)===";"){n.push(new V(t));continue}const E=t.lookahead,y=new ge(t);if(t.lookahead===E&&t.lookahead!==null)throw new r(`Unexpected token: "${t.lookahead.value}" không thể bắt đầu một câu lệnh`,{file:t.filename,source:t.syntax,offset:t.lookahead.start});for(y!==void 0&&Object.keys(y).length>0&&n.push(y),((l=t.lookahead)==null?void 0:l.type)===";"&&t.eat(";");((h=t.lookahead)==null?void 0:h.type)===";";)n.push(new V(t))}this.body=n}}class Te{constructor(t){a(this,"type","Program");a(this,"body");this.body=new P(t).body}}class H{constructor(){a(this,"syntax");a(this,"tokenizer");a(this,"lookahead");a(this,"filename");a(this,"ternaryDepth",0);this.syntax="",this.tokenizer=new D(this),this.lookahead=null}parse(t){return this.lookahead=null,this.syntax="",this.syntax=t,this.tokenizer=new D(this),this.lookahead=this.tokenizer.getNextToken(),new Te(this)}eat(t){const s=this.lookahead;if(s===null)throw new r(`Unexpected end of input, expected: "${t}"`,{file:this.filename,source:this.syntax,offset:this.syntax.length});if(s.type!==t)throw t===e.IDENTIFIER?new r(`Unexpected token: "${s.value}", cannot use keyword "${s.value}" for the beginning of the identifer`,{file:this.filename,source:this.syntax,offset:s.start}):new r(`Unexpected token: "${s.value}", expected: "${t}"`,{file:this.filename,source:this.syntax,offset:s.start});return this.lookahead=this.tokenizer.getNextToken(),s}}const nt=new H;typeof window<"u"&&(window.VietScript={parser:nt}),w.Parser=H,w.Tokenizer=D,w.VietScriptError=r,w.default=nt,Object.defineProperties(w,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
diff --git a/packages/parser/src/__test__/errors.test.ts b/packages/parser/src/__test__/errors.test.ts
new file mode 100644
index 0000000..1becdae
--- /dev/null
+++ b/packages/parser/src/__test__/errors.test.ts
@@ -0,0 +1,74 @@
+import { VietScriptError, positionToLocation, formatSnippet } from "@parser/errors";
+
+describe("errors.test", () => {
+ describe("positionToLocation", () => {
+ it("should return line 1 col 1 for offset 0", () => {
+ expect(positionToLocation("abc", 0)).toEqual({ line: 1, column: 1 });
+ });
+
+ it("should count newlines", () => {
+ expect(positionToLocation("abc\ndef", 4)).toEqual({ line: 2, column: 1 });
+ });
+
+ it("should count columns within a line", () => {
+ expect(positionToLocation("abc\ndef", 6)).toEqual({ line: 2, column: 3 });
+ });
+
+ it("should clamp offset beyond source length", () => {
+ const loc = positionToLocation("ab", 100);
+ expect(loc.line).toBe(1);
+ expect(loc.column).toBeGreaterThan(1);
+ });
+ });
+
+ describe("formatSnippet", () => {
+ it("should show source line with caret", () => {
+ const snippet = formatSnippet("line1\nline2\nline3", 6);
+ expect(snippet).toContain("> 2 | line2");
+ expect(snippet).toContain("^");
+ });
+
+ it("should include context lines above/below", () => {
+ const snippet = formatSnippet("a\nb\nc\nd", 2);
+ expect(snippet).toContain("1 | a");
+ expect(snippet).toContain("> 2 | b");
+ expect(snippet).toContain("3 | c");
+ });
+ });
+
+ describe("VietScriptError", () => {
+ it("should format basic error", () => {
+ const err = new VietScriptError("lỗi gì đó");
+ expect(err.format()).toContain("VietScriptError: lỗi gì đó");
+ });
+
+ it("should include file:line:col", () => {
+ const err = new VietScriptError("msg", {
+ file: "test.vjs",
+ source: "abc\ndef",
+ offset: 4,
+ });
+ expect(err.format()).toContain("tại test.vjs:2:1");
+ });
+
+ it("should include snippet", () => {
+ const err = new VietScriptError("msg", {
+ source: "line1\nbad\nline3",
+ offset: 6,
+ });
+ expect(err.format()).toContain("> 2 | bad");
+ });
+
+ it("should include hint when provided", () => {
+ const err = new VietScriptError("msg", {
+ hint: "thử cách khác",
+ });
+ expect(err.format()).toContain("gợi ý: thử cách khác");
+ });
+
+ it("should be instanceof Error", () => {
+ const err = new VietScriptError("x");
+ expect(err instanceof Error).toBe(true);
+ });
+ });
+});
diff --git a/packages/parser/src/__test__/identifier-match-keyword.test.ts b/packages/parser/src/__test__/identifier-match-keyword.test.ts
index 5fbb526..a5720f5 100644
--- a/packages/parser/src/__test__/identifier-match-keyword.test.ts
+++ b/packages/parser/src/__test__/identifier-match-keyword.test.ts
@@ -4,10 +4,16 @@ import parser from "../setup-test";
import toPlainObject from "../toPlainObject";
describe("identifier-match-keyword.test", () => {
- it("should parse the syntax normally", () => {
- const result = parser.parse(`khai báo một lớp gì đó = 1`, Program);
+ it("should tokenize embedded keyword as separator, not as part of identifier", () => {
+ expect(() => {
+ parser.parse(`khai báo một lớp gì đó = 1`, Program);
+ }).toThrowError();
+ });
+
+ it("should parse multi-word identifier without embedded keyword", () => {
+ const result = parser.parse(`khai báo con mèo đẹp = 1`, Program);
- expect(toPlainObject(result)).toStrictEqual({
+ expect(toPlainObject(result)).toMatchObject({
type: "Program",
body: [
{
@@ -15,20 +21,8 @@ describe("identifier-match-keyword.test", () => {
declarations: [
{
type: "VariableDeclarator",
- id: {
- type: "Identifier",
- name: "_m7897t_l7899p_g236_273243",
- },
- init: {
- type: "NumericLiteral",
- value: 1,
- extra: {
- rawValue: 1,
- raw: "1",
- },
- start: 25,
- end: 26,
- },
+ id: { type: "Identifier" },
+ init: { type: "NumericLiteral", value: 1 },
},
],
kind: "var",
@@ -37,9 +31,9 @@ describe("identifier-match-keyword.test", () => {
});
});
- it("should show the syntax error", () => {
+ it("should show the syntax error when identifier starts with keyword", () => {
expect(() => {
parser.parse("khai báo lớp gì đó = 1", Program);
- }).toThrowError(/Unexpected token: "lớp", cannot use keyword "lớp" for the beginning of the identifer/);
+ }).toThrowError(/Unexpected token: "lớp"/);
});
});
diff --git a/packages/parser/src/__test__/program.test.ts b/packages/parser/src/__test__/program.test.ts
index 2e141e6..cfb9f1d 100644
--- a/packages/parser/src/__test__/program.test.ts
+++ b/packages/parser/src/__test__/program.test.ts
@@ -54,7 +54,7 @@ describe("program.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_tu7893i",
+ name: "tuổi",
},
init: {
type: "NumericLiteral",
@@ -83,7 +83,7 @@ describe("program.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_t234n",
+ name: "tên",
},
init: {
type: "StringLiteral",
@@ -106,11 +106,11 @@ describe("program.test", () => {
type: "ClassDeclaration",
id: {
type: "Identifier",
- name: "_Con_M232o",
+ name: "Con_Mèo",
},
superClass: {
type: "Identifier",
- name: "_2727897ng_V7853t",
+ name: "Động_Vật",
},
body: {
type: "ClassBody",
@@ -121,7 +121,7 @@ describe("program.test", () => {
computed: false,
key: {
type: "Identifier",
- name: "_s7889_ch226n",
+ name: "số_chân",
},
value: null,
},
@@ -131,7 +131,7 @@ describe("program.test", () => {
computed: false,
key: {
type: "Identifier",
- name: "_k234u",
+ name: "kêu",
},
kind: "method",
id: null,
@@ -140,11 +140,11 @@ describe("program.test", () => {
params: [
{
type: "Identifier",
- name: "_s7889_l7847n",
+ name: "số_lần",
},
{
type: "Identifier",
- name: "_hmm",
+ name: "hmm",
},
],
body: {
@@ -174,7 +174,7 @@ describe("program.test", () => {
type: "SwitchStatement",
discriminant: {
type: "Identifier",
- name: "_tu7893i_t225c",
+ name: "tuổi_tác",
},
cases: [
{
@@ -197,7 +197,7 @@ describe("program.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_xyz",
+ name: "xyz",
},
init: {
type: "NumericLiteral",
@@ -249,7 +249,7 @@ describe("program.test", () => {
type: "IfStatement",
test: {
type: "Identifier",
- name: "_273i7873u_ki7879n_m7897t",
+ name: "điều_kiện_một",
},
consequent: {
type: "BlockStatement",
@@ -261,12 +261,9 @@ describe("program.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_g236_273243",
- },
- init: {
- type: "Identifier",
- name: "undefined",
+ name: "gì_đó",
},
+ init: null,
},
],
kind: "var",
@@ -278,7 +275,7 @@ describe("program.test", () => {
type: "IfStatement",
test: {
type: "Identifier",
- name: "_273i7873u_ki7879n_hai",
+ name: "điều_kiện_hai",
},
consequent: {
type: "BlockStatement",
@@ -290,12 +287,9 @@ describe("program.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_g236_273243_kh225c",
- },
- init: {
- type: "Identifier",
- name: "undefined",
+ name: "gì_đó_khác",
},
+ init: null,
},
],
kind: "var",
@@ -313,12 +307,9 @@ describe("program.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_g236_273243_kh225c_n7919a",
- },
- init: {
- type: "Identifier",
- name: "undefined",
+ name: "gì_đó_khác_nữa",
},
+ init: null,
},
],
kind: "var",
@@ -340,12 +331,9 @@ describe("program.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_g236_273243",
- },
- init: {
- type: "Identifier",
- name: "undefined",
+ name: "gì_đó",
},
+ init: null,
},
],
kind: "var",
@@ -365,12 +353,9 @@ describe("program.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_g236_273243_kh225c",
- },
- init: {
- type: "Identifier",
- name: "undefined",
+ name: "gì_đó_khác",
},
+ init: null,
},
],
kind: "var",
@@ -380,7 +365,7 @@ describe("program.test", () => {
},
param: {
type: "Identifier",
- name: "_l7895i",
+ name: "lỗi",
},
},
finalizer: {
@@ -393,12 +378,9 @@ describe("program.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_g236_273243_kh225c_n7919a",
- },
- init: {
- type: "Identifier",
- name: "undefined",
+ name: "gì_đó_khác_nữa",
},
+ init: null,
},
],
kind: "var",
@@ -411,7 +393,7 @@ describe("program.test", () => {
type: "FunctionDeclaration",
id: {
type: "Identifier",
- name: "_ng7851u_nhi234n",
+ name: "ngẫu_nhiên",
},
expression: false,
generator: false,
@@ -427,7 +409,7 @@ describe("program.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_h224nh_2737897ng",
+ name: "hành_động",
},
init: {
type: "StringLiteral",
@@ -453,18 +435,18 @@ describe("program.test", () => {
type: "MemberExpression",
object: {
type: "Identifier",
- name: "_con_ch243",
+ name: "con_chó",
},
property: {
type: "Identifier",
- name: "_m7891m",
+ name: "mồm",
},
computed: false,
optional: false,
},
property: {
type: "Identifier",
- name: "_s7911a",
+ name: "sủa",
},
computed: false,
optional: false,
@@ -483,18 +465,18 @@ describe("program.test", () => {
type: "MemberExpression",
object: {
type: "Identifier",
- name: "_con_ch243",
+ name: "con_chó",
},
property: {
type: "Identifier",
- name: "_m7891m",
+ name: "mồm",
},
computed: false,
optional: false,
},
property: {
type: "Identifier",
- name: "_h224nh_2737897ng",
+ name: "hành_động",
},
computed: true,
optional: false,
@@ -527,7 +509,7 @@ describe("program.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_tu7893i",
+ name: "tuổi",
},
init: {
type: "NumericLiteral",
@@ -550,7 +532,7 @@ describe("program.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_2737883a_ch7881",
+ name: "địa_chỉ",
},
init: {
type: "StringLiteral",
@@ -573,7 +555,7 @@ describe("program.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_t234n",
+ name: "tên",
},
init: {
type: "StringLiteral",
diff --git a/packages/parser/src/__test__/tokenizer-edge.test.ts b/packages/parser/src/__test__/tokenizer-edge.test.ts
new file mode 100644
index 0000000..5e92e17
--- /dev/null
+++ b/packages/parser/src/__test__/tokenizer-edge.test.ts
@@ -0,0 +1,64 @@
+import { Keyword } from "@vietscript/shared";
+
+import { Parser } from "@parser/parser";
+import { Tokenizer } from "@parser/tokenizer";
+
+function tokenize(source: string) {
+ const parser = new Parser();
+ parser.syntax = source;
+ parser.tokenizer = new Tokenizer(parser);
+ const tokens = [];
+ let next = parser.tokenizer.getNextToken();
+ while (next !== null) {
+ tokens.push(next);
+ next = parser.tokenizer.getNextToken();
+ }
+ return tokens;
+}
+
+describe("tokenizer-edge.test", () => {
+ it("should skip whitespace and comments", () => {
+ const tokens = tokenize(" \n // comment\n /* block */\n42");
+ expect(tokens).toHaveLength(1);
+ expect(tokens[0].type).toBe(Keyword.NUMBER);
+ });
+
+ it("should handle empty source", () => {
+ const tokens = tokenize("");
+ expect(tokens).toHaveLength(0);
+ });
+
+ it("should throw on unknown token", () => {
+ expect(() => tokenize("@@@")).toThrow();
+ });
+
+ it("should tokenize complex operator sequence", () => {
+ const tokens = tokenize("a >>>= b");
+ expect(tokens.map((t) => t.type)).toEqual([Keyword.IDENTIFIER, ">>>=", Keyword.IDENTIFIER]);
+ });
+
+ it("should tokenize template with embedded interpolation", () => {
+ const tokens = tokenize("`a${b}c`");
+ expect(tokens).toHaveLength(1);
+ expect(tokens[0].type).toBe("TemplateLiteral");
+ });
+
+ it("should distinguish regex from division by context", () => {
+ const afterOp = tokenize("x = /abc/g");
+ expect(afterOp[2].type).toBe("RegExpLiteral");
+
+ const afterIdent = tokenize("x / y");
+ expect(afterIdent[1].type).toBe("/");
+ });
+
+ it("should tokenize nested destructuring-safe content", () => {
+ const tokens = tokenize("{ a: { b } }");
+ expect(tokens.length).toBeGreaterThan(5);
+ });
+
+ it("should handle unicode escape in string", () => {
+ const tokens = tokenize('"\\u00E1"');
+ expect(tokens).toHaveLength(1);
+ expect(tokens[0].type).toBe(Keyword.STRING);
+ });
+});
diff --git a/packages/parser/src/__test__/vietnamese-keywords.test.ts b/packages/parser/src/__test__/vietnamese-keywords.test.ts
new file mode 100644
index 0000000..9ad651f
--- /dev/null
+++ b/packages/parser/src/__test__/vietnamese-keywords.test.ts
@@ -0,0 +1,81 @@
+import { Keyword } from "@vietscript/shared";
+
+import { Parser } from "@parser/parser";
+import { Tokenizer } from "@parser/tokenizer";
+
+function tokenize(source: string) {
+ const parser = new Parser();
+ parser.syntax = source;
+ parser.tokenizer = new Tokenizer(parser);
+ parser.lookahead = null;
+
+ const tokens = [];
+ let next = parser.tokenizer.getNextToken();
+ while (next !== null) {
+ tokens.push(next);
+ next = parser.tokenizer.getNextToken();
+ }
+ return tokens;
+}
+
+describe("vietnamese-keywords.test", () => {
+ const cases: Array<[string, string, Keyword]> = [
+ ["biến", "let", Keyword.LET],
+ ["biến", "biến", Keyword.LET],
+ ["yield", "yield", Keyword.YIELD],
+ ["yield", "nhường", Keyword.YIELD],
+ ["this", "this", Keyword.THIS],
+ ["this", "đây", Keyword.THIS],
+ ["null", "null", Keyword.NULL],
+ ["null", "rỗng", Keyword.NULL],
+ ["instanceof", "instanceof", Keyword.INSTANCEOF],
+ ["instanceof", "là kiểu", Keyword.INSTANCEOF],
+ ["static", "static", Keyword.STATIC],
+ ["static", "tĩnh", Keyword.STATIC],
+ ["public", "public", Keyword.PUBLIC],
+ ["public", "công khai", Keyword.PUBLIC],
+ ["private", "private", Keyword.PRIVATE],
+ ["private", "riêng tư", Keyword.PRIVATE],
+ ["protected", "protected", Keyword.PROTECTED],
+ ["protected", "bảo vệ", Keyword.PROTECTED],
+ ["get", "get", Keyword.GET],
+ ["get", "lấy", Keyword.GET],
+ ["set", "set", Keyword.SET],
+ ["set", "gán", Keyword.SET],
+ ["of", "of", Keyword.OF],
+ ["of", "của", Keyword.OF],
+ ["Infinity", "Infinity", Keyword.INFINITY],
+ ["Infinity", "vô cực", Keyword.INFINITY],
+ ["for", "for", Keyword.FOR],
+ ["for", "lặp", Keyword.FOR],
+ ];
+
+ for (const [label, source, expected] of cases) {
+ it(`should tokenize "${source}" as ${label} keyword`, () => {
+ const tokens = tokenize(source);
+ expect(tokens).toHaveLength(1);
+ expect(tokens[0].type).toBe(expected);
+ expect(tokens[0].value).toBe(source);
+ });
+ }
+
+ it("should keep english keywords working after adding vietnamese aliases", () => {
+ const tokens = tokenize("let x");
+ expect(tokens.map((t) => t.type)).toEqual([Keyword.LET, Keyword.IDENTIFIER]);
+ });
+
+ it("should not consume trailing letters after vietnamese keyword ending in non-ASCII", () => {
+ const tokens = tokenize("riêng tư x");
+ expect(tokens).toHaveLength(2);
+ expect(tokens[0].type).toBe(Keyword.PRIVATE);
+ expect(tokens[1].type).toBe(Keyword.IDENTIFIER);
+ expect(tokens[1].value).toBe("x");
+ });
+
+ it("should tokenize bảo vệ followed by identifier", () => {
+ const tokens = tokenize("bảo vệ phương thức");
+ expect(tokens).toHaveLength(2);
+ expect(tokens[0].type).toBe(Keyword.PROTECTED);
+ expect(tokens[1].type).toBe(Keyword.IDENTIFIER);
+ });
+});
diff --git a/packages/parser/src/constants/specs.ts b/packages/parser/src/constants/specs.ts
index b7f4e34..23459f0 100644
--- a/packages/parser/src/constants/specs.ts
+++ b/packages/parser/src/constants/specs.ts
@@ -1,6 +1,6 @@
import { Keyword, Spec } from "@vietscript/shared";
-export const SpecIdentifier = [/^[A-Za-z\u00C0-\u1EF9]+(\s[A-Za-z\u00C0-\u1EF9]+)*/, Keyword.IDENTIFIER] as Spec;
+export const SpecIdentifier = [/^[A-Za-z\u00C0-\u1EF9][A-Za-z0-9\u00C0-\u1EF9]*(\s[A-Za-z\u00C0-\u1EF9][A-Za-z0-9\u00C0-\u1EF9]*)*/, Keyword.IDENTIFIER] as Spec;
export const Specs: Array = [
// --------------------------------------
@@ -9,141 +9,146 @@ export const Specs: Array = [
// --------------------------------------
// Comments:
- // Skip single-line comments:
[/^\/\/.*/, null],
- // Skip multi-line comments:
[/^\/\*[\S\s]*?\*\//, null],
// --------------------------------------
- // Symbols and delimiters:
- [/^\[/, "["], // OpenBracket
- [/^]/, "]"], // CloseBracket
- [/^\(/, "("], // OpenParen
- [/^\)/, ")"], // CloseParen
- [/^{/, "{"], // OpenBrace
- [/^}/, "}"], // CloseBrace
- [/^;/, ";"], // SemiColon
- [/^,/, ","], // Comma
- [/^===/, "==="], // IdentityEquals
- [/^==/, "=="], // Equals_
- [/^!==/, "!=="], // IdentityNotEquals
- [/^!=/, "!="], // NotEquals
- [/^=/, "="], // Assign
- [/^\?/, "?"], // QuestionMark
- [/^\?\.?/, "?."], // QuestionMarkDot
- [/^:/, ":"], // Colon
- [/^\.{3}/, "..."], // Ellipsis
- [/^\./, "."], // Dot
- [/^\+\+/, "++"], // PlusPlus
- [/^\+/, "+"], // Plus
- [/^--/, "--"], // MinusMinus
- [/^-/, "-"], // Minus
- [/^~/, "~"], // BitNot
- [/^!/, "!"], // Not
- [/^\*/, "*"], // Multiply
- [/^\//, "/"], // Divide
- [/^%/, "%"], // Modulus
- [/^\*\*/, "**"], // Power
- [/^\?\?/, "??"], // NullCoalesce
- [/^#/, "#"], // Hashtag
- [/^>>>=/, ">>>="], // RightShiftLogicalAssign
- [/^>>>/, ">>>"], // RightShiftLogical
- [/^>>/, ">>"], // RightShiftArithmetic
- [/^>>=/, ">>="], // RightShiftArithmeticAssign
- [/^<<=/, "<<="], // LeftShiftArithmeticAssign
- [/^<, "<<"], // LeftShiftArithmetic
- [/^<=/, "<="], // LessThanEquals
- [/^>=/, ">="], // GreaterThanEquals
- [/^, "<"], // LessThan
- [/^>/, ">"], // MoreThan
- [/^&&/, "&&"], // And
- [/^\|\|/, "||"], // Or
- [/^&/, "&"], // BitAnd
- [/^\^/, "^"], // BitXOr
- [/^\|/, "|"], // BitOr
- [/^\*=/, "*="], // MultiplyAssign
- [/^\//, "/="], // DivideAssign
- [/^%=/, "%="], // ModulusAssign
- [/^\+=/, "+="], // PlusAssign
- [/^-=/, "-="], // MinusAssign
- [/^&=/, "&="], // BitAndAssign
- [/^\^=/, "^="], // BitXorAssign
- [/^\|=/, "|="], // BitOrAssign
- [/^\*\*=/, "**="], // PowerAssign
- [/^=>/, "=>"], // ARROW
+ // Symbols and delimiters (ordered longest-first to avoid prefix conflicts):
+ [/^\[/, "["],
+ [/^]/, "]"],
+ [/^\(/, "("],
+ [/^\)/, ")"],
+ [/^{/, "{"],
+ [/^}/, "}"],
+ [/^;/, ";"],
+ [/^,/, ","],
+ [/^:/, ":"],
+ [/^\.{3}/, "..."],
+ [/^\.[\d_]+([eE][+-]?\d[\d_]*)?n?/, Keyword.NUMBER],
+ [/^\./, "."],
+ [/^#/, "#"],
+
+ [/^>>>=/, ">>>="],
+ [/^>>>/, ">>>"],
+ [/^>>=/, ">>="],
+ [/^>>/, ">>"],
+ [/^<<=/, "<<="],
+ [/^<, "<<"],
+ [/^<=/, "<="],
+ [/^>=/, ">="],
+ [/^, "<"],
+ [/^>/, ">"],
+
+ [/^===/, "==="],
+ [/^!==/, "!=="],
+ [/^==/, "=="],
+ [/^!=/, "!="],
+
+ [/^=>/, "=>"],
+ [/^\*\*=/, "**="],
+ [/^\*\*/, "**"],
+ [/^\*=/, "*="],
+ [/^\*/, "*"],
+ [/^\+\+/, "++"],
+ [/^\+=/, "+="],
+ [/^\+/, "+"],
+ [/^--/, "--"],
+ [/^-=/, "-="],
+ [/^-/, "-"],
+ [/^\/=/, "/="],
+ [/^\//, "/"],
+ [/^%=/, "%="],
+ [/^%/, "%"],
+
+ [/^&&=/, "&&="],
+ [/^&&/, "&&"],
+ [/^&=/, "&="],
+ [/^&/, "&"],
+ [/^\|\|=/, "||="],
+ [/^\|\|/, "||"],
+ [/^\|=/, "|="],
+ [/^\|/, "|"],
+ [/^\^=/, "^="],
+ [/^\^/, "^"],
+ [/^~/, "~"],
+ [/^!/, "!"],
+
+ [/^\?\?=/, "??="],
+ [/^\?\?/, "??"],
+ [/^\?\./, "?."],
+ [/^\?/, "?"],
+
+ [/^=/, "="],
// --------------------------------------
// Keywords
- [/^\b(var|khai b\u00E1o)\b/, Keyword.VAR], // khai báo
- [/^\b(break|ph\u00E1 v\u00F2ng l\u1EB7p)\b/, Keyword.BREAK], // phá vòng lặp
- [/^\b(do|th\u1EF1c hi\u1EC7n)\b/, Keyword.DO], // thực hiện
- [/^\binstanceof\b/, Keyword.INSTANCEOF],
- [/^\b(typeof|ki\u1EC3u c\u1EE7a)\b/, Keyword.TYPEOF], // kiểu của
- [/^\b(switch|duy\u1EC7t)\b/, Keyword.SWITCH], // duyệt
- [/^\b(case|tr\u01B0\u1EDDng h\u1EE3p)\b/, Keyword.CASE], // trường hợp
- [/^\b(if|n\u1EBFu)\b/, Keyword.IF], // nếu
- [/^\b(else|kh\u00F4ng th\u00EC)/, Keyword.ELSE], // không thì
+ [/^\b(var|khai b\u00E1o)\b/, Keyword.VAR],
+ [/^\b(break|ph\u00E1 v\u00F2ng l\u1EB7p)\b/, Keyword.BREAK],
+ [/^\b(do|th\u1EF1c hi\u1EC7n)\b/, Keyword.DO],
+ [/^\b(instanceof|l\u00E0 ki\u1EC3u)\b/, Keyword.INSTANCEOF],
+ [/^\b(typeof|ki\u1EC3u c\u1EE7a)\b/, Keyword.TYPEOF],
+ [/^\b(switch|duy\u1EC7t)\b/, Keyword.SWITCH],
+ [/^\b(case|tr\u01B0\u1EDDng h\u1EE3p)\b/, Keyword.CASE],
+ [/^\b(if|n\u1EBFu)\b/, Keyword.IF],
+ [/^\b(else|kh\u00F4ng th\u00EC)/, Keyword.ELSE],
[/^\bnew\b/, Keyword.NEW],
- [/^\b(catch|b\u1EAFt l\u1ED7i)\b/, Keyword.CATCH], // bắt lỗi
- [/^\b(finally|cu\u1ED1i c\u00F9ng)\b/, Keyword.FINALLY], // cuối cùng
- [/^\b(return|tr\u1EA3 v\u1EC1)/, Keyword.RETURN], // trả về
+ [/^\b(catch|b\u1EAFt l\u1ED7i)\b/, Keyword.CATCH],
+ [/^\b(finally|cu\u1ED1i c\u00F9ng)\b/, Keyword.FINALLY],
+ [/^\b(return|tr\u1EA3 v\u1EC1)/, Keyword.RETURN],
[/^\bvoid\b/, Keyword.VOID],
- [/^\b(continue|ti\u1EBFp t\u1EE5c)\b/, Keyword.CONTINUE], // tiếp tục
- [/^\bfor\b/, Keyword.FOR], // lặp
- [/^\b(while|khi m\u00E0)\b/, Keyword.WHILE], // khi mà
+ [/^\b(continue|ti\u1EBFp t\u1EE5c)\b/, Keyword.CONTINUE],
+ [/^\b(for|l\u1EB7p)\b/, Keyword.FOR],
+ [/^(while\b|khi m\u00E0(?![A-Za-z\u00C0-\u1EF9]))/, Keyword.WHILE],
[/^\bdebugger\b/, Keyword.DEBUGGER],
- [/^\b(function|h\u00E0m)\b/, Keyword.FUNCTION], // hàm
- [/^\bthis\b/, Keyword.THIS], // đối tượng này
- [/^\bwith\b/, Keyword.WITH], // với
- [/^\b(default|m\u1EB7c \u0111\u1ECBnh)\b/, Keyword.DEFAULT], // mặc định
- [/^\b(throw|b\u00E1o l\u1ED7i)\b/, Keyword.THROW], // báo lỗi
- [/^\b(delete|xo\u00E1)\b/, Keyword.DELETE], // xoá
- [/^\b(in|trong)\b/, Keyword.IN], // trong
- [/^\b(try|th\u1EED)/, Keyword.TRY], // thử
- [new RegExp("^\\b(as|như là)"), Keyword.AS], // như là
- [new RegExp("^\\b(from|từ)"), Keyword.FROM], // từ
+ [/^\b(function|h\u00E0m)\b/, Keyword.FUNCTION],
+ [/^(this\b|\u0111\u00E2y\b)/, Keyword.THIS],
+ [/^\bwith\b/, Keyword.WITH],
+ [/^\b(default|m\u1EB7c \u0111\u1ECBnh)\b/, Keyword.DEFAULT],
+ [/^\b(throw|b\u00E1o l\u1ED7i)\b/, Keyword.THROW],
+ [/^(delete\b|xo\u00E1(?![A-Za-z\u00C0-\u1EF9]))/, Keyword.DELETE],
+ [/^\b(in|trong)\b/, Keyword.IN],
+ [/^\b(of|c\u1EE7a)\b/, Keyword.OF],
+ [/^\b(try|th\u1EED)/, Keyword.TRY],
+ [new RegExp("^\\b(as|như là)"), Keyword.AS],
+ [new RegExp("^\\b(from|từ)"), Keyword.FROM],
// --------------------------------------
// Future Reserved Words
- [/^const|h\u1EB1ng s\u1ED1/, Keyword.CONST], // hằng số
- [/^\b(class|l\u1EDBp)\b/, Keyword.CLASS], // lớp
- [/^\b(super|kh\u1EDFi t\u1EA1o cha)\b/, Keyword.SUPER], // khởi tạo cha
- [/^\b(constructor|kh\u1EDFi t\u1EA1o)\b/, Keyword.CONSTRUCTOR], // khởi tạo
- [/^\b(extends|k\u1EBF th\u1EEBa)\b/, Keyword.EXTENDS], // kế thừa
- [/^\b(export|cho ph\u00E9p)\b/, Keyword.EXPORT], // xuất, cho phép
- [/^\b(import|s\u1EED d\u1EE5ng)\b/, Keyword.IMPORT], // nhập, sử dụng
- [/^\b(async|b\u1EA5t \u0111\u1ED3ng b\u1ED9)/, Keyword.ASYNC], // bất đồng bộ
- [/^\b(await|ch\u1EDD)\b/, Keyword.AWAIT], // chờ
- [/^\byield\b/, Keyword.YIELD],
- [/^\blet\b/, Keyword.LET],
- [/^\bprivate\b/, Keyword.PRIVATE],
- [/^\bpublic\b/, Keyword.PUBLIC],
- [/^\bprotected\b/, Keyword.PROTECTED],
- [/^\bstatic\b/, Keyword.STATIC],
-
- // --------------------------------------
- // Primitive Literals:
-
- // --------------------------------------
- // Numbers:
- [/^(\d+(\.|)(\d+|)([Ee]([+-]|)\d+|))/, Keyword.NUMBER],
-
- // --------------------------------------
- // Double quoted String:
- [/^"[^"]*"/, Keyword.STRING],
+ [/^const|h\u1EB1ng s\u1ED1/, Keyword.CONST],
+ [/^\b(class|l\u1EDBp)\b/, Keyword.CLASS],
+ [/^\b(super|kh\u1EDFi t\u1EA1o cha)\b/, Keyword.SUPER],
+ [/^\b(constructor|kh\u1EDFi t\u1EA1o)\b/, Keyword.CONSTRUCTOR],
+ [/^\b(extends|k\u1EBF th\u1EEBa)\b/, Keyword.EXTENDS],
+ [/^\b(export|cho ph\u00E9p)\b/, Keyword.EXPORT],
+ [/^\b(import|s\u1EED d\u1EE5ng)\b/, Keyword.IMPORT],
+ [/^\b(async|b\u1EA5t \u0111\u1ED3ng b\u1ED9)/, Keyword.ASYNC],
+ [/^(await\b|ch\u1EDD(?![A-Za-z\u00C0-\u1EF9]))/, Keyword.AWAIT],
+ [/^\b(yield|nh\u01B0\u1EDDng)\b/, Keyword.YIELD],
+ [/^\b(let|bi\u1EBFn)\b/, Keyword.LET],
+ [/^(private\b|ri\u00EAng t\u01B0(?![A-Za-z\u00C0-\u1EF9]))/, Keyword.PRIVATE],
+ [/^\b(public|c\u00F4ng khai)\b/, Keyword.PUBLIC],
+ [/^(protected\b|b\u1EA3o v\u1EC7(?![A-Za-z\u00C0-\u1EF9]))/, Keyword.PROTECTED],
+ [/^\b(static|t\u0129nh)\b/, Keyword.STATIC],
+ [/^\b(get|l\u1EA5y)\b/, Keyword.GET],
+ [/^\b(set|g\u00E1n)\b/, Keyword.SET],
// --------------------------------------
- // Single quoted String:
- [/^'[^']*'/, Keyword.STRING],
+ // Numbers (order matters: hex/oct/bin before decimal):
+ [/^0[xX][0-9a-fA-F][0-9a-fA-F_]*n?/, Keyword.NUMBER],
+ [/^0[oO][0-7][0-7_]*n?/, Keyword.NUMBER],
+ [/^0[bB][01][01_]*n?/, Keyword.NUMBER],
+ [/^(\d[\d_]*(\.[\d_]*)?|\.[\d_]+)([eE][+-]?\d[\d_]*)?n?/, Keyword.NUMBER],
// --------------------------------------
- // Template quoted String:
- [/^`[^`]*`/, Keyword.STRING],
+ // Strings (with escape support):
+ [/^"(?:\\[\s\S]|[^"\\])*"/, Keyword.STRING],
+ [/^'(?:\\[\s\S]|[^'\\])*'/, Keyword.STRING],
// --------------------------------------
// Literal with Keyword:
- [/^\bnull\b/, Keyword.NULL],
+ [/^\b(null|r\u1ED7ng)\b/, Keyword.NULL],
[/^\bNaN\b/, Keyword.NAN],
+ [/^\b(Infinity|v\u00F4 c\u1EF1c)\b/, Keyword.INFINITY],
[/^\b(undefined|kh\u00F4ng x\u00E1c \u0111\u1ECBnh)\b/, Keyword.UNDEFINED],
[/(true|false|\u0111\u00FAng|sai)\b/, Keyword.BOOLEAN],
diff --git a/packages/parser/src/errors.ts b/packages/parser/src/errors.ts
new file mode 100644
index 0000000..ca6737b
--- /dev/null
+++ b/packages/parser/src/errors.ts
@@ -0,0 +1,77 @@
+export interface Location {
+ line: number;
+ column: number;
+}
+
+export function positionToLocation(source: string, offset: number): Location {
+ let line = 1;
+ let column = 1;
+ for (let i = 0; i < offset && i < source.length; i++) {
+ if (source[i] === "\n") {
+ line++;
+ column = 1;
+ } else {
+ column++;
+ }
+ }
+ return { line, column };
+}
+
+export function formatSnippet(source: string, offset: number): string {
+ const loc = positionToLocation(source, offset);
+ const lines = source.split("\n");
+ const lineIndex = loc.line - 1;
+
+ const out: string[] = [];
+ const pad = String(loc.line + 1).length;
+
+ if (lineIndex - 1 >= 0) {
+ out.push(` ${String(lineIndex).padStart(pad)} | ${lines[lineIndex - 1]}`);
+ }
+ out.push(`> ${String(loc.line).padStart(pad)} | ${lines[lineIndex] ?? ""}`);
+ out.push(` ${" ".repeat(pad)} | ${" ".repeat(Math.max(0, loc.column - 1))}^`);
+ if (lineIndex + 1 < lines.length) {
+ out.push(` ${String(loc.line + 1).padStart(pad)} | ${lines[lineIndex + 1]}`);
+ }
+ return out.join("\n");
+}
+
+export class VietScriptError extends Error {
+ file?: string;
+ line?: number;
+ column?: number;
+ snippet?: string;
+ hint?: string;
+
+ constructor(
+ message: string,
+ options: { file?: string; source?: string; offset?: number; hint?: string } = {},
+ ) {
+ super(message);
+ this.name = "VietScriptError";
+ this.file = options.file;
+ this.hint = options.hint;
+
+ if (options.source !== undefined && options.offset !== undefined) {
+ const loc = positionToLocation(options.source, options.offset);
+ this.line = loc.line;
+ this.column = loc.column;
+ this.snippet = formatSnippet(options.source, options.offset);
+ }
+ }
+
+ format(): string {
+ const parts: string[] = [];
+ parts.push(`VietScriptError: ${this.message}`);
+ if (this.file && this.line !== undefined) {
+ parts.push(` tại ${this.file}:${this.line}:${this.column}`);
+ }
+ if (this.snippet) {
+ parts.push(this.snippet);
+ }
+ if (this.hint) {
+ parts.push(` gợi ý: ${this.hint}`);
+ }
+ return parts.join("\n");
+ }
+}
diff --git a/packages/parser/src/index.ts b/packages/parser/src/index.ts
index e418911..7369704 100644
--- a/packages/parser/src/index.ts
+++ b/packages/parser/src/index.ts
@@ -6,29 +6,8 @@ export default parser;
export { Tokenizer } from "./tokenizer";
export { Parser } from "./parser";
+export { VietScriptError } from "./errors";
if (typeof window !== "undefined") {
- customElements.define(
- "vi-script",
- class extends HTMLElement {
- constructor() {
- super();
- }
-
- connectedCallback() {
- setTimeout(() => {
- const script = this.childNodes.item(0).textContent?.trim();
-
- this.innerHTML = "";
-
- if (script) {
- // executor.execute(script);
- }
- });
- }
- },
- );
-
- // @ts-ignore
- window.VietScript = { parser };
+ (window as unknown as { VietScript: { parser: Parser } }).VietScript = { parser };
}
diff --git a/packages/parser/src/nodes/declarations/ClassDeclaration.ts b/packages/parser/src/nodes/declarations/ClassDeclaration.ts
index 4af27a6..728532a 100644
--- a/packages/parser/src/nodes/declarations/ClassDeclaration.ts
+++ b/packages/parser/src/nodes/declarations/ClassDeclaration.ts
@@ -20,10 +20,11 @@ export class ClassDeclaration {
if (parser.lookahead?.type === "(") {
parser.eat("(");
-
this.superClass = new Identifier(parser);
-
parser.eat(")");
+ } else if (parser.lookahead?.type === Keyword.EXTENDS) {
+ parser.eat(Keyword.EXTENDS);
+ this.superClass = new Identifier(parser);
}
this.body = new ClassBody(parser);
diff --git a/packages/parser/src/nodes/declarations/ParameterList.ts b/packages/parser/src/nodes/declarations/ParameterList.ts
index 311039a..fea2fea 100644
--- a/packages/parser/src/nodes/declarations/ParameterList.ts
+++ b/packages/parser/src/nodes/declarations/ParameterList.ts
@@ -1,13 +1,52 @@
import { Parser } from "@parser/parser";
+import { Expression } from "../expressions/Expression";
import { Identifier } from "../identifier/Identifier";
+import { ObjectPattern } from "../patterns/ObjectPattern";
+import { ArrayPattern } from "../patterns/ArrayPattern";
+
+export interface AssignmentPattern {
+ type: "AssignmentPattern";
+ left: Identifier | ObjectPattern | ArrayPattern;
+ right: Expression;
+}
+
+export interface RestElement {
+ type: "RestElement";
+ argument: Identifier | ObjectPattern | ArrayPattern;
+}
+
+export type Parameter = Identifier | ObjectPattern | ArrayPattern | AssignmentPattern | RestElement;
export class ParameterList {
- parameters: Array = [];
+ parameters: Array = [];
constructor(parser: Parser, stopToken = ")") {
while (parser.lookahead?.type !== stopToken) {
- this.parameters.push(new Identifier(parser));
+ if (parser.lookahead?.type === "...") {
+ parser.eat("...");
+ const argument = parseBindingTarget(parser);
+ this.parameters.push({ type: "RestElement", argument });
+
+ if (parser.lookahead?.type !== stopToken) {
+ throw new SyntaxError(`Rest parameter phải ở vị trí cuối, không được theo sau bởi tham số khác`);
+ }
+ break;
+ }
+
+ const left = parseBindingTarget(parser);
+
+ if (parser.lookahead?.type === "=") {
+ parser.eat("=");
+ const right = new Expression(parser);
+ this.parameters.push({
+ type: "AssignmentPattern",
+ left,
+ right,
+ });
+ } else {
+ this.parameters.push(left);
+ }
if (parser.lookahead?.type !== stopToken) {
parser.eat(",");
@@ -15,3 +54,9 @@ export class ParameterList {
}
}
}
+
+function parseBindingTarget(parser: Parser): Identifier | ObjectPattern | ArrayPattern {
+ if (parser.lookahead?.type === "{") return new ObjectPattern(parser);
+ if (parser.lookahead?.type === "[") return new ArrayPattern(parser);
+ return new Identifier(parser);
+}
diff --git a/packages/parser/src/nodes/declarations/VariableDeclarator.ts b/packages/parser/src/nodes/declarations/VariableDeclarator.ts
index beafd87..bbd58c9 100644
--- a/packages/parser/src/nodes/declarations/VariableDeclarator.ts
+++ b/packages/parser/src/nodes/declarations/VariableDeclarator.ts
@@ -1,27 +1,27 @@
import { Parser } from "@parser/parser";
import { Expression } from "@parser/nodes/expressions/Expression";
import { Identifier } from "@parser/nodes/identifier/Identifier";
-import { Keyword } from "@vietscript/shared";
+import { ObjectPattern } from "@parser/nodes/patterns/ObjectPattern";
+import { ArrayPattern } from "@parser/nodes/patterns/ArrayPattern";
export class VariableDeclarator {
type = "VariableDeclarator";
- id: Identifier;
+ id: Identifier | ObjectPattern | ArrayPattern;
- init: Expression = {
- type: Keyword.IDENTIFIER,
- name: "undefined",
- };
+ init: Expression | null = null;
constructor(parser: Parser, isConstant = false) {
- const identifier = new Identifier(parser);
+ let id: Identifier | ObjectPattern | ArrayPattern;
+ if (parser.lookahead?.type === "{") id = new ObjectPattern(parser);
+ else if (parser.lookahead?.type === "[") id = new ArrayPattern(parser);
+ else id = new Identifier(parser);
- if (isConstant || parser.lookahead?.type === "=") {
+ if (parser.lookahead?.type === "=") {
parser.eat("=");
-
this.init = new Expression(parser);
}
- this.id = identifier;
+ this.id = id;
}
}
diff --git a/packages/parser/src/nodes/declarations/__test__/declaration-class-extras.test.ts b/packages/parser/src/nodes/declarations/__test__/declaration-class-extras.test.ts
new file mode 100644
index 0000000..d3e4224
--- /dev/null
+++ b/packages/parser/src/nodes/declarations/__test__/declaration-class-extras.test.ts
@@ -0,0 +1,83 @@
+import { ClassDeclaration } from "@parser/nodes/declarations/ClassDeclaration";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("declaration-class-extras.test", () => {
+ it("should parse static property", () => {
+ const result = parser.parse(`lớp A { static x = 1 }`, ClassDeclaration);
+ expect(toPlainObject(result)).toMatchObject({
+ body: {
+ body: [
+ {
+ type: "ClassProperty",
+ static: true,
+ key: { type: "Identifier", name: "x" },
+ },
+ ],
+ },
+ });
+ });
+
+ it("should parse static method", () => {
+ const result = parser.parse(`lớp A { static foo() {} }`, ClassDeclaration);
+ expect(toPlainObject(result)).toMatchObject({
+ body: {
+ body: [
+ {
+ type: "ClassMethod",
+ static: true,
+ },
+ ],
+ },
+ });
+ });
+
+ it("should parse tĩnh as static (Vietnamese)", () => {
+ const result = parser.parse(`lớp A { tĩnh x = 1 }`, ClassDeclaration);
+ expect(toPlainObject(result)).toMatchObject({
+ body: {
+ body: [{ static: true }],
+ },
+ });
+ });
+
+ it("should parse super.method call", () => {
+ const result = parser.parse(
+ `lớp B kế thừa A {
+ khởi tạo() { khởi tạo cha() }
+ }`,
+ ClassDeclaration,
+ );
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ClassDeclaration",
+ superClass: { type: "Identifier" },
+ });
+ });
+
+ it("should parse generator method", () => {
+ const result = parser.parse(
+ `lớp A {
+ *foo() {
+ nhường 1
+ }
+ }`,
+ ClassDeclaration,
+ );
+ const plain = toPlainObject(result) as any;
+ expect(plain.type).toBe("ClassDeclaration");
+ });
+
+ it("should parse access modifiers", () => {
+ const result = parser.parse(
+ `lớp A {
+ công khai x = 1
+ riêng tư y = 2
+ bảo vệ z = 3
+ }`,
+ ClassDeclaration,
+ );
+ const plain = toPlainObject(result) as any;
+ expect(plain.body.body).toHaveLength(3);
+ });
+});
diff --git a/packages/parser/src/nodes/declarations/__test__/declaration-class.test.ts b/packages/parser/src/nodes/declarations/__test__/declaration-class.test.ts
index ff98f88..bc4c063 100644
--- a/packages/parser/src/nodes/declarations/__test__/declaration-class.test.ts
+++ b/packages/parser/src/nodes/declarations/__test__/declaration-class.test.ts
@@ -22,11 +22,11 @@ describe("declaration-class.test", () => {
type: "ClassDeclaration",
id: {
type: "Identifier",
- name: "_Con_M232o",
+ name: "Con_Mèo",
},
superClass: {
type: "Identifier",
- name: "_2727897ng_V7853t",
+ name: "Động_Vật",
},
body: {
type: "ClassBody",
@@ -37,7 +37,7 @@ describe("declaration-class.test", () => {
computed: false,
key: {
type: "Identifier",
- name: "_s7889_ch226n",
+ name: "số_chân",
},
value: {
type: "NumericLiteral",
@@ -56,7 +56,7 @@ describe("declaration-class.test", () => {
computed: false,
key: {
type: "Identifier",
- name: "_k234u",
+ name: "kêu",
},
kind: "method",
id: null,
@@ -65,11 +65,11 @@ describe("declaration-class.test", () => {
params: [
{
type: "Identifier",
- name: "_s7889_l7847n",
+ name: "số_lần",
},
{
type: "Identifier",
- name: "_hmm",
+ name: "hmm",
},
],
body: {
diff --git a/packages/parser/src/nodes/declarations/__test__/declaration-default-params.test.ts b/packages/parser/src/nodes/declarations/__test__/declaration-default-params.test.ts
new file mode 100644
index 0000000..6128e28
--- /dev/null
+++ b/packages/parser/src/nodes/declarations/__test__/declaration-default-params.test.ts
@@ -0,0 +1,68 @@
+import { FunctionDeclaration } from "@parser/nodes/declarations/FunctionDeclaration";
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("declaration-default-params.test", () => {
+ it("should parse default param in function declaration", () => {
+ const result = parser.parse(`hàm f(a = 1) {}`, FunctionDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "FunctionDeclaration",
+ params: [
+ {
+ type: "AssignmentPattern",
+ left: { type: "Identifier", name: "a" },
+ right: { type: "NumericLiteral", value: 1 },
+ },
+ ],
+ });
+ });
+
+ it("should parse mixed normal and default params", () => {
+ const result = parser.parse(`hàm f(a, b = 2, c) {}`, FunctionDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "FunctionDeclaration",
+ params: [
+ { type: "Identifier", name: "a" },
+ {
+ type: "AssignmentPattern",
+ left: { type: "Identifier", name: "b" },
+ right: { type: "NumericLiteral", value: 2 },
+ },
+ { type: "Identifier", name: "c" },
+ ],
+ });
+ });
+
+ it("should parse default param in arrow function", () => {
+ const result = parser.parse(`(a = 1) => a`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ArrowFunctionExpression",
+ params: [
+ {
+ type: "AssignmentPattern",
+ left: { type: "Identifier", name: "a" },
+ right: { type: "NumericLiteral", value: 1 },
+ },
+ ],
+ });
+ });
+
+ it("should parse string default", () => {
+ const result = parser.parse(`hàm f(name = "Tú") {}`, FunctionDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ params: [
+ {
+ type: "AssignmentPattern",
+ left: { type: "Identifier" },
+ right: { type: "StringLiteral", value: "Tú" },
+ },
+ ],
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/declarations/__test__/declaration-dynamic-import.test.ts b/packages/parser/src/nodes/declarations/__test__/declaration-dynamic-import.test.ts
new file mode 100644
index 0000000..e6c3855
--- /dev/null
+++ b/packages/parser/src/nodes/declarations/__test__/declaration-dynamic-import.test.ts
@@ -0,0 +1,37 @@
+import { Program } from "@parser/nodes/Program";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("declaration-dynamic-import.test", () => {
+ it("should parse dynamic import with string literal", () => {
+ const result = parser.parse(`sử dụng("./module")`, Program);
+ const plain = toPlainObject(result) as any;
+ expect(plain.body[0]).toMatchObject({
+ type: "ExpressionStatement",
+ expression: {
+ type: "CallExpression",
+ callee: { type: "Import" },
+ arguments: [{ type: "StringLiteral", value: "./module" }],
+ },
+ });
+ });
+
+ it("should parse dynamic import with English keyword", () => {
+ const result = parser.parse(`import("./module")`, Program);
+ const plain = toPlainObject(result) as any;
+ expect(plain.body[0]).toMatchObject({
+ type: "ExpressionStatement",
+ expression: {
+ type: "CallExpression",
+ callee: { type: "Import" },
+ },
+ });
+ });
+
+ it("should still parse static import normally", () => {
+ const result = parser.parse(`sử dụng { foo } từ "./bar"`, Program);
+ const plain = toPlainObject(result) as any;
+ expect(plain.body[0].type).toBe("ImportDeclaration");
+ });
+});
diff --git a/packages/parser/src/nodes/declarations/__test__/declaration-export.test.ts b/packages/parser/src/nodes/declarations/__test__/declaration-export.test.ts
index 6df4bd2..bdae91b 100644
--- a/packages/parser/src/nodes/declarations/__test__/declaration-export.test.ts
+++ b/packages/parser/src/nodes/declarations/__test__/declaration-export.test.ts
@@ -32,11 +32,11 @@ describe("declaration-export.test", () => {
type: "ExportSpecifier",
local: {
type: "Identifier",
- name: "_t237nh_n259ng_g236_2737845y",
+ name: "tính_năng_gì_đấy",
},
exported: {
type: "Identifier",
- name: "_t237nh_n259ng_g236_2737845y",
+ name: "tính_năng_gì_đấy",
},
},
],
@@ -70,33 +70,33 @@ describe("declaration-export.test", () => {
type: "ExportSpecifier",
local: {
type: "Identifier",
- name: "_t237nh_n259ng_c7897ng",
+ name: "tính_năng_cộng",
},
exported: {
type: "Identifier",
- name: "_ph432417ng_th7913c_c7897ng",
+ name: "phương_thức_cộng",
},
},
{
type: "ExportSpecifier",
local: {
type: "Identifier",
- name: "_t237nh_n259ng_chia",
+ name: "tính_năng_chia",
},
exported: {
type: "Identifier",
- name: "_ph432417ng_th7913c_chia",
+ name: "phương_thức_chia",
},
},
{
type: "ExportSpecifier",
local: {
type: "Identifier",
- name: "_ph432417ng_th7913c_tr7915",
+ name: "phương_thức_trừ",
},
exported: {
type: "Identifier",
- name: "_ph432417ng_th7913c_tr7915",
+ name: "phương_thức_trừ",
},
},
],
@@ -120,7 +120,7 @@ describe("declaration-export.test", () => {
type: "ExportDefaultDeclaration",
declaration: {
type: "Identifier",
- name: "_con_m232o",
+ name: "con_mèo",
},
} as ExportDeclaration);
});
diff --git a/packages/parser/src/nodes/declarations/__test__/declaration-getter-setter.test.ts b/packages/parser/src/nodes/declarations/__test__/declaration-getter-setter.test.ts
new file mode 100644
index 0000000..d0e4df5
--- /dev/null
+++ b/packages/parser/src/nodes/declarations/__test__/declaration-getter-setter.test.ts
@@ -0,0 +1,67 @@
+import { ClassDeclaration } from "@parser/nodes/declarations/ClassDeclaration";
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("declaration-getter-setter.test", () => {
+ it("should parse getter in class", () => {
+ const result = parser.parse(`lớp Cat { lấy tuổi() { trả về 3 } }`, ClassDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ body: {
+ body: [
+ {
+ type: "ClassMethod",
+ kind: "get",
+ key: { type: "Identifier" },
+ },
+ ],
+ },
+ });
+ });
+
+ it("should parse setter in class", () => {
+ const result = parser.parse(`lớp Cat { gán tuổi(v) {} }`, ClassDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ body: {
+ body: [
+ {
+ type: "ClassMethod",
+ kind: "set",
+ key: { type: "Identifier" },
+ params: [{ type: "Identifier" }],
+ },
+ ],
+ },
+ });
+ });
+
+ it("should parse getter in object literal", () => {
+ const result = parser.parse(`{ lấy tuổi() { trả về 3 } }`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ObjectExpression",
+ properties: [
+ {
+ type: "ObjectMethod",
+ kind: "get",
+ },
+ ],
+ });
+ });
+
+ it("should parse setter in object literal", () => {
+ const result = parser.parse(`{ gán tuổi(v) {} }`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ properties: [
+ {
+ type: "ObjectMethod",
+ kind: "set",
+ },
+ ],
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/declarations/__test__/declaration-import.test.ts b/packages/parser/src/nodes/declarations/__test__/declaration-import.test.ts
index 7333b89..148c6f6 100644
--- a/packages/parser/src/nodes/declarations/__test__/declaration-import.test.ts
+++ b/packages/parser/src/nodes/declarations/__test__/declaration-import.test.ts
@@ -5,30 +5,31 @@ import { ImportDeclaration } from "../import/ImportDeclaration";
describe("declaration-import.test", () => {
it("should parse the syntax normally", () => {
- const result = parser.parse(`sử dụng "./test-path"`, Declaration);
+ const result = parser.parse(`sử dụng "./test-path-1"`, Declaration);
expect(toPlainObject(result)).toStrictEqual({
type: "ImportDeclaration",
specifiers: [],
source: {
type: "StringLiteral",
- value: "./test-path",
+ value: "./test-path-1",
extra: {
- rawValue: "./test-path",
- raw: '"./test-path"',
+ rawValue: "./test-path-1",
+ raw: '"./test-path-1"',
},
start: 8,
- end: 21,
+ end: 23,
},
assertions: [],
} as ImportDeclaration);
});
it("should parse the syntax normally", () => {
- const result = parser.parse(`sử dụng * như là abc từ "./test-path"`, Declaration);
+ const result = parser.parse(`sử dụng * như là abc từ "./test-path-2"`, Declaration);
expect(toPlainObject(result)).toStrictEqual({
type: "ImportDeclaration",
+ importType: "value",
specifiers: [
{
type: "ImportNamespaceSpecifier",
@@ -40,78 +41,80 @@ describe("declaration-import.test", () => {
],
source: {
type: "StringLiteral",
- value: "./test-path",
+ value: "./test-path-2",
extra: {
- rawValue: "./test-path",
- raw: '"./test-path"',
+ rawValue: "./test-path-2",
+ raw: '"./test-path-2"',
},
start: 24,
- end: 37,
+ end: 39,
},
assertions: [],
} as ImportDeclaration);
});
it("should parse the syntax normally", () => {
- const result = parser.parse(`sử dụng cái gì đó từ "./test-path"`, Declaration);
+ const result = parser.parse(`sử dụng cái gì đó từ "./test-path-3"`, Declaration);
expect(toPlainObject(result)).toStrictEqual({
type: "ImportDeclaration",
+ importType: "value",
specifiers: [
{
type: "ImportDefaultSpecifier",
local: {
type: "Identifier",
- name: "_c225i_g236_273243",
+ name: "cái_gì_đó",
},
},
],
source: {
type: "StringLiteral",
- value: "./test-path",
+ value: "./test-path-3",
extra: {
- rawValue: "./test-path",
- raw: '"./test-path"',
+ rawValue: "./test-path-3",
+ raw: '"./test-path-3"',
},
- start: 20,
- end: 33,
+ start: 21,
+ end: 36,
},
assertions: [],
} as ImportDeclaration);
});
it("should parse the syntax normally", () => {
- const result = parser.parse(`sử dụng cái gì đó, { con cún : con chó con } từ "./test-path"`, Declaration);
+ const result = parser.parse(`sử dụng cái gì đó, { con cún : con chó con } từ "./test-path-4"`, Declaration);
expect(toPlainObject(result)).toStrictEqual({
type: "ImportDeclaration",
specifiers: [
{
+ type: "ImportDefaultSpecifier",
local: {
type: "Identifier",
- name: "_c225i_g236_273243",
+ name: "cái_gì_đó",
},
},
{
type: "ImportSpecifier",
imported: {
type: "Identifier",
- name: "_con_c250n",
+ name: "con_cún",
},
local: {
type: "Identifier",
- name: "_con_ch243_con",
+ name: "con_chó_con",
},
},
],
source: {
type: "StringLiteral",
- value: "./test-path",
+ value: "./test-path-4",
extra: {
- rawValue: "./test-path",
- raw: '"./test-path"',
+ rawValue: "./test-path-4",
+ raw: '"./test-path-4"',
},
start: 48,
- end: 61,
+ end: 63,
},
assertions: [],
importType: "value",
diff --git a/packages/parser/src/nodes/declarations/__test__/declaration-private-field.test.ts b/packages/parser/src/nodes/declarations/__test__/declaration-private-field.test.ts
new file mode 100644
index 0000000..0bfbc6d
--- /dev/null
+++ b/packages/parser/src/nodes/declarations/__test__/declaration-private-field.test.ts
@@ -0,0 +1,49 @@
+import { ClassDeclaration } from "@parser/nodes/declarations/ClassDeclaration";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("declaration-private-field.test", () => {
+ it("should parse private field with initializer", () => {
+ const result = parser.parse(
+ `lớp Cat { #tuổi = 3 }`,
+ ClassDeclaration,
+ );
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ClassDeclaration",
+ body: {
+ type: "ClassBody",
+ body: [
+ {
+ type: "ClassPrivateProperty",
+ key: {
+ type: "PrivateName",
+ id: { type: "Identifier" },
+ },
+ value: { type: "NumericLiteral", value: 3 },
+ },
+ ],
+ },
+ });
+ });
+
+ it("should parse private field without initializer", () => {
+ const result = parser.parse(
+ `lớp Cat { #tuổi }`,
+ ClassDeclaration,
+ );
+
+ expect(toPlainObject(result)).toMatchObject({
+ body: {
+ body: [
+ {
+ type: "ClassPrivateProperty",
+ key: { type: "PrivateName" },
+ value: null,
+ },
+ ],
+ },
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/declarations/__test__/declaration-rest-params.test.ts b/packages/parser/src/nodes/declarations/__test__/declaration-rest-params.test.ts
new file mode 100644
index 0000000..cecb7a5
--- /dev/null
+++ b/packages/parser/src/nodes/declarations/__test__/declaration-rest-params.test.ts
@@ -0,0 +1,56 @@
+import { FunctionDeclaration } from "@parser/nodes/declarations/FunctionDeclaration";
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("declaration-rest-params.test", () => {
+ it("should parse rest param alone", () => {
+ const result = parser.parse(`hàm f(...args) {}`, FunctionDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "FunctionDeclaration",
+ params: [
+ {
+ type: "RestElement",
+ argument: { type: "Identifier", name: "args" },
+ },
+ ],
+ });
+ });
+
+ it("should parse rest param after normal params", () => {
+ const result = parser.parse(`hàm f(a, b, ...rest) {}`, FunctionDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ params: [
+ { type: "Identifier", name: "a" },
+ { type: "Identifier", name: "b" },
+ {
+ type: "RestElement",
+ argument: { type: "Identifier", name: "rest" },
+ },
+ ],
+ });
+ });
+
+ it("should parse rest param in arrow", () => {
+ const result = parser.parse(`(...args) => args`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ArrowFunctionExpression",
+ params: [
+ {
+ type: "RestElement",
+ argument: { type: "Identifier", name: "args" },
+ },
+ ],
+ });
+ });
+
+ it("should throw when rest param is not last", () => {
+ expect(() => {
+ parser.parse(`hàm f(...a, b) {}`, FunctionDeclaration);
+ }).toThrowError(/Rest parameter/);
+ });
+});
diff --git a/packages/parser/src/nodes/declarations/__test__/declaration-variable.test.ts b/packages/parser/src/nodes/declarations/__test__/declaration-variable.test.ts
index c2dc094..01a1f8e 100644
--- a/packages/parser/src/nodes/declarations/__test__/declaration-variable.test.ts
+++ b/packages/parser/src/nodes/declarations/__test__/declaration-variable.test.ts
@@ -15,7 +15,7 @@ describe("declaration-variable.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_a",
+ name: "a",
},
init: {
type: "NumericLiteral",
@@ -43,7 +43,7 @@ describe("declaration-variable.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_a",
+ name: "a",
},
init: {
type: "NumericLiteral",
@@ -60,12 +60,9 @@ describe("declaration-variable.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_b",
- },
- init: {
- type: "Identifier",
- name: "undefined",
+ name: "b",
},
+ init: null,
},
],
kind: "var",
@@ -82,7 +79,7 @@ describe("declaration-variable.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_a",
+ name: "a",
},
init: {
type: "NumericLiteral",
@@ -110,7 +107,7 @@ describe("declaration-variable.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_a",
+ name: "a",
},
init: {
type: "NumericLiteral",
@@ -127,12 +124,9 @@ describe("declaration-variable.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_b",
- },
- init: {
- type: "Identifier",
- name: "undefined",
+ name: "b",
},
+ init: null,
},
],
kind: "let",
@@ -149,7 +143,7 @@ describe("declaration-variable.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_a",
+ name: "a",
},
init: {
type: "NumericLiteral",
@@ -177,7 +171,7 @@ describe("declaration-variable.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_s7889_m7897t",
+ name: "số_một",
},
init: {
type: "NumericLiteral",
@@ -205,7 +199,7 @@ describe("declaration-variable.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_a",
+ name: "a",
},
init: {
type: "NumericLiteral",
@@ -222,12 +216,9 @@ describe("declaration-variable.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_b",
- },
- init: {
- type: "Identifier",
- name: "undefined",
+ name: "b",
},
+ init: null,
},
],
kind: "var",
@@ -244,7 +235,7 @@ describe("declaration-variable.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_h7885_v224_t234n",
+ name: "họ_và_tên",
},
init: {
type: "StringLiteral",
@@ -272,11 +263,11 @@ describe("declaration-variable.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_tu7893i",
+ name: "tuổi",
},
init: {
type: "Identifier",
- name: "Undefined",
+ name: "undefined",
start: 16,
end: 30,
},
@@ -285,7 +276,7 @@ describe("declaration-variable.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_t234n",
+ name: "tên",
},
init: {
type: "StringLiteral",
diff --git a/packages/parser/src/nodes/declarations/__test__/generator/__snapshots__/generator-default-params.test.ts.snap b/packages/parser/src/nodes/declarations/__test__/generator/__snapshots__/generator-default-params.test.ts.snap
new file mode 100644
index 0000000..1281858
--- /dev/null
+++ b/packages/parser/src/nodes/declarations/__test__/generator/__snapshots__/generator-default-params.test.ts.snap
@@ -0,0 +1,7 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`generator-default-params.test > should generate default param in arrow 1`] = `"(a = 1) => a"`;
+
+exports[`generator-default-params.test > should generate default param in function 1`] = `"function f(a = 1) {}"`;
+
+exports[`generator-default-params.test > should generate mixed params 1`] = `"function f(a, b = 2, c) {}"`;
diff --git a/packages/parser/src/nodes/declarations/__test__/generator/__snapshots__/generator-getter-setter.test.ts.snap b/packages/parser/src/nodes/declarations/__test__/generator/__snapshots__/generator-getter-setter.test.ts.snap
new file mode 100644
index 0000000..24bb1a0
--- /dev/null
+++ b/packages/parser/src/nodes/declarations/__test__/generator/__snapshots__/generator-getter-setter.test.ts.snap
@@ -0,0 +1,23 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`generator-getter-setter.test > should generate getter in class 1`] = `
+"class Cat {
+ get tuổi() {
+ return 3;
+ }
+}"
+`;
+
+exports[`generator-getter-setter.test > should generate getter in object literal 1`] = `
+"{
+ get tuổi() {
+ return 3;
+ }
+}"
+`;
+
+exports[`generator-getter-setter.test > should generate setter in class 1`] = `
+"class Cat {
+ set tuổi(v) {}
+}"
+`;
diff --git a/packages/parser/src/nodes/declarations/__test__/generator/__snapshots__/generator-private-field.test.ts.snap b/packages/parser/src/nodes/declarations/__test__/generator/__snapshots__/generator-private-field.test.ts.snap
new file mode 100644
index 0000000..1ef7268
--- /dev/null
+++ b/packages/parser/src/nodes/declarations/__test__/generator/__snapshots__/generator-private-field.test.ts.snap
@@ -0,0 +1,13 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`generator-private-field.test > should generate private field 1`] = `
+"class Cat {
+ #tuổi = 3;
+}"
+`;
+
+exports[`generator-private-field.test > should generate private field without init 1`] = `
+"class Cat {
+ #tuổi;
+}"
+`;
diff --git a/packages/parser/src/nodes/declarations/__test__/generator/__snapshots__/generator-rest-params.test.ts.snap b/packages/parser/src/nodes/declarations/__test__/generator/__snapshots__/generator-rest-params.test.ts.snap
new file mode 100644
index 0000000..20a1e9d
--- /dev/null
+++ b/packages/parser/src/nodes/declarations/__test__/generator/__snapshots__/generator-rest-params.test.ts.snap
@@ -0,0 +1,7 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`generator-rest-params.test > should generate rest after normal 1`] = `"function f(a, b, ...rest) {}"`;
+
+exports[`generator-rest-params.test > should generate rest in arrow 1`] = `"(...args) => args"`;
+
+exports[`generator-rest-params.test > should generate rest param 1`] = `"function f(...args) {}"`;
diff --git a/packages/parser/src/nodes/declarations/__test__/generator/generator-declaration-class.test.ts b/packages/parser/src/nodes/declarations/__test__/generator/generator-declaration-class.test.ts
index 9362f79..9f3ed79 100644
--- a/packages/parser/src/nodes/declarations/__test__/generator/generator-declaration-class.test.ts
+++ b/packages/parser/src/nodes/declarations/__test__/generator/generator-declaration-class.test.ts
@@ -18,9 +18,9 @@ describe("generator-declaration-class.test", () => {
const result = generate(ast);
- expect(result.code).toBe(`class _Con_M232o extends _2727897ng_V7853t {
- _s7889_ch226n = 4;
- async _k234u(_s7889_l7847n, _hmm) {
+ expect(result.code).toBe(`class Con_Mèo extends Động_Vật {
+ số_chân = 4;
+ async kêu(số_lần, hmm) {
return "Meo meo";
}
}`);
diff --git a/packages/parser/src/nodes/declarations/__test__/generator/generator-declaration-variable.test.ts b/packages/parser/src/nodes/declarations/__test__/generator/generator-declaration-variable.test.ts
index f8b4167..2d904c1 100644
--- a/packages/parser/src/nodes/declarations/__test__/generator/generator-declaration-variable.test.ts
+++ b/packages/parser/src/nodes/declarations/__test__/generator/generator-declaration-variable.test.ts
@@ -11,6 +11,6 @@ describe("generator-expression-array.test", () => {
const result = generate(ast);
- expect(result.code).toBe(`var _s7889_m7897t = 1;`);
+ expect(result.code).toBe(`var số_một = 1;`);
});
});
diff --git a/packages/parser/src/nodes/declarations/__test__/generator/generator-default-params.test.ts b/packages/parser/src/nodes/declarations/__test__/generator/generator-default-params.test.ts
new file mode 100644
index 0000000..162fa7e
--- /dev/null
+++ b/packages/parser/src/nodes/declarations/__test__/generator/generator-default-params.test.ts
@@ -0,0 +1,26 @@
+import generate from "@babel/generator";
+
+import { FunctionDeclaration } from "@parser/nodes/declarations/FunctionDeclaration";
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../../setup-test";
+
+describe("generator-default-params.test", () => {
+ it("should generate default param in function", () => {
+ const ast = parser.parse(`hàm f(a = 1) {}`, FunctionDeclaration);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+
+ it("should generate mixed params", () => {
+ const ast = parser.parse(`hàm f(a, b = 2, c) {}`, FunctionDeclaration);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+
+ it("should generate default param in arrow", () => {
+ const ast = parser.parse(`(a = 1) => a`, Expression);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+});
diff --git a/packages/parser/src/nodes/declarations/__test__/generator/generator-getter-setter.test.ts b/packages/parser/src/nodes/declarations/__test__/generator/generator-getter-setter.test.ts
new file mode 100644
index 0000000..bbe6c42
--- /dev/null
+++ b/packages/parser/src/nodes/declarations/__test__/generator/generator-getter-setter.test.ts
@@ -0,0 +1,23 @@
+import generate from "@babel/generator";
+
+import { ClassDeclaration } from "@parser/nodes/declarations/ClassDeclaration";
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../../setup-test";
+
+describe("generator-getter-setter.test", () => {
+ it("should generate getter in class", () => {
+ const ast = parser.parse(`lớp Cat { lấy tuổi() { trả về 3 } }`, ClassDeclaration);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate setter in class", () => {
+ const ast = parser.parse(`lớp Cat { gán tuổi(v) {} }`, ClassDeclaration);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate getter in object literal", () => {
+ const ast = parser.parse(`{ lấy tuổi() { trả về 3 } }`, Expression);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+});
diff --git a/packages/parser/src/nodes/declarations/__test__/generator/generator-private-field.test.ts b/packages/parser/src/nodes/declarations/__test__/generator/generator-private-field.test.ts
new file mode 100644
index 0000000..921df63
--- /dev/null
+++ b/packages/parser/src/nodes/declarations/__test__/generator/generator-private-field.test.ts
@@ -0,0 +1,17 @@
+import generate from "@babel/generator";
+
+import { ClassDeclaration } from "@parser/nodes/declarations/ClassDeclaration";
+
+import parser from "../../../../setup-test";
+
+describe("generator-private-field.test", () => {
+ it("should generate private field", () => {
+ const ast = parser.parse(`lớp Cat { #tuổi = 3 }`, ClassDeclaration);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate private field without init", () => {
+ const ast = parser.parse(`lớp Cat { #tuổi }`, ClassDeclaration);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+});
diff --git a/packages/parser/src/nodes/declarations/__test__/generator/generator-rest-params.test.ts b/packages/parser/src/nodes/declarations/__test__/generator/generator-rest-params.test.ts
new file mode 100644
index 0000000..b10d7da
--- /dev/null
+++ b/packages/parser/src/nodes/declarations/__test__/generator/generator-rest-params.test.ts
@@ -0,0 +1,26 @@
+import generate from "@babel/generator";
+
+import { FunctionDeclaration } from "@parser/nodes/declarations/FunctionDeclaration";
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../../setup-test";
+
+describe("generator-rest-params.test", () => {
+ it("should generate rest param", () => {
+ const ast = parser.parse(`hàm f(...args) {}`, FunctionDeclaration);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+
+ it("should generate rest after normal", () => {
+ const ast = parser.parse(`hàm f(a, b, ...rest) {}`, FunctionDeclaration);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+
+ it("should generate rest in arrow", () => {
+ const ast = parser.parse(`(...args) => args`, Expression);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+});
diff --git a/packages/parser/src/nodes/declarations/class/ClassMethod.ts b/packages/parser/src/nodes/declarations/class/ClassMethod.ts
index f85c04b..3743b28 100644
--- a/packages/parser/src/nodes/declarations/class/ClassMethod.ts
+++ b/packages/parser/src/nodes/declarations/class/ClassMethod.ts
@@ -1,6 +1,7 @@
import { Expression } from "@parser/nodes/expressions/Expression";
import { FunctionExpression } from "@parser/nodes/expressions/FunctionExpression";
import { Identifier } from "@parser/nodes/identifier/Identifier";
+import { PrivateName } from "@parser/nodes/identifier/PrivateName";
import { BlockStatement } from "@parser/nodes/statements/BlockStatement";
import { Parser } from "@parser/parser";
import { Keyword } from "@vietscript/shared";
@@ -10,7 +11,7 @@ export class ClassMethod {
static = false;
- key: Identifier | Expression;
+ key: Identifier | Expression | PrivateName;
computed = false;
@@ -22,7 +23,7 @@ export class ClassMethod {
async = false;
- params: Identifier[] = [];
+ params: any[] = [];
body: BlockStatement;
@@ -51,6 +52,9 @@ export class ClassMethod {
this.key = new Expression(parser);
this.computed = true;
parser.eat("]");
+ } else if (parser.lookahead?.type === "#") {
+ this.key = new PrivateName(parser);
+ this.type = "ClassPrivateMethod" as any;
} else {
this.key = new Identifier(parser);
}
diff --git a/packages/parser/src/nodes/declarations/class/ClassProperty.ts b/packages/parser/src/nodes/declarations/class/ClassProperty.ts
index 502962d..0a4ade5 100644
--- a/packages/parser/src/nodes/declarations/class/ClassProperty.ts
+++ b/packages/parser/src/nodes/declarations/class/ClassProperty.ts
@@ -1,22 +1,40 @@
import { Expression } from "@parser/nodes/expressions/Expression";
import { Identifier } from "@parser/nodes/identifier/Identifier";
+import { PrivateName } from "@parser/nodes/identifier/PrivateName";
+import { FunctionExpression } from "@parser/nodes/expressions/FunctionExpression";
import { Parser } from "@parser/parser";
import { Keyword } from "@vietscript/shared";
import { ClassMethod } from "./ClassMethod";
+const ACCESS_MODIFIERS: Record = {
+ [Keyword.PUBLIC]: "public",
+ [Keyword.PRIVATE]: "private",
+ [Keyword.PROTECTED]: "protected",
+};
+
export class ClassProperty {
+ [key: string]: any;
+
type = "ClassProperty";
static = false;
computed = false;
- key: Identifier | Expression;
+ accessibility?: string;
+
+ key: Identifier | Expression | PrivateName;
value: null | Expression;
constructor(parser: Parser) {
+ const type = parser.lookahead?.type as string;
+ if (ACCESS_MODIFIERS[type]) {
+ parser.eat(type);
+ this.accessibility = ACCESS_MODIFIERS[type];
+ }
+
if (parser.lookahead?.type === Keyword.STATIC) {
parser.eat(Keyword.STATIC);
this.static = true;
@@ -26,7 +44,13 @@ export class ClassProperty {
case Keyword.GET:
case Keyword.SET:
case Keyword.ASYNC: {
- Object.assign(this, new ClassMethod(parser));
+ Object.assign(this, new ClassMethod(parser, this.static));
+ break;
+ }
+ case "*": {
+ parser.eat("*");
+ Object.assign(this, new ClassMethod(parser, this.static));
+ (this as any).generator = true;
break;
}
default: {
@@ -35,11 +59,26 @@ export class ClassProperty {
this.key = new Expression(parser);
this.computed = true;
parser.eat("]");
+ } else if (parser.lookahead?.type === "#") {
+ this.key = new PrivateName(parser);
+ this.type = "ClassPrivateProperty";
+ } else if (parser.lookahead?.type === Keyword.CONSTRUCTOR) {
+ const tok = parser.eat(Keyword.CONSTRUCTOR);
+ this.key = { type: "Identifier", name: "constructor", start: tok.start, end: tok.end } as unknown as Identifier;
} else {
this.key = new Identifier(parser);
}
- if (parser.lookahead?.type === "=") {
+ if (parser.lookahead?.type === "(") {
+ const method = new FunctionExpression(parser, true);
+ this.type = this.type === "ClassPrivateProperty" ? "ClassPrivateMethod" : "ClassMethod";
+ (this as any).kind = "method";
+ (this as any).generator = method.generator;
+ (this as any).async = method.async;
+ (this as any).params = method.params;
+ (this as any).body = method.body;
+ (this as any).id = null;
+ } else if (parser.lookahead?.type === "=") {
parser.eat("=");
this.value = new Expression(parser);
} else {
diff --git a/packages/parser/src/nodes/declarations/import/ImportDeclaration.ts b/packages/parser/src/nodes/declarations/import/ImportDeclaration.ts
index 233c6cd..3b0946d 100644
--- a/packages/parser/src/nodes/declarations/import/ImportDeclaration.ts
+++ b/packages/parser/src/nodes/declarations/import/ImportDeclaration.ts
@@ -2,6 +2,7 @@ import { Keyword } from "@vietscript/shared";
import { Parser } from "@parser/parser";
import { StringLiteral } from "../../literals/StringLiteral";
+import { Expression } from "../../expressions/Expression";
import { ImportSpecifier } from "./ImportSpecifier";
import { ImportClause } from "./ImportClause";
@@ -9,19 +10,36 @@ import { ImportDefaultSpecifier } from "./ImportDefaultSpecifier";
import { ImportNamespaceSpecifier } from "./ImportNamespaceSpecifier";
export class ImportDeclaration {
+ [key: string]: any;
+
type = "ImportDeclaration";
specifiers: Array = [];
- source: StringLiteral;
+ source!: StringLiteral;
- assertions = [];
+ assertions: any[] = [];
importType?: "value";
constructor(parser: Parser) {
parser.eat(Keyword.IMPORT);
+ if (parser.lookahead?.type === "(") {
+ parser.eat("(");
+ const argument = new Expression(parser);
+ parser.eat(")");
+ Object.assign(this, {
+ type: "ExpressionStatement",
+ expression: {
+ type: "CallExpression",
+ callee: { type: "Import" },
+ arguments: [argument],
+ },
+ });
+ return;
+ }
+
if (
parser.lookahead?.type === Keyword.IDENTIFIER ||
parser.lookahead?.type === "*" ||
diff --git a/packages/parser/src/nodes/declarations/import/ImportDefaultSpecifier.ts b/packages/parser/src/nodes/declarations/import/ImportDefaultSpecifier.ts
index c89db7f..7c7e284 100644
--- a/packages/parser/src/nodes/declarations/import/ImportDefaultSpecifier.ts
+++ b/packages/parser/src/nodes/declarations/import/ImportDefaultSpecifier.ts
@@ -2,11 +2,21 @@ import { Identifier } from "@parser/nodes/identifier/Identifier";
import { Parser } from "@parser/parser";
export class ImportDefaultSpecifier {
- type: "ImportDefaultSpecifier";
+ type = "ImportDefaultSpecifier";
local: Identifier;
constructor(parser: Parser) {
+ if (typeof parser.lookahead?.value === "string") {
+ const fromToken = /\s+từ$/g.exec(parser.lookahead.value)?.[0];
+
+ if (fromToken) {
+ parser.tokenizer.rollback(fromToken.length);
+
+ parser.lookahead.value = parser.lookahead.value.replace(/\s+từ$/g, "");
+ }
+ }
+
this.local = new Identifier(parser);
}
}
diff --git a/packages/parser/src/nodes/declarations/import/ImportNamespaceSpecifier.ts b/packages/parser/src/nodes/declarations/import/ImportNamespaceSpecifier.ts
index c239861..17b32d2 100644
--- a/packages/parser/src/nodes/declarations/import/ImportNamespaceSpecifier.ts
+++ b/packages/parser/src/nodes/declarations/import/ImportNamespaceSpecifier.ts
@@ -3,7 +3,7 @@ import { Parser } from "@parser/parser";
import { Keyword } from "@vietscript/shared";
export class ImportNamespaceSpecifier {
- type: "ImportNamespaceSpecifier";
+ type = "ImportNamespaceSpecifier";
local: Identifier;
@@ -12,6 +12,16 @@ export class ImportNamespaceSpecifier {
parser.eat(Keyword.AS);
+ if (typeof parser.lookahead?.value === "string") {
+ const fromToken = /\s+từ$/g.exec(parser.lookahead.value)?.[0];
+
+ if (fromToken) {
+ parser.tokenizer.rollback(fromToken.length);
+
+ parser.lookahead.value = parser.lookahead.value.replace(/\s+từ$/g, "");
+ }
+ }
+
this.local = new Identifier(parser);
}
}
diff --git a/packages/parser/src/nodes/expressions/ArrowFunctionExpression.ts b/packages/parser/src/nodes/expressions/ArrowFunctionExpression.ts
new file mode 100644
index 0000000..a361802
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/ArrowFunctionExpression.ts
@@ -0,0 +1,36 @@
+import { Parser } from "@parser/parser";
+import { BlockStatement } from "@parser/nodes/statements/BlockStatement";
+import { Parameter } from "@parser/nodes/declarations/ParameterList";
+
+import { Expression } from "./Expression";
+
+export class ArrowFunctionExpression {
+ type = "ArrowFunctionExpression";
+
+ id: null = null;
+
+ expression = true;
+
+ generator = false;
+
+ async = false;
+
+ params: Array = [];
+
+ body: Expression | BlockStatement;
+
+ constructor(parser: Parser, params: Array, isAsync = false) {
+ parser.eat("=>");
+
+ this.params = params;
+ this.async = isAsync;
+
+ if (parser.lookahead?.type === "{") {
+ this.body = new BlockStatement(parser);
+ this.expression = false;
+ } else {
+ this.body = new Expression(parser);
+ this.expression = true;
+ }
+ }
+}
diff --git a/packages/parser/src/nodes/expressions/AssignmentExpression.ts b/packages/parser/src/nodes/expressions/AssignmentExpression.ts
index 9496634..a11a3d8 100644
--- a/packages/parser/src/nodes/expressions/AssignmentExpression.ts
+++ b/packages/parser/src/nodes/expressions/AssignmentExpression.ts
@@ -3,6 +3,25 @@ import { Identifier } from "@parser/nodes/identifier/Identifier";
import { Expression } from "@parser/nodes/expressions/Expression";
import { Keyword } from "@vietscript/shared";
+const ASSIGNMENT_OPERATORS = new Set([
+ "=",
+ "+=",
+ "-=",
+ "*=",
+ "/=",
+ "%=",
+ "**=",
+ "&=",
+ "|=",
+ "^=",
+ "<<=",
+ ">>=",
+ ">>>=",
+ "&&=",
+ "||=",
+ "??=",
+]);
+
export class AssignmentExpression {
type = "AssignmentExpression";
@@ -16,7 +35,12 @@ export class AssignmentExpression {
this.left =
identifier ?? (parser.lookahead?.type === Keyword.IDENTIFIER ? new Identifier(parser) : new Expression(parser));
- this.operator = String(parser.eat("=").value);
+ const op = String(parser.lookahead?.type);
+ if (!ASSIGNMENT_OPERATORS.has(op)) {
+ throw new SyntaxError(`Expected assignment operator, got: "${op}"`);
+ }
+
+ this.operator = String(parser.eat(op).value);
this.right = parser.lookahead?.type === Keyword.IDENTIFIER ? new Identifier(parser) : new Expression(parser);
}
diff --git a/packages/parser/src/nodes/expressions/BinaryExpression.ts b/packages/parser/src/nodes/expressions/BinaryExpression.ts
index 854e4a3..9b43a68 100644
--- a/packages/parser/src/nodes/expressions/BinaryExpression.ts
+++ b/packages/parser/src/nodes/expressions/BinaryExpression.ts
@@ -3,12 +3,40 @@ import { Identifier } from "@parser/nodes/identifier/Identifier";
import { Expression } from "@parser/nodes/expressions/Expression";
import { Keyword } from "@vietscript/shared";
+const KEYWORD_OPERATORS: Record = {
+ [Keyword.INSTANCEOF]: "instanceof",
+ [Keyword.IN]: "in",
+};
+
+const BINARY_OPERATORS = new Set([
+ "+",
+ "-",
+ "*",
+ "/",
+ "%",
+ "**",
+ "&",
+ "|",
+ "^",
+ ">",
+ ">>",
+ ">>>",
+ "<",
+ "<<",
+ ">=",
+ "<=",
+ "==",
+ "===",
+ "!=",
+ "!==",
+]);
+
export class BinaryExpression {
type = "BinaryExpression";
left: Identifier | Expression;
- operator: string;
+ operator!: string;
right: Identifier | Expression;
@@ -16,27 +44,13 @@ export class BinaryExpression {
this.left =
identifier ?? (parser.lookahead?.type === Keyword.IDENTIFIER ? new Identifier(parser) : new Expression(parser));
- switch (parser.lookahead?.type) {
- case "+":
- case "-":
- case "*":
- case "/":
- case "%":
- case "**":
- case "^":
- case ">":
- case ">>":
- case ">>>":
- case "<":
- case "<<":
- case "<<<":
- case ">=":
- case "<=":
- case "==":
- case "===": {
- this.operator = String(parser.eat(parser.lookahead?.type).value);
- break;
- }
+ const type = parser.lookahead?.type as string;
+
+ if (KEYWORD_OPERATORS[type]) {
+ parser.eat(type);
+ this.operator = KEYWORD_OPERATORS[type];
+ } else if (BINARY_OPERATORS.has(type)) {
+ this.operator = String(parser.eat(type).value);
}
this.right = parser.lookahead?.type === Keyword.IDENTIFIER ? new Identifier(parser) : new Expression(parser);
diff --git a/packages/parser/src/nodes/expressions/CallExpression.ts b/packages/parser/src/nodes/expressions/CallExpression.ts
index 002663b..07f6f1d 100644
--- a/packages/parser/src/nodes/expressions/CallExpression.ts
+++ b/packages/parser/src/nodes/expressions/CallExpression.ts
@@ -2,12 +2,14 @@ import { Parser } from "@parser/parser";
import { Identifier } from "@parser/nodes/identifier/Identifier";
import { Expression } from "@parser/nodes/expressions/Expression";
+import { SpreadElement } from "./SpreadElement";
+
export class CallExpression {
type = "CallExpression";
callee: Identifier | Expression;
- arguments: Array = [];
+ arguments: Array = [];
optional = false;
@@ -21,7 +23,11 @@ export class CallExpression {
parser.eat(",");
}
- this.arguments.push(new Expression(parser));
+ if (parser.lookahead?.type === "...") {
+ this.arguments.push(new SpreadElement(parser));
+ } else {
+ this.arguments.push(new Expression(parser));
+ }
}
parser.eat(")");
diff --git a/packages/parser/src/nodes/expressions/ConditionalExpression.ts b/packages/parser/src/nodes/expressions/ConditionalExpression.ts
new file mode 100644
index 0000000..4aab8b8
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/ConditionalExpression.ts
@@ -0,0 +1,26 @@
+import { Parser } from "@parser/parser";
+
+import { Expression } from "./Expression";
+
+export class ConditionalExpression {
+ type = "ConditionalExpression";
+
+ test: Expression;
+
+ consequent: Expression;
+
+ alternate: Expression;
+
+ constructor(parser: Parser, test: Expression) {
+ this.test = test;
+ parser.eat("?");
+ parser.ternaryDepth++;
+ try {
+ this.consequent = new Expression(parser);
+ } finally {
+ parser.ternaryDepth--;
+ }
+ parser.eat(":");
+ this.alternate = new Expression(parser);
+ }
+}
diff --git a/packages/parser/src/nodes/expressions/Expression.ts b/packages/parser/src/nodes/expressions/Expression.ts
index f370603..8b52628 100644
--- a/packages/parser/src/nodes/expressions/Expression.ts
+++ b/packages/parser/src/nodes/expressions/Expression.ts
@@ -5,7 +5,10 @@ import { Keyword } from "@vietscript/shared";
import { LabelledStatement } from "../statements/LabelledStatement";
+import { ParameterList } from "../declarations/ParameterList";
+
import { ArrayExpression } from "./ArrayExpression";
+import { ArrowFunctionExpression } from "./ArrowFunctionExpression";
import { FunctionExpression } from "./FunctionExpression";
import { BinaryExpression } from "./BinaryExpression";
import { AssignmentExpression } from "./AssignmentExpression";
@@ -13,14 +16,43 @@ import { UpdateExpression } from "./UpdateExpression";
import { UnaryExpression } from "./UnaryExpression";
import { LogicalExpression } from "./LogicalExpression";
import { AwaitExpression } from "./AwaitExpression";
+import { YieldExpression } from "./YieldExpression";
import { ThisExpression } from "./ThisExpression";
import { MemberExpression } from "./MemberExpression";
import { ObjectExpression } from "./ObjectExpression";
import { CallExpression } from "./CallExpression";
+import { NewExpression } from "./NewExpression";
+import { ConditionalExpression } from "./ConditionalExpression";
+import { TaggedTemplateExpression } from "./TaggedTemplateExpression";
export class Expression {
[key: string]: any;
+ applyPostfix(parser: Parser) {
+ while (true) {
+ const t = parser.lookahead?.type as string;
+ if (t === "." || t === "[" || t === "?.") {
+ const snapshot = { ...this } as Expression;
+ for (const key of Object.keys(this)) delete (this as any)[key];
+ Object.assign(this, new MemberExpression(parser, snapshot));
+ continue;
+ }
+ if (t === "(") {
+ const snapshot = { ...this } as Expression;
+ for (const key of Object.keys(this)) delete (this as any)[key];
+ Object.assign(this, new CallExpression(parser, snapshot));
+ continue;
+ }
+ if (t === "??" || t === "||" || t === "&&") {
+ const snapshot = { ...this } as Expression;
+ for (const key of Object.keys(this)) delete (this as any)[key];
+ Object.assign(this, new LogicalExpression(parser, snapshot as any));
+ continue;
+ }
+ break;
+ }
+ }
+
constructor(parser: Parser) {
switch (parser.lookahead?.type as string) {
case Keyword.ASYNC:
@@ -30,16 +62,28 @@ export class Expression {
}
case "[": {
Object.assign(this, new ArrayExpression(parser));
+ this.applyPostfix(parser);
+ break;
+ }
+ case "(": {
+ parser.eat("(");
+ const arrowParams = new ParameterList(parser, ")").parameters;
+ parser.eat(")");
+ Object.assign(this, new ArrowFunctionExpression(parser, arrowParams));
break;
}
case "{": {
Object.assign(this, new ObjectExpression(parser));
+ this.applyPostfix(parser);
break;
}
case Keyword.NUMBER:
case Keyword.STRING:
+ case "TemplateLiteral":
+ case "RegExpLiteral":
case Keyword.BOOLEAN:
case Keyword.NAN:
+ case Keyword.INFINITY:
case Keyword.NULL:
case Keyword.UNDEFINED: {
Object.assign(this, new Literal(parser));
@@ -50,9 +94,9 @@ export class Expression {
Object.assign(this, new UpdateExpression(parser));
break;
}
- case "delete":
- case "void":
- case "typeof":
+ case Keyword.DELETE:
+ case Keyword.VOID:
+ case Keyword.TYPEOF:
case "+":
case "-":
case "~":
@@ -64,18 +108,112 @@ export class Expression {
Object.assign(this, new AwaitExpression(parser));
break;
}
+ case Keyword.YIELD: {
+ Object.assign(this, new YieldExpression(parser));
+ break;
+ }
+ case Keyword.NEW: {
+ Object.assign(this, new NewExpression(parser));
+ break;
+ }
+ case Keyword.IMPORT: {
+ parser.eat(Keyword.IMPORT);
+ if ((parser.lookahead?.type as string) === "(") {
+ const importNode = { type: "Import" };
+ Object.assign(this, new CallExpression(parser, importNode as any));
+ }
+ break;
+ }
+ case Keyword.SUPER: {
+ parser.eat(Keyword.SUPER);
+ const superNode = { type: "Super" };
+ if ((parser.lookahead?.type as string) === "(") {
+ Object.assign(this, new CallExpression(parser, superNode as any));
+ } else if ((parser.lookahead?.type as string) === ".") {
+ Object.assign(this, new MemberExpression(parser, superNode as any));
+ } else {
+ Object.assign(this, superNode);
+ }
+ break;
+ }
case Keyword.THIS: {
Object.assign(this, new ThisExpression(parser));
- if (parser.lookahead?.type === ".") {
- Object.assign(this, new MemberExpression(parser, this));
+ if (
+ parser.lookahead?.type === "." ||
+ parser.lookahead?.type === "[" ||
+ parser.lookahead?.type === "?."
+ ) {
+ const member = new MemberExpression(parser, { ...this } as Expression);
+ Object.assign(this, member);
+ }
+
+ switch (parser.lookahead?.type as string) {
+ case "=":
+ case "+=":
+ case "-=":
+ case "*=":
+ case "/=":
+ case "%=":
+ case "**=":
+ case "&=":
+ case "|=":
+ case "^=":
+ case "<<=":
+ case ">>=":
+ case ">>>=":
+ case "&&=":
+ case "||=":
+ case "??=": {
+ Object.assign(this, new AssignmentExpression(parser, { ...this } as Expression));
+ break;
+ }
+ case "(": {
+ Object.assign(this, new CallExpression(parser, { ...this } as Expression));
+ break;
+ }
+ case "++":
+ case "--": {
+ const op = parser.lookahead?.value as string;
+ parser.eat(op);
+ const snapshot = { ...this } as Expression;
+ for (const key of Object.keys(this)) delete (this as any)[key];
+ Object.assign(this, {
+ type: "UpdateExpression",
+ operator: op,
+ argument: snapshot,
+ prefix: false,
+ });
+ break;
+ }
}
break;
}
case Keyword.IDENTIFIER: {
const identifier = new Identifier(parser);
- // TODO: handle more cases
+ if ((parser.lookahead?.type as string) === "=>") {
+ Object.assign(this, new ArrowFunctionExpression(parser, [identifier]));
+ break;
+ }
+
+ if ((parser.lookahead?.type as string) === "TemplateLiteral") {
+ Object.assign(this, new TaggedTemplateExpression(parser, identifier));
+ break;
+ }
+
+ if ((parser.lookahead?.type as string) === "++" || (parser.lookahead?.type as string) === "--") {
+ const op = parser.lookahead?.value as string;
+ parser.eat(op);
+ Object.assign(this, {
+ type: "UpdateExpression",
+ operator: op,
+ argument: identifier,
+ prefix: false,
+ });
+ break;
+ }
+
switch (parser.lookahead?.type as string) {
case "+":
case "-":
@@ -83,21 +221,41 @@ export class Expression {
case "/":
case "%":
case "**":
+ case "&":
+ case "|":
case "^":
case ">":
case ">>":
case ">>>":
case "<":
case "<<":
- case "<<<":
case ">=":
case "<=":
case "==":
- case "===": {
+ case "===":
+ case "!=":
+ case "!==":
+ case Keyword.INSTANCEOF:
+ case Keyword.IN: {
Object.assign(this, new BinaryExpression(parser, identifier));
break;
}
- case "=": {
+ case "=":
+ case "+=":
+ case "-=":
+ case "*=":
+ case "/=":
+ case "%=":
+ case "**=":
+ case "&=":
+ case "|=":
+ case "^=":
+ case "<<=":
+ case ">>=":
+ case ">>>=":
+ case "&&=":
+ case "||=":
+ case "??=": {
Object.assign(this, new AssignmentExpression(parser, identifier));
break;
}
@@ -107,16 +265,45 @@ export class Expression {
Object.assign(this, new LogicalExpression(parser, identifier));
break;
}
+ case "?": {
+ Object.assign(this, new ConditionalExpression(parser, identifier));
+ break;
+ }
case ":": {
- Object.assign(this, new LabelledStatement(parser, identifier));
+ if (parser.ternaryDepth > 0) {
+ Object.assign(this, identifier);
+ } else {
+ Object.assign(this, new LabelledStatement(parser, identifier));
+ }
+ break;
+ }
+ case "(": {
+ Object.assign(this, new CallExpression(parser, identifier));
+ this.applyPostfix(parser);
break;
}
case "[":
- case ".": {
+ case ".":
+ case "?.": {
const memberExpression = new MemberExpression(parser, identifier);
- switch (parser.lookahead?.type) {
- case "=": {
+ switch (parser.lookahead?.type as string) {
+ case "=":
+ case "+=":
+ case "-=":
+ case "*=":
+ case "/=":
+ case "%=":
+ case "**=":
+ case "&=":
+ case "|=":
+ case "^=":
+ case "<<=":
+ case ">>=":
+ case ">>>=":
+ case "&&=":
+ case "||=":
+ case "??=": {
Object.assign(this, new AssignmentExpression(parser, memberExpression));
break;
}
@@ -126,6 +313,7 @@ export class Expression {
}
default: {
Object.assign(this, memberExpression);
+ this.applyPostfix(parser);
break;
}
}
diff --git a/packages/parser/src/nodes/expressions/FunctionExpression.ts b/packages/parser/src/nodes/expressions/FunctionExpression.ts
index b547e12..26ad81c 100644
--- a/packages/parser/src/nodes/expressions/FunctionExpression.ts
+++ b/packages/parser/src/nodes/expressions/FunctionExpression.ts
@@ -1,6 +1,6 @@
import { Parser } from "@parser/parser";
import { BlockStatement } from "@parser/nodes/statements/BlockStatement";
-import { Identifier } from "@parser/nodes/identifier/Identifier";
+import { ParameterList, Parameter } from "@parser/nodes/declarations/ParameterList";
import { Keyword } from "@vietscript/shared";
export class FunctionExpression {
@@ -14,7 +14,7 @@ export class FunctionExpression {
async = false;
- params: Array = [];
+ params: Array = [];
body: BlockStatement;
@@ -35,23 +35,11 @@ export class FunctionExpression {
parser.eat("(");
- const parameters: Array = [];
-
- while (parser.lookahead?.type !== ")") {
- if (parameters.length > 0) {
- parser.eat(",");
- }
-
- parameters.push(new Identifier(parser));
- }
+ this.params = new ParameterList(parser, ")").parameters;
parser.eat(")");
- const body = new BlockStatement(parser);
-
+ this.body = new BlockStatement(parser);
this.id = null;
- this.expression = false;
- this.params = parameters;
- this.body = body;
}
}
diff --git a/packages/parser/src/nodes/expressions/MemberExpression.ts b/packages/parser/src/nodes/expressions/MemberExpression.ts
index 7ddd730..e6f3da7 100644
--- a/packages/parser/src/nodes/expressions/MemberExpression.ts
+++ b/packages/parser/src/nodes/expressions/MemberExpression.ts
@@ -1,6 +1,7 @@
import { Parser } from "@parser";
import { Identifier } from "../identifier/Identifier";
+import { PrivateName } from "../identifier/PrivateName";
import { Expression } from "./Expression";
@@ -24,23 +25,45 @@ export class MemberExpression {
parser.eat("[");
this.object = this.property ? { ...this } : { ...this.object };
this.property = new Expression(parser);
-
parser.eat("]");
-
this.computed = true;
-
+ this.optional = false;
break;
}
case ".": {
parser.eat(".");
this.object = this.property ? { ...this } : { ...this.object };
- this.property = new Identifier(parser);
-
+ if ((parser.lookahead?.type as string) === "#") {
+ this.property = new PrivateName(parser) as any;
+ } else {
+ this.property = new Identifier(parser);
+ }
this.computed = false;
+ this.optional = false;
+ break;
+ }
+ case "?.": {
+ parser.eat("?.");
+ this.object = this.property ? { ...this } : { ...this.object };
+ this.type = "OptionalMemberExpression";
+ this.optional = true;
+ if ((parser.lookahead?.type as string) === "[") {
+ parser.eat("[");
+ this.property = new Expression(parser);
+ parser.eat("]");
+ this.computed = true;
+ } else {
+ this.property = new Identifier(parser);
+ this.computed = false;
+ }
break;
}
}
- } while (parser.lookahead?.type === "." || parser.lookahead?.type === "[");
+ } while (
+ parser.lookahead?.type === "." ||
+ parser.lookahead?.type === "[" ||
+ parser.lookahead?.type === "?."
+ );
}
}
diff --git a/packages/parser/src/nodes/expressions/NewExpression.ts b/packages/parser/src/nodes/expressions/NewExpression.ts
new file mode 100644
index 0000000..ba03ff9
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/NewExpression.ts
@@ -0,0 +1,33 @@
+import { Parser } from "@parser/parser";
+import { Identifier } from "@parser/nodes/identifier/Identifier";
+import { Keyword } from "@vietscript/shared";
+
+import { Expression } from "./Expression";
+import { SpreadElement } from "./SpreadElement";
+
+export class NewExpression {
+ type = "NewExpression";
+
+ callee: Identifier | Expression;
+
+ arguments: Array = [];
+
+ constructor(parser: Parser) {
+ parser.eat(Keyword.NEW);
+
+ this.callee = new Identifier(parser);
+
+ if (parser.lookahead?.type === "(") {
+ parser.eat("(");
+ while (parser.lookahead?.type !== ")") {
+ if (this.arguments.length > 0) parser.eat(",");
+ if (parser.lookahead?.type === "...") {
+ this.arguments.push(new SpreadElement(parser));
+ } else {
+ this.arguments.push(new Expression(parser));
+ }
+ }
+ parser.eat(")");
+ }
+ }
+}
diff --git a/packages/parser/src/nodes/expressions/SpreadElement.ts b/packages/parser/src/nodes/expressions/SpreadElement.ts
new file mode 100644
index 0000000..dd65094
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/SpreadElement.ts
@@ -0,0 +1,14 @@
+import { Parser } from "@parser/parser";
+
+import { Expression } from "./Expression";
+
+export class SpreadElement {
+ type = "SpreadElement";
+
+ argument: Expression;
+
+ constructor(parser: Parser) {
+ parser.eat("...");
+ this.argument = new Expression(parser);
+ }
+}
diff --git a/packages/parser/src/nodes/expressions/TaggedTemplateExpression.ts b/packages/parser/src/nodes/expressions/TaggedTemplateExpression.ts
new file mode 100644
index 0000000..aed994f
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/TaggedTemplateExpression.ts
@@ -0,0 +1,17 @@
+import { Parser } from "@parser/parser";
+import { Identifier } from "@parser/nodes/identifier/Identifier";
+
+import { TemplateLiteral } from "../literals/TemplateLiteral";
+
+export class TaggedTemplateExpression {
+ type = "TaggedTemplateExpression";
+
+ tag: Identifier;
+
+ quasi: TemplateLiteral;
+
+ constructor(parser: Parser, tag: Identifier) {
+ this.tag = tag;
+ this.quasi = new TemplateLiteral(parser);
+ }
+}
diff --git a/packages/parser/src/nodes/expressions/UnaryExpression.ts b/packages/parser/src/nodes/expressions/UnaryExpression.ts
index ddae523..c87a35a 100644
--- a/packages/parser/src/nodes/expressions/UnaryExpression.ts
+++ b/packages/parser/src/nodes/expressions/UnaryExpression.ts
@@ -1,6 +1,13 @@
import { Parser } from "@parser/parser";
+import { Keyword } from "@vietscript/shared";
import { Expression } from "@parser/nodes/expressions/Expression";
+const UNARY_KEYWORD_OPERATORS: Record = {
+ [Keyword.DELETE]: "delete",
+ [Keyword.VOID]: "void",
+ [Keyword.TYPEOF]: "typeof",
+};
+
export class UnaryExpression {
type = "UnaryExpression";
@@ -11,19 +18,16 @@ export class UnaryExpression {
argument: Expression;
constructor(parser: Parser) {
- switch (parser.lookahead?.type) {
- case "delete":
- case "void":
- case "typeof":
- case "+":
- case "-":
- case "~":
- case "!": {
- this.operator = String(parser.eat(parser.lookahead?.type).value);
- this.prefix = true;
- this.argument = new Expression(parser);
- break;
- }
+ const type = parser.lookahead?.type as string;
+
+ if (UNARY_KEYWORD_OPERATORS[type]) {
+ parser.eat(type);
+ this.operator = UNARY_KEYWORD_OPERATORS[type];
+ } else {
+ this.operator = String(parser.eat(type).value);
}
+
+ this.prefix = true;
+ this.argument = new Expression(parser);
}
}
diff --git a/packages/parser/src/nodes/expressions/YieldExpression.ts b/packages/parser/src/nodes/expressions/YieldExpression.ts
index b5ad838..31a572a 100644
--- a/packages/parser/src/nodes/expressions/YieldExpression.ts
+++ b/packages/parser/src/nodes/expressions/YieldExpression.ts
@@ -15,8 +15,8 @@ export class YieldExpression {
parser.eat(Keyword.YIELD);
if (parser.lookahead?.type === "*") {
+ parser.eat("*");
this.delegate = true;
-
this.argument = new Expression(parser);
} else if (isExpression(parser)) {
this.argument = new Expression(parser);
diff --git a/packages/parser/src/nodes/expressions/__test__/expression-arrow.test.ts b/packages/parser/src/nodes/expressions/__test__/expression-arrow.test.ts
new file mode 100644
index 0000000..1d2fb01
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/expression-arrow.test.ts
@@ -0,0 +1,61 @@
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("expression-arrow.test", () => {
+ it("should parse single param without paren", () => {
+ const result = parser.parse(`x => x`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ArrowFunctionExpression",
+ async: false,
+ generator: false,
+ expression: true,
+ params: [{ type: "Identifier", name: "x" }],
+ body: { type: "Identifier", name: "x" },
+ });
+ });
+
+ it("should parse single param with paren", () => {
+ const result = parser.parse(`(x) => x`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ArrowFunctionExpression",
+ params: [{ type: "Identifier", name: "x" }],
+ body: { type: "Identifier", name: "x" },
+ });
+ });
+
+ it("should parse multiple params", () => {
+ const result = parser.parse(`(x, y) => x`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ArrowFunctionExpression",
+ params: [
+ { type: "Identifier", name: "x" },
+ { type: "Identifier", name: "y" },
+ ],
+ });
+ });
+
+ it("should parse empty params", () => {
+ const result = parser.parse(`() => 1`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ArrowFunctionExpression",
+ params: [],
+ body: { type: "NumericLiteral", value: 1 },
+ });
+ });
+
+ it("should parse block body", () => {
+ const result = parser.parse(`() => { trả về 1 }`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ArrowFunctionExpression",
+ expression: false,
+ body: { type: "BlockStatement" },
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/expressions/__test__/expression-binary.test.ts b/packages/parser/src/nodes/expressions/__test__/expression-binary.test.ts
index 52ab3fe..131ad94 100644
--- a/packages/parser/src/nodes/expressions/__test__/expression-binary.test.ts
+++ b/packages/parser/src/nodes/expressions/__test__/expression-binary.test.ts
@@ -13,11 +13,11 @@ describe("expression-binary.test", () => {
operator: "===",
left: {
type: "Identifier",
- name: "_xin_ch224o",
+ name: "xin_chào",
},
right: {
type: "Identifier",
- name: "_hello",
+ name: "hello",
},
} as BinaryExpression);
});
diff --git a/packages/parser/src/nodes/expressions/__test__/expression-call.test.ts b/packages/parser/src/nodes/expressions/__test__/expression-call.test.ts
index f727479..055cdb9 100644
--- a/packages/parser/src/nodes/expressions/__test__/expression-call.test.ts
+++ b/packages/parser/src/nodes/expressions/__test__/expression-call.test.ts
@@ -16,11 +16,11 @@ describe("expression-call.test", () => {
type: "MemberExpression",
object: {
type: "Identifier",
- name: "_con_ch243",
+ name: "con_chó",
},
property: {
type: "Identifier",
- name: "_k234u",
+ name: "kêu",
},
computed: false,
optional: false,
@@ -41,18 +41,18 @@ describe("expression-call.test", () => {
type: "MemberExpression",
object: {
type: "Identifier",
- name: "_con_ch243",
+ name: "con_chó",
},
property: {
type: "Identifier",
- name: "_ch226n_ph7843i",
+ name: "chân_phải",
},
computed: false,
optional: false,
},
property: {
type: "Identifier",
- name: "_273225",
+ name: "đá",
},
computed: false,
optional: false,
@@ -73,18 +73,18 @@ describe("expression-call.test", () => {
type: "MemberExpression",
object: {
type: "Identifier",
- name: "_con_ch243",
+ name: "con_chó",
},
property: {
type: "Identifier",
- name: "_ch226n",
+ name: "chân",
},
computed: true,
optional: false,
},
property: {
type: "Identifier",
- name: "_273225",
+ name: "đá",
},
computed: false,
optional: false,
diff --git a/packages/parser/src/nodes/expressions/__test__/expression-chain.test.ts b/packages/parser/src/nodes/expressions/__test__/expression-chain.test.ts
new file mode 100644
index 0000000..3665e86
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/expression-chain.test.ts
@@ -0,0 +1,48 @@
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("expression-chain.test", () => {
+ it("should parse method chain after array literal", () => {
+ const result = parser.parse(`[1, 2, 3].map(x => x * 2)`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "CallExpression",
+ });
+ });
+
+ it("should parse method chain after object literal via statement context", () => {
+ const result = parser.parse(`{ a: 1 }`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ObjectExpression",
+ });
+ });
+
+ it("should parse logical chain", () => {
+ const result = parser.parse(`a ?? b ?? c`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "LogicalExpression",
+ });
+ });
+
+ it("should parse optional chain with logical", () => {
+ const result = parser.parse(`obj?.prop ?? "default"`, Expression);
+ const plain = toPlainObject(result) as any;
+ expect(plain.type).toBe("LogicalExpression");
+ expect(plain.operator).toBe("??");
+ });
+
+ it("should parse call followed by member access", () => {
+ const result = parser.parse(`foo().bar`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "MemberExpression",
+ });
+ });
+
+ it("should parse call chain", () => {
+ const result = parser.parse(`foo().bar()`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "CallExpression",
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/expressions/__test__/expression-compound-assign.test.ts b/packages/parser/src/nodes/expressions/__test__/expression-compound-assign.test.ts
new file mode 100644
index 0000000..24220a3
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/expression-compound-assign.test.ts
@@ -0,0 +1,34 @@
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("expression-compound-assign.test", () => {
+ const cases: Array<[string, string]> = [
+ ["+=", "a += 1"],
+ ["-=", "a -= 1"],
+ ["*=", "a *= 2"],
+ ["/=", "a /= 2"],
+ ["%=", "a %= 2"],
+ ["**=", "a **= 2"],
+ ["&=", "a &= 1"],
+ ["|=", "a |= 1"],
+ ["^=", "a ^= 1"],
+ ["<<=", "a <<= 1"],
+ [">>=", "a >>= 1"],
+ [">>>=", "a >>>= 1"],
+ ["||=", "a ||= 1"],
+ ["&&=", "a &&= 1"],
+ ["??=", "a ??= 1"],
+ ];
+
+ for (const [op, code] of cases) {
+ it(`should parse ${op}`, () => {
+ const result = parser.parse(code, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "AssignmentExpression",
+ operator: op,
+ });
+ });
+ }
+});
diff --git a/packages/parser/src/nodes/expressions/__test__/expression-member.test.ts b/packages/parser/src/nodes/expressions/__test__/expression-member.test.ts
index b724291..d829470 100644
--- a/packages/parser/src/nodes/expressions/__test__/expression-member.test.ts
+++ b/packages/parser/src/nodes/expressions/__test__/expression-member.test.ts
@@ -17,25 +17,25 @@ describe("expression-member.test", () => {
type: "MemberExpression",
object: {
type: "Identifier",
- name: "_con_ch243",
+ name: "con_chó",
},
property: {
type: "Identifier",
- name: "_ch226n_ph7843i",
+ name: "chân_phải",
},
computed: false,
optional: false,
},
property: {
type: "Identifier",
- name: "_m243ng_ch226n",
+ name: "móng_chân",
},
computed: false,
optional: false,
},
property: {
type: "Identifier",
- name: "_2737897_d224i",
+ name: "độ_dài",
},
computed: false,
optional: false,
@@ -53,25 +53,25 @@ describe("expression-member.test", () => {
type: "MemberExpression",
object: {
type: "Identifier",
- name: "_con_ch243",
+ name: "con_chó",
},
property: {
type: "Identifier",
- name: "_ch226n_ph7843i",
+ name: "chân_phải",
},
computed: true,
optional: false,
},
property: {
type: "Identifier",
- name: "_m243ng_ch226n",
+ name: "móng_chân",
},
computed: false,
optional: false,
},
property: {
type: "Identifier",
- name: "_2737897_d224i",
+ name: "độ_dài",
},
computed: false,
optional: false,
@@ -91,25 +91,25 @@ describe("expression-member.test", () => {
type: "MemberExpression",
object: {
type: "Identifier",
- name: "_con_ch243",
+ name: "con_chó",
},
property: {
type: "Identifier",
- name: "_ch226n_ph7843i",
+ name: "chân_phải",
},
computed: false,
optional: false,
},
property: {
type: "Identifier",
- name: "_m243ng_ch226n",
+ name: "móng_chân",
},
computed: false,
optional: false,
},
property: {
type: "Identifier",
- name: "_2737897_d224i",
+ name: "độ_dài",
},
computed: false,
optional: false,
diff --git a/packages/parser/src/nodes/expressions/__test__/expression-new.test.ts b/packages/parser/src/nodes/expressions/__test__/expression-new.test.ts
new file mode 100644
index 0000000..16fe52c
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/expression-new.test.ts
@@ -0,0 +1,40 @@
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("expression-new.test", () => {
+ it("should parse new without args", () => {
+ const result = parser.parse(`new Cat`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "NewExpression",
+ callee: { type: "Identifier", name: "Cat" },
+ arguments: [],
+ });
+ });
+
+ it("should parse new with args", () => {
+ const result = parser.parse(`new Cat("Mimi", 3)`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "NewExpression",
+ callee: { type: "Identifier", name: "Cat" },
+ arguments: [
+ { type: "StringLiteral", value: "Mimi" },
+ { type: "NumericLiteral", value: 3 },
+ ],
+ });
+ });
+
+ it("should parse new with spread", () => {
+ const result = parser.parse(`new Cat(...args)`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "NewExpression",
+ arguments: [
+ {
+ type: "SpreadElement",
+ argument: { type: "Identifier", name: "args" },
+ },
+ ],
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/expressions/__test__/expression-object-shorthand.test.ts b/packages/parser/src/nodes/expressions/__test__/expression-object-shorthand.test.ts
new file mode 100644
index 0000000..1e331d8
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/expression-object-shorthand.test.ts
@@ -0,0 +1,70 @@
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("expression-object-shorthand.test", () => {
+ it("should parse shorthand property", () => {
+ const result = parser.parse(`{ a, b }`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ObjectExpression",
+ properties: [
+ {
+ type: "ObjectProperty",
+ shorthand: true,
+ key: { type: "Identifier", name: "a" },
+ value: { type: "Identifier", name: "a" },
+ },
+ {
+ type: "ObjectProperty",
+ shorthand: true,
+ key: { type: "Identifier", name: "b" },
+ value: { type: "Identifier", name: "b" },
+ },
+ ],
+ });
+ });
+
+ it("should parse regular property as non-shorthand", () => {
+ const result = parser.parse(`{ a: 1 }`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ properties: [
+ {
+ type: "ObjectProperty",
+ shorthand: false,
+ key: { type: "Identifier", name: "a" },
+ value: { type: "NumericLiteral", value: 1 },
+ },
+ ],
+ });
+ });
+
+ it("should parse computed property", () => {
+ const result = parser.parse(`{ [key]: 1 }`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ properties: [
+ {
+ type: "ObjectProperty",
+ computed: true,
+ key: { type: "Identifier", name: "key" },
+ value: { type: "NumericLiteral", value: 1 },
+ },
+ ],
+ });
+ });
+
+ it("should parse mixed shorthand and regular", () => {
+ const result = parser.parse(`{ a, b: 2, c }`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ properties: [
+ { type: "ObjectProperty", shorthand: true },
+ { type: "ObjectProperty", shorthand: false },
+ { type: "ObjectProperty", shorthand: true },
+ ],
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/expressions/__test__/expression-object.test.ts b/packages/parser/src/nodes/expressions/__test__/expression-object.test.ts
index 113ec32..09b9f4a 100644
--- a/packages/parser/src/nodes/expressions/__test__/expression-object.test.ts
+++ b/packages/parser/src/nodes/expressions/__test__/expression-object.test.ts
@@ -24,9 +24,10 @@ describe("expression-object.test", () => {
type: "ObjectProperty",
method: false,
computed: false,
+ shorthand: false,
key: {
type: "Identifier",
- name: "_ti7871ng_k234u",
+ name: "tiếng_kêu",
},
value: {
type: "StringLiteral",
@@ -43,9 +44,10 @@ describe("expression-object.test", () => {
type: "ObjectProperty",
method: false,
computed: false,
+ shorthand: false,
key: {
type: "Identifier",
- name: "_s7889_ch226n",
+ name: "số_chân",
},
value: {
type: "NumericLiteral",
@@ -63,7 +65,7 @@ describe("expression-object.test", () => {
method: true,
key: {
type: "Identifier",
- name: "_k234u",
+ name: "kêu",
},
computed: false,
kind: "method",
@@ -72,11 +74,11 @@ describe("expression-object.test", () => {
params: [
{
type: "Identifier",
- name: "_s7889_l7847n",
+ name: "số_lần",
},
{
type: "Identifier",
- name: "_hmm",
+ name: "hmm",
},
],
body: {
diff --git a/packages/parser/src/nodes/expressions/__test__/expression-operators.test.ts b/packages/parser/src/nodes/expressions/__test__/expression-operators.test.ts
new file mode 100644
index 0000000..cd9628a
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/expression-operators.test.ts
@@ -0,0 +1,96 @@
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("expression-operators.test", () => {
+ describe("bitwise operators", () => {
+ const cases: Array<[string, string]> = [
+ ["&", "a & b"],
+ ["|", "a | b"],
+ ["^", "a ^ b"],
+ [">>", "a >> b"],
+ [">>>", "a >>> b"],
+ ["<<", "a << b"],
+ ];
+
+ for (const [op, code] of cases) {
+ it(`should parse ${op}`, () => {
+ const result = parser.parse(code, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "BinaryExpression",
+ operator: op,
+ });
+ });
+ }
+ });
+
+ describe("unary operators", () => {
+ it("should parse typeof", () => {
+ const result = parser.parse(`typeof x`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "UnaryExpression",
+ operator: "typeof",
+ argument: { type: "Identifier", name: "x" },
+ });
+ });
+
+ it("should parse void", () => {
+ const result = parser.parse(`void 0`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "UnaryExpression",
+ operator: "void",
+ });
+ });
+
+ it("should parse delete", () => {
+ const result = parser.parse(`xoá o.x`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "UnaryExpression",
+ operator: "delete",
+ });
+ });
+
+ it("should parse ! (not)", () => {
+ const result = parser.parse(`!x`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "UnaryExpression",
+ operator: "!",
+ });
+ });
+ });
+
+ describe("nullish coalescing", () => {
+ it("should parse a ?? b", () => {
+ const result = parser.parse(`a ?? b`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "LogicalExpression",
+ operator: "??",
+ });
+ });
+ });
+
+ describe("this and new", () => {
+ it("should parse this", () => {
+ const result = parser.parse(`this`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ThisExpression",
+ });
+ });
+
+ it("should parse đây (Vietnamese this)", () => {
+ const result = parser.parse(`đây`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ThisExpression",
+ });
+ });
+
+ it("should parse new A()", () => {
+ const result = parser.parse(`new A()`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "NewExpression",
+ callee: { type: "Identifier", name: "A" },
+ });
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/expressions/__test__/expression-optional-chain.test.ts b/packages/parser/src/nodes/expressions/__test__/expression-optional-chain.test.ts
new file mode 100644
index 0000000..e6f7600
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/expression-optional-chain.test.ts
@@ -0,0 +1,45 @@
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("expression-optional-chain.test", () => {
+ it("should parse optional member access", () => {
+ const result = parser.parse(`a?.b`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "OptionalMemberExpression",
+ optional: true,
+ computed: false,
+ object: { type: "Identifier", name: "a" },
+ property: { type: "Identifier", name: "b" },
+ });
+ });
+
+ it("should parse optional computed access", () => {
+ const result = parser.parse(`a?.[k]`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "OptionalMemberExpression",
+ optional: true,
+ computed: true,
+ property: { type: "Identifier", name: "k" },
+ });
+ });
+
+ it("should parse chained optional", () => {
+ const result = parser.parse(`a?.b?.c`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "OptionalMemberExpression",
+ });
+ });
+
+ it("should parse mixed regular and optional", () => {
+ const result = parser.parse(`a.b?.c`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "OptionalMemberExpression",
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/expressions/__test__/expression-spread.test.ts b/packages/parser/src/nodes/expressions/__test__/expression-spread.test.ts
new file mode 100644
index 0000000..9903d18
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/expression-spread.test.ts
@@ -0,0 +1,51 @@
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("expression-spread.test", () => {
+ it("should parse spread in array", () => {
+ const result = parser.parse(`[1, ...a, 2]`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ArrayExpression",
+ elements: [
+ { type: "NumericLiteral", value: 1 },
+ {
+ type: "SpreadElement",
+ argument: { type: "Identifier", name: "a" },
+ },
+ { type: "NumericLiteral", value: 2 },
+ ],
+ });
+ });
+
+ it("should parse spread-only array", () => {
+ const result = parser.parse(`[...a]`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ArrayExpression",
+ elements: [
+ {
+ type: "SpreadElement",
+ argument: { type: "Identifier", name: "a" },
+ },
+ ],
+ });
+ });
+
+ it("should parse spread in object", () => {
+ const result = parser.parse(`{ a: 1, ...o }`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ObjectExpression",
+ properties: [
+ { type: "ObjectProperty" },
+ {
+ type: "SpreadElement",
+ argument: { type: "Identifier", name: "o" },
+ },
+ ],
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/expressions/__test__/expression-tagged-template.test.ts b/packages/parser/src/nodes/expressions/__test__/expression-tagged-template.test.ts
new file mode 100644
index 0000000..3fe2f80
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/expression-tagged-template.test.ts
@@ -0,0 +1,30 @@
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("expression-tagged-template.test", () => {
+ it("should parse simple tagged template", () => {
+ const result = parser.parse("tag`hello`", Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "TaggedTemplateExpression",
+ tag: { type: "Identifier", name: "tag" },
+ quasi: {
+ type: "TemplateLiteral",
+ quasis: [{ value: { cooked: "hello" } }],
+ },
+ });
+ });
+
+ it("should parse tagged template with interpolation", () => {
+ const result = parser.parse("html`${name}
`", Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "TaggedTemplateExpression",
+ tag: { type: "Identifier", name: "html" },
+ quasi: {
+ type: "TemplateLiteral",
+ expressions: [{ type: "Identifier", name: "name" }],
+ },
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/expressions/__test__/expression-ternary.test.ts b/packages/parser/src/nodes/expressions/__test__/expression-ternary.test.ts
new file mode 100644
index 0000000..2e2a9b1
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/expression-ternary.test.ts
@@ -0,0 +1,23 @@
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("expression-ternary.test", () => {
+ it("should parse simple ternary", () => {
+ const result = parser.parse(`c ? a : b`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ConditionalExpression",
+ test: { type: "Identifier", name: "c" },
+ consequent: { type: "Identifier", name: "a" },
+ alternate: { type: "Identifier", name: "b" },
+ });
+ });
+
+ it("should parse ternary with literals", () => {
+ const result = parser.parse(`x ? 1 : 2`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ConditionalExpression",
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/expressions/__test__/expression-yield-async.test.ts b/packages/parser/src/nodes/expressions/__test__/expression-yield-async.test.ts
new file mode 100644
index 0000000..157ff70
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/expression-yield-async.test.ts
@@ -0,0 +1,68 @@
+import { FunctionDeclaration } from "@parser/nodes/declarations/FunctionDeclaration";
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("expression-yield-async.test", () => {
+ it("should parse generator function", () => {
+ const result = parser.parse(`hàm* gen() { nhường 1 }`, FunctionDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "FunctionDeclaration",
+ generator: true,
+ async: false,
+ body: {
+ body: [
+ {
+ type: "ExpressionStatement",
+ expression: {
+ type: "YieldExpression",
+ delegate: false,
+ argument: { type: "NumericLiteral", value: 1 },
+ },
+ },
+ ],
+ },
+ });
+ });
+
+ it("should parse async generator function", () => {
+ const result = parser.parse(`bất đồng bộ hàm* gen() { nhường 1 }`, FunctionDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "FunctionDeclaration",
+ generator: true,
+ async: true,
+ });
+ });
+
+ it("should parse yield delegation", () => {
+ const result = parser.parse(`nhường* other`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "YieldExpression",
+ delegate: true,
+ argument: { type: "Identifier", name: "other" },
+ });
+ });
+
+ it("should parse bare yield", () => {
+ const result = parser.parse(`nhường`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "YieldExpression",
+ delegate: false,
+ argument: null,
+ });
+ });
+
+ it("should parse await expression", () => {
+ const result = parser.parse(`chờ promise`, Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "AwaitExpression",
+ argument: { type: "Identifier", name: "promise" },
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/expressions/__test__/generator/__snapshots__/generator-expression-arrow.test.ts.snap b/packages/parser/src/nodes/expressions/__test__/generator/__snapshots__/generator-expression-arrow.test.ts.snap
new file mode 100644
index 0000000..038266d
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/generator/__snapshots__/generator-expression-arrow.test.ts.snap
@@ -0,0 +1,13 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`generator-expression-arrow.test > should generate javascript from block body arrow 1`] = `
+"() => {
+ return 1;
+}"
+`;
+
+exports[`generator-expression-arrow.test > should generate javascript from empty param arrow 1`] = `"() => 1"`;
+
+exports[`generator-expression-arrow.test > should generate javascript from multi param arrow 1`] = `"(x, y) => x"`;
+
+exports[`generator-expression-arrow.test > should generate javascript from single param arrow 1`] = `"x => x"`;
diff --git a/packages/parser/src/nodes/expressions/__test__/generator/__snapshots__/generator-expression-object-shorthand.test.ts.snap b/packages/parser/src/nodes/expressions/__test__/generator/__snapshots__/generator-expression-object-shorthand.test.ts.snap
new file mode 100644
index 0000000..7e144c6
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/generator/__snapshots__/generator-expression-object-shorthand.test.ts.snap
@@ -0,0 +1,22 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`generator-expression-object-shorthand.test > should generate computed property 1`] = `
+"{
+ [key]: 1
+}"
+`;
+
+exports[`generator-expression-object-shorthand.test > should generate mixed shorthand and regular 1`] = `
+"{
+ a,
+ b: 2,
+ c
+}"
+`;
+
+exports[`generator-expression-object-shorthand.test > should generate shorthand property 1`] = `
+"{
+ a,
+ b
+}"
+`;
diff --git a/packages/parser/src/nodes/expressions/__test__/generator/__snapshots__/generator-expression-spread.test.ts.snap b/packages/parser/src/nodes/expressions/__test__/generator/__snapshots__/generator-expression-spread.test.ts.snap
new file mode 100644
index 0000000..062f0e2
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/generator/__snapshots__/generator-expression-spread.test.ts.snap
@@ -0,0 +1,12 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`generator-expression-spread.test > should generate spread in array 1`] = `"[1, ...a, 2]"`;
+
+exports[`generator-expression-spread.test > should generate spread in call 1`] = `"f(1, ...args)"`;
+
+exports[`generator-expression-spread.test > should generate spread in object 1`] = `
+"{
+ a: 1,
+ ...o
+}"
+`;
diff --git a/packages/parser/src/nodes/expressions/__test__/generator/__snapshots__/generator-optional-chain.test.ts.snap b/packages/parser/src/nodes/expressions/__test__/generator/__snapshots__/generator-optional-chain.test.ts.snap
new file mode 100644
index 0000000..68a65e0
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/generator/__snapshots__/generator-optional-chain.test.ts.snap
@@ -0,0 +1,7 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`generator-optional-chain.test > should generate chained optional 1`] = `"a?.b?.c"`;
+
+exports[`generator-optional-chain.test > should generate optional computed 1`] = `"a?.[k]"`;
+
+exports[`generator-optional-chain.test > should generate optional member 1`] = `"a?.b"`;
diff --git a/packages/parser/src/nodes/expressions/__test__/generator/__snapshots__/generator-yield-async.test.ts.snap b/packages/parser/src/nodes/expressions/__test__/generator/__snapshots__/generator-yield-async.test.ts.snap
new file mode 100644
index 0000000..597bf20
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/generator/__snapshots__/generator-yield-async.test.ts.snap
@@ -0,0 +1,15 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`generator-yield-async.test > should generate async generator function 1`] = `
+"async function* gen() {
+ yield 1;
+}"
+`;
+
+exports[`generator-yield-async.test > should generate generator function 1`] = `
+"function* gen() {
+ yield 1;
+}"
+`;
+
+exports[`generator-yield-async.test > should generate yield delegation 1`] = `"yield* other"`;
diff --git a/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-arrow.test.ts b/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-arrow.test.ts
new file mode 100644
index 0000000..1210137
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-arrow.test.ts
@@ -0,0 +1,31 @@
+import generate from "@babel/generator";
+
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../../setup-test";
+
+describe("generator-expression-arrow.test", () => {
+ it("should generate javascript from single param arrow", () => {
+ const ast = parser.parse(`x => x`, Expression);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+
+ it("should generate javascript from multi param arrow", () => {
+ const ast = parser.parse(`(x, y) => x`, Expression);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+
+ it("should generate javascript from empty param arrow", () => {
+ const ast = parser.parse(`() => 1`, Expression);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+
+ it("should generate javascript from block body arrow", () => {
+ const ast = parser.parse(`() => { trả về 1 }`, Expression);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+});
diff --git a/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-binary.test.ts b/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-binary.test.ts
index d8bfe77..7ce55af 100644
--- a/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-binary.test.ts
+++ b/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-binary.test.ts
@@ -11,6 +11,6 @@ describe("generator-expression-array.test", () => {
const result = generate(ast);
- expect(result.code).toBe(`_xin_ch224o === _hello`);
+ expect(result.code).toBe(`xin_chào === hello`);
});
});
diff --git a/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-call.test.ts b/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-call.test.ts
index 795c92e..9b27a04 100644
--- a/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-call.test.ts
+++ b/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-call.test.ts
@@ -11,7 +11,7 @@ describe("generator-expression-call.test", () => {
const result = generate(ast);
- expect(result.code).toBe(`_con_ch243._k234u()`);
+ expect(result.code).toBe(`con_chó.kêu()`);
});
it("should parse the syntax normally", () => {
@@ -21,7 +21,7 @@ describe("generator-expression-call.test", () => {
const result = generate(ast);
- expect(result.code).toBe(`_con_ch243._ch226n_ph7843i._273225()`);
+ expect(result.code).toBe(`con_chó.chân_phải.đá()`);
});
it("should parse the syntax normally", () => {
@@ -31,6 +31,6 @@ describe("generator-expression-call.test", () => {
const result = generate(ast);
- expect(result.code).toBe(`_con_ch243[_ch226n]._273225()`);
+ expect(result.code).toBe(`con_chó[chân].đá()`);
});
});
diff --git a/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-member.test.ts b/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-member.test.ts
index 6d4ed8b..5e09a7e 100644
--- a/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-member.test.ts
+++ b/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-member.test.ts
@@ -11,7 +11,7 @@ describe("generator-expression-call.test", () => {
const result = generate(ast);
- expect(result.code).toBe(`_con_ch243._ch226n_ph7843i._m243ng_ch226n._2737897_d224i`);
+ expect(result.code).toBe(`con_chó.chân_phải.móng_chân.độ_dài`);
});
it("should parse the syntax normally", () => {
@@ -21,7 +21,7 @@ describe("generator-expression-call.test", () => {
const result = generate(ast);
- expect(result.code).toBe(`_con_ch243[_ch226n_ph7843i]._m243ng_ch226n._2737897_d224i`);
+ expect(result.code).toBe(`con_chó[chân_phải].móng_chân.độ_dài`);
});
it("should parse the syntax normally", () => {
@@ -31,6 +31,6 @@ describe("generator-expression-call.test", () => {
const result = generate(ast);
- expect(result.code).toBe(`_con_ch243._ch226n_ph7843i._m243ng_ch226n._2737897_d224i = 123`);
+ expect(result.code).toBe(`con_chó.chân_phải.móng_chân.độ_dài = 123`);
});
});
diff --git a/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-object-shorthand.test.ts b/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-object-shorthand.test.ts
new file mode 100644
index 0000000..a93a7b5
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-object-shorthand.test.ts
@@ -0,0 +1,22 @@
+import generate from "@babel/generator";
+
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../../setup-test";
+
+describe("generator-expression-object-shorthand.test", () => {
+ it("should generate shorthand property", () => {
+ const ast = parser.parse(`{ a, b }`, Expression);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate mixed shorthand and regular", () => {
+ const ast = parser.parse(`{ a, b: 2, c }`, Expression);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate computed property", () => {
+ const ast = parser.parse(`{ [key]: 1 }`, Expression);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+});
diff --git a/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-object.test.ts b/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-object.test.ts
index 3a5658a..802f652 100644
--- a/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-object.test.ts
+++ b/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-object.test.ts
@@ -19,9 +19,9 @@ describe("generator-expression-object.test", () => {
const result = generate(ast);
expect(result.code).toBe(`{
- _ti7871ng_k234u: "Meo meo",
- _s7889_ch226n: 4,
- async _k234u(_s7889_l7847n, _hmm) {
+ tiếng_kêu: "Meo meo",
+ số_chân: 4,
+ async kêu(số_lần, hmm) {
return "Meo meo";
}
}`);
diff --git a/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-spread.test.ts b/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-spread.test.ts
new file mode 100644
index 0000000..a748ec3
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/generator/generator-expression-spread.test.ts
@@ -0,0 +1,25 @@
+import generate from "@babel/generator";
+
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../../setup-test";
+
+describe("generator-expression-spread.test", () => {
+ it("should generate spread in array", () => {
+ const ast = parser.parse(`[1, ...a, 2]`, Expression);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+
+ it("should generate spread in object", () => {
+ const ast = parser.parse(`{ a: 1, ...o }`, Expression);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+
+ it("should generate spread in call", () => {
+ const ast = parser.parse(`f(1, ...args)`, Expression);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+});
diff --git a/packages/parser/src/nodes/expressions/__test__/generator/generator-optional-chain.test.ts b/packages/parser/src/nodes/expressions/__test__/generator/generator-optional-chain.test.ts
new file mode 100644
index 0000000..8b98440
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/generator/generator-optional-chain.test.ts
@@ -0,0 +1,22 @@
+import generate from "@babel/generator";
+
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../../setup-test";
+
+describe("generator-optional-chain.test", () => {
+ it("should generate optional member", () => {
+ const ast = parser.parse(`a?.b`, Expression);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate optional computed", () => {
+ const ast = parser.parse(`a?.[k]`, Expression);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate chained optional", () => {
+ const ast = parser.parse(`a?.b?.c`, Expression);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+});
diff --git a/packages/parser/src/nodes/expressions/__test__/generator/generator-yield-async.test.ts b/packages/parser/src/nodes/expressions/__test__/generator/generator-yield-async.test.ts
new file mode 100644
index 0000000..b94abe1
--- /dev/null
+++ b/packages/parser/src/nodes/expressions/__test__/generator/generator-yield-async.test.ts
@@ -0,0 +1,23 @@
+import generate from "@babel/generator";
+
+import { FunctionDeclaration } from "@parser/nodes/declarations/FunctionDeclaration";
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../../setup-test";
+
+describe("generator-yield-async.test", () => {
+ it("should generate generator function", () => {
+ const ast = parser.parse(`hàm* gen() { nhường 1 }`, FunctionDeclaration);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate async generator function", () => {
+ const ast = parser.parse(`bất đồng bộ hàm* gen() { nhường 1 }`, FunctionDeclaration);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate yield delegation", () => {
+ const ast = parser.parse(`nhường* other`, Expression);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+});
diff --git a/packages/parser/src/nodes/identifier/Identifier.ts b/packages/parser/src/nodes/identifier/Identifier.ts
index 096bf05..be787ce 100644
--- a/packages/parser/src/nodes/identifier/Identifier.ts
+++ b/packages/parser/src/nodes/identifier/Identifier.ts
@@ -7,14 +7,9 @@ export class Identifier {
name: string;
constructor(parser: Parser) {
- // TODO: optimize later
- this.name = `_${String(parser.eat(Keyword.IDENTIFIER)?.value).replace(
- /(\s)|(^[0-9]+)|([^\sA-Za-z])/g,
- (_, p1, p2, p3) => {
- if (p1) return "_";
- else if (p2) return "_" + p2;
- else return String(p3.codePointAt(0));
- },
- )}`;
+ const raw = String(parser.eat(Keyword.IDENTIFIER)?.value);
+ let name = raw.replace(/\s+/g, "_");
+ if (/^\d/.test(name)) name = "_" + name;
+ this.name = name;
}
}
diff --git a/packages/parser/src/nodes/identifier/PrivateName.ts b/packages/parser/src/nodes/identifier/PrivateName.ts
new file mode 100644
index 0000000..bb463e0
--- /dev/null
+++ b/packages/parser/src/nodes/identifier/PrivateName.ts
@@ -0,0 +1,14 @@
+import { Parser } from "@parser/parser";
+
+import { Identifier } from "./Identifier";
+
+export class PrivateName {
+ type = "PrivateName";
+
+ id: Identifier;
+
+ constructor(parser: Parser) {
+ parser.eat("#");
+ this.id = new Identifier(parser);
+ }
+}
diff --git a/packages/parser/src/nodes/identifier/__test__/identifier.test.ts b/packages/parser/src/nodes/identifier/__test__/identifier.test.ts
index ed04ebd..c1ebddc 100644
--- a/packages/parser/src/nodes/identifier/__test__/identifier.test.ts
+++ b/packages/parser/src/nodes/identifier/__test__/identifier.test.ts
@@ -4,40 +4,22 @@ import parser from "../../../setup-test";
import toPlainObject from "../../../toPlainObject";
describe("identifier.test", () => {
- it("should parse the syntax normally", () => {
+ it("should parse long Vietnamese identifier without embedded keyword", () => {
const result = parser.parse(
`
{
- // Object này có key siêu dài và câu này cũng có khá đa dạng về ký tự để test việc parse identifier
- Trăm năm trong cõi người ta chữ tài chữ mệnh khéo là ghét nhau Trải qua một cuộc bể dâu những điều trông thấy mà đau đớn lòng Lạ gì bỉ sắc tư phong trời xanh quen với má hồng đánh ghen: "Giá trị pha ke"
+ Con mèo đen xinh đẹp chạy nhanh sân: "Giá trị pha ke"
}
`,
ObjectExpression,
);
- expect(toPlainObject(result)).toStrictEqual({
- type: "ObjectExpression",
- properties: [
- {
- type: "ObjectProperty",
- method: false,
- computed: false,
- key: {
- type: "Identifier",
- name: "_Tr259m_n259m_trong_c245i_ng4327901i_ta_ch7919_t224i_ch7919_m7879nh_kh233o_l224_gh233t_nhau_Tr7843i_qua_m7897t_cu7897c_b7875_d226u_nh7919ng_273i7873u_tr244ng_th7845y_m224_273au_2737899n_l242ng_L7841_g236_b7881_s7855c_t432_phong_tr7901i_xanh_quen_v7899i_m225_h7891ng_273225nh_ghen",
- },
- value: {
- type: "StringLiteral",
- value: "Giá trị pha ke",
- extra: {
- rawValue: "Giá trị pha ke",
- raw: '"Giá trị pha ke"',
- },
- start: 305,
- end: 321,
- },
- },
- ],
- } as ObjectExpression);
+ const plain = toPlainObject(result) as any;
+ expect(plain.type).toBe("ObjectExpression");
+ expect(plain.properties).toHaveLength(1);
+ expect(plain.properties[0].type).toBe("ObjectProperty");
+ expect(plain.properties[0].key.type).toBe("Identifier");
+ expect(plain.properties[0].value.type).toBe("StringLiteral");
+ expect(plain.properties[0].value.value).toBe("Giá trị pha ke");
});
});
diff --git a/packages/parser/src/nodes/initializers/array/ArrayElementList.ts b/packages/parser/src/nodes/initializers/array/ArrayElementList.ts
index c743d9d..9b54b03 100644
--- a/packages/parser/src/nodes/initializers/array/ArrayElementList.ts
+++ b/packages/parser/src/nodes/initializers/array/ArrayElementList.ts
@@ -1,15 +1,20 @@
import { Parser } from "@parser/parser";
import { Expression } from "../../expressions/Expression";
+import { SpreadElement } from "../../expressions/SpreadElement";
export class ArrayElementList {
type = "ArrayElementList";
- elements: Array = [];
+ elements: Array = [];
constructor(parser: Parser, stopToken = "]") {
while (parser.lookahead?.type !== stopToken) {
- this.elements.push(new Expression(parser));
+ if (parser.lookahead?.type === "...") {
+ this.elements.push(new SpreadElement(parser));
+ } else {
+ this.elements.push(new Expression(parser));
+ }
if (parser.lookahead?.type !== stopToken) {
parser.eat(",");
diff --git a/packages/parser/src/nodes/initializers/object/ObjectProperty.ts b/packages/parser/src/nodes/initializers/object/ObjectProperty.ts
index fd9fb9a..882b945 100644
--- a/packages/parser/src/nodes/initializers/object/ObjectProperty.ts
+++ b/packages/parser/src/nodes/initializers/object/ObjectProperty.ts
@@ -9,9 +9,11 @@ export class ObjectProperty {
computed = false;
+ shorthand = false;
+
key: Identifier | Expression;
- value: null | Expression;
+ value: Identifier | Expression;
constructor(parser: Parser) {
if (parser.lookahead?.type === "[") {
@@ -26,8 +28,11 @@ export class ObjectProperty {
if (parser.lookahead?.type === ":") {
parser.eat(":");
this.value = new Expression(parser);
+ } else if (!this.computed) {
+ this.value = this.key;
+ this.shorthand = true;
} else {
- this.value = null;
+ throw new SyntaxError(`Computed property key phải có giá trị theo sau`);
}
}
}
diff --git a/packages/parser/src/nodes/initializers/object/ObjectPropertyList.ts b/packages/parser/src/nodes/initializers/object/ObjectPropertyList.ts
index 27358cf..7efa814 100644
--- a/packages/parser/src/nodes/initializers/object/ObjectPropertyList.ts
+++ b/packages/parser/src/nodes/initializers/object/ObjectPropertyList.ts
@@ -1,23 +1,29 @@
import { Parser } from "@parser/parser";
import { Keyword } from "@vietscript/shared";
+import { SpreadElement } from "../../expressions/SpreadElement";
+
import { ObjectProperty } from "./ObjectProperty";
import { ObjectMethod } from "./ObjectMethod";
export class ObjectPropertyList {
- properties: Array = [];
+ properties: Array = [];
constructor(parser: Parser, stopToken = "}") {
while (parser.lookahead?.type !== stopToken) {
- switch (parser.lookahead?.type) {
- case Keyword.GET:
- case Keyword.SET:
- case Keyword.ASYNC: {
- this.properties.push(new ObjectMethod(parser));
- break;
- }
- default: {
- this.properties.push(new ObjectProperty(parser));
+ if (parser.lookahead?.type === "...") {
+ this.properties.push(new SpreadElement(parser));
+ } else {
+ switch (parser.lookahead?.type) {
+ case Keyword.GET:
+ case Keyword.SET:
+ case Keyword.ASYNC: {
+ this.properties.push(new ObjectMethod(parser));
+ break;
+ }
+ default: {
+ this.properties.push(new ObjectProperty(parser));
+ }
}
}
diff --git a/packages/parser/src/nodes/literals/InfinityIdentifier.ts b/packages/parser/src/nodes/literals/InfinityIdentifier.ts
new file mode 100644
index 0000000..8371781
--- /dev/null
+++ b/packages/parser/src/nodes/literals/InfinityIdentifier.ts
@@ -0,0 +1,19 @@
+import { Parser } from "@parser/parser";
+import { Keyword, Node } from "@vietscript/shared";
+
+export class InfinityIdentifier implements Node {
+ type = Keyword.IDENTIFIER;
+
+ name = "Infinity";
+
+ start: number;
+
+ end: number;
+
+ constructor(parser: Parser) {
+ const token = parser.eat(Keyword.INFINITY);
+
+ this.start = token.start;
+ this.end = token.end;
+ }
+}
diff --git a/packages/parser/src/nodes/literals/Literal.ts b/packages/parser/src/nodes/literals/Literal.ts
index 6ad8076..2f88145 100644
--- a/packages/parser/src/nodes/literals/Literal.ts
+++ b/packages/parser/src/nodes/literals/Literal.ts
@@ -2,10 +2,13 @@ import { Parser } from "@parser/parser";
import { Keyword } from "@vietscript/shared";
import { BooleanLiteral } from "./BooleanLiteral";
+import { InfinityIdentifier } from "./InfinityIdentifier";
import { NaNIdentifier } from "./NaNIdentifier";
import { NullLiteral } from "./NullLiteral";
import { NumericLiteral } from "./NumericLiteral";
+import { RegExpLiteral } from "./RegExpLiteral";
import { StringLiteral } from "./StringLiteral";
+import { TemplateLiteral } from "./TemplateLiteral";
import { UndefinedIdentifier } from "./UndefinedIdentifier";
export class Literal {
@@ -19,6 +22,14 @@ export class Literal {
Object.assign(this, new StringLiteral(parser));
break;
}
+ case "TemplateLiteral": {
+ Object.assign(this, new TemplateLiteral(parser));
+ break;
+ }
+ case "RegExpLiteral": {
+ Object.assign(this, new RegExpLiteral(parser));
+ break;
+ }
case Keyword.BOOLEAN: {
Object.assign(this, new BooleanLiteral(parser));
break;
@@ -31,6 +42,10 @@ export class Literal {
Object.assign(this, new NaNIdentifier(parser));
break;
}
+ case Keyword.INFINITY: {
+ Object.assign(this, new InfinityIdentifier(parser));
+ break;
+ }
case Keyword.UNDEFINED: {
Object.assign(this, new UndefinedIdentifier(parser));
break;
diff --git a/packages/parser/src/nodes/literals/NumericLiteral.ts b/packages/parser/src/nodes/literals/NumericLiteral.ts
index 8ae1d06..a803f64 100644
--- a/packages/parser/src/nodes/literals/NumericLiteral.ts
+++ b/packages/parser/src/nodes/literals/NumericLiteral.ts
@@ -4,10 +4,10 @@ import { Keyword, Node } from "@vietscript/shared";
export class NumericLiteral implements Node {
type = "NumericLiteral";
- value: number;
+ value: number | string = 0;
extra: {
- rawValue: number;
+ rawValue: number | string;
raw: string;
};
@@ -17,15 +17,20 @@ export class NumericLiteral implements Node {
constructor(parser: Parser) {
const token = parser.eat(Keyword.NUMBER);
+ const raw = String(token.value);
this.start = token.start;
this.end = token.end;
- this.value = Number(token.value);
-
- this.extra = {
- rawValue: this.value,
- raw: String(token.value),
- };
+ if (raw.endsWith("n")) {
+ this.type = "BigIntLiteral";
+ const clean = raw.slice(0, -1).replace(/_/g, "");
+ this.value = clean;
+ this.extra = { rawValue: clean, raw };
+ } else {
+ const clean = raw.replace(/_/g, "");
+ this.value = Number(clean);
+ this.extra = { rawValue: this.value, raw };
+ }
}
}
diff --git a/packages/parser/src/nodes/literals/RegExpLiteral.ts b/packages/parser/src/nodes/literals/RegExpLiteral.ts
new file mode 100644
index 0000000..d73cfba
--- /dev/null
+++ b/packages/parser/src/nodes/literals/RegExpLiteral.ts
@@ -0,0 +1,17 @@
+import { Parser } from "@parser/parser";
+
+export class RegExpLiteral {
+ type = "RegExpLiteral";
+
+ pattern: string;
+
+ flags: string;
+
+ constructor(parser: Parser) {
+ const token = parser.eat("RegExpLiteral");
+ const raw = String(token?.value);
+ const lastSlash = raw.lastIndexOf("/");
+ this.pattern = raw.slice(1, lastSlash);
+ this.flags = raw.slice(lastSlash + 1);
+ }
+}
diff --git a/packages/parser/src/nodes/literals/StringLiteral.ts b/packages/parser/src/nodes/literals/StringLiteral.ts
index 9896c10..4854433 100644
--- a/packages/parser/src/nodes/literals/StringLiteral.ts
+++ b/packages/parser/src/nodes/literals/StringLiteral.ts
@@ -1,6 +1,42 @@
import { Parser } from "@parser/parser";
import { Keyword, Node } from "@vietscript/shared";
+function cook(raw: string): string {
+ return raw.replace(/\\(x[0-9a-fA-F]{2}|u\{[0-9a-fA-F]+\}|u[0-9a-fA-F]{4}|.)/g, (_, seq) => {
+ if (seq[0] === "x") {
+ return String.fromCharCode(parseInt(seq.slice(1), 16));
+ }
+ if (seq[0] === "u") {
+ const body = seq[1] === "{" ? seq.slice(2, -1) : seq.slice(1);
+ return String.fromCodePoint(parseInt(body, 16));
+ }
+ switch (seq) {
+ case "n":
+ return "\n";
+ case "r":
+ return "\r";
+ case "t":
+ return "\t";
+ case "b":
+ return "\b";
+ case "f":
+ return "\f";
+ case "v":
+ return "\v";
+ case "0":
+ return "\0";
+ case "\\":
+ return "\\";
+ case "'":
+ return "'";
+ case '"':
+ return '"';
+ default:
+ return seq;
+ }
+ });
+}
+
export class StringLiteral implements Node {
type = "StringLiteral";
@@ -21,7 +57,9 @@ export class StringLiteral implements Node {
this.start = token.start;
this.end = token.end;
- const value = String(token.value).slice(1, -1);
+ const raw = String(token.value);
+ const inner = raw.slice(1, -1);
+ const value = cook(inner);
this.value = value;
diff --git a/packages/parser/src/nodes/literals/TemplateLiteral.ts b/packages/parser/src/nodes/literals/TemplateLiteral.ts
new file mode 100644
index 0000000..ffbfde2
--- /dev/null
+++ b/packages/parser/src/nodes/literals/TemplateLiteral.ts
@@ -0,0 +1,131 @@
+import { Parser } from "@parser/parser";
+import { Tokenizer } from "@parser/tokenizer";
+
+import { Expression } from "../expressions/Expression";
+
+interface TemplateElement {
+ type: "TemplateElement";
+ value: { raw: string; cooked: string };
+ tail: boolean;
+}
+
+function cook(raw: string): string {
+ return raw.replace(/\\(.)/g, (_, ch) => {
+ switch (ch) {
+ case "n":
+ return "\n";
+ case "r":
+ return "\r";
+ case "t":
+ return "\t";
+ case "\\":
+ return "\\";
+ case "`":
+ return "`";
+ case "$":
+ return "$";
+ default:
+ return ch;
+ }
+ });
+}
+
+function splitTemplate(inner: string): { quasis: string[]; expressions: string[] } {
+ const quasis: string[] = [];
+ const expressions: string[] = [];
+ let current = "";
+ let i = 0;
+
+ while (i < inner.length) {
+ if (inner[i] === "\\" && i + 1 < inner.length) {
+ current += inner[i] + inner[i + 1];
+ i += 2;
+ continue;
+ }
+
+ if (inner[i] === "$" && inner[i + 1] === "{") {
+ quasis.push(current);
+ current = "";
+ i += 2;
+
+ let depth = 1;
+ let expr = "";
+ while (i < inner.length && depth > 0) {
+ const ch = inner[i];
+ if (ch === "\\") {
+ expr += ch + inner[i + 1];
+ i += 2;
+ continue;
+ }
+ if (ch === '"' || ch === "'") {
+ const quote = ch;
+ expr += ch;
+ i++;
+ while (i < inner.length && inner[i] !== quote) {
+ if (inner[i] === "\\") {
+ expr += inner[i] + inner[i + 1];
+ i += 2;
+ continue;
+ }
+ expr += inner[i];
+ i++;
+ }
+ expr += inner[i];
+ i++;
+ continue;
+ }
+ if (ch === "{") depth++;
+ else if (ch === "}") {
+ depth--;
+ if (depth === 0) {
+ i++;
+ break;
+ }
+ }
+ expr += ch;
+ i++;
+ }
+ expressions.push(expr);
+ continue;
+ }
+
+ current += inner[i];
+ i++;
+ }
+
+ quasis.push(current);
+ return { quasis, expressions };
+}
+
+export class TemplateLiteral {
+ type = "TemplateLiteral";
+
+ quasis: Array = [];
+
+ expressions: Array = [];
+
+ constructor(parser: Parser) {
+ const token = parser.eat("TemplateLiteral");
+ const raw = String(token?.value);
+ const inner = raw.slice(1, -1);
+
+ const { quasis, expressions } = splitTemplate(inner);
+
+ for (let idx = 0; idx < quasis.length; idx++) {
+ const rawText = quasis[idx];
+ this.quasis.push({
+ type: "TemplateElement",
+ value: { raw: rawText, cooked: cook(rawText) },
+ tail: idx === quasis.length - 1,
+ });
+ }
+
+ for (const exprSource of expressions) {
+ const subParser = new Parser();
+ subParser.syntax = exprSource;
+ subParser.tokenizer = new Tokenizer(subParser);
+ subParser.lookahead = subParser.tokenizer.getNextToken();
+ this.expressions.push(new Expression(subParser));
+ }
+ }
+}
diff --git a/packages/parser/src/nodes/literals/UndefinedIdentifier.ts b/packages/parser/src/nodes/literals/UndefinedIdentifier.ts
index ebd9e61..886a799 100644
--- a/packages/parser/src/nodes/literals/UndefinedIdentifier.ts
+++ b/packages/parser/src/nodes/literals/UndefinedIdentifier.ts
@@ -4,7 +4,7 @@ import { Keyword, Node } from "@vietscript/shared";
export class UndefinedIdentifier implements Node {
type = Keyword.IDENTIFIER;
- name = Keyword.UNDEFINED;
+ name = "undefined";
start: number;
diff --git a/packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-boolean.test.ts.snap b/packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-boolean.test.ts.snap
index 8579ad3..1dd2dcb 100644
--- a/packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-boolean.test.ts.snap
+++ b/packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-boolean.test.ts.snap
@@ -1,4 +1,4 @@
-// Vitest Snapshot v1
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`generator-literal-boolean.test > should parse the syntax normally 1`] = `"true"`;
diff --git a/packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-numeric.test.ts.snap b/packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-numeric.test.ts.snap
index ebff2a4..de8b984 100644
--- a/packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-numeric.test.ts.snap
+++ b/packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-numeric.test.ts.snap
@@ -1,4 +1,4 @@
-// Vitest Snapshot v1
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`generator-literal-numeric.test > should parse the syntax normally 1`] = `"0"`;
diff --git a/packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-string.test.ts.snap b/packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-string.test.ts.snap
index 53167d9..2df9bad 100644
--- a/packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-string.test.ts.snap
+++ b/packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-string.test.ts.snap
@@ -1,3 +1,3 @@
-// Vitest Snapshot v1
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`generator-literal-string.test > should generate the javascript from the syntax normally 1`] = `"\\"Chào thế giới!\\""`;
+exports[`generator-literal-string.test > should generate the javascript from the syntax normally 1`] = `""Chào thế giới!""`;
diff --git a/packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-template.test.ts.snap b/packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-template.test.ts.snap
new file mode 100644
index 0000000..774409f
--- /dev/null
+++ b/packages/parser/src/nodes/literals/__test__/generator/__snapshots__/generator-literal-template.test.ts.snap
@@ -0,0 +1,7 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`generator-literal-template.test > should generate multiple interpolations 1`] = `"\`\${a}-\${b}-\${c}\`"`;
+
+exports[`generator-literal-template.test > should generate plain template 1`] = `"\`Xin chào!\`"`;
+
+exports[`generator-literal-template.test > should generate single interpolation 1`] = `"\`Xin chào \${ten}!\`"`;
diff --git a/packages/parser/src/nodes/literals/__test__/generator/generator-literal-template.test.ts b/packages/parser/src/nodes/literals/__test__/generator/generator-literal-template.test.ts
new file mode 100644
index 0000000..db888e1
--- /dev/null
+++ b/packages/parser/src/nodes/literals/__test__/generator/generator-literal-template.test.ts
@@ -0,0 +1,22 @@
+import generate from "@babel/generator";
+
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../../setup-test";
+
+describe("generator-literal-template.test", () => {
+ it("should generate plain template", () => {
+ const ast = parser.parse("`Xin chào!`", Expression);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate single interpolation", () => {
+ const ast = parser.parse("`Xin chào ${ten}!`", Expression);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate multiple interpolations", () => {
+ const ast = parser.parse("`${a}-${b}-${c}`", Expression);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+});
diff --git a/packages/parser/src/nodes/literals/__test__/literal-numeric-advanced.test.ts b/packages/parser/src/nodes/literals/__test__/literal-numeric-advanced.test.ts
new file mode 100644
index 0000000..41073ab
--- /dev/null
+++ b/packages/parser/src/nodes/literals/__test__/literal-numeric-advanced.test.ts
@@ -0,0 +1,73 @@
+import { Literal } from "@parser/nodes/literals/Literal";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("literal-numeric-advanced.test", () => {
+ const decimalCases: Array<[string, number]> = [
+ ["42", 42],
+ ["3.14", 3.14],
+ [".5", 0.5],
+ ["1e3", 1000],
+ ["1_000_000", 1_000_000],
+ ["1.5e2", 150],
+ ];
+
+ for (const [input, expected] of decimalCases) {
+ it(`should parse "${input}" as ${expected}`, () => {
+ const result = parser.parse(input, Literal);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "NumericLiteral",
+ value: expected,
+ });
+ });
+ }
+
+ it("should parse hex literal", () => {
+ const result = parser.parse("0xff", Literal);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "NumericLiteral",
+ value: 255,
+ });
+ });
+
+ it("should parse octal literal", () => {
+ const result = parser.parse("0o17", Literal);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "NumericLiteral",
+ value: 15,
+ });
+ });
+
+ it("should parse binary literal", () => {
+ const result = parser.parse("0b1010", Literal);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "NumericLiteral",
+ value: 10,
+ });
+ });
+
+ it("should parse BigInt literal", () => {
+ const result = parser.parse("123n", Literal);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "BigIntLiteral",
+ value: "123",
+ });
+ });
+
+ it("should parse hex BigInt", () => {
+ const result = parser.parse("0xffn", Literal);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "BigIntLiteral",
+ value: "0xff",
+ });
+ });
+
+ it("should parse numeric separator in hex", () => {
+ const result = parser.parse("0xff_ff", Literal);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "NumericLiteral",
+ value: 0xffff,
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/literals/__test__/literal-numeric.test.ts b/packages/parser/src/nodes/literals/__test__/literal-numeric.test.ts
index 4b289a2..16f6cb7 100644
--- a/packages/parser/src/nodes/literals/__test__/literal-numeric.test.ts
+++ b/packages/parser/src/nodes/literals/__test__/literal-numeric.test.ts
@@ -55,9 +55,9 @@ describe("literal-numeric.test", () => {
expect(toPlainObject(result)).toStrictEqual({
type: "NumericLiteral",
- value: 0.001_123,
+ value: 0.001123,
extra: {
- rawValue: 0.001_123,
+ rawValue: 0.001123,
raw: "0.001123",
},
start: 0,
diff --git a/packages/parser/src/nodes/literals/__test__/literal-primitives.test.ts b/packages/parser/src/nodes/literals/__test__/literal-primitives.test.ts
new file mode 100644
index 0000000..c108593
--- /dev/null
+++ b/packages/parser/src/nodes/literals/__test__/literal-primitives.test.ts
@@ -0,0 +1,67 @@
+import { Expression } from "@parser/nodes/expressions/Expression";
+import { Literal } from "@parser/nodes/literals/Literal";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("literal-primitives.test", () => {
+ it("should parse NaN", () => {
+ const result = parser.parse(`NaN`, Literal);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "Identifier",
+ name: "NaN",
+ });
+ });
+
+ it("should parse Infinity", () => {
+ const result = parser.parse(`Infinity`, Expression);
+ expect(toPlainObject(result)).toMatchObject({ type: "Identifier", name: "Infinity" });
+ });
+
+ it("should parse Vietnamese vô cực as Infinity identifier", () => {
+ const result = parser.parse(`vô cực`, Expression);
+ expect(toPlainObject(result)).toMatchObject({ type: "Identifier" });
+ });
+
+ it("should parse null with English keyword", () => {
+ const result = parser.parse(`null`, Literal);
+ expect(toPlainObject(result)).toMatchObject({ type: "NullLiteral" });
+ });
+
+ it("should parse null with Vietnamese rỗng", () => {
+ const result = parser.parse(`rỗng`, Literal);
+ expect(toPlainObject(result)).toMatchObject({ type: "NullLiteral" });
+ });
+
+ it("should parse undefined", () => {
+ const result = parser.parse(`undefined`, Literal);
+ expect(toPlainObject(result)).toMatchObject({ type: "Identifier", name: "undefined" });
+ });
+
+ it("should parse không xác định", () => {
+ const result = parser.parse(`không xác định`, Literal);
+ expect(toPlainObject(result)).toMatchObject({ type: "Identifier" });
+ });
+
+ it("should parse scientific notation", () => {
+ const result = parser.parse(`1.5e2`, Literal);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "NumericLiteral",
+ value: 150,
+ });
+ });
+
+ it("should parse booleans", () => {
+ const t = parser.parse(`true`, Literal);
+ const f = parser.parse(`false`, Literal);
+ expect(toPlainObject(t)).toMatchObject({ type: "BooleanLiteral", value: true });
+ expect(toPlainObject(f)).toMatchObject({ type: "BooleanLiteral", value: false });
+ });
+
+ it("should parse Vietnamese đúng and sai", () => {
+ const t = parser.parse(`đúng`, Literal);
+ const f = parser.parse(`sai`, Literal);
+ expect(toPlainObject(t)).toMatchObject({ type: "BooleanLiteral", value: true });
+ expect(toPlainObject(f)).toMatchObject({ type: "BooleanLiteral", value: false });
+ });
+});
diff --git a/packages/parser/src/nodes/literals/__test__/literal-regex.test.ts b/packages/parser/src/nodes/literals/__test__/literal-regex.test.ts
new file mode 100644
index 0000000..2ec5f46
--- /dev/null
+++ b/packages/parser/src/nodes/literals/__test__/literal-regex.test.ts
@@ -0,0 +1,49 @@
+import { Literal } from "@parser/nodes/literals/Literal";
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("literal-regex.test", () => {
+ it("should parse simple regex", () => {
+ const result = parser.parse(`/abc/`, Literal);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "RegExpLiteral",
+ pattern: "abc",
+ flags: "",
+ });
+ });
+
+ it("should parse regex with flags", () => {
+ const result = parser.parse(`/abc/gi`, Literal);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "RegExpLiteral",
+ pattern: "abc",
+ flags: "gi",
+ });
+ });
+
+ it("should parse regex with escapes", () => {
+ const result = parser.parse(`/a\\/b/`, Literal);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "RegExpLiteral",
+ pattern: "a\\/b",
+ });
+ });
+
+ it("should parse regex with character class containing slash", () => {
+ const result = parser.parse(`/[/]/`, Literal);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "RegExpLiteral",
+ pattern: "[/]",
+ });
+ });
+
+ it("should treat division as division, not regex, in expression context", () => {
+ const result = parser.parse(`a / b`, Expression);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "BinaryExpression",
+ operator: "/",
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/literals/__test__/literal-string-escape.test.ts b/packages/parser/src/nodes/literals/__test__/literal-string-escape.test.ts
new file mode 100644
index 0000000..5b8e4a8
--- /dev/null
+++ b/packages/parser/src/nodes/literals/__test__/literal-string-escape.test.ts
@@ -0,0 +1,29 @@
+import { Literal } from "@parser/nodes/literals/Literal";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("literal-string-escape.test", () => {
+ const cases: Array<[string, string]> = [
+ ['"a\\nb"', "a\nb"],
+ ['"a\\tb"', "a\tb"],
+ ['"a\\rb"', "a\rb"],
+ ['"a\\\\b"', "a\\b"],
+ ['"a\\"b"', 'a"b'],
+ ["'a\\'b'", "a'b"],
+ ['"\\x41"', "A"],
+ ['"\\u0041"', "A"],
+ ['"\\u{1F600}"', "😀"],
+ ['"\\0"', "\0"],
+ ];
+
+ for (const [input, expected] of cases) {
+ it(`should parse ${input} as ${JSON.stringify(expected)}`, () => {
+ const result = parser.parse(input, Literal);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "StringLiteral",
+ value: expected,
+ });
+ });
+ }
+});
diff --git a/packages/parser/src/nodes/literals/__test__/literal-string.test.ts b/packages/parser/src/nodes/literals/__test__/literal-string.test.ts
index ca42db2..185e029 100644
--- a/packages/parser/src/nodes/literals/__test__/literal-string.test.ts
+++ b/packages/parser/src/nodes/literals/__test__/literal-string.test.ts
@@ -35,18 +35,19 @@ describe("literal-string.test", () => {
} as StringLiteral);
});
- it("should parse the syntax normally", () => {
+ it("should parse backtick as template literal", () => {
const result = parser.parse("`Chào thế giới!`", Literal);
- expect(toPlainObject(result)).toStrictEqual({
- type: "StringLiteral",
- value: "Chào thế giới!",
- extra: {
- rawValue: "Chào thế giới!",
- raw: '"Chào thế giới!"',
- },
- start: 0,
- end: 16,
- } as StringLiteral);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "TemplateLiteral",
+ quasis: [
+ {
+ type: "TemplateElement",
+ value: { raw: "Chào thế giới!", cooked: "Chào thế giới!" },
+ tail: true,
+ },
+ ],
+ expressions: [],
+ });
});
});
diff --git a/packages/parser/src/nodes/literals/__test__/literal-template.test.ts b/packages/parser/src/nodes/literals/__test__/literal-template.test.ts
new file mode 100644
index 0000000..1652cf3
--- /dev/null
+++ b/packages/parser/src/nodes/literals/__test__/literal-template.test.ts
@@ -0,0 +1,61 @@
+import { Expression } from "@parser/nodes/expressions/Expression";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("literal-template.test", () => {
+ it("should parse plain template without interpolation", () => {
+ const result = parser.parse("`Xin chào!`", Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "TemplateLiteral",
+ quasis: [
+ {
+ type: "TemplateElement",
+ value: { raw: "Xin chào!", cooked: "Xin chào!" },
+ tail: true,
+ },
+ ],
+ expressions: [],
+ });
+ });
+
+ it("should parse single interpolation", () => {
+ const result = parser.parse("`Xin chào ${ten}!`", Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "TemplateLiteral",
+ quasis: [
+ { type: "TemplateElement", value: { raw: "Xin chào ", cooked: "Xin chào " }, tail: false },
+ { type: "TemplateElement", value: { raw: "!", cooked: "!" }, tail: true },
+ ],
+ expressions: [{ type: "Identifier", name: "ten" }],
+ });
+ });
+
+ it("should parse multiple interpolations", () => {
+ const result = parser.parse("`${a}-${b}-${c}`", Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "TemplateLiteral",
+ expressions: [
+ { type: "Identifier", name: "a" },
+ { type: "Identifier", name: "b" },
+ { type: "Identifier", name: "c" },
+ ],
+ });
+ expect((result as any).quasis).toHaveLength(4);
+ });
+
+ it("should handle escape sequences in cooked", () => {
+ const result = parser.parse("`a\\nb`", Expression);
+
+ expect(toPlainObject(result)).toMatchObject({
+ quasis: [
+ {
+ value: { raw: "a\\nb", cooked: "a\nb" },
+ },
+ ],
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/patterns/ArrayPattern.ts b/packages/parser/src/nodes/patterns/ArrayPattern.ts
new file mode 100644
index 0000000..2f7a47d
--- /dev/null
+++ b/packages/parser/src/nodes/patterns/ArrayPattern.ts
@@ -0,0 +1,70 @@
+import { Parser } from "@parser/parser";
+
+import { Expression } from "../expressions/Expression";
+import { Identifier } from "../identifier/Identifier";
+
+import { ObjectPattern } from "./ObjectPattern";
+
+export interface ArrayPatternAssignment {
+ type: "AssignmentPattern";
+ left: Identifier | ObjectPattern | ArrayPattern;
+ right: Expression;
+}
+
+export interface ArrayPatternRest {
+ type: "RestElement";
+ argument: Identifier | ObjectPattern | ArrayPattern;
+}
+
+export type ArrayPatternElement = Identifier | ObjectPattern | ArrayPattern | ArrayPatternAssignment | ArrayPatternRest | null;
+
+export class ArrayPattern {
+ type = "ArrayPattern";
+
+ elements: Array = [];
+
+ constructor(parser: Parser) {
+ parser.eat("[");
+
+ while (parser.lookahead?.type !== "]") {
+ if (parser.lookahead?.type === ",") {
+ this.elements.push(null);
+ parser.eat(",");
+ continue;
+ }
+
+ if (parser.lookahead?.type === "...") {
+ parser.eat("...");
+ const argument = parseBindingTarget(parser);
+ this.elements.push({ type: "RestElement", argument });
+
+ if (parser.lookahead?.type !== "]") {
+ throw new SyntaxError(`Rest element phải ở vị trí cuối trong array destructuring`);
+ }
+ break;
+ }
+
+ const left = parseBindingTarget(parser);
+
+ if (parser.lookahead?.type === "=") {
+ parser.eat("=");
+ const right = new Expression(parser);
+ this.elements.push({ type: "AssignmentPattern", left, right });
+ } else {
+ this.elements.push(left);
+ }
+
+ if (parser.lookahead?.type !== "]") {
+ parser.eat(",");
+ }
+ }
+
+ parser.eat("]");
+ }
+}
+
+function parseBindingTarget(parser: Parser): Identifier | ObjectPattern | ArrayPattern {
+ if (parser.lookahead?.type === "{") return new ObjectPattern(parser);
+ if (parser.lookahead?.type === "[") return new ArrayPattern(parser);
+ return new Identifier(parser);
+}
diff --git a/packages/parser/src/nodes/patterns/ObjectPattern.ts b/packages/parser/src/nodes/patterns/ObjectPattern.ts
new file mode 100644
index 0000000..d40f40c
--- /dev/null
+++ b/packages/parser/src/nodes/patterns/ObjectPattern.ts
@@ -0,0 +1,85 @@
+import { Parser } from "@parser/parser";
+
+import { Expression } from "../expressions/Expression";
+import { Identifier } from "../identifier/Identifier";
+
+import { ArrayPattern } from "./ArrayPattern";
+
+export interface PatternAssignment {
+ type: "AssignmentPattern";
+ left: Identifier | ObjectPattern | ArrayPattern;
+ right: Expression;
+}
+
+export interface PatternRest {
+ type: "RestElement";
+ argument: Identifier;
+}
+
+export interface ObjectPatternProperty {
+ type: "ObjectProperty";
+ key: Identifier;
+ value: Identifier | PatternAssignment | ObjectPattern | ArrayPattern;
+ shorthand: boolean;
+ computed: boolean;
+}
+
+export class ObjectPattern {
+ type = "ObjectPattern";
+
+ properties: Array = [];
+
+ constructor(parser: Parser) {
+ parser.eat("{");
+
+ while (parser.lookahead?.type !== "}") {
+ if (parser.lookahead?.type === "...") {
+ parser.eat("...");
+ const argument = new Identifier(parser);
+ this.properties.push({ type: "RestElement", argument });
+
+ if (parser.lookahead?.type !== "}") {
+ throw new SyntaxError(`Rest element phải ở vị trí cuối trong object destructuring`);
+ }
+ break;
+ }
+
+ const key = new Identifier(parser);
+ let value: Identifier | PatternAssignment | ObjectPattern | ArrayPattern = key;
+ let shorthand = true;
+
+ if (parser.lookahead?.type === ":") {
+ parser.eat(":");
+ const next = (parser.lookahead as { type: string } | null)?.type;
+ if (next === "{") {
+ value = new ObjectPattern(parser);
+ } else if (next === "[") {
+ value = new ArrayPattern(parser);
+ } else {
+ value = new Identifier(parser);
+ }
+ shorthand = false;
+ }
+
+ if (parser.lookahead?.type === "=") {
+ parser.eat("=");
+ const right = new Expression(parser);
+ value = { type: "AssignmentPattern", left: value as Identifier | ObjectPattern, right };
+ }
+
+ this.properties.push({
+ type: "ObjectProperty",
+ key,
+ value,
+ shorthand,
+ computed: false,
+ });
+
+ if (parser.lookahead?.type !== "}") {
+ parser.eat(",");
+ }
+ }
+
+ parser.eat("}");
+ }
+}
diff --git a/packages/parser/src/nodes/patterns/__test__/array-pattern.test.ts b/packages/parser/src/nodes/patterns/__test__/array-pattern.test.ts
new file mode 100644
index 0000000..23273db
--- /dev/null
+++ b/packages/parser/src/nodes/patterns/__test__/array-pattern.test.ts
@@ -0,0 +1,139 @@
+import { VariableDeclaration } from "@parser/nodes/declarations/VariableDeclaration";
+import { FunctionDeclaration } from "@parser/nodes/declarations/FunctionDeclaration";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("array-pattern.test", () => {
+ it("should parse simple array destructuring", () => {
+ const result = parser.parse(`khai báo [a, b] = arr`, VariableDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ declarations: [
+ {
+ id: {
+ type: "ArrayPattern",
+ elements: [
+ { type: "Identifier", name: "a" },
+ { type: "Identifier", name: "b" },
+ ],
+ },
+ },
+ ],
+ });
+ });
+
+ it("should parse hole in array destructuring", () => {
+ const result = parser.parse(`khai báo [, b] = arr`, VariableDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ declarations: [
+ {
+ id: {
+ type: "ArrayPattern",
+ elements: [null, { type: "Identifier", name: "b" }],
+ },
+ },
+ ],
+ });
+ });
+
+ it("should parse default in array destructuring", () => {
+ const result = parser.parse(`khai báo [a = 1] = arr`, VariableDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ declarations: [
+ {
+ id: {
+ elements: [
+ {
+ type: "AssignmentPattern",
+ left: { type: "Identifier", name: "a" },
+ right: { type: "NumericLiteral", value: 1 },
+ },
+ ],
+ },
+ },
+ ],
+ });
+ });
+
+ it("should parse rest in array destructuring", () => {
+ const result = parser.parse(`khai báo [a, ...rest] = arr`, VariableDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ declarations: [
+ {
+ id: {
+ elements: [
+ { type: "Identifier", name: "a" },
+ {
+ type: "RestElement",
+ argument: { type: "Identifier", name: "rest" },
+ },
+ ],
+ },
+ },
+ ],
+ });
+ });
+
+ it("should parse nested array pattern", () => {
+ const result = parser.parse(`khai báo [a, [b, c]] = arr`, VariableDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ declarations: [
+ {
+ id: {
+ elements: [
+ { type: "Identifier", name: "a" },
+ {
+ type: "ArrayPattern",
+ elements: [
+ { type: "Identifier", name: "b" },
+ { type: "Identifier", name: "c" },
+ ],
+ },
+ ],
+ },
+ },
+ ],
+ });
+ });
+
+ it("should parse mixed with object pattern", () => {
+ const result = parser.parse(`khai báo [{a}, b] = arr`, VariableDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ declarations: [
+ {
+ id: {
+ elements: [
+ {
+ type: "ObjectPattern",
+ properties: [{ type: "ObjectProperty" }],
+ },
+ { type: "Identifier", name: "b" },
+ ],
+ },
+ },
+ ],
+ });
+ });
+
+ it("should parse array destructuring in function param", () => {
+ const result = parser.parse(`hàm f([a, b]) {}`, FunctionDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ params: [
+ {
+ type: "ArrayPattern",
+ elements: [
+ { type: "Identifier", name: "a" },
+ { type: "Identifier", name: "b" },
+ ],
+ },
+ ],
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/patterns/__test__/generator/__snapshots__/generator-array-pattern.test.ts.snap b/packages/parser/src/nodes/patterns/__test__/generator/__snapshots__/generator-array-pattern.test.ts.snap
new file mode 100644
index 0000000..7c956a1
--- /dev/null
+++ b/packages/parser/src/nodes/patterns/__test__/generator/__snapshots__/generator-array-pattern.test.ts.snap
@@ -0,0 +1,11 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`generator-array-pattern.test > should generate array pattern in param 1`] = `"function f([a, b]) {}"`;
+
+exports[`generator-array-pattern.test > should generate default array destructuring 1`] = `"var [a = 1] = arr;"`;
+
+exports[`generator-array-pattern.test > should generate nested array pattern 1`] = `"var [a, [b, c]] = arr;"`;
+
+exports[`generator-array-pattern.test > should generate rest array destructuring 1`] = `"var [a, ...rest] = arr;"`;
+
+exports[`generator-array-pattern.test > should generate simple array destructuring 1`] = `"var [a, b] = arr;"`;
diff --git a/packages/parser/src/nodes/patterns/__test__/generator/__snapshots__/generator-object-pattern.test.ts.snap b/packages/parser/src/nodes/patterns/__test__/generator/__snapshots__/generator-object-pattern.test.ts.snap
new file mode 100644
index 0000000..9110fff
--- /dev/null
+++ b/packages/parser/src/nodes/patterns/__test__/generator/__snapshots__/generator-object-pattern.test.ts.snap
@@ -0,0 +1,34 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`generator-object-pattern.test > should generate default destructuring 1`] = `
+"var {
+ a = 1
+} = o;"
+`;
+
+exports[`generator-object-pattern.test > should generate destructuring in function param 1`] = `
+"function f({
+ a,
+ b
+}) {}"
+`;
+
+exports[`generator-object-pattern.test > should generate rename destructuring 1`] = `
+"var {
+ a: b
+} = o;"
+`;
+
+exports[`generator-object-pattern.test > should generate rest destructuring 1`] = `
+"var {
+ a,
+ ...rest
+} = o;"
+`;
+
+exports[`generator-object-pattern.test > should generate shorthand destructuring 1`] = `
+"var {
+ a,
+ b
+} = o;"
+`;
diff --git a/packages/parser/src/nodes/patterns/__test__/generator/generator-array-pattern.test.ts b/packages/parser/src/nodes/patterns/__test__/generator/generator-array-pattern.test.ts
new file mode 100644
index 0000000..051d717
--- /dev/null
+++ b/packages/parser/src/nodes/patterns/__test__/generator/generator-array-pattern.test.ts
@@ -0,0 +1,33 @@
+import generate from "@babel/generator";
+
+import { VariableDeclaration } from "@parser/nodes/declarations/VariableDeclaration";
+import { FunctionDeclaration } from "@parser/nodes/declarations/FunctionDeclaration";
+
+import parser from "../../../../setup-test";
+
+describe("generator-array-pattern.test", () => {
+ it("should generate simple array destructuring", () => {
+ const ast = parser.parse(`khai báo [a, b] = arr`, VariableDeclaration);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate default array destructuring", () => {
+ const ast = parser.parse(`khai báo [a = 1] = arr`, VariableDeclaration);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate rest array destructuring", () => {
+ const ast = parser.parse(`khai báo [a, ...rest] = arr`, VariableDeclaration);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate nested array pattern", () => {
+ const ast = parser.parse(`khai báo [a, [b, c]] = arr`, VariableDeclaration);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate array pattern in param", () => {
+ const ast = parser.parse(`hàm f([a, b]) {}`, FunctionDeclaration);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+});
diff --git a/packages/parser/src/nodes/patterns/__test__/generator/generator-object-pattern.test.ts b/packages/parser/src/nodes/patterns/__test__/generator/generator-object-pattern.test.ts
new file mode 100644
index 0000000..2ed7e3e
--- /dev/null
+++ b/packages/parser/src/nodes/patterns/__test__/generator/generator-object-pattern.test.ts
@@ -0,0 +1,38 @@
+import generate from "@babel/generator";
+
+import { VariableDeclaration } from "@parser/nodes/declarations/VariableDeclaration";
+import { FunctionDeclaration } from "@parser/nodes/declarations/FunctionDeclaration";
+
+import parser from "../../../../setup-test";
+
+describe("generator-object-pattern.test", () => {
+ it("should generate shorthand destructuring", () => {
+ const ast = parser.parse(`khai báo { a, b } = o`, VariableDeclaration);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+
+ it("should generate rename destructuring", () => {
+ const ast = parser.parse(`khai báo { a: b } = o`, VariableDeclaration);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+
+ it("should generate default destructuring", () => {
+ const ast = parser.parse(`khai báo { a = 1 } = o`, VariableDeclaration);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+
+ it("should generate rest destructuring", () => {
+ const ast = parser.parse(`khai báo { a, ...rest } = o`, VariableDeclaration);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+
+ it("should generate destructuring in function param", () => {
+ const ast = parser.parse(`hàm f({ a, b }) {}`, FunctionDeclaration);
+ const result = generate(ast);
+ expect(result.code).toMatchSnapshot();
+ });
+});
diff --git a/packages/parser/src/nodes/patterns/__test__/object-pattern.test.ts b/packages/parser/src/nodes/patterns/__test__/object-pattern.test.ts
new file mode 100644
index 0000000..8aed3cf
--- /dev/null
+++ b/packages/parser/src/nodes/patterns/__test__/object-pattern.test.ts
@@ -0,0 +1,139 @@
+import { VariableDeclaration } from "@parser/nodes/declarations/VariableDeclaration";
+import { FunctionDeclaration } from "@parser/nodes/declarations/FunctionDeclaration";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("object-pattern.test", () => {
+ it("should parse shorthand destructuring", () => {
+ const result = parser.parse(`khai báo { a, b } = o`, VariableDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "VariableDeclaration",
+ declarations: [
+ {
+ id: {
+ type: "ObjectPattern",
+ properties: [
+ {
+ type: "ObjectProperty",
+ shorthand: true,
+ key: { type: "Identifier", name: "a" },
+ value: { type: "Identifier", name: "a" },
+ },
+ {
+ type: "ObjectProperty",
+ shorthand: true,
+ key: { type: "Identifier", name: "b" },
+ value: { type: "Identifier", name: "b" },
+ },
+ ],
+ },
+ init: { type: "Identifier", name: "o" },
+ },
+ ],
+ });
+ });
+
+ it("should parse rename destructuring", () => {
+ const result = parser.parse(`khai báo { a: b } = o`, VariableDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ declarations: [
+ {
+ id: {
+ type: "ObjectPattern",
+ properties: [
+ {
+ type: "ObjectProperty",
+ shorthand: false,
+ key: { type: "Identifier", name: "a" },
+ value: { type: "Identifier", name: "b" },
+ },
+ ],
+ },
+ },
+ ],
+ });
+ });
+
+ it("should parse default in destructuring", () => {
+ const result = parser.parse(`khai báo { a = 1 } = o`, VariableDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ declarations: [
+ {
+ id: {
+ properties: [
+ {
+ value: {
+ type: "AssignmentPattern",
+ left: { type: "Identifier", name: "a" },
+ right: { type: "NumericLiteral", value: 1 },
+ },
+ },
+ ],
+ },
+ },
+ ],
+ });
+ });
+
+ it("should parse rest in destructuring", () => {
+ const result = parser.parse(`khai báo { a, ...rest } = o`, VariableDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ declarations: [
+ {
+ id: {
+ properties: [
+ { type: "ObjectProperty" },
+ {
+ type: "RestElement",
+ argument: { type: "Identifier", name: "rest" },
+ },
+ ],
+ },
+ },
+ ],
+ });
+ });
+
+ it("should parse nested object pattern", () => {
+ const result = parser.parse(`khai báo { a: { b } } = o`, VariableDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ declarations: [
+ {
+ id: {
+ properties: [
+ {
+ value: {
+ type: "ObjectPattern",
+ properties: [
+ {
+ key: { type: "Identifier", name: "b" },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
+ ],
+ });
+ });
+
+ it("should parse destructuring in function param", () => {
+ const result = parser.parse(`hàm f({ a, b }) {}`, FunctionDeclaration);
+
+ expect(toPlainObject(result)).toMatchObject({
+ params: [
+ {
+ type: "ObjectPattern",
+ properties: [{ type: "ObjectProperty" }, { type: "ObjectProperty" }],
+ },
+ ],
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/statements/BreakStatement.ts b/packages/parser/src/nodes/statements/BreakStatement.ts
index 1fbd0a8..009d96e 100644
--- a/packages/parser/src/nodes/statements/BreakStatement.ts
+++ b/packages/parser/src/nodes/statements/BreakStatement.ts
@@ -6,17 +6,13 @@ import { Identifier } from "../identifier/Identifier";
export class BreakStatement {
type = "BreakStatement";
- label: null | string;
+ label: Identifier | null = null;
constructor(parser: Parser) {
parser.eat(Keyword.BREAK);
- let label: BreakStatement["label"] = null;
-
if (parser.lookahead?.type === Keyword.IDENTIFIER) {
- label = new Identifier(parser).name;
+ this.label = new Identifier(parser);
}
-
- this.label = label;
}
}
diff --git a/packages/parser/src/nodes/statements/ContinueStatement.ts b/packages/parser/src/nodes/statements/ContinueStatement.ts
index 6fd95af..f590699 100644
--- a/packages/parser/src/nodes/statements/ContinueStatement.ts
+++ b/packages/parser/src/nodes/statements/ContinueStatement.ts
@@ -6,17 +6,13 @@ import { Identifier } from "../identifier/Identifier";
export class ContinueStatement {
type = "ContinueStatement";
- label: null | string;
+ label: Identifier | null = null;
constructor(parser: Parser) {
- parser.eat(Keyword.BREAK);
-
- let label: ContinueStatement["label"] = null;
+ parser.eat(Keyword.CONTINUE);
if (parser.lookahead?.type === Keyword.IDENTIFIER) {
- label = new Identifier(parser).name;
+ this.label = new Identifier(parser);
}
-
- this.label = label;
}
}
diff --git a/packages/parser/src/nodes/statements/ExpressionStatement.ts b/packages/parser/src/nodes/statements/ExpressionStatement.ts
index 3c6abdc..247c4e2 100644
--- a/packages/parser/src/nodes/statements/ExpressionStatement.ts
+++ b/packages/parser/src/nodes/statements/ExpressionStatement.ts
@@ -2,12 +2,18 @@ import { Parser } from "@parser/parser";
import { Expression } from "../expressions/Expression";
-export class ExpressionStatement {
- type = "ExpressionStatement";
+const STATEMENT_TYPES = new Set(["LabeledStatement"]);
- expression: Expression;
+export class ExpressionStatement {
+ [key: string]: any;
constructor(parser: Parser) {
- this.expression = new Expression(parser);
+ const expr = new Expression(parser);
+ if (STATEMENT_TYPES.has((expr as any).type)) {
+ Object.assign(this, expr);
+ return;
+ }
+ this.type = "ExpressionStatement";
+ this.expression = expr;
}
}
diff --git a/packages/parser/src/nodes/statements/LabelledStatement.ts b/packages/parser/src/nodes/statements/LabelledStatement.ts
index 2115185..388ec9a 100644
--- a/packages/parser/src/nodes/statements/LabelledStatement.ts
+++ b/packages/parser/src/nodes/statements/LabelledStatement.ts
@@ -2,20 +2,21 @@ import { Parser } from "@parser/parser";
import { Identifier } from "../identifier/Identifier";
-import { IterationStatement } from "./breakable/iteration/IterationStatement";
+import { Statement } from "./Statement";
+import { BlockStatement } from "./BlockStatement";
export class LabelledStatement {
- type = "LabelledStatement";
+ type = "LabeledStatement";
label: Identifier;
- body: IterationStatement;
+ body: Statement | BlockStatement;
constructor(parser: Parser, identifier: Identifier) {
this.label = identifier;
parser.eat(":");
- this.body = new IterationStatement(parser);
+ this.body = parser.lookahead?.type === "{" ? new BlockStatement(parser) : new Statement(parser);
}
}
diff --git a/packages/parser/src/nodes/statements/StatementList.ts b/packages/parser/src/nodes/statements/StatementList.ts
index 6985654..5650de1 100644
--- a/packages/parser/src/nodes/statements/StatementList.ts
+++ b/packages/parser/src/nodes/statements/StatementList.ts
@@ -1,5 +1,7 @@
import { Parser } from "@parser/parser";
+import { VietScriptError } from "../../errors";
+
import { EmptyStatement } from "./EmptyStatement";
import { StatementListItem } from "./StatementListItem";
@@ -13,18 +15,33 @@ export class StatementList {
parser.tokenizer.isEOF() === false &&
!stopTokens?.includes(String(parser.lookahead?.type))
) {
+ if (parser.lookahead?.type === ";") {
+ statements.push(new EmptyStatement(parser));
+ continue;
+ }
+
+ const beforeToken = parser.lookahead;
const statement = new StatementListItem(parser);
- if (statement !== undefined) {
+ if (parser.lookahead === beforeToken && parser.lookahead !== null) {
+ throw new VietScriptError(
+ `Unexpected token: "${parser.lookahead.value}" không thể bắt đầu một câu lệnh`,
+ {
+ file: parser.filename,
+ source: parser.syntax,
+ offset: parser.lookahead.start,
+ },
+ );
+ }
+
+ if (statement !== undefined && Object.keys(statement).length > 0) {
statements.push(statement);
}
- // Eat the first ";" after every statement
if (parser.lookahead?.type === ";") {
parser.eat(";");
}
- // The rest would be empty statements
while (parser.lookahead?.type === ";") {
statements.push(new EmptyStatement(parser));
}
diff --git a/packages/parser/src/nodes/statements/StatementListItem.ts b/packages/parser/src/nodes/statements/StatementListItem.ts
index e34d87c..b45a79d 100644
--- a/packages/parser/src/nodes/statements/StatementListItem.ts
+++ b/packages/parser/src/nodes/statements/StatementListItem.ts
@@ -15,10 +15,13 @@ export class StatementListItem {
case Keyword.CONST:
case Keyword.ASYNC:
case Keyword.FUNCTION:
- case Keyword.CLASS: {
+ case Keyword.CLASS:
+ case Keyword.IMPORT:
+ case Keyword.EXPORT: {
Object.assign(this, new Declaration(parser));
break;
}
+ case "{":
case Keyword.IF:
case Keyword.DO:
case Keyword.WHILE:
@@ -31,7 +34,19 @@ export class StatementListItem {
case Keyword.IDENTIFIER:
case Keyword.THROW:
case Keyword.TRY:
- case Keyword.DEBUGGER: {
+ case Keyword.DEBUGGER:
+ case Keyword.YIELD:
+ case Keyword.AWAIT:
+ case Keyword.THIS:
+ case Keyword.NUMBER:
+ case Keyword.STRING:
+ case Keyword.BOOLEAN:
+ case Keyword.NULL:
+ case Keyword.NAN:
+ case Keyword.INFINITY:
+ case Keyword.UNDEFINED:
+ case Keyword.SUPER:
+ case Keyword.NEW: {
Object.assign(this, new Statement(parser));
break;
}
diff --git a/packages/parser/src/nodes/statements/WithStatement.ts b/packages/parser/src/nodes/statements/WithStatement.ts
index 4d08026..1b2f827 100644
--- a/packages/parser/src/nodes/statements/WithStatement.ts
+++ b/packages/parser/src/nodes/statements/WithStatement.ts
@@ -1,10 +1,23 @@
import { Keyword } from "@vietscript/shared";
import { Parser } from "@parser/parser";
+import { Expression } from "../expressions/Expression";
+
+import { BlockStatement } from "./BlockStatement";
+import { Statement } from "./Statement";
+
export class WithStatement {
type = "WithStatement";
+ object: Expression;
+
+ body: Statement | BlockStatement;
+
constructor(parser: Parser) {
parser.eat(Keyword.WITH);
+ parser.eat("(");
+ this.object = new Expression(parser);
+ parser.eat(")");
+ this.body = parser.lookahead?.type === "{" ? new BlockStatement(parser) : new Statement(parser);
}
}
diff --git a/packages/parser/src/nodes/statements/__test__/statement-all.test.ts b/packages/parser/src/nodes/statements/__test__/statement-all.test.ts
new file mode 100644
index 0000000..fd0cb39
--- /dev/null
+++ b/packages/parser/src/nodes/statements/__test__/statement-all.test.ts
@@ -0,0 +1,43 @@
+import { Program } from "@parser/nodes/Program";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("statement-all.test", () => {
+ it("should parse throw", () => {
+ const result = parser.parse(`báo lỗi new Error("x")`, Program);
+ const plain = toPlainObject(result) as any;
+ expect(plain.body[0].type).toBe("ThrowStatement");
+ });
+
+ it("should parse return", () => {
+ const result = parser.parse(`hàm f() { trả về 42 }`, Program);
+ const plain = toPlainObject(result) as any;
+ expect(plain.body[0].body.body[0].type).toBe("ReturnStatement");
+ });
+
+ it("should parse return no arg", () => {
+ const result = parser.parse(`hàm f() { trả về }`, Program);
+ const plain = toPlainObject(result) as any;
+ expect(plain.body[0].body.body[0].type).toBe("ReturnStatement");
+ });
+
+ it("should parse break", () => {
+ const result = parser.parse(`lặp (biến i = 0; i < 10; i++) { phá vòng lặp }`, Program);
+ const plain = toPlainObject(result) as any;
+ expect(plain.body[0].body.body[0].type).toBe("BreakStatement");
+ });
+
+ it("should parse continue", () => {
+ const result = parser.parse(`lặp (biến i = 0; i < 10; i++) { tiếp tục }`, Program);
+ const plain = toPlainObject(result) as any;
+ expect(plain.body[0].body.body[0].type).toBe("ContinueStatement");
+ });
+
+ it("should parse nested blocks", () => {
+ const result = parser.parse(`{ { hằng số x = 1 } }`, Program);
+ const plain = toPlainObject(result) as any;
+ expect(plain.body[0].type).toBe("BlockStatement");
+ expect(plain.body[0].body[0].type).toBe("BlockStatement");
+ });
+});
diff --git a/packages/parser/src/nodes/statements/__test__/statement-if.test.ts b/packages/parser/src/nodes/statements/__test__/statement-if.test.ts
index f763bd1..4eefb06 100644
--- a/packages/parser/src/nodes/statements/__test__/statement-if.test.ts
+++ b/packages/parser/src/nodes/statements/__test__/statement-if.test.ts
@@ -22,7 +22,7 @@ describe("statement-if.test", () => {
type: "IfStatement",
test: {
type: "Identifier",
- name: "_273i7873u_ki7879n_m7897t",
+ name: "điều_kiện_một",
},
consequent: {
type: "BlockStatement",
@@ -34,12 +34,9 @@ describe("statement-if.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_g236_273243",
- },
- init: {
- type: "Identifier",
- name: "undefined",
+ name: "gì_đó",
},
+ init: null,
},
],
kind: "var",
@@ -51,7 +48,7 @@ describe("statement-if.test", () => {
type: "IfStatement",
test: {
type: "Identifier",
- name: "_273i7873u_ki7879n_hai",
+ name: "điều_kiện_hai",
},
consequent: {
type: "BlockStatement",
@@ -63,12 +60,9 @@ describe("statement-if.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_g236_273243_kh225c",
- },
- init: {
- type: "Identifier",
- name: "undefined",
+ name: "gì_đó_khác",
},
+ init: null,
},
],
kind: "var",
@@ -86,12 +80,9 @@ describe("statement-if.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_g236_273243_kh225c_n7919a",
- },
- init: {
- type: "Identifier",
- name: "undefined",
+ name: "gì_đó_khác_nữa",
},
+ init: null,
},
],
kind: "var",
diff --git a/packages/parser/src/nodes/statements/__test__/statement-labelled.test.ts b/packages/parser/src/nodes/statements/__test__/statement-labelled.test.ts
new file mode 100644
index 0000000..7ad3863
--- /dev/null
+++ b/packages/parser/src/nodes/statements/__test__/statement-labelled.test.ts
@@ -0,0 +1,53 @@
+import { Program } from "@parser/nodes/Program";
+import { BreakStatement } from "@parser/nodes/statements/BreakStatement";
+import { ContinueStatement } from "@parser/nodes/statements/ContinueStatement";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("statement-labelled.test", () => {
+ it("should parse labelled for loop", () => {
+ const result = parser.parse(
+ `outer: lặp (khai báo i = 0; i < 3; i++) {}`,
+ Program,
+ );
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "Program",
+ body: [
+ {
+ type: "LabeledStatement",
+ label: { type: "Identifier", name: "outer" },
+ body: { type: "ForStatement" },
+ },
+ ],
+ });
+ });
+
+ it("should parse break with label", () => {
+ const result = parser.parse(`phá vòng lặp outer`, BreakStatement);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "BreakStatement",
+ label: { type: "Identifier", name: "outer" },
+ });
+ });
+
+ it("should parse break without label", () => {
+ const result = parser.parse(`phá vòng lặp`, BreakStatement);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "BreakStatement",
+ label: null,
+ });
+ });
+
+ it("should parse continue with label", () => {
+ const result = parser.parse(`tiếp tục outer`, ContinueStatement);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ContinueStatement",
+ label: { type: "Identifier", name: "outer" },
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/statements/__test__/statement-misc.test.ts b/packages/parser/src/nodes/statements/__test__/statement-misc.test.ts
new file mode 100644
index 0000000..04bf352
--- /dev/null
+++ b/packages/parser/src/nodes/statements/__test__/statement-misc.test.ts
@@ -0,0 +1,48 @@
+import { Program } from "@parser/nodes/Program";
+import { DebuggerStatement } from "@parser/nodes/statements/DebuggerStatement";
+import { WithStatement } from "@parser/nodes/statements/WithStatement";
+import { EmptyStatement } from "@parser/nodes/statements/EmptyStatement";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("statement-misc.test", () => {
+ it("should parse debugger statement", () => {
+ const result = parser.parse(`debugger`, DebuggerStatement);
+ expect(toPlainObject(result)).toMatchObject({ type: "DebuggerStatement" });
+ });
+
+ it("should parse with statement (English)", () => {
+ const result = parser.parse(`with (obj) {}`, WithStatement);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "WithStatement",
+ object: { type: "Identifier", name: "obj" },
+ body: { type: "BlockStatement" },
+ });
+ });
+
+ it("should parse empty statement", () => {
+ const result = parser.parse(`;`, EmptyStatement);
+ expect(toPlainObject(result)).toMatchObject({ type: "EmptyStatement" });
+ });
+
+ it("should parse multiple empty statements in program", () => {
+ const result = parser.parse(`;;;`, Program);
+ const plain = toPlainObject(result) as any;
+ expect(plain.body.length).toBeGreaterThan(0);
+ });
+
+ it("should skip single-line comment", () => {
+ const result = parser.parse(`// this is a comment\nhằng số x = 1`, Program);
+ const plain = toPlainObject(result) as any;
+ expect(plain.body).toHaveLength(1);
+ expect(plain.body[0].type).toBe("VariableDeclaration");
+ });
+
+ it("should skip multi-line comment", () => {
+ const result = parser.parse(`/* multi\nline\ncomment */\nhằng số x = 1`, Program);
+ const plain = toPlainObject(result) as any;
+ expect(plain.body).toHaveLength(1);
+ expect(plain.body[0].type).toBe("VariableDeclaration");
+ });
+});
diff --git a/packages/parser/src/nodes/statements/__test__/statement-while.test.ts b/packages/parser/src/nodes/statements/__test__/statement-while.test.ts
new file mode 100644
index 0000000..58bf522
--- /dev/null
+++ b/packages/parser/src/nodes/statements/__test__/statement-while.test.ts
@@ -0,0 +1,39 @@
+import { WhileStatement } from "@parser/nodes/statements/breakable/iteration/WhileStatement";
+import { DoWhileStatement } from "@parser/nodes/statements/breakable/iteration/DoWhileStatement";
+
+import parser from "../../../setup-test";
+import toPlainObject from "../../../toPlainObject";
+
+describe("statement-while.test", () => {
+ it("should parse while with English keyword", () => {
+ const result = parser.parse(`while (a < 10) { a++ }`, WhileStatement);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "WhileStatement",
+ test: { type: "BinaryExpression", operator: "<" },
+ body: { type: "BlockStatement" },
+ });
+ });
+
+ it("should parse while with Vietnamese keyword", () => {
+ const result = parser.parse(`khi mà (a < 10) { a++ }`, WhileStatement);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "WhileStatement",
+ });
+ });
+
+ it("should parse do-while with English keyword", () => {
+ const result = parser.parse(`do { a++ } while (a < 10)`, DoWhileStatement);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "DoWhileStatement",
+ test: { type: "BinaryExpression" },
+ body: { type: "BlockStatement" },
+ });
+ });
+
+ it("should parse do-while with Vietnamese keyword", () => {
+ const result = parser.parse(`thực hiện { a++ } khi mà (a < 10)`, DoWhileStatement);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "DoWhileStatement",
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/statements/breakable/__test__/generator/__snapshots__/generator-statement-for.test.ts.snap b/packages/parser/src/nodes/statements/breakable/__test__/generator/__snapshots__/generator-statement-for.test.ts.snap
new file mode 100644
index 0000000..742d72e
--- /dev/null
+++ b/packages/parser/src/nodes/statements/breakable/__test__/generator/__snapshots__/generator-statement-for.test.ts.snap
@@ -0,0 +1,9 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`generator-statement-for.test > should generate for-await-of 1`] = `"for await (let x of arr) {}"`;
+
+exports[`generator-statement-for.test > should generate for-in 1`] = `"for (let k in obj) {}"`;
+
+exports[`generator-statement-for.test > should generate for-of 1`] = `"for (let x of arr) {}"`;
+
+exports[`generator-statement-for.test > should generate traditional for 1`] = `"for (var i = 0; i < 10; i++) {}"`;
diff --git a/packages/parser/src/nodes/statements/breakable/__test__/generator/generator-statement-for.test.ts b/packages/parser/src/nodes/statements/breakable/__test__/generator/generator-statement-for.test.ts
new file mode 100644
index 0000000..6192389
--- /dev/null
+++ b/packages/parser/src/nodes/statements/breakable/__test__/generator/generator-statement-for.test.ts
@@ -0,0 +1,27 @@
+import generate from "@babel/generator";
+
+import { ForStatement } from "@parser/nodes/statements/breakable/iteration/ForStatement";
+
+import parser from "../../../../../setup-test";
+
+describe("generator-statement-for.test", () => {
+ it("should generate traditional for", () => {
+ const ast = parser.parse(`lặp (khai báo i = 0; i < 10; i++) {}`, ForStatement);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate for-of", () => {
+ const ast = parser.parse(`lặp (biến x của arr) {}`, ForStatement);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate for-in", () => {
+ const ast = parser.parse(`lặp (biến k trong obj) {}`, ForStatement);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+
+ it("should generate for-await-of", () => {
+ const ast = parser.parse(`lặp chờ (biến x của arr) {}`, ForStatement);
+ expect(generate(ast).code).toMatchSnapshot();
+ });
+});
diff --git a/packages/parser/src/nodes/statements/breakable/__test__/statement-breakable-switch.test.ts b/packages/parser/src/nodes/statements/breakable/__test__/statement-breakable-switch.test.ts
index d88dfb7..4c1d193 100644
--- a/packages/parser/src/nodes/statements/breakable/__test__/statement-breakable-switch.test.ts
+++ b/packages/parser/src/nodes/statements/breakable/__test__/statement-breakable-switch.test.ts
@@ -21,7 +21,7 @@ describe("statement-breakable-switch.test", () => {
type: "SwitchStatement",
discriminant: {
type: "Identifier",
- name: "_tu7893i_t225c",
+ name: "tuổi_tác",
},
cases: [
{
@@ -44,7 +44,7 @@ describe("statement-breakable-switch.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_xyz",
+ name: "xyz",
},
init: {
type: "NumericLiteral",
diff --git a/packages/parser/src/nodes/statements/breakable/__test__/statement-for.test.ts b/packages/parser/src/nodes/statements/breakable/__test__/statement-for.test.ts
new file mode 100644
index 0000000..cf2c967
--- /dev/null
+++ b/packages/parser/src/nodes/statements/breakable/__test__/statement-for.test.ts
@@ -0,0 +1,68 @@
+import { ForStatement } from "@parser/nodes/statements/breakable/iteration/ForStatement";
+
+import parser from "../../../../setup-test";
+import toPlainObject from "../../../../toPlainObject";
+
+describe("statement-for.test", () => {
+ it("should parse traditional for loop", () => {
+ const result = parser.parse(`lặp (khai báo i = 0; i < 10; i++) {}`, ForStatement);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ForStatement",
+ init: { type: "VariableDeclaration" },
+ body: { type: "BlockStatement" },
+ });
+ });
+
+ it("should parse for-of with let", () => {
+ const result = parser.parse(`lặp (biến x của arr) {}`, ForStatement);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ForOfStatement",
+ await: false,
+ left: {
+ type: "VariableDeclaration",
+ kind: "let",
+ },
+ right: { type: "Identifier", name: "arr" },
+ });
+ });
+
+ it("should parse for-in with let", () => {
+ const result = parser.parse(`lặp (biến k trong obj) {}`, ForStatement);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ForInStatement",
+ left: {
+ type: "VariableDeclaration",
+ kind: "let",
+ },
+ right: { type: "Identifier", name: "obj" },
+ });
+ });
+
+ it("should parse for-of with destructuring", () => {
+ const result = parser.parse(`lặp (hằng số { a } của arr) {}`, ForStatement);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ForOfStatement",
+ left: {
+ type: "VariableDeclaration",
+ declarations: [
+ {
+ id: { type: "ObjectPattern" },
+ },
+ ],
+ },
+ });
+ });
+
+ it("should parse for-await-of", () => {
+ const result = parser.parse(`lặp chờ (biến x của arr) {}`, ForStatement);
+
+ expect(toPlainObject(result)).toMatchObject({
+ type: "ForOfStatement",
+ await: true,
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/statements/breakable/iteration/ForStatement.ts b/packages/parser/src/nodes/statements/breakable/iteration/ForStatement.ts
index 4b3df61..50fa2ee 100644
--- a/packages/parser/src/nodes/statements/breakable/iteration/ForStatement.ts
+++ b/packages/parser/src/nodes/statements/breakable/iteration/ForStatement.ts
@@ -4,69 +4,98 @@ import { Statement } from "@parser/nodes/statements/Statement";
import { BlockStatement } from "@parser/nodes/statements/BlockStatement";
import { Expression } from "@parser/nodes/expressions/Expression";
import { Identifier } from "@parser/nodes/identifier/Identifier";
+import { ObjectPattern } from "@parser/nodes/patterns/ObjectPattern";
+import { ArrayPattern } from "@parser/nodes/patterns/ArrayPattern";
import { Keyword } from "@vietscript/shared";
-// import { ForInOfStatement } from "./ForInOfStatement";
+type ForInit = VariableDeclaration | Identifier | ObjectPattern | ArrayPattern | undefined;
export class ForStatement {
- type = "ForStatement";
-
- init: VariableDeclaration | Identifier;
-
- test: Expression;
-
- update: Expression;
-
- body: Statement | BlockStatement;
+ [key: string]: any;
constructor(parser: Parser) {
parser.eat(Keyword.FOR);
- let isAsync = false;
-
+ let isAwait = false;
if (parser.lookahead?.type === Keyword.AWAIT) {
parser.eat(Keyword.AWAIT);
- isAsync = true;
+ isAwait = true;
}
parser.eat("(");
- if (!isAsync && parser.lookahead?.type !== Keyword.IN && parser.lookahead?.type !== Keyword.OF) {
+ let left: ForInit;
+ if (parser.lookahead?.type !== ";") {
switch (parser.lookahead?.type) {
case Keyword.VAR:
- case Keyword.LET: {
- this.init = new VariableDeclaration(parser);
+ case Keyword.LET:
+ case Keyword.CONST: {
+ left = new VariableDeclaration(parser);
break;
}
- case Keyword.CONST: {
- throw new Error("Const declarations are not allowed in for loops");
+ case "{": {
+ left = new ObjectPattern(parser);
+ break;
}
- default: {
- // TODO: LexicalDeclaration instead of Identifier
- this.init = new Identifier(parser);
+ case "[": {
+ left = new ArrayPattern(parser);
break;
}
+ default: {
+ left = new Identifier(parser);
+ }
}
+ }
- parser.eat(";");
+ if (parser.lookahead?.type === Keyword.OF) {
+ parser.eat(Keyword.OF);
+ const right = new Expression(parser);
+ parser.eat(")");
+ const body = parser.lookahead?.type === "{" ? new BlockStatement(parser) : new Statement(parser);
+
+ Object.assign(this, {
+ type: "ForOfStatement",
+ left,
+ right,
+ body,
+ await: isAwait,
+ });
+ return;
+ }
- this.test = new Expression(parser);
+ if (parser.lookahead?.type === Keyword.IN) {
+ parser.eat(Keyword.IN);
+ const right = new Expression(parser);
+ parser.eat(")");
+ const body = parser.lookahead?.type === "{" ? new BlockStatement(parser) : new Statement(parser);
+
+ Object.assign(this, {
+ type: "ForInStatement",
+ left,
+ right,
+ body,
+ });
+ return;
+ }
- if (parser.lookahead?.type === ";") {
- parser.eat(";");
- }
+ parser.eat(";");
- if (parser.lookahead?.type !== ")") {
- this.update = new Expression(parser);
- }
- } else {
- // Contains only type, left and right as LeftHandSideExpression
- // Object.assign(this, new ForInOfStatement(parser, isAsync));
- throw new Error("For loop with in or of is not implemented");
- }
+ const test = parser.lookahead?.type === ";" ? null : new Expression(parser);
+
+ parser.eat(";");
+
+ const update = (parser.lookahead?.type as string) === ")" ? null : new Expression(parser);
parser.eat(")");
- this.body = parser.lookahead?.type === "{" ? new BlockStatement(parser) : new Statement(parser);
+ const body = parser.lookahead?.type === "{" ? new BlockStatement(parser) : new Statement(parser);
+
+ Object.assign(this, {
+ type: "ForStatement",
+ init: left,
+ test,
+ update,
+ body,
+ });
}
}
diff --git a/packages/parser/src/nodes/statements/try/__test__/catch-optional-binding.test.ts b/packages/parser/src/nodes/statements/try/__test__/catch-optional-binding.test.ts
new file mode 100644
index 0000000..0dae565
--- /dev/null
+++ b/packages/parser/src/nodes/statements/try/__test__/catch-optional-binding.test.ts
@@ -0,0 +1,25 @@
+import { TryStatement } from "@parser/nodes/statements/try/TryStatement";
+
+import parser from "../../../../setup-test";
+import toPlainObject from "../../../../toPlainObject";
+
+describe("catch-optional-binding.test", () => {
+ it("should parse try-catch with no binding", () => {
+ const result = parser.parse(`thử {} bắt lỗi {}`, TryStatement);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "TryStatement",
+ handler: { type: "CatchClause", param: null },
+ });
+ });
+
+ it("should parse try-catch with binding", () => {
+ const result = parser.parse(`thử {} bắt lỗi (e) {}`, TryStatement);
+ expect(toPlainObject(result)).toMatchObject({
+ type: "TryStatement",
+ handler: {
+ type: "CatchClause",
+ param: { type: "Identifier", name: "e" },
+ },
+ });
+ });
+});
diff --git a/packages/parser/src/nodes/statements/try/__test__/statement-try.test.ts b/packages/parser/src/nodes/statements/try/__test__/statement-try.test.ts
index fb7b323..6f6a57e 100644
--- a/packages/parser/src/nodes/statements/try/__test__/statement-try.test.ts
+++ b/packages/parser/src/nodes/statements/try/__test__/statement-try.test.ts
@@ -31,12 +31,9 @@ describe("statement-try.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_g236_273243",
- },
- init: {
- type: "Identifier",
- name: "undefined",
+ name: "gì_đó",
},
+ init: null,
},
],
kind: "var",
@@ -56,12 +53,9 @@ describe("statement-try.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_g236_273243_kh225c",
- },
- init: {
- type: "Identifier",
- name: "undefined",
+ name: "gì_đó_khác",
},
+ init: null,
},
],
kind: "var",
@@ -70,7 +64,7 @@ describe("statement-try.test", () => {
},
param: {
type: "Identifier",
- name: "_l7895i",
+ name: "lỗi",
},
},
finalizer: {
@@ -84,12 +78,9 @@ describe("statement-try.test", () => {
type: "VariableDeclarator",
id: {
type: "Identifier",
- name: "_g236_273243_kh225c_n7919a",
- },
- init: {
- type: "Identifier",
- name: "undefined",
+ name: "gì_đó_khác_nữa",
},
+ init: null,
},
],
kind: "var",
diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts
index 0350cef..4f8b11b 100644
--- a/packages/parser/src/parser.ts
+++ b/packages/parser/src/parser.ts
@@ -2,6 +2,7 @@ import { Keyword, Token } from "@vietscript/shared";
import { Tokenizer } from "./tokenizer";
import { Program } from "./nodes/Program";
+import { VietScriptError } from "./errors";
export class Parser {
public syntax: string;
@@ -10,9 +11,10 @@ export class Parser {
public lookahead: Token | null;
- /**
- * Initializes the parser.
- */
+ public filename?: string;
+
+ public ternaryDepth = 0;
+
constructor() {
this.syntax = "";
this.tokenizer = new Tokenizer(this);
@@ -21,9 +23,6 @@ export class Parser {
public parse(syntax: string, InitAtsNodeClass?: any): any;
- /**
- * Parse a Formkl syntax string into Formkl object
- */
public parse(syntax: string) {
this.lookahead = null;
this.syntax = "";
@@ -36,27 +35,33 @@ export class Parser {
return new Program(this);
}
- /**
- * Expects a token of a given type.
- */
eat(tokenType: Token["type"]) {
const token = this.lookahead;
if (token === null) {
- throw new SyntaxError(`Unexpected end of input, expected: "${tokenType}"`);
+ throw new VietScriptError(`Unexpected end of input, expected: "${tokenType}"`, {
+ file: this.filename,
+ source: this.syntax,
+ offset: this.syntax.length,
+ });
}
if (token.type !== tokenType) {
- switch (tokenType) {
- case Keyword.IDENTIFIER: {
- throw new SyntaxError(
- `Unexpected token: "${token.value}", cannot use keyword "${token.value}" for the beginning of the identifer`,
- );
- }
- default: {
- throw new SyntaxError(`Unexpected token: "${token.value}", expected: "${tokenType}"`);
- }
+ if (tokenType === Keyword.IDENTIFIER) {
+ throw new VietScriptError(
+ `Unexpected token: "${token.value}", cannot use keyword "${token.value}" for the beginning of the identifer`,
+ {
+ file: this.filename,
+ source: this.syntax,
+ offset: token.start,
+ },
+ );
}
+ throw new VietScriptError(`Unexpected token: "${token.value}", expected: "${tokenType}"`, {
+ file: this.filename,
+ source: this.syntax,
+ offset: token.start,
+ });
}
this.lookahead = this.tokenizer.getNextToken();
diff --git a/packages/parser/src/tokenizer.ts b/packages/parser/src/tokenizer.ts
index bf1bc7c..a286e27 100644
--- a/packages/parser/src/tokenizer.ts
+++ b/packages/parser/src/tokenizer.ts
@@ -1,66 +1,154 @@
-import { Token } from "@vietscript/shared";
+import { Keyword, Token } from "@vietscript/shared";
import { Specs } from "./constants/specs";
import { Parser } from "./parser";
-/**
- * Tokenizer spec.
- */
+const REGEX_PRECEDING_TOKENS = new Set([
+ "(",
+ "[",
+ "{",
+ ",",
+ ";",
+ ":",
+ "=",
+ "!",
+ "?",
+ "+",
+ "-",
+ "*",
+ "/",
+ "%",
+ "&&",
+ "||",
+ "??",
+ "=>",
+ "==",
+ "===",
+ "!=",
+ "!==",
+ "<",
+ ">",
+ "<=",
+ ">=",
+ "&",
+ "|",
+ "^",
+ "~",
+ "<<",
+ ">>",
+ ">>>",
+ "+=",
+ "-=",
+ "*=",
+ "/=",
+ "%=",
+ "**=",
+ "&=",
+ "|=",
+ "^=",
+ "<<=",
+ ">>=",
+ ">>>=",
+ "&&=",
+ "||=",
+ "??=",
+ "...",
+ Keyword.RETURN,
+ Keyword.YIELD,
+ Keyword.AWAIT,
+ Keyword.TYPEOF,
+ Keyword.VOID,
+ Keyword.DELETE,
+ Keyword.NEW,
+ Keyword.THROW,
+ Keyword.IN,
+ Keyword.OF,
+ Keyword.INSTANCEOF,
+ Keyword.CASE,
+ Keyword.DEFAULT,
+]);
-/**
- * Tokenizer class
- * Lazily pulls a token from a stream.
- */
export class Tokenizer {
private parser: Parser;
private cursor: number;
- /**
- * Initializes the string.
- */
+ private lastTokenType: string | null = null;
+
constructor(parser: Parser) {
this.parser = parser;
- this.cursor = 0; // track the position of each character
+ this.cursor = 0;
+ }
+
+ public rollback(step: number) {
+ if (this.parser.lookahead) this.parser.lookahead.end -= step;
+
+ this.cursor -= step;
+
+ return this.cursor;
}
- /**
- * Whether the tokenizer reached EOF.
- */
public isEOF() {
return this.cursor === this.parser.syntax.length;
}
- /**
- * Whether we still have more tokens.
- */
protected hasMoreTokens() {
return this.cursor < this.parser.syntax.length;
}
- /**
- * Obtains next token.
- */
public getNextToken(): Token | null {
if (!this.hasMoreTokens()) {
return null;
}
+
+ const whitespaceMatch = /^\s+/.exec(this.parser.syntax.slice(this.cursor));
+ if (whitespaceMatch) {
+ this.cursor += whitespaceMatch[0].length;
+ return this.getNextToken();
+ }
+
const string = this.parser.syntax.slice(this.cursor);
+ if (string[0] === "`") {
+ const tok = this.scanTemplateLiteral();
+ this.lastTokenType = tok.type as string;
+ return tok;
+ }
+
+ if (string[0] === "/" && string[1] !== "/" && string[1] !== "*" && this.isRegexExpected()) {
+ const tok = this.scanRegexLiteral();
+ if (tok) {
+ this.lastTokenType = tok.type as string;
+ return tok;
+ }
+ }
+
for (const [regexp, tokenType] of Specs) {
const tokenValue = this.match(regexp, string);
- // Couldn't match this rule, continue.
if (tokenValue === null) {
continue;
}
- // Should skip this null token because could be a whitespace or something else
if (tokenType === null) {
return this.getNextToken();
}
- // We return the token
+ if (tokenType === Keyword.IDENTIFIER && tokenValue.includes(" ")) {
+ const truncated = this.truncateBeforeEmbeddedKeyword(tokenValue);
+ if (truncated.length !== tokenValue.length) {
+ this.cursor -= tokenValue.length - truncated.length;
+ this.lastTokenType = tokenType as string;
+ return {
+ type: tokenType,
+ value: truncated,
+ start: this.cursor - truncated.length,
+ end: this.cursor,
+ };
+ }
+ }
+
+ this.lastTokenType = tokenType as string;
return {
type: tokenType,
value: tokenValue,
@@ -72,9 +160,155 @@ export class Tokenizer {
throw new SyntaxError(`Unexpected token: "${string[0]}"`);
}
- /**
- * Matches a token for a regular expression.
- */
+ private isRegexExpected(): boolean {
+ if (this.lastTokenType === null) return true;
+ return REGEX_PRECEDING_TOKENS.has(this.lastTokenType);
+ }
+
+ private truncateBeforeEmbeddedKeyword(value: string): string {
+ const words = value.split(/\s+/);
+ let truncated = words[0];
+ for (let i = 1; i < words.length; i++) {
+ const rest = words.slice(i).join(" ");
+ for (const [regexp, tokenType] of Specs) {
+ if (tokenType === null || tokenType === Keyword.IDENTIFIER) continue;
+ const m = regexp.exec(rest + ";");
+ if (m && m.index === 0 && /^[A-Za-z\u00C0-\u1EF9]/.test(m[0])) {
+ return truncated;
+ }
+ }
+ truncated += " " + words[i];
+ }
+ return value;
+ }
+
+ private scanRegexLiteral(): Token | null {
+ const source = this.parser.syntax;
+ const start = this.cursor;
+ let i = start + 1;
+ let inCharClass = false;
+
+ while (i < source.length) {
+ const ch = source[i];
+ if (ch === "\\") {
+ i += 2;
+ continue;
+ }
+ if (ch === "[") {
+ inCharClass = true;
+ i++;
+ continue;
+ }
+ if (ch === "]") {
+ inCharClass = false;
+ i++;
+ continue;
+ }
+ if (ch === "/" && !inCharClass) {
+ i++;
+ while (i < source.length && /[a-z]/.test(source[i])) {
+ i++;
+ }
+ const value = source.slice(start, i);
+ this.cursor = i;
+ return {
+ type: "RegExpLiteral",
+ value,
+ start,
+ end: i,
+ };
+ }
+ if (ch === "\n") {
+ return null;
+ }
+ i++;
+ }
+
+ return null;
+ }
+
+ private scanTemplateLiteral(): Token {
+ const source = this.parser.syntax;
+ const start = this.cursor;
+ let i = start + 1;
+
+ while (i < source.length) {
+ const ch = source[i];
+
+ if (ch === "\\") {
+ i += 2;
+ continue;
+ }
+
+ if (ch === "`") {
+ i++;
+ const value = source.slice(start, i);
+ this.cursor = i;
+ return {
+ type: "TemplateLiteral",
+ value,
+ start,
+ end: i,
+ };
+ }
+
+ if (ch === "$" && source[i + 1] === "{") {
+ i += 2;
+ let depth = 1;
+ while (i < source.length && depth > 0) {
+ const inner = source[i];
+ if (inner === "\\") {
+ i += 2;
+ continue;
+ }
+ if (inner === '"' || inner === "'") {
+ const quote = inner;
+ i++;
+ while (i < source.length && source[i] !== quote) {
+ if (source[i] === "\\") i++;
+ i++;
+ }
+ i++;
+ continue;
+ }
+ if (inner === "`") {
+ i++;
+ while (i < source.length) {
+ if (source[i] === "\\") {
+ i += 2;
+ continue;
+ }
+ if (source[i] === "$" && source[i + 1] === "{") {
+ i += 2;
+ let innerDepth = 1;
+ while (i < source.length && innerDepth > 0) {
+ if (source[i] === "{") innerDepth++;
+ else if (source[i] === "}") innerDepth--;
+ i++;
+ }
+ continue;
+ }
+ if (source[i] === "`") {
+ i++;
+ break;
+ }
+ i++;
+ }
+ continue;
+ }
+ if (inner === "{") depth++;
+ else if (inner === "}") depth--;
+ i++;
+ }
+ continue;
+ }
+
+ i++;
+ }
+
+ throw new SyntaxError(`Template literal không đóng, bắt đầu tại vị trí ${start}`);
+ }
+
private match(regexp: RegExp, syntax: string) {
const formattedSyntax = syntax.split(";");
const matched = regexp.exec(formattedSyntax[0].concat(";"));
diff --git a/packages/plugins/vite/package.json b/packages/plugins/vite/package.json
index c08be99..e7d11a2 100644
--- a/packages/plugins/vite/package.json
+++ b/packages/plugins/vite/package.json
@@ -1,7 +1,7 @@
{
"name": "@vietscript/plugin-vite",
"description": "A VietScript Vite plugin to load .vjs files.",
- "version": "0.0.1",
+ "version": "1.0.0-beta.1",
"type": "module",
"exports": {
".": {
@@ -36,6 +36,7 @@
"vitest": "^0.28.3"
},
"dependencies": {
+ "@babel/generator": "^7.22.10",
"@vietscript/parser": "workspace:*"
}
}
diff --git a/packages/plugins/vite/src/index.ts b/packages/plugins/vite/src/index.ts
index 641faef..f18e43c 100644
--- a/packages/plugins/vite/src/index.ts
+++ b/packages/plugins/vite/src/index.ts
@@ -1,18 +1,25 @@
-import parser from "@vietscript/parser";
+import { Parser } from "@vietscript/parser";
+import generateDefault from "@babel/generator";
+
+const generate = (generateDefault as any).default ?? generateDefault;
export default () => ({
name: "vietscript-loader",
+
transform(code: string, id: string) {
- if (id.endsWith(".vjs")) {
- let loadedFormModule = {};
+ if (!id.endsWith(".vjs")) return null;
- try {
- loadedFormModule = parser.parse(code);
- } catch (error) {
- console.error(error);
- }
+ const parser = new Parser();
+ parser.filename = id;
+ const ast = parser.parse(code);
+ const result = generate(ast, {
+ sourceMaps: true,
+ sourceFileName: id,
+ });
- return `export default ${JSON.stringify(loadedFormModule)}`;
- }
+ return {
+ code: result.code,
+ map: result.map,
+ };
},
});
diff --git a/packages/plugins/webpack/package.json b/packages/plugins/webpack/package.json
index 4425179..da6e990 100644
--- a/packages/plugins/webpack/package.json
+++ b/packages/plugins/webpack/package.json
@@ -1,6 +1,6 @@
{
"name": "@vietscript/plugin-webpack",
- "version": "0.0.1",
+ "version": "1.0.0-beta.1",
"description": "A VietScript plugin to include .vjs file into Webpack build",
"type": "module",
"exports": {
@@ -38,6 +38,7 @@
"webpack-cli": "^5.0.1"
},
"dependencies": {
+ "@babel/generator": "^7.22.10",
"@vietscript/parser": "workspace:*"
}
}
diff --git a/packages/plugins/webpack/src/index.ts b/packages/plugins/webpack/src/index.ts
index 3ec133f..4e8dfef 100644
--- a/packages/plugins/webpack/src/index.ts
+++ b/packages/plugins/webpack/src/index.ts
@@ -1,7 +1,22 @@
-import parser from "@vietscript/parser";
+import { Parser } from "@vietscript/parser";
+import generateDefault from "@babel/generator";
-export default function (source: string) {
- const form = parser.parse(source);
+const generate = (generateDefault as any).default ?? generateDefault;
- return `export default ${JSON.stringify(form)}`;
+export default function vietScriptLoader(this: any, source: string) {
+ const filename = this?.resourcePath as string | undefined;
+ const parser = new Parser();
+ parser.filename = filename;
+ const ast = parser.parse(source);
+ const result = generate(ast, {
+ sourceMaps: true,
+ sourceFileName: filename,
+ });
+
+ if (this?.callback) {
+ this.callback(null, result.code, result.map);
+ return;
+ }
+
+ return result.code;
}
diff --git a/packages/shared/package.json b/packages/shared/package.json
index 9ab632e..4b715ed 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -1,6 +1,6 @@
{
"name": "@vietscript/shared",
- "version": "0.0.1",
+ "version": "1.0.0-beta.1",
"main": "./index.ts",
"files": [
"./"
diff --git a/packages/stdlib/package.json b/packages/stdlib/package.json
new file mode 100644
index 0000000..f2ba988
--- /dev/null
+++ b/packages/stdlib/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "@vietscript/stdlib",
+ "version": "1.0.0-beta.1",
+ "description": "VietScript standard library — thin Vietnamese wrappers over JS globals.",
+ "type": "module",
+ "exports": {
+ ".": {
+ "import": "./dist/index.js",
+ "default": "./dist/index.js"
+ }
+ },
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "build": "tsc",
+ "typecheck": "tsc --noEmit"
+ },
+ "devDependencies": {
+ "@types/node": "^18.14.1",
+ "typescript": "^4.9.4"
+ }
+}
diff --git a/packages/stdlib/src/__test__/index.test.ts b/packages/stdlib/src/__test__/index.test.ts
new file mode 100644
index 0000000..d21ac07
--- /dev/null
+++ b/packages/stdlib/src/__test__/index.test.ts
@@ -0,0 +1,99 @@
+import {
+ tự_số,
+ tự_chuỗi,
+ tự_bool,
+ kiểu,
+ độ_dài,
+ tròn,
+ làm_tròn_xuống,
+ làm_tròn_lên,
+ giá_trị_tuyệt_đối,
+ nhỏ_nhất,
+ lớn_nhất,
+ ngẫu_nhiên,
+ phân_tích_JSON,
+ JSON_sang_chuỗi,
+ đợi,
+} from "../index";
+
+describe("stdlib.test", () => {
+ describe("ép kiểu", () => {
+ it("tự_số", () => {
+ expect(tự_số("42")).toBe(42);
+ expect(tự_số("3.14")).toBe(3.14);
+ });
+
+ it("tự_chuỗi", () => {
+ expect(tự_chuỗi(42)).toBe("42");
+ expect(tự_chuỗi(true)).toBe("true");
+ });
+
+ it("tự_bool", () => {
+ expect(tự_bool(1)).toBe(true);
+ expect(tự_bool(0)).toBe(false);
+ expect(tự_bool("")).toBe(false);
+ });
+
+ it("kiểu", () => {
+ expect(kiểu(42)).toBe("number");
+ expect(kiểu("s")).toBe("string");
+ });
+
+ it("độ_dài", () => {
+ expect(độ_dài([1, 2, 3])).toBe(3);
+ expect(độ_dài("abc")).toBe(3);
+ });
+ });
+
+ describe("Math", () => {
+ it("tròn", () => {
+ expect(tròn(3.5)).toBe(4);
+ expect(tròn(3.4)).toBe(3);
+ });
+
+ it("làm_tròn_xuống", () => {
+ expect(làm_tròn_xuống(3.9)).toBe(3);
+ });
+
+ it("làm_tròn_lên", () => {
+ expect(làm_tròn_lên(3.1)).toBe(4);
+ });
+
+ it("giá_trị_tuyệt_đối", () => {
+ expect(giá_trị_tuyệt_đối(-5)).toBe(5);
+ });
+
+ it("nhỏ_nhất / lớn_nhất", () => {
+ expect(nhỏ_nhất(3, 1, 4, 1, 5)).toBe(1);
+ expect(lớn_nhất(3, 1, 4, 1, 5)).toBe(5);
+ });
+
+ it("ngẫu_nhiên in range", () => {
+ const x = ngẫu_nhiên(10, 20);
+ expect(x).toBeGreaterThanOrEqual(10);
+ expect(x).toBeLessThan(20);
+ });
+ });
+
+ describe("JSON", () => {
+ it("phân_tích_JSON", () => {
+ expect(phân_tích_JSON('{"a": 1}')).toEqual({ a: 1 });
+ });
+
+ it("JSON_sang_chuỗi", () => {
+ expect(JSON_sang_chuỗi({ a: 1 })).toBe('{"a":1}');
+ });
+
+ it("JSON_sang_chuỗi with indent", () => {
+ expect(JSON_sang_chuỗi({ a: 1 }, 2)).toContain("\n");
+ });
+ });
+
+ describe("async", () => {
+ it("đợi resolves after time", async () => {
+ const start = Date.now();
+ await đợi(20);
+ expect(Date.now() - start).toBeGreaterThanOrEqual(15);
+ });
+ });
+});
diff --git a/packages/stdlib/src/index.ts b/packages/stdlib/src/index.ts
new file mode 100644
index 0000000..21dd425
--- /dev/null
+++ b/packages/stdlib/src/index.ts
@@ -0,0 +1,81 @@
+export function ghi(...args: unknown[]): void {
+ console.log(...args);
+}
+
+export function ghi_lỗi(...args: unknown[]): void {
+ console.error(...args);
+}
+
+export function ghi_cảnh_báo(...args: unknown[]): void {
+ console.warn(...args);
+}
+
+export function tự_số(value: unknown): number {
+ return Number(value);
+}
+
+export function tự_chuỗi(value: unknown): string {
+ return String(value);
+}
+
+export function tự_bool(value: unknown): boolean {
+ return Boolean(value);
+}
+
+export function kiểu(value: unknown): string {
+ return typeof value;
+}
+
+export function độ_dài(value: { length: number } | string | unknown[]): number {
+ return (value as { length: number }).length;
+}
+
+export function đợi(ms: number): Promise {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
+
+export function ngẫu_nhiên(min = 0, max = 1): number {
+ return Math.random() * (max - min) + min;
+}
+
+export function tròn(value: number): number {
+ return Math.round(value);
+}
+
+export function làm_tròn_xuống(value: number): number {
+ return Math.floor(value);
+}
+
+export function làm_tròn_lên(value: number): number {
+ return Math.ceil(value);
+}
+
+export function giá_trị_tuyệt_đối(value: number): number {
+ return Math.abs(value);
+}
+
+export function nhỏ_nhất(...values: number[]): number {
+ return Math.min(...values);
+}
+
+export function lớn_nhất(...values: number[]): number {
+ return Math.max(...values);
+}
+
+export function phân_tích_JSON(text: string): unknown {
+ return JSON.parse(text);
+}
+
+export function JSON_sang_chuỗi(value: unknown, space?: string | number): string {
+ return JSON.stringify(value, null, space);
+}
+
+export async function tải_văn_bản(url: string | URL): Promise {
+ const response = await fetch(url);
+ return response.text();
+}
+
+export async function tải_JSON(url: string | URL): Promise {
+ const response = await fetch(url);
+ return response.json() as Promise;
+}
diff --git a/packages/stdlib/tsconfig.json b/packages/stdlib/tsconfig.json
new file mode 100644
index 0000000..e61a1e7
--- /dev/null
+++ b/packages/stdlib/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "declaration": true,
+ "esModuleInterop": true,
+ "strict": true,
+ "skipLibCheck": true
+ },
+ "include": ["src/**/*"]
+}
diff --git a/playground/stats.html b/playground/stats.html
new file mode 100644
index 0000000..c5477d6
--- /dev/null
+++ b/playground/stats.html
@@ -0,0 +1,4842 @@
+
+
+
+
+
+
+
+ Rollup Visualizer
+
+
+
+
+
+
+
+
+
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c406858..b7c769c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,16 +1,18 @@
lockfileVersion: '6.0'
-settings:
- autoInstallPeers: true
- excludeLinksFromLockfile: false
-
importers:
.:
dependencies:
+ '@vietscript/cli':
+ specifier: workspace:*
+ version: link:packages/cli
'@vietscript/parser':
specifier: workspace:*
version: link:packages/parser
+ '@vietscript/stdlib':
+ specifier: workspace:*
+ version: link:packages/stdlib
devDependencies:
'@babel/generator':
specifier: ^7.22.10
@@ -54,9 +56,12 @@ importers:
'@vitejs/plugin-vue-jsx':
specifier: ^2.0.1
version: 2.1.1(vite@3.2.7)(vue@3.3.4)
+ '@vitest/coverage-v8':
+ specifier: ^1.6.0
+ version: 1.6.0(vitest@1.6.0)
'@vitest/ui':
- specifier: ^0.24.5
- version: 0.24.5
+ specifier: ^1.6.0
+ version: 1.6.0(vitest@1.6.0)
'@vue/test-utils':
specifier: ^2.2.1
version: 2.4.1(vue@3.3.4)
@@ -100,8 +105,8 @@ importers:
specifier: 1.0.0-alpha.49
version: 1.0.0-alpha.49(@algolia/client-search@4.19.1)(@types/node@18.17.14)(search-insights@2.8.1)
vitest:
- specifier: ^0.24.5
- version: 0.24.5(@vitest/ui@0.24.5)(jsdom@20.0.3)
+ specifier: ^1.6.0
+ version: 1.6.0(@types/node@18.17.14)(@vitest/ui@1.6.0)(jsdom@20.0.3)
vue:
specifier: ^3.2.41
version: 3.3.4
@@ -109,6 +114,25 @@ importers:
specifier: ^1.0.9
version: 1.8.8(typescript@4.9.5)
+ packages/cli:
+ dependencies:
+ '@babel/generator':
+ specifier: ^7.22.10
+ version: 7.25.0
+ '@vietscript/parser':
+ specifier: workspace:*
+ version: link:../parser
+ chokidar:
+ specifier: ^3.6.0
+ version: 3.6.0
+ devDependencies:
+ '@types/node':
+ specifier: ^18.14.1
+ version: 18.17.14
+ typescript:
+ specifier: ^4.9.4
+ version: 4.9.5
+
packages/parser:
devDependencies:
'@types/node':
@@ -141,6 +165,9 @@ importers:
packages/plugins/vite:
dependencies:
+ '@babel/generator':
+ specifier: ^7.22.10
+ version: 7.25.0
'@vietscript/parser':
specifier: workspace:*
version: link:../../parser
@@ -166,6 +193,9 @@ importers:
packages/plugins/webpack:
dependencies:
+ '@babel/generator':
+ specifier: ^7.22.10
+ version: 7.25.0
'@vietscript/parser':
specifier: workspace:*
version: link:../../parser
@@ -216,6 +246,15 @@ importers:
specifier: ^0.28.3
version: 0.28.5(@vitest/ui@0.24.5)(jsdom@20.0.3)
+ packages/stdlib:
+ devDependencies:
+ '@types/node':
+ specifier: ^18.14.1
+ version: 18.17.14
+ typescript:
+ specifier: ^4.9.4
+ version: 4.9.5
+
playground:
dependencies:
'@ant-design/icons':
@@ -274,7 +313,7 @@ importers:
version: 5.5.4
vitest:
specifier: ^0.29.8
- version: 0.29.8(@vitest/ui@0.24.5)(jsdom@20.0.3)
+ version: 0.29.8(@vitest/ui@1.6.0)(jsdom@20.0.3)
devDependencies:
'@babel/generator':
specifier: ^7.24.4
@@ -530,8 +569,8 @@ packages:
resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
engines: {node: '>=6.0.0'}
dependencies:
- '@jridgewell/gen-mapping': 0.3.3
- '@jridgewell/trace-mapping': 0.3.19
+ '@jridgewell/gen-mapping': 0.3.5
+ '@jridgewell/trace-mapping': 0.3.25
dev: true
/@ant-design/colors@7.1.0:
@@ -629,7 +668,7 @@ packages:
'@babel/parser': 7.22.14
'@babel/template': 7.22.5
'@babel/traverse': 7.22.11
- '@babel/types': 7.22.11
+ '@babel/types': 7.29.0
convert-source-map: 1.9.0
debug: 4.3.4
gensync: 1.0.0-beta.2
@@ -657,13 +696,12 @@ packages:
'@jridgewell/gen-mapping': 0.3.5
'@jridgewell/trace-mapping': 0.3.25
jsesc: 2.5.2
- dev: true
/@babel/helper-annotate-as-pure@7.22.5:
resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.25.2
+ '@babel/types': 7.29.0
dev: true
/@babel/helper-compilation-targets@7.22.10:
@@ -705,28 +743,28 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/template': 7.22.5
- '@babel/types': 7.25.2
+ '@babel/types': 7.29.0
dev: true
/@babel/helper-hoist-variables@7.22.5:
resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.25.2
+ '@babel/types': 7.29.0
dev: true
/@babel/helper-member-expression-to-functions@7.22.5:
resolution: {integrity: sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.25.2
+ '@babel/types': 7.29.0
dev: true
/@babel/helper-module-imports@7.22.5:
resolution: {integrity: sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.25.2
+ '@babel/types': 7.29.0
dev: true
/@babel/helper-module-transforms@7.22.9(@babel/core@7.22.11):
@@ -740,14 +778,14 @@ packages:
'@babel/helper-module-imports': 7.22.5
'@babel/helper-simple-access': 7.22.5
'@babel/helper-split-export-declaration': 7.22.6
- '@babel/helper-validator-identifier': 7.22.5
+ '@babel/helper-validator-identifier': 7.28.5
dev: true
/@babel/helper-optimise-call-expression@7.22.5:
resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.25.2
+ '@babel/types': 7.29.0
dev: true
/@babel/helper-plugin-utils@7.22.5:
@@ -776,42 +814,35 @@ packages:
resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.25.2
+ '@babel/types': 7.29.0
dev: true
/@babel/helper-skip-transparent-expression-wrappers@7.22.5:
resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.25.2
+ '@babel/types': 7.29.0
dev: true
/@babel/helper-split-export-declaration@7.22.6:
resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/types': 7.25.2
+ '@babel/types': 7.29.0
dev: true
- /@babel/helper-string-parser@7.22.5:
- resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==}
+ /@babel/helper-string-parser@7.27.1:
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
- dev: true
-
- /@babel/helper-string-parser@7.24.8:
- resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==}
- engines: {node: '>=6.9.0'}
- dev: true
/@babel/helper-validator-identifier@7.22.5:
resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==}
engines: {node: '>=6.9.0'}
dev: true
- /@babel/helper-validator-identifier@7.24.7:
- resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==}
+ /@babel/helper-validator-identifier@7.28.5:
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
- dev: true
/@babel/helper-validator-option@7.22.5:
resolution: {integrity: sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==}
@@ -824,7 +855,7 @@ packages:
dependencies:
'@babel/template': 7.22.5
'@babel/traverse': 7.22.11
- '@babel/types': 7.25.2
+ '@babel/types': 7.29.0
transitivePeerDependencies:
- supports-color
dev: true
@@ -833,7 +864,7 @@ packages:
resolution: {integrity: sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/helper-validator-identifier': 7.24.7
+ '@babel/helper-validator-identifier': 7.28.5
chalk: 2.4.2
js-tokens: 4.0.0
dev: true
@@ -843,7 +874,15 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
- '@babel/types': 7.25.2
+ '@babel/types': 7.29.0
+ dev: true
+
+ /@babel/parser@7.29.2:
+ resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+ dependencies:
+ '@babel/types': 7.29.0
dev: true
/@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.22.11):
@@ -911,8 +950,8 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
'@babel/code-frame': 7.22.13
- '@babel/parser': 7.22.14
- '@babel/types': 7.25.2
+ '@babel/parser': 7.29.2
+ '@babel/types': 7.29.0
dev: true
/@babel/traverse@7.22.11:
@@ -925,8 +964,8 @@ packages:
'@babel/helper-function-name': 7.22.5
'@babel/helper-hoist-variables': 7.22.5
'@babel/helper-split-export-declaration': 7.22.6
- '@babel/parser': 7.22.14
- '@babel/types': 7.25.2
+ '@babel/parser': 7.29.2
+ '@babel/types': 7.29.0
debug: 4.3.4
globals: 11.12.0
transitivePeerDependencies:
@@ -937,8 +976,8 @@ packages:
resolution: {integrity: sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/helper-string-parser': 7.22.5
- '@babel/helper-validator-identifier': 7.22.5
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
to-fast-properties: 2.0.0
dev: true
@@ -946,9 +985,20 @@ packages:
resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==}
engines: {node: '>=6.9.0'}
dependencies:
- '@babel/helper-string-parser': 7.24.8
- '@babel/helper-validator-identifier': 7.24.7
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
to-fast-properties: 2.0.0
+
+ /@babel/types@7.29.0:
+ resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+ dev: true
+
+ /@bcoe/v8-coverage@0.2.3:
+ resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
dev: true
/@commitlint/cli@17.7.1:
@@ -1040,13 +1090,13 @@ packages:
'@types/node': 20.4.7
chalk: 4.1.2
cosmiconfig: 8.3.2(typescript@4.9.5)
- cosmiconfig-typescript-loader: 4.4.0(@types/node@20.4.7)(cosmiconfig@8.3.2)(ts-node@10.9.1)(typescript@4.9.5)
+ cosmiconfig-typescript-loader: 4.4.0(@types/node@20.4.7)(cosmiconfig@8.3.2)(ts-node@10.9.1)(typescript@5.5.4)
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
lodash.uniq: 4.5.0
resolve-from: 5.0.0
- ts-node: 10.9.1(@types/node@20.4.7)(typescript@4.9.5)
- typescript: 4.9.5
+ ts-node: 10.9.1(@types/node@20.4.7)(typescript@5.5.4)
+ typescript: 5.5.4
transitivePeerDependencies:
- '@swc/core'
- '@swc/wasm'
@@ -1187,6 +1237,14 @@ packages:
resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==}
dev: false
+ /@esbuild/aix-ppc64@0.21.5:
+ resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [aix]
+ requiresBuild: true
+ optional: true
+
/@esbuild/android-arm64@0.18.20:
resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
engines: {node: '>=12'}
@@ -1195,6 +1253,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/android-arm64@0.21.5:
+ resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ optional: true
+
/@esbuild/android-arm@0.15.18:
resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==}
engines: {node: '>=12'}
@@ -1212,6 +1278,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/android-arm@0.21.5:
+ resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ optional: true
+
/@esbuild/android-x64@0.18.20:
resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
engines: {node: '>=12'}
@@ -1220,6 +1294,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/android-x64@0.21.5:
+ resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+ requiresBuild: true
+ optional: true
+
/@esbuild/darwin-arm64@0.18.20:
resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
engines: {node: '>=12'}
@@ -1228,6 +1310,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/darwin-arm64@0.21.5:
+ resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ optional: true
+
/@esbuild/darwin-x64@0.18.20:
resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
engines: {node: '>=12'}
@@ -1236,6 +1326,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/darwin-x64@0.21.5:
+ resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ optional: true
+
/@esbuild/freebsd-arm64@0.18.20:
resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
engines: {node: '>=12'}
@@ -1244,6 +1342,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/freebsd-arm64@0.21.5:
+ resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+ requiresBuild: true
+ optional: true
+
/@esbuild/freebsd-x64@0.18.20:
resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
engines: {node: '>=12'}
@@ -1252,6 +1358,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/freebsd-x64@0.21.5:
+ resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ optional: true
+
/@esbuild/linux-arm64@0.18.20:
resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
engines: {node: '>=12'}
@@ -1260,6 +1374,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/linux-arm64@0.21.5:
+ resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
/@esbuild/linux-arm@0.18.20:
resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
engines: {node: '>=12'}
@@ -1268,6 +1390,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/linux-arm@0.21.5:
+ resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
/@esbuild/linux-ia32@0.18.20:
resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
engines: {node: '>=12'}
@@ -1276,6 +1406,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/linux-ia32@0.21.5:
+ resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
/@esbuild/linux-loong64@0.15.18:
resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==}
engines: {node: '>=12'}
@@ -1293,6 +1431,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/linux-loong64@0.21.5:
+ resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
/@esbuild/linux-mips64el@0.18.20:
resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
engines: {node: '>=12'}
@@ -1301,6 +1447,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/linux-mips64el@0.21.5:
+ resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
/@esbuild/linux-ppc64@0.18.20:
resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
engines: {node: '>=12'}
@@ -1309,6 +1463,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/linux-ppc64@0.21.5:
+ resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
/@esbuild/linux-riscv64@0.18.20:
resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
engines: {node: '>=12'}
@@ -1317,6 +1479,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/linux-riscv64@0.21.5:
+ resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
/@esbuild/linux-s390x@0.18.20:
resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
engines: {node: '>=12'}
@@ -1325,6 +1495,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/linux-s390x@0.21.5:
+ resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
/@esbuild/linux-x64@0.18.20:
resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
engines: {node: '>=12'}
@@ -1333,6 +1511,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/linux-x64@0.21.5:
+ resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
/@esbuild/netbsd-x64@0.18.20:
resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
engines: {node: '>=12'}
@@ -1341,6 +1527,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/netbsd-x64@0.21.5:
+ resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+ requiresBuild: true
+ optional: true
+
/@esbuild/openbsd-x64@0.18.20:
resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
engines: {node: '>=12'}
@@ -1349,6 +1543,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/openbsd-x64@0.21.5:
+ resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+ requiresBuild: true
+ optional: true
+
/@esbuild/sunos-x64@0.18.20:
resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
engines: {node: '>=12'}
@@ -1357,6 +1559,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/sunos-x64@0.21.5:
+ resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+ requiresBuild: true
+ optional: true
+
/@esbuild/win32-arm64@0.18.20:
resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
engines: {node: '>=12'}
@@ -1365,6 +1575,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/win32-arm64@0.21.5:
+ resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
/@esbuild/win32-ia32@0.18.20:
resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
engines: {node: '>=12'}
@@ -1373,6 +1591,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/win32-ia32@0.21.5:
+ resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
/@esbuild/win32-x64@0.18.20:
resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
engines: {node: '>=12'}
@@ -1381,6 +1607,14 @@ packages:
requiresBuild: true
optional: true
+ /@esbuild/win32-x64@0.21.5:
+ resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
/@eslint-community/eslint-utils@4.4.0(eslint@8.48.0):
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -1459,13 +1693,24 @@ packages:
wrap-ansi-cjs: /wrap-ansi@7.0.0
dev: true
+ /@istanbuljs/schema@0.1.6:
+ resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /@jest/schemas@29.6.3:
+ resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ dependencies:
+ '@sinclair/typebox': 0.27.10
+
/@jridgewell/gen-mapping@0.3.3:
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
engines: {node: '>=6.0.0'}
dependencies:
- '@jridgewell/set-array': 1.1.2
- '@jridgewell/sourcemap-codec': 1.4.15
- '@jridgewell/trace-mapping': 0.3.19
+ '@jridgewell/set-array': 1.2.1
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.25
dev: true
/@jridgewell/gen-mapping@0.3.5:
@@ -1473,55 +1718,45 @@ packages:
engines: {node: '>=6.0.0'}
dependencies:
'@jridgewell/set-array': 1.2.1
- '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/sourcemap-codec': 1.5.5
'@jridgewell/trace-mapping': 0.3.25
- dev: true
/@jridgewell/resolve-uri@3.1.1:
resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
engines: {node: '>=6.0.0'}
- dev: true
-
- /@jridgewell/set-array@1.1.2:
- resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
- engines: {node: '>=6.0.0'}
- dev: true
/@jridgewell/set-array@1.2.1:
resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
engines: {node: '>=6.0.0'}
- dev: true
/@jridgewell/source-map@0.3.5:
resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==}
dependencies:
- '@jridgewell/gen-mapping': 0.3.3
- '@jridgewell/trace-mapping': 0.3.19
+ '@jridgewell/gen-mapping': 0.3.5
+ '@jridgewell/trace-mapping': 0.3.25
dev: true
- /@jridgewell/sourcemap-codec@1.4.15:
- resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
- dev: true
+ /@jridgewell/sourcemap-codec@1.5.5:
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
/@jridgewell/trace-mapping@0.3.19:
resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==}
dependencies:
'@jridgewell/resolve-uri': 3.1.1
- '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/sourcemap-codec': 1.5.5
dev: true
/@jridgewell/trace-mapping@0.3.25:
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
dependencies:
'@jridgewell/resolve-uri': 3.1.1
- '@jridgewell/sourcemap-codec': 1.4.15
- dev: true
+ '@jridgewell/sourcemap-codec': 1.5.5
/@jridgewell/trace-mapping@0.3.9:
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
dependencies:
'@jridgewell/resolve-uri': 3.1.1
- '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/sourcemap-codec': 1.5.5
dev: true
/@microsoft/api-extractor-model@7.27.6(@types/node@18.17.14):
@@ -1595,12 +1830,10 @@ packages:
dependencies:
'@nodelib/fs.stat': 2.0.5
run-parallel: 1.2.0
- dev: true
/@nodelib/fs.stat@2.0.5:
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
engines: {node: '>= 8'}
- dev: true
/@nodelib/fs.walk@1.2.8:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
@@ -1608,7 +1841,6 @@ packages:
dependencies:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.15.0
- dev: true
/@nrwl/devkit@16.6.0(nx@16.6.0):
resolution: {integrity: sha512-xZEN6wfA1uJwv+FVRQFOHsCcpvGvIYGx2zutbzungDodWkfzlJ3tzIGqYjIpPCBVT83erM6Gscnka2W46AuKfA==}
@@ -1936,6 +2168,10 @@ packages:
/@polka/url@1.0.0-next.21:
resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
+ dev: true
+
+ /@polka/url@1.0.0-next.29:
+ resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
/@rc-component/async-validator@5.0.4:
resolution: {integrity: sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==}
@@ -2081,7 +2317,7 @@ packages:
dependencies:
'@rollup/pluginutils': 5.0.4
estree-walker: 2.0.2
- magic-string: 0.30.3
+ magic-string: 0.30.21
dev: true
/@rollup/pluginutils@5.0.4:
@@ -2098,6 +2334,181 @@ packages:
picomatch: 2.3.1
dev: true
+ /@rollup/rollup-android-arm-eabi@4.60.2:
+ resolution: {integrity: sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-android-arm64@4.60.2:
+ resolution: {integrity: sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-darwin-arm64@4.60.2:
+ resolution: {integrity: sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-darwin-x64@4.60.2:
+ resolution: {integrity: sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-freebsd-arm64@4.60.2:
+ resolution: {integrity: sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==}
+ cpu: [arm64]
+ os: [freebsd]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-freebsd-x64@4.60.2:
+ resolution: {integrity: sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-arm-gnueabihf@4.60.2:
+ resolution: {integrity: sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-arm-musleabihf@4.60.2:
+ resolution: {integrity: sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-arm64-gnu@4.60.2:
+ resolution: {integrity: sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-arm64-musl@4.60.2:
+ resolution: {integrity: sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-loong64-gnu@4.60.2:
+ resolution: {integrity: sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-loong64-musl@4.60.2:
+ resolution: {integrity: sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-ppc64-gnu@4.60.2:
+ resolution: {integrity: sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-ppc64-musl@4.60.2:
+ resolution: {integrity: sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-riscv64-gnu@4.60.2:
+ resolution: {integrity: sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-riscv64-musl@4.60.2:
+ resolution: {integrity: sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-s390x-gnu@4.60.2:
+ resolution: {integrity: sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==}
+ cpu: [s390x]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-x64-gnu@4.60.2:
+ resolution: {integrity: sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-linux-x64-musl@4.60.2:
+ resolution: {integrity: sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-openbsd-x64@4.60.2:
+ resolution: {integrity: sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==}
+ cpu: [x64]
+ os: [openbsd]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-openharmony-arm64@4.60.2:
+ resolution: {integrity: sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==}
+ cpu: [arm64]
+ os: [openharmony]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-win32-arm64-msvc@4.60.2:
+ resolution: {integrity: sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-win32-ia32-msvc@4.60.2:
+ resolution: {integrity: sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-win32-x64-gnu@4.60.2:
+ resolution: {integrity: sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
+ /@rollup/rollup-win32-x64-msvc@4.60.2:
+ resolution: {integrity: sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ optional: true
+
/@rushstack/node-core-library@3.59.7(@types/node@18.17.14):
resolution: {integrity: sha512-ln1Drq0h+Hwa1JVA65x5mlSgUrBa1uHL+V89FqVWQgXd1vVIMhrtqtWGQrhTnFHxru5ppX+FY39VWELF/FjQCw==}
peerDependencies:
@@ -2132,6 +2543,9 @@ packages:
string-argv: 0.3.2
dev: true
+ /@sinclair/typebox@0.27.10:
+ resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==}
+
/@sindresorhus/is@5.6.0:
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
engines: {node: '>=14.16'}
@@ -2208,7 +2622,7 @@ packages:
/@ts-morph/common@0.18.1:
resolution: {integrity: sha512-RVE+zSRICWRsfrkAw5qCAK+4ZH9kwEFv5h0+/YeHTLieWP7F4wWq4JsKFuNWG+fYh/KF+8rAtgdj5zb2mm+DVA==}
dependencies:
- fast-glob: 3.3.1
+ fast-glob: 3.3.3
minimatch: 5.1.6
mkdirp: 1.0.4
path-browserify: 1.0.1
@@ -2268,13 +2682,15 @@ packages:
/@types/eslint@8.44.2:
resolution: {integrity: sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==}
dependencies:
- '@types/estree': 1.0.1
+ '@types/estree': 1.0.8
'@types/json-schema': 7.0.12
dev: true
/@types/estree@1.0.1:
resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==}
- dev: true
+
+ /@types/estree@1.0.8:
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
/@types/hast@2.3.10:
resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
@@ -2683,6 +3099,29 @@ packages:
vue: 3.3.4
dev: true
+ /@vitest/coverage-v8@1.6.0(vitest@1.6.0):
+ resolution: {integrity: sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==}
+ peerDependencies:
+ vitest: 1.6.0
+ dependencies:
+ '@ampproject/remapping': 2.2.1
+ '@bcoe/v8-coverage': 0.2.3
+ debug: 4.3.4
+ istanbul-lib-coverage: 3.2.2
+ istanbul-lib-report: 3.0.1
+ istanbul-lib-source-maps: 5.0.6
+ istanbul-reports: 3.2.0
+ magic-string: 0.30.21
+ magicast: 0.3.5
+ picocolors: 1.0.1
+ std-env: 3.10.0
+ strip-literal: 2.1.1
+ test-exclude: 6.0.0
+ vitest: 1.6.0(@types/node@18.17.14)(@vitest/ui@1.6.0)(jsdom@20.0.3)
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@vitest/expect@0.28.5:
resolution: {integrity: sha512-gqTZwoUTwepwGIatnw4UKpQfnoyV0Z9Czn9+Lo2/jLIt4/AXLTn+oVZxlQ7Ng8bzcNkR+3DqLJ08kNr8jRmdNQ==}
dependencies:
@@ -2696,9 +3135,16 @@ packages:
dependencies:
'@vitest/spy': 0.29.8
'@vitest/utils': 0.29.8
- chai: 4.3.8
+ chai: 4.5.0
dev: false
+ /@vitest/expect@1.6.0:
+ resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==}
+ dependencies:
+ '@vitest/spy': 1.6.0
+ '@vitest/utils': 1.6.0
+ chai: 4.5.0
+
/@vitest/runner@0.28.5:
resolution: {integrity: sha512-NKkHtLB+FGjpp5KmneQjTcPLWPTDfB7ie+MmF1PnUBf/tGe2OjGxWyB62ySYZ25EYp9krR5Bw0YPLS/VWh1QiA==}
dependencies:
@@ -2715,6 +3161,20 @@ packages:
pathe: 1.1.1
dev: false
+ /@vitest/runner@1.6.0:
+ resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==}
+ dependencies:
+ '@vitest/utils': 1.6.0
+ p-limit: 5.0.0
+ pathe: 1.1.1
+
+ /@vitest/snapshot@1.6.0:
+ resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==}
+ dependencies:
+ magic-string: 0.30.21
+ pathe: 1.1.1
+ pretty-format: 29.7.0
+
/@vitest/spy@0.28.5:
resolution: {integrity: sha512-7if6rsHQr9zbmvxN7h+gGh2L9eIIErgf8nSKYDlg07HHimCxp4H6I/X/DPXktVPPLQfiZ1Cw2cbDIx9fSqDjGw==}
dependencies:
@@ -2727,10 +3187,30 @@ packages:
tinyspy: 1.1.1
dev: false
+ /@vitest/spy@1.6.0:
+ resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==}
+ dependencies:
+ tinyspy: 2.2.1
+
/@vitest/ui@0.24.5:
resolution: {integrity: sha512-hdj6cmlrVPt3Mpgn0ITytTqnxQLGB4hRgueFuzlKSrqAG30wUrrxQjrW55krE1NR15r1c2jzUBwjzvIJsyoUwg==}
dependencies:
sirv: 2.0.3
+ dev: true
+
+ /@vitest/ui@1.6.0(vitest@1.6.0):
+ resolution: {integrity: sha512-k3Lyo+ONLOgylctiGovRKy7V4+dIN2yxstX3eY5cWFXH6WP+ooVX79YSyi0GagdTQzLmT43BF27T0s6dOIPBXA==}
+ peerDependencies:
+ vitest: 1.6.0
+ dependencies:
+ '@vitest/utils': 1.6.0
+ fast-glob: 3.3.3
+ fflate: 0.8.2
+ flatted: 3.4.2
+ pathe: 1.1.1
+ picocolors: 1.0.1
+ sirv: 2.0.4
+ vitest: 1.6.0(@types/node@18.17.14)(@vitest/ui@1.6.0)(jsdom@20.0.3)
/@vitest/utils@0.28.5:
resolution: {integrity: sha512-UyZdYwdULlOa4LTUSwZ+Paz7nBHGTT72jKwdFSV4IjHF1xsokp+CabMdhjvVhYwkLfO88ylJT46YMilnkSARZA==}
@@ -2747,10 +3227,18 @@ packages:
dependencies:
cli-truncate: 3.1.0
diff: 5.1.0
- loupe: 2.3.6
+ loupe: 2.3.7
pretty-format: 27.5.1
dev: false
+ /@vitest/utils@1.6.0:
+ resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==}
+ dependencies:
+ diff-sequences: 29.6.3
+ estree-walker: 3.0.3
+ loupe: 2.3.7
+ pretty-format: 29.7.0
+
/@volar/language-core@1.10.1:
resolution: {integrity: sha512-JnsM1mIPdfGPxmoOcK1c7HYAsL6YOv0TCJ4aW3AXPZN/Jb4R77epDyMZIVudSGjWMbvv/JfUa+rQ+dGKTmgwBA==}
dependencies:
@@ -2783,7 +3271,7 @@ packages:
'@babel/plugin-syntax-jsx': 7.22.5(@babel/core@7.22.11)
'@babel/template': 7.22.5
'@babel/traverse': 7.22.11
- '@babel/types': 7.22.11
+ '@babel/types': 7.29.0
'@vue/babel-helper-vue-transform-on': 1.1.5
camelcase: 6.3.0
html-tags: 3.3.1
@@ -2795,10 +3283,10 @@ packages:
/@vue/compiler-core@3.3.4:
resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==}
dependencies:
- '@babel/parser': 7.22.14
+ '@babel/parser': 7.29.2
'@vue/shared': 3.3.4
estree-walker: 2.0.2
- source-map-js: 1.0.2
+ source-map-js: 1.2.1
dev: true
/@vue/compiler-dom@3.3.4:
@@ -2811,16 +3299,16 @@ packages:
/@vue/compiler-sfc@3.3.4:
resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==}
dependencies:
- '@babel/parser': 7.22.14
+ '@babel/parser': 7.29.2
'@vue/compiler-core': 3.3.4
'@vue/compiler-dom': 3.3.4
'@vue/compiler-ssr': 3.3.4
'@vue/reactivity-transform': 3.3.4
'@vue/shared': 3.3.4
estree-walker: 2.0.2
- magic-string: 0.30.3
- postcss: 8.4.29
- source-map-js: 1.0.2
+ magic-string: 0.30.21
+ postcss: 8.5.10
+ source-map-js: 1.2.1
dev: true
/@vue/compiler-ssr@3.3.4:
@@ -2847,7 +3335,7 @@ packages:
'@vue/compiler-dom': 3.3.4
'@vue/reactivity': 3.3.4
'@vue/shared': 3.3.4
- minimatch: 9.0.3
+ minimatch: 9.0.5
muggle-string: 0.3.1
typescript: 4.9.5
vue-template-compiler: 2.7.14
@@ -2856,11 +3344,11 @@ packages:
/@vue/reactivity-transform@3.3.4:
resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==}
dependencies:
- '@babel/parser': 7.22.14
+ '@babel/parser': 7.29.2
'@vue/compiler-core': 3.3.4
'@vue/shared': 3.3.4
estree-walker: 2.0.2
- magic-string: 0.30.3
+ magic-string: 0.30.21
dev: true
/@vue/reactivity@3.3.4:
@@ -3134,8 +3622,8 @@ packages:
/acorn-globals@7.0.1:
resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==}
dependencies:
- acorn: 8.10.0
- acorn-walk: 8.2.0
+ acorn: 8.16.0
+ acorn-walk: 8.3.5
/acorn-import-assertions@1.9.0(acorn@8.10.0):
resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==}
@@ -3157,11 +3645,22 @@ packages:
resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
engines: {node: '>=0.4.0'}
+ /acorn-walk@8.3.5:
+ resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==}
+ engines: {node: '>=0.4.0'}
+ dependencies:
+ acorn: 8.16.0
+
/acorn@8.10.0:
resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==}
engines: {node: '>=0.4.0'}
hasBin: true
+ /acorn@8.16.0:
+ resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
/add-stream@1.0.0:
resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==}
dev: true
@@ -3359,7 +3858,6 @@ packages:
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
- dev: true
/arg@4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
@@ -3684,7 +4182,6 @@ packages:
/binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
- dev: true
/bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
@@ -3747,12 +4244,11 @@ packages:
balanced-match: 1.0.2
dev: true
- /braces@3.0.2:
- resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
+ /braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
dependencies:
- fill-range: 7.0.1
- dev: true
+ fill-range: 7.1.1
/broadcast-channel@3.7.0:
resolution: {integrity: sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==}
@@ -3992,6 +4488,18 @@ packages:
pathval: 1.1.1
type-detect: 4.0.8
+ /chai@4.5.0:
+ resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==}
+ engines: {node: '>=4'}
+ dependencies:
+ assertion-error: 1.1.0
+ check-error: 1.0.3
+ deep-eql: 4.1.3
+ get-func-name: 2.0.2
+ loupe: 2.3.6
+ pathval: 1.1.1
+ type-detect: 4.1.0
+
/chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
@@ -4042,12 +4550,17 @@ packages:
/check-error@1.0.2:
resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
+ /check-error@1.0.3:
+ resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
+ dependencies:
+ get-func-name: 2.0.2
+
/chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
dependencies:
anymatch: 3.1.3
- braces: 3.0.2
+ braces: 3.0.3
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
@@ -4055,7 +4568,6 @@ packages:
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.3
- dev: true
/chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
@@ -4267,6 +4779,9 @@ packages:
typedarray: 0.0.6
dev: true
+ /confbox@0.1.8:
+ resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
+
/config-chain@1.1.13:
resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
dependencies:
@@ -4493,7 +5008,7 @@ packages:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
dev: true
- /cosmiconfig-typescript-loader@4.4.0(@types/node@20.4.7)(cosmiconfig@8.3.2)(ts-node@10.9.1)(typescript@4.9.5):
+ /cosmiconfig-typescript-loader@4.4.0(@types/node@20.4.7)(cosmiconfig@8.3.2)(ts-node@10.9.1)(typescript@5.5.4):
resolution: {integrity: sha512-BabizFdC3wBHhbI4kJh0VkQP9GkBfoHPydD0COMce1nJ1kJAB3F2TmJ/I7diULBKtmEWSwEbuN/KDtgnmUUVmw==}
engines: {node: '>=v14.21.3'}
peerDependencies:
@@ -4504,8 +5019,8 @@ packages:
dependencies:
'@types/node': 20.4.7
cosmiconfig: 8.3.2(typescript@4.9.5)
- ts-node: 10.9.1(@types/node@20.4.7)(typescript@4.9.5)
- typescript: 4.9.5
+ ts-node: 10.9.1(@types/node@20.4.7)(typescript@5.5.4)
+ typescript: 5.5.4
dev: true
/cosmiconfig@8.1.3:
@@ -4573,7 +5088,6 @@ packages:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
- dev: true
/crypto-browserify@3.12.0:
resolution: {integrity: sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==}
@@ -4745,7 +5259,7 @@ packages:
resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
engines: {node: '>=6'}
dependencies:
- type-detect: 4.0.8
+ type-detect: 4.1.0
/deep-equal@2.2.3:
resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==}
@@ -4794,7 +5308,7 @@ packages:
dependencies:
bundle-name: 3.0.0
default-browser-id: 3.0.0
- execa: 7.1.1
+ execa: 7.2.0
titleize: 3.0.0
dev: true
@@ -4888,6 +5402,10 @@ packages:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
dev: true
+ /diff-sequences@29.6.3:
+ resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
/diff@4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
@@ -5489,6 +6007,36 @@ packages:
'@esbuild/win32-ia32': 0.18.20
'@esbuild/win32-x64': 0.18.20
+ /esbuild@0.21.5:
+ resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
+ engines: {node: '>=12'}
+ hasBin: true
+ requiresBuild: true
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.21.5
+ '@esbuild/android-arm': 0.21.5
+ '@esbuild/android-arm64': 0.21.5
+ '@esbuild/android-x64': 0.21.5
+ '@esbuild/darwin-arm64': 0.21.5
+ '@esbuild/darwin-x64': 0.21.5
+ '@esbuild/freebsd-arm64': 0.21.5
+ '@esbuild/freebsd-x64': 0.21.5
+ '@esbuild/linux-arm': 0.21.5
+ '@esbuild/linux-arm64': 0.21.5
+ '@esbuild/linux-ia32': 0.21.5
+ '@esbuild/linux-loong64': 0.21.5
+ '@esbuild/linux-mips64el': 0.21.5
+ '@esbuild/linux-ppc64': 0.21.5
+ '@esbuild/linux-riscv64': 0.21.5
+ '@esbuild/linux-s390x': 0.21.5
+ '@esbuild/linux-x64': 0.21.5
+ '@esbuild/netbsd-x64': 0.21.5
+ '@esbuild/openbsd-x64': 0.21.5
+ '@esbuild/sunos-x64': 0.21.5
+ '@esbuild/win32-arm64': 0.21.5
+ '@esbuild/win32-ia32': 0.21.5
+ '@esbuild/win32-x64': 0.21.5
+
/escalade@3.1.1:
resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
engines: {node: '>=6'}
@@ -5582,7 +6130,7 @@ packages:
eslint-import-resolver-webpack:
optional: true
dependencies:
- '@typescript-eslint/parser': 5.62.0(eslint@8.48.0)(typescript@4.9.5)
+ '@typescript-eslint/parser': 5.62.0(eslint@8.48.0)(typescript@5.5.4)
debug: 3.2.7
eslint: 8.48.0
eslint-import-resolver-node: 0.3.9
@@ -5600,7 +6148,7 @@ packages:
'@typescript-eslint/parser':
optional: true
dependencies:
- '@typescript-eslint/parser': 5.62.0(eslint@8.48.0)(typescript@4.9.5)
+ '@typescript-eslint/parser': 5.62.0(eslint@8.48.0)(typescript@5.5.4)
array-includes: 3.1.6
array.prototype.findlastindex: 1.2.3
array.prototype.flat: 1.3.1
@@ -5836,6 +6384,11 @@ packages:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
dev: true
+ /estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+ dependencies:
+ '@types/estree': 1.0.1
+
/esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@@ -5901,6 +6454,20 @@ packages:
strip-final-newline: 3.0.0
dev: true
+ /execa@8.0.1:
+ resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
+ engines: {node: '>=16.17'}
+ dependencies:
+ cross-spawn: 7.0.3
+ get-stream: 8.0.1
+ human-signals: 5.0.0
+ is-stream: 3.0.0
+ merge-stream: 2.0.0
+ npm-run-path: 5.1.0
+ onetime: 6.0.0
+ signal-exit: 4.1.0
+ strip-final-newline: 3.0.0
+
/extend@3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
dev: false
@@ -5930,7 +6497,7 @@ packages:
'@nodelib/fs.walk': 1.2.8
glob-parent: 5.1.2
merge2: 1.4.1
- micromatch: 4.0.5
+ micromatch: 4.0.8
dev: true
/fast-glob@3.3.1:
@@ -5944,6 +6511,16 @@ packages:
micromatch: 4.0.5
dev: true
+ /fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+ engines: {node: '>=8.6.0'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
/fast-json-stable-stringify@2.1.0:
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
dev: true
@@ -5961,7 +6538,6 @@ packages:
resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==}
dependencies:
reusify: 1.0.4
- dev: true
/fetch-blob@3.2.0:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
@@ -5971,6 +6547,9 @@ packages:
web-streams-polyfill: 3.2.1
dev: true
+ /fflate@0.8.2:
+ resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
+
/figures@3.2.0:
resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==}
engines: {node: '>=8'}
@@ -6013,12 +6592,11 @@ packages:
trim-repeated: 1.0.0
dev: true
- /fill-range@7.0.1:
- resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
+ /fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
dependencies:
to-regex-range: 5.0.1
- dev: true
/find-cache-dir@3.3.2:
resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
@@ -6056,7 +6634,7 @@ packages:
resolution: {integrity: sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==}
engines: {node: '>=12.0.0'}
dependencies:
- flatted: 3.2.7
+ flatted: 3.4.2
keyv: 4.5.3
rimraf: 3.0.2
dev: true
@@ -6066,9 +6644,8 @@ packages:
hasBin: true
dev: true
- /flatted@3.2.7:
- resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==}
- dev: true
+ /flatted@3.4.2:
+ resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
/follow-redirects@1.15.2:
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
@@ -6208,6 +6785,9 @@ packages:
/get-func-name@2.0.0:
resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
+ /get-func-name@2.0.2:
+ resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
+
/get-intrinsic@1.2.1:
resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==}
dependencies:
@@ -6244,6 +6824,10 @@ packages:
engines: {node: '>=10'}
dev: true
+ /get-stream@8.0.1:
+ resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
+ engines: {node: '>=16'}
+
/get-symbol-description@1.0.0:
resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
engines: {node: '>= 0.4'}
@@ -6340,7 +6924,6 @@ packages:
engines: {node: '>= 6'}
dependencies:
is-glob: 4.0.3
- dev: true
/glob-parent@6.0.2:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
@@ -6355,6 +6938,7 @@ packages:
/glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
dependencies:
foreground-child: 3.2.1
@@ -6389,6 +6973,7 @@ packages:
/glob@8.1.0:
resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
engines: {node: '>=12'}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
@@ -6436,7 +7021,7 @@ packages:
dependencies:
array-union: 2.1.0
dir-glob: 3.0.1
- fast-glob: 3.3.1
+ fast-glob: 3.3.3
ignore: 5.2.4
merge2: 1.4.1
slash: 3.0.0
@@ -6447,7 +7032,7 @@ packages:
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
dir-glob: 3.0.1
- fast-glob: 3.3.1
+ fast-glob: 3.3.3
ignore: 5.2.4
merge2: 1.4.1
slash: 4.0.0
@@ -6772,6 +7357,10 @@ packages:
dependencies:
whatwg-encoding: 2.0.0
+ /html-escaper@2.0.2:
+ resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+ dev: true
+
/html-tags@3.3.1:
resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
engines: {node: '>=8'}
@@ -6846,6 +7435,10 @@ packages:
engines: {node: '>=14.18.0'}
dev: true
+ /human-signals@5.0.0:
+ resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
+ engines: {node: '>=16.17.0'}
+
/husky@8.0.3:
resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==}
engines: {node: '>=14'}
@@ -7038,7 +7631,6 @@ packages:
engines: {node: '>=8'}
dependencies:
binary-extensions: 2.3.0
- dev: true
/is-boolean-object@1.1.2:
resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==}
@@ -7110,7 +7702,6 @@ packages:
/is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
- dev: true
/is-finalizationregistry@1.0.2:
resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==}
@@ -7138,7 +7729,6 @@ packages:
engines: {node: '>=0.10.0'}
dependencies:
is-extglob: 2.1.1
- dev: true
/is-hexadecimal@2.0.1:
resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==}
@@ -7212,7 +7802,6 @@ packages:
/is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
- dev: true
/is-obj@2.0.0:
resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
@@ -7293,7 +7882,6 @@ packages:
/is-stream@3.0.0:
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- dev: true
/is-string@1.0.7:
resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
@@ -7384,7 +7972,6 @@ packages:
/isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
- dev: true
/isobject@3.0.1:
resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
@@ -7407,6 +7994,39 @@ packages:
lodash.uniqby: 4.7.0
dev: true
+ /istanbul-lib-coverage@3.2.2:
+ resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /istanbul-lib-report@3.0.1:
+ resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
+ engines: {node: '>=10'}
+ dependencies:
+ istanbul-lib-coverage: 3.2.2
+ make-dir: 4.0.0
+ supports-color: 7.2.0
+ dev: true
+
+ /istanbul-lib-source-maps@5.0.6:
+ resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==}
+ engines: {node: '>=10'}
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.25
+ debug: 4.3.4
+ istanbul-lib-coverage: 3.2.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /istanbul-reports@3.2.0:
+ resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
+ engines: {node: '>=8'}
+ dependencies:
+ html-escaper: 2.0.2
+ istanbul-lib-report: 3.0.1
+ dev: true
+
/iterate-iterator@1.0.2:
resolution: {integrity: sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw==}
dev: true
@@ -7483,6 +8103,9 @@ packages:
/js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+ /js-tokens@9.0.1:
+ resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
+
/js-yaml@3.14.1:
resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
hasBin: true
@@ -7547,7 +8170,6 @@ packages:
resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
engines: {node: '>=4'}
hasBin: true
- dev: true
/jsesc@3.0.2:
resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==}
@@ -7604,6 +8226,7 @@ packages:
/jsonc-parser@3.2.0:
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
+ dev: true
/jsonfile@4.0.0:
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
@@ -7758,6 +8381,13 @@ packages:
resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
engines: {node: '>=14'}
+ /local-pkg@0.5.1:
+ resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==}
+ engines: {node: '>=14'}
+ dependencies:
+ mlly: 1.8.2
+ pkg-types: 1.3.1
+
/locate-path@2.0.0:
resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==}
engines: {node: '>=4'}
@@ -7890,8 +8520,14 @@ packages:
/loupe@2.3.6:
resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==}
+ deprecated: Please upgrade to 2.3.7 which fixes GHSA-4q6p-r6v2-jvc5
dependencies:
- get-func-name: 2.0.0
+ get-func-name: 2.0.2
+
+ /loupe@2.3.7:
+ resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
+ dependencies:
+ get-func-name: 2.0.2
/lowercase-keys@3.0.0:
resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==}
@@ -7929,14 +8565,20 @@ packages:
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
engines: {node: '>=12'}
dependencies:
- '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/sourcemap-codec': 1.5.5
dev: true
- /magic-string@0.30.3:
- resolution: {integrity: sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==}
- engines: {node: '>=12'}
+ /magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
dependencies:
- '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ /magicast@0.3.5:
+ resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
+ dependencies:
+ '@babel/parser': 7.29.2
+ '@babel/types': 7.29.0
+ source-map-js: 1.2.1
dev: true
/make-dir@3.1.0:
@@ -7946,6 +8588,13 @@ packages:
semver: 6.3.1
dev: true
+ /make-dir@4.0.0:
+ resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
+ engines: {node: '>=10'}
+ dependencies:
+ semver: 7.5.4
+ dev: true
+
/make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
@@ -8008,12 +8657,10 @@ packages:
/merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
- dev: true
/merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
- dev: true
/micromark-util-character@2.1.0:
resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==}
@@ -8046,10 +8693,17 @@ packages:
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
engines: {node: '>=8.6'}
dependencies:
- braces: 3.0.2
+ braces: 3.0.3
picomatch: 2.3.1
dev: true
+ /micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.1
+
/microseconds@0.2.0:
resolution: {integrity: sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==}
dev: false
@@ -8080,7 +8734,6 @@ packages:
/mimic-fn@4.0.0:
resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==}
engines: {node: '>=12'}
- dev: true
/mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
@@ -8130,13 +8783,6 @@ packages:
brace-expansion: 2.0.1
dev: true
- /minimatch@9.0.3:
- resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
- engines: {node: '>=16 || 14 >=14.17'}
- dependencies:
- brace-expansion: 2.0.1
- dev: true
-
/minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
@@ -8186,10 +8832,19 @@ packages:
/mlly@1.4.1:
resolution: {integrity: sha512-SCDs78Q2o09jiZiE2WziwVBEqXQ02XkGdUy45cbJf+BpYRIjArXRJ1Wbowxkb+NaM9DWvS3UC9GiO/6eqvQ/pg==}
dependencies:
- acorn: 8.10.0
+ acorn: 8.16.0
pathe: 1.1.1
- pkg-types: 1.0.3
- ufo: 1.3.0
+ pkg-types: 1.3.1
+ ufo: 1.6.3
+ dev: true
+
+ /mlly@1.8.2:
+ resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
+ dependencies:
+ acorn: 8.16.0
+ pathe: 2.0.3
+ pkg-types: 1.3.1
+ ufo: 1.6.3
/modify-values@1.0.1:
resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==}
@@ -8207,6 +8862,11 @@ packages:
/mrmime@1.0.1:
resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==}
engines: {node: '>=10'}
+ dev: true
+
+ /mrmime@2.0.1:
+ resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
+ engines: {node: '>=10'}
/ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
@@ -8238,6 +8898,11 @@ packages:
big-integer: 1.6.51
dev: false
+ /nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
/nanoid@3.3.6:
resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -8377,7 +9042,6 @@ packages:
/normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
- dev: true
/normalize-range@0.1.2:
resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==}
@@ -8401,7 +9065,6 @@ packages:
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dependencies:
path-key: 4.0.0
- dev: true
/nwsapi@2.2.7:
resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==}
@@ -8615,7 +9278,6 @@ packages:
engines: {node: '>=12'}
dependencies:
mimic-fn: 4.0.0
- dev: true
/open@8.4.2:
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
@@ -8738,6 +9400,12 @@ packages:
dependencies:
yocto-queue: 1.0.0
+ /p-limit@5.0.0:
+ resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
+ engines: {node: '>=18'}
+ dependencies:
+ yocto-queue: 1.0.0
+
/p-locate@2.0.0:
resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==}
engines: {node: '>=4'}
@@ -8907,12 +9575,10 @@ packages:
/path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
- dev: true
/path-key@4.0.0:
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
engines: {node: '>=12'}
- dev: true
/path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
@@ -8941,6 +9607,9 @@ packages:
/pathe@1.1.1:
resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==}
+ /pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
/pathval@1.1.1:
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
@@ -8960,7 +9629,9 @@ packages:
/picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
- dev: true
+
+ /picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
/picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
@@ -9013,12 +9684,12 @@ packages:
find-up: 5.0.0
dev: true
- /pkg-types@1.0.3:
- resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==}
+ /pkg-types@1.3.1:
+ resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
dependencies:
- jsonc-parser: 3.2.0
- mlly: 1.4.1
- pathe: 1.1.1
+ confbox: 0.1.8
+ mlly: 1.8.2
+ pathe: 2.0.3
/pluralize@8.0.0:
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
@@ -9100,6 +9771,14 @@ packages:
picocolors: 1.0.0
source-map-js: 1.0.2
+ /postcss@8.5.10:
+ resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==}
+ engines: {node: ^10 || ^12 || >=14}
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
/preact@10.17.1:
resolution: {integrity: sha512-X9BODrvQ4Ekwv9GURm9AKAGaomqXmip7NQTZgY7gcNmr7XE83adOMJvd3N42id1tMFU7ojiynRsYnY6/BRFxLA==}
dev: true
@@ -9187,6 +9866,14 @@ packages:
ansi-styles: 5.2.0
react-is: 17.0.2
+ /pretty-format@29.7.0:
+ resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ dependencies:
+ '@jest/schemas': 29.6.3
+ ansi-styles: 5.2.0
+ react-is: 18.3.1
+
/process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
dev: true
@@ -9298,7 +9985,6 @@ packages:
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
- dev: true
/quick-lru@4.0.1:
resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==}
@@ -9864,7 +10550,6 @@ packages:
/react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
- dev: false
/react-query@3.39.3(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==}
@@ -9999,7 +10684,6 @@ packages:
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.1
- dev: true
/rechoir@0.6.2:
resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
@@ -10280,7 +10964,6 @@ packages:
/reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
- dev: true
/rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
@@ -10330,6 +11013,40 @@ packages:
optionalDependencies:
fsevents: 2.3.3
+ /rollup@4.60.2:
+ resolution: {integrity: sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.60.2
+ '@rollup/rollup-android-arm64': 4.60.2
+ '@rollup/rollup-darwin-arm64': 4.60.2
+ '@rollup/rollup-darwin-x64': 4.60.2
+ '@rollup/rollup-freebsd-arm64': 4.60.2
+ '@rollup/rollup-freebsd-x64': 4.60.2
+ '@rollup/rollup-linux-arm-gnueabihf': 4.60.2
+ '@rollup/rollup-linux-arm-musleabihf': 4.60.2
+ '@rollup/rollup-linux-arm64-gnu': 4.60.2
+ '@rollup/rollup-linux-arm64-musl': 4.60.2
+ '@rollup/rollup-linux-loong64-gnu': 4.60.2
+ '@rollup/rollup-linux-loong64-musl': 4.60.2
+ '@rollup/rollup-linux-ppc64-gnu': 4.60.2
+ '@rollup/rollup-linux-ppc64-musl': 4.60.2
+ '@rollup/rollup-linux-riscv64-gnu': 4.60.2
+ '@rollup/rollup-linux-riscv64-musl': 4.60.2
+ '@rollup/rollup-linux-s390x-gnu': 4.60.2
+ '@rollup/rollup-linux-x64-gnu': 4.60.2
+ '@rollup/rollup-linux-x64-musl': 4.60.2
+ '@rollup/rollup-openbsd-x64': 4.60.2
+ '@rollup/rollup-openharmony-arm64': 4.60.2
+ '@rollup/rollup-win32-arm64-msvc': 4.60.2
+ '@rollup/rollup-win32-ia32-msvc': 4.60.2
+ '@rollup/rollup-win32-x64-gnu': 4.60.2
+ '@rollup/rollup-win32-x64-msvc': 4.60.2
+ fsevents: 2.3.3
+
/run-applescript@5.0.0:
resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==}
engines: {node: '>=12'}
@@ -10346,7 +11063,6 @@ packages:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
dependencies:
queue-microtask: 1.2.3
- dev: true
/rxjs@7.8.1:
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
@@ -10540,12 +11256,10 @@ packages:
engines: {node: '>=8'}
dependencies:
shebang-regex: 3.0.0
- dev: true
/shebang-regex@3.0.0:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
- dev: true
/shelljs@0.8.5:
resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==}
@@ -10594,7 +11308,6 @@ packages:
/signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
- dev: true
/sirv@2.0.3:
resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==}
@@ -10603,6 +11316,15 @@ packages:
'@polka/url': 1.0.0-next.21
mrmime: 1.0.1
totalist: 3.0.1
+ dev: true
+
+ /sirv@2.0.4:
+ resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==}
+ engines: {node: '>= 10'}
+ dependencies:
+ '@polka/url': 1.0.0-next.29
+ mrmime: 2.0.1
+ totalist: 3.0.1
/slash@3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
@@ -10649,6 +11371,10 @@ packages:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
+ /source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
/source-map-support@0.5.21:
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
dependencies:
@@ -10714,6 +11440,9 @@ packages:
resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
dev: false
+ /std-env@3.10.0:
+ resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
+
/std-env@3.4.3:
resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==}
@@ -10900,7 +11629,6 @@ packages:
/strip-final-newline@3.0.0:
resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==}
engines: {node: '>=12'}
- dev: true
/strip-indent@3.0.0:
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
@@ -10919,17 +11647,16 @@ packages:
engines: {node: '>=8'}
dev: true
- /strip-literal@0.4.2:
- resolution: {integrity: sha512-pv48ybn4iE1O9RLgCAN0iU4Xv7RlBTiit6DKmMiErbs9x1wH6vXBs45tWc0H5wUIF6TLTrKweqkmYF/iraQKNw==}
- dependencies:
- acorn: 8.10.0
- dev: true
-
/strip-literal@1.3.0:
resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==}
dependencies:
acorn: 8.10.0
+ /strip-literal@2.1.1:
+ resolution: {integrity: sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==}
+ dependencies:
+ js-tokens: 9.0.1
+
/strip-outer@1.0.1:
resolution: {integrity: sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==}
engines: {node: '>=0.10.0'}
@@ -10956,7 +11683,7 @@ packages:
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
dependencies:
- '@jridgewell/gen-mapping': 0.3.3
+ '@jridgewell/gen-mapping': 0.3.5
commander: 4.1.1
glob: 10.4.5
lines-and-columns: 1.2.4
@@ -11073,7 +11800,7 @@ packages:
uglify-js:
optional: true
dependencies:
- '@jridgewell/trace-mapping': 0.3.19
+ '@jridgewell/trace-mapping': 0.3.25
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.1
@@ -11087,11 +11814,20 @@ packages:
hasBin: true
dependencies:
'@jridgewell/source-map': 0.3.5
- acorn: 8.10.0
+ acorn: 8.16.0
commander: 2.20.3
source-map-support: 0.5.21
dev: true
+ /test-exclude@6.0.0:
+ resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
+ engines: {node: '>=8'}
+ dependencies:
+ '@istanbuljs/schema': 0.1.6
+ glob: 7.2.3
+ minimatch: 3.1.2
+ dev: true
+
/text-extensions@1.9.0:
resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==}
engines: {node: '>=0.10'}
@@ -11146,6 +11882,9 @@ packages:
/tinybench@2.5.0:
resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==}
+ /tinybench@2.9.0:
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
/tinypool@0.3.1:
resolution: {integrity: sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==}
engines: {node: '>=14.0.0'}
@@ -11156,10 +11895,18 @@ packages:
engines: {node: '>=14.0.0'}
dev: false
+ /tinypool@0.8.4:
+ resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==}
+ engines: {node: '>=14.0.0'}
+
/tinyspy@1.1.1:
resolution: {integrity: sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==}
engines: {node: '>=14.0.0'}
+ /tinyspy@2.2.1:
+ resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==}
+ engines: {node: '>=14.0.0'}
+
/titleize@3.0.0:
resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==}
engines: {node: '>=12'}
@@ -11182,14 +11929,12 @@ packages:
/to-fast-properties@2.0.0:
resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'}
- dev: true
/to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
dependencies:
is-number: 7.0.0
- dev: true
/toggle-selection@1.0.6:
resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==}
@@ -11279,7 +12024,7 @@ packages:
yn: 3.1.1
dev: true
- /ts-node@10.9.1(@types/node@20.4.7)(typescript@4.9.5):
+ /ts-node@10.9.1(@types/node@20.4.7)(typescript@5.5.4):
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
peerDependencies:
@@ -11305,7 +12050,7 @@ packages:
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
- typescript: 4.9.5
+ typescript: 5.5.4
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
@@ -11378,6 +12123,10 @@ packages:
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
engines: {node: '>=4'}
+ /type-detect@4.1.0:
+ resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==}
+ engines: {node: '>=4'}
+
/type-fest@0.18.1:
resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==}
engines: {node: '>=10'}
@@ -11522,8 +12271,8 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
- /ufo@1.3.0:
- resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==}
+ /ufo@1.6.3:
+ resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
/uglify-js@3.17.4:
resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==}
@@ -11679,7 +12428,7 @@ packages:
dependencies:
browserslist: 4.21.10
escalade: 3.1.1
- picocolors: 1.0.0
+ picocolors: 1.1.1
dev: true
/update-browserslist-db@1.1.0(browserslist@4.23.2):
@@ -11690,7 +12439,7 @@ packages:
dependencies:
browserslist: 4.23.2
escalade: 3.1.2
- picocolors: 1.0.1
+ picocolors: 1.1.1
dev: true
/update-notifier@6.0.2:
@@ -11817,7 +12566,7 @@ packages:
vfile-message: 4.0.2
dev: false
- /vite-node@0.28.5(@types/node@20.4.7):
+ /vite-node@0.28.5(@types/node@18.17.14):
resolution: {integrity: sha512-LmXb9saMGlrMZbXTvOveJKwMTBTNUH66c8rJnQ0ZPNX+myPEol64+szRzXtV5ORb0Hb/91yq+/D3oERoyAt6LA==}
engines: {node: '>=v14.16.0'}
hasBin: true
@@ -11829,7 +12578,7 @@ packages:
picocolors: 1.0.0
source-map: 0.6.1
source-map-support: 0.5.21
- vite: 4.4.9(@types/node@20.4.7)
+ vite: 4.4.9(@types/node@18.17.14)
transitivePeerDependencies:
- '@types/node'
- less
@@ -11848,9 +12597,9 @@ packages:
dependencies:
cac: 6.7.14
debug: 4.3.4
- mlly: 1.4.1
+ mlly: 1.8.2
pathe: 1.1.1
- picocolors: 1.0.0
+ picocolors: 1.0.1
vite: 4.4.9(@types/node@18.17.14)
transitivePeerDependencies:
- '@types/node'
@@ -11863,6 +12612,27 @@ packages:
- terser
dev: false
+ /vite-node@1.6.0(@types/node@18.17.14):
+ resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ dependencies:
+ cac: 6.7.14
+ debug: 4.3.4
+ pathe: 1.1.1
+ picocolors: 1.0.1
+ vite: 5.4.21(@types/node@18.17.14)
+ transitivePeerDependencies:
+ - '@types/node'
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+
/vite-plugin-dts@1.7.3(@types/node@18.17.14)(vite@4.4.9):
resolution: {integrity: sha512-u3t45p6fTbzUPMkwYe0ESwuUeiRMlwdPfD3dRyDKUwLe2WmEYcFyVp2o9/ke2EMrM51lQcmNWdV9eLcgjD1/ng==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -11923,7 +12693,7 @@ packages:
dependencies:
'@types/node': 18.17.14
esbuild: 0.15.18
- postcss: 8.4.29
+ postcss: 8.5.10
resolve: 1.22.4
rollup: 2.79.1
optionalDependencies:
@@ -11965,15 +12735,16 @@ packages:
optionalDependencies:
fsevents: 2.3.3
- /vite@4.4.9(@types/node@20.4.7):
- resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==}
- engines: {node: ^14.18.0 || >=16.0.0}
+ /vite@5.4.21(@types/node@18.17.14):
+ resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==}
+ engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
peerDependencies:
- '@types/node': '>= 14'
+ '@types/node': ^18.0.0 || >=20.0.0
less: '*'
lightningcss: ^1.21.0
sass: '*'
+ sass-embedded: '*'
stylus: '*'
sugarss: '*'
terser: ^5.4.0
@@ -11986,6 +12757,8 @@ packages:
optional: true
sass:
optional: true
+ sass-embedded:
+ optional: true
stylus:
optional: true
sugarss:
@@ -11993,13 +12766,12 @@ packages:
terser:
optional: true
dependencies:
- '@types/node': 20.4.7
- esbuild: 0.18.20
- postcss: 8.4.29
- rollup: 3.28.1
+ '@types/node': 18.17.14
+ esbuild: 0.21.5
+ postcss: 8.5.10
+ rollup: 4.60.2
optionalDependencies:
fsevents: 2.3.3
- dev: true
/vitepress@1.0.0-alpha.49(@algolia/client-search@4.19.1)(@types/node@18.17.14)(search-insights@2.8.1):
resolution: {integrity: sha512-3nUZJow4qL8NHRWYatqqVj45AJDxWst/TuOj+IbQRhxesEswa+Fpwayj9/FxzRzBl665fuiG5y+QeVhOeUm0OA==}
@@ -12030,50 +12802,6 @@ packages:
- terser
dev: true
- /vitest@0.24.5(@vitest/ui@0.24.5)(jsdom@20.0.3):
- resolution: {integrity: sha512-zw6JhPUHtLILQDe5Q39b/SzoITkG+R7hcFjuthp4xsi6zpmfQPOZcHodZ+3bqoWl4EdGK/p1fuMiEwdxgbGLOA==}
- engines: {node: '>=v14.16.0'}
- hasBin: true
- peerDependencies:
- '@edge-runtime/vm': '*'
- '@vitest/browser': '*'
- '@vitest/ui': '*'
- happy-dom: '*'
- jsdom: '*'
- peerDependenciesMeta:
- '@edge-runtime/vm':
- optional: true
- '@vitest/browser':
- optional: true
- '@vitest/ui':
- optional: true
- happy-dom:
- optional: true
- jsdom:
- optional: true
- dependencies:
- '@types/chai': 4.3.5
- '@types/chai-subset': 1.3.3
- '@types/node': 18.17.14
- '@vitest/ui': 0.24.5
- chai: 4.3.8
- debug: 4.3.4
- jsdom: 20.0.3
- local-pkg: 0.4.3
- strip-literal: 0.4.2
- tinybench: 2.5.0
- tinypool: 0.3.1
- tinyspy: 1.1.1
- vite: 3.2.7(@types/node@18.17.14)
- transitivePeerDependencies:
- - less
- - sass
- - stylus
- - sugarss
- - supports-color
- - terser
- dev: true
-
/vitest@0.28.5(@vitest/ui@0.24.5)(jsdom@20.0.3):
resolution: {integrity: sha512-pyCQ+wcAOX7mKMcBNkzDwEHRGqQvHUl0XnoHR+3Pb1hytAHISgSxv9h0gUiSiYtISXUU3rMrKiKzFYDrI6ZIHA==}
engines: {node: '>=v14.16.0'}
@@ -12098,7 +12826,7 @@ packages:
dependencies:
'@types/chai': 4.3.5
'@types/chai-subset': 1.3.3
- '@types/node': 20.4.7
+ '@types/node': 18.17.14
'@vitest/expect': 0.28.5
'@vitest/runner': 0.28.5
'@vitest/spy': 0.28.5
@@ -12119,8 +12847,8 @@ packages:
tinybench: 2.5.0
tinypool: 0.3.1
tinyspy: 1.1.1
- vite: 4.4.9(@types/node@20.4.7)
- vite-node: 0.28.5(@types/node@20.4.7)
+ vite: 4.4.9(@types/node@18.17.14)
+ vite-node: 0.28.5(@types/node@18.17.14)
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less
@@ -12132,7 +12860,7 @@ packages:
- terser
dev: true
- /vitest@0.29.8(@vitest/ui@0.24.5)(jsdom@20.0.3):
+ /vitest@0.29.8(@vitest/ui@1.6.0)(jsdom@20.0.3):
resolution: {integrity: sha512-JIAVi2GK5cvA6awGpH0HvH/gEG9PZ0a/WoxdiV3PmqK+3CjQMf8c+J/Vhv4mdZ2nRyXFw66sAg6qz7VNkaHfDQ==}
engines: {node: '>=v14.16.0'}
hasBin: true
@@ -12169,7 +12897,7 @@ packages:
'@vitest/expect': 0.29.8
'@vitest/runner': 0.29.8
'@vitest/spy': 0.29.8
- '@vitest/ui': 0.24.5
+ '@vitest/ui': 1.6.0(vitest@1.6.0)
'@vitest/utils': 0.29.8
acorn: 8.10.0
acorn-walk: 8.2.0
@@ -12199,6 +12927,64 @@ packages:
- terser
dev: false
+ /vitest@1.6.0(@types/node@18.17.14)(@vitest/ui@1.6.0)(jsdom@20.0.3):
+ resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@types/node': ^18.0.0 || >=20.0.0
+ '@vitest/browser': 1.6.0
+ '@vitest/ui': 1.6.0
+ happy-dom: '*'
+ jsdom: '*'
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+ dependencies:
+ '@types/node': 18.17.14
+ '@vitest/expect': 1.6.0
+ '@vitest/runner': 1.6.0
+ '@vitest/snapshot': 1.6.0
+ '@vitest/spy': 1.6.0
+ '@vitest/ui': 1.6.0(vitest@1.6.0)
+ '@vitest/utils': 1.6.0
+ acorn-walk: 8.3.5
+ chai: 4.5.0
+ debug: 4.3.4
+ execa: 8.0.1
+ jsdom: 20.0.3
+ local-pkg: 0.5.1
+ magic-string: 0.30.21
+ pathe: 1.1.1
+ picocolors: 1.0.1
+ std-env: 3.10.0
+ strip-literal: 2.1.1
+ tinybench: 2.9.0
+ tinypool: 0.8.4
+ vite: 5.4.21(@types/node@18.17.14)
+ vite-node: 1.6.0(@types/node@18.17.14)
+ why-is-node-running: 2.2.2
+ transitivePeerDependencies:
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+
/vm-browserify@1.1.2:
resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==}
dev: true
@@ -12209,8 +12995,8 @@ packages:
deprecated: The library contains critical security issues and should not be used for production! The maintenance of the project has been discontinued. Consider migrating your code to isolated-vm.
hasBin: true
dependencies:
- acorn: 8.10.0
- acorn-walk: 8.2.0
+ acorn: 8.16.0
+ acorn-walk: 8.3.5
dev: true
/vscode-oniguruma@1.7.0:
@@ -12481,7 +13267,6 @@ packages:
hasBin: true
dependencies:
isexe: 2.0.0
- dev: true
/why-is-node-running@2.2.2:
resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==}
@@ -12688,3 +13473,7 @@ packages:
/zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: false
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
diff --git a/scripts/compat-summary.mjs b/scripts/compat-summary.mjs
new file mode 100644
index 0000000..c587e1d
--- /dev/null
+++ b/scripts/compat-summary.mjs
@@ -0,0 +1,104 @@
+#!/usr/bin/env node
+import { readFileSync, existsSync } from "node:fs";
+import { fileURLToPath } from "node:url";
+import { dirname, resolve } from "node:path";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const repoRoot = resolve(__dirname, "..");
+const matrixPath = resolve(repoRoot, "docs/compatibility.md");
+const coveragePath = resolve(repoRoot, "coverage/coverage-summary.json");
+
+function parseMatrix(md) {
+ const sections = [];
+ const lines = md.split(/\r?\n/);
+ let currentSection = null;
+
+ for (const line of lines) {
+ const sectionMatch = line.match(/^##\s+\d+\.\s+(.+?)\s*$/);
+ if (sectionMatch) {
+ currentSection = { name: sectionMatch[1], done: 0, partial: 0, missing: 0, total: 0 };
+ sections.push(currentSection);
+ continue;
+ }
+ if (!currentSection) continue;
+ if (!line.startsWith("|")) continue;
+ if (line.includes("---")) continue;
+ if (/^\|\s*Feature/i.test(line) || /^\|\s*JS/i.test(line)) continue;
+
+ const cells = line.split("|").map((c) => c.trim());
+ const cellJoined = cells.join(" ");
+ if (!/✅|🟡|❌/.test(cellJoined)) continue;
+
+ const doneCount = (cellJoined.match(/✅/g) || []).length;
+ const partialCount = (cellJoined.match(/🟡/g) || []).length;
+ const missingCount = (cellJoined.match(/❌/g) || []).length;
+
+ if (doneCount > 0 && partialCount === 0 && missingCount === 0) {
+ currentSection.done++;
+ } else if (missingCount > 0 && doneCount === 0 && partialCount === 0) {
+ currentSection.missing++;
+ } else {
+ currentSection.partial++;
+ }
+ currentSection.total++;
+ }
+
+ return sections;
+}
+
+function formatPct(part, total) {
+ if (total === 0) return "—";
+ return `${((part / total) * 100).toFixed(1)}%`;
+}
+
+function main() {
+ if (!existsSync(matrixPath)) {
+ console.error(`Không tìm thấy ${matrixPath}`);
+ process.exit(1);
+ }
+
+ const md = readFileSync(matrixPath, "utf8");
+ const sections = parseMatrix(md);
+
+ const totals = sections.reduce(
+ (acc, s) => ({
+ done: acc.done + s.done,
+ partial: acc.partial + s.partial,
+ missing: acc.missing + s.missing,
+ total: acc.total + s.total,
+ }),
+ { done: 0, partial: 0, missing: 0, total: 0 },
+ );
+
+ let out = "";
+ out += "## VietScript — Compatibility summary\n\n";
+ out += `**Tổng feature tracked:** ${totals.total}\n\n`;
+ out += `- ✅ Hoàn thiện: **${totals.done}** (${formatPct(totals.done, totals.total)})\n`;
+ out += `- 🟡 Một phần: **${totals.partial}** (${formatPct(totals.partial, totals.total)})\n`;
+ out += `- ❌ Chưa có: **${totals.missing}** (${formatPct(totals.missing, totals.total)})\n\n`;
+
+ out += "### Theo từng nhóm\n\n";
+ out += "| Nhóm | ✅ | 🟡 | ❌ | Tổng | % hoàn thiện |\n";
+ out += "|---|---:|---:|---:|---:|---:|\n";
+ for (const s of sections) {
+ out += `| ${s.name} | ${s.done} | ${s.partial} | ${s.missing} | ${s.total} | ${formatPct(s.done, s.total)} |\n`;
+ }
+
+ if (existsSync(coveragePath)) {
+ const cov = JSON.parse(readFileSync(coveragePath, "utf8"));
+ const t = cov.total;
+ out += "\n### Test coverage\n\n";
+ out += "| Metric | % | Covered / Total |\n";
+ out += "|---|---:|---:|\n";
+ out += `| Statements | ${t.statements.pct}% | ${t.statements.covered}/${t.statements.total} |\n`;
+ out += `| Branches | ${t.branches.pct}% | ${t.branches.covered}/${t.branches.total} |\n`;
+ out += `| Functions | ${t.functions.pct}% | ${t.functions.covered}/${t.functions.total} |\n`;
+ out += `| Lines | ${t.lines.pct}% | ${t.lines.covered}/${t.lines.total} |\n`;
+ } else {
+ out += "\n_Coverage chưa được generate. Chạy `pnpm test:coverage` trước._\n";
+ }
+
+ process.stdout.write(out);
+}
+
+main();
diff --git a/scripts/fix-mangled-names.mjs b/scripts/fix-mangled-names.mjs
new file mode 100644
index 0000000..4c65ff9
--- /dev/null
+++ b/scripts/fix-mangled-names.mjs
@@ -0,0 +1,64 @@
+#!/usr/bin/env node
+import { readFileSync, writeFileSync } from "node:fs";
+import { execSync } from "node:child_process";
+
+const files = execSync('git ls-files "packages/parser/src/**/*.test.ts" "packages/parser/src/**/*.snap"')
+ .toString()
+ .trim()
+ .split("\n");
+
+function decodeMangled(name) {
+ return name.replace(/(\d+)/g, (_, digits) => {
+ let out = "";
+ let i = 0;
+ while (i < digits.length) {
+ const ch = digits[i];
+ let consumed = 0;
+ let code = 0;
+ if (ch === "7") {
+ const four = Number(digits.slice(i, i + 4));
+ if (four >= 7840 && four <= 7929) {
+ code = four;
+ consumed = 4;
+ }
+ }
+ if (!consumed) {
+ const three = Number(digits.slice(i, i + 3));
+ if (three >= 224 && three <= 511) {
+ code = three;
+ consumed = 3;
+ }
+ }
+ if (consumed) {
+ out += String.fromCodePoint(code);
+ i += consumed;
+ } else {
+ out += digits[i];
+ i++;
+ }
+ }
+ return out;
+ });
+}
+
+let changedCount = 0;
+for (const file of files) {
+ if (!file) continue;
+ const content = readFileSync(file, "utf8");
+ let changed = content;
+
+ changed = changed.replace(/_[a-zA-Z\u00C0-\u1EF90-9][a-zA-Z\u00C0-\u1EF9_0-9]*/g, (match) => {
+ const inner = match.slice(1);
+ if (!/\d/.test(inner)) return match;
+ const decoded = decodeMangled(inner);
+ return decoded;
+ });
+
+ if (changed !== content) {
+ writeFileSync(file, changed);
+ changedCount++;
+ console.log(`Updated: ${file}`);
+ }
+}
+
+console.log(`\nTotal files changed: ${changedCount}`);
diff --git a/vitest.config.ts b/vitest.config.ts
index 622ae06..30c58ec 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -15,5 +15,18 @@ export default defineConfig({
globals: true,
environment: "jsdom",
testTimeout: 10_000,
+ coverage: {
+ provider: "v8",
+ reporter: ["text", "html", "json-summary"],
+ include: ["packages/parser/src/**", "packages/shared/**"],
+ exclude: [
+ "**/__test__/**",
+ "**/*.test.ts",
+ "**/*.spec.ts",
+ "**/dist/**",
+ "**/node_modules/**",
+ ],
+ reportsDirectory: "./coverage",
+ },
},
});