├── .nvmrc
├── dist
└── .keep
├── .prettierignore
├── src
├── tests
│ ├── fixtures
│ │ ├── syntax
│ │ │ ├── export-class.js
│ │ │ ├── number.js
│ │ │ ├── export-function.js
│ │ │ ├── await.js
│ │ │ ├── debugger.js
│ │ │ ├── array.js
│ │ │ ├── sequence.js
│ │ │ ├── literal.js
│ │ │ ├── error.js
│ │ │ ├── call.js
│ │ │ ├── variable.js
│ │ │ ├── export.js
│ │ │ ├── import.js
│ │ │ ├── template.js
│ │ │ ├── object.js
│ │ │ ├── operators.js
│ │ │ ├── loop.js
│ │ │ ├── control.js
│ │ │ ├── function.js
│ │ │ ├── class.js
│ │ │ └── precedence.js
│ │ ├── other
│ │ │ └── a.json
│ │ ├── deprecated
│ │ │ └── es5.js
│ │ ├── comment
│ │ │ ├── block.js
│ │ │ └── line.js
│ │ └── tree
│ │ │ └── es6.js
│ ├── tools.js
│ ├── performance.js
│ ├── scripts.js
│ ├── benchmark.js
│ └── astring.js
└── astring.js
├── .github
├── dependencies.yml
├── dependabot.yml
└── workflows
│ ├── dependencies.yml
│ └── tests.yml
├── .gitignore
├── .eslintignore
├── docs
├── index.html
├── ISSUE_TEMPLATE.md
├── demo
│ ├── style.css
│ ├── index.html
│ ├── astring.min.js
│ └── astring.min.js.map
└── CONTRIBUTING.md
├── deno.json
├── .npmignore
├── babel.config.js
├── astring.sublime-project
├── LICENSE
├── .eslintrc.json
├── astring.d.ts
├── bin
└── astring
├── package.json
├── README.md
└── CHANGELOG.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | 12
2 |
--------------------------------------------------------------------------------
/dist/.keep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | src/tests/fixtures/**/*.js
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/export-class.js:
--------------------------------------------------------------------------------
1 | export default class {}
2 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/number.js:
--------------------------------------------------------------------------------
1 | let a;
2 | a = 1n;
3 | a = 1;
4 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/export-function.js:
--------------------------------------------------------------------------------
1 | export default function () {}
2 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/await.js:
--------------------------------------------------------------------------------
1 | async function f() {
2 | await (() => 1);
3 | }
4 |
--------------------------------------------------------------------------------
/src/tests/fixtures/other/a.json:
--------------------------------------------------------------------------------
1 | {
2 | "content": "For testing import-attributes"
3 | }
--------------------------------------------------------------------------------
/.github/dependencies.yml:
--------------------------------------------------------------------------------
1 | - match:
2 | dependency_type: all
3 | update_type: 'semver:minor'
4 |
--------------------------------------------------------------------------------
/src/tests/fixtures/deprecated/es5.js:
--------------------------------------------------------------------------------
1 | var a = {};
2 | with (a) {
3 | b = 1;
4 | c = 2;
5 | }
6 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/debugger.js:
--------------------------------------------------------------------------------
1 | debugger;
2 | "this string should be mapped to the second line";
3 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/array.js:
--------------------------------------------------------------------------------
1 | let a = [];
2 | let b = [42];
3 | let c = [42, 7];
4 | let [d, ...e] = [1, 2, 3, 4, 5];
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.DS_Store
2 | *.sublime-workspace
3 | test/_*.js
4 | .nyc_output
5 | coverage
6 | dist
7 | local
8 | node_modules
9 | npm-debug.log
10 | .nova/
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/sequence.js:
--------------------------------------------------------------------------------
1 | let a = (x => (x, x * 2), 3);
2 | let b = ((x, y) => (x, x * y), 1);
3 | let c = (x => x * x)(2);
4 | let d = (1, 2, 3);
5 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/literal.js:
--------------------------------------------------------------------------------
1 | const a = Infinity;
2 | const b = -Infinity;
3 | const c = +Infinity;
4 | const d = /abc/;
5 | const e = /abc/g;
6 | const f = /abc/gi;
7 |
--------------------------------------------------------------------------------
/src/tests/tools.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import normalizeNewline from 'normalize-newline'
3 |
4 | export function readFile(filePath) {
5 | return normalizeNewline(fs.readFileSync(filePath, 'utf8'))
6 | }
7 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | local/*
3 | vendor/*
4 | dist/*
5 | src/tests/fixtures/deprecated/es5.js
6 | src/tests/fixtures/syntax/import.js
7 | src/tests/fixtures/syntax/export.js
8 | src/tests/fixtures/tree/es6.js
9 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/error.js:
--------------------------------------------------------------------------------
1 | try {
2 | let a = 42;
3 | } catch (error) {
4 | let b = error;
5 | }
6 | try {
7 | let a = 42;
8 | } catch (error) {
9 | let b = error;
10 | } finally {
11 | let c = "done";
12 | }
13 | throw new Error("this is an error");
14 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/call.js:
--------------------------------------------------------------------------------
1 | f();
2 | g(a);
3 | h(a, b);
4 | i(a, b, ...c);
5 | j(...a);
6 | a.k();
7 | (a + b).l();
8 | a.m().n();
9 | new A();
10 | new A(a);
11 | new a.B();
12 | new a.b.C();
13 | new (a().B)();
14 | new A().b();
15 | new new A()();
16 | new (A, B)();
17 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/variable.js:
--------------------------------------------------------------------------------
1 | var a;
2 | let b = 42;
3 | const c = 21 * 2;
4 | let {e} = d;
5 | var f, g = 42, h = false;
6 | let {i, j: k} = l;
7 | let [m, n] = c;
8 | var {a: [o, {p}]} = d;
9 | let {q = 42} = f;
10 | const {g: r = 42} = i;
11 | const {s, ...t} = r;
12 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Astring
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@davidbonnet/astring",
3 | "version": "1.8.6",
4 | "exports": "./src/astring.js",
5 | "publish": {
6 | "include": [
7 | "LICENSE",
8 | "README.md",
9 | "src/astring.js",
10 | "astring.d.ts",
11 | "deno.json"
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | *.sublime-project
2 | *.sublime-workspace
3 | .nova
4 | .eslintignore
5 | .gitignore
6 | .npmignore
7 | .nyc_output
8 | .travis.yml
9 | .prettierignore
10 | .nvmrc
11 | .github
12 | CHANGELOG.md
13 | coverage/
14 | dist/.keep
15 | docs/
16 | local/
17 | node_modules/
18 | package-lock.json
19 | src/
20 | babel.config.js
21 | *.DS_Store
--------------------------------------------------------------------------------
/docs/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | # Motivation
2 |
3 | _Describe cases that motivate the implementation of the expected behavior._
4 |
5 | # Expected behavior
6 |
7 | _Describe how the feature should work._
8 |
9 | # Actual behavior
10 |
11 | _Describe the actual behavior of the feature or remove this section if the feature has not been implemented yet._
12 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/export.js:
--------------------------------------------------------------------------------
1 | export * from "module";
2 | export * as m from "module";
3 | export {name} from "module";
4 | export {a as b, c as d} from "module";
5 | let e, g;
6 | export {e as f, g as h};
7 | export {};
8 | export default i = 42;
9 | export var j = 42;
10 | export let k = 42;
11 | export function l() {}
12 | export {val} from '../other/a.json' with { type: "json" };
13 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: monthly
7 | time: "12:00"
8 | open-pull-requests-limit: 10
9 | versioning-strategy: increase
10 | ignore:
11 | - dependency-name: "@babel/parser"
12 | versions:
13 | - 7.12.13
14 | - 7.13.10
15 | - dependency-name: "@babel/generator"
16 | versions:
17 | - 7.12.13
18 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/import.js:
--------------------------------------------------------------------------------
1 | import a from "module";
2 | import b, * as c from "module";
3 | import * as d from "module";
4 | import e, {f as g, h as i, j} from "module";
5 | import {k as l, m} from "module";
6 | import {n, o as p} from "module";
7 | import * as q from "../other/a.json" with { type: "json" };
8 | import r from "../other/a.json" with { type: "json" };
9 | import("module");
10 | const x = import("module");
11 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/template.js:
--------------------------------------------------------------------------------
1 | let a;
2 | let b = `this is a template`;
3 | let c = `this is a template\
4 | with multiple
5 | lines`;
6 | let d = f`template with function`;
7 | let e = f`template with ${some} ${variables}`;
8 | let f = f`template with ${some}${variables}${attached}`;
9 | let g = (f())`template with function call before`;
10 | let h = (f().g)`template with more complex function call`;
11 | let i = (() => {})`test`;
12 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/object.js:
--------------------------------------------------------------------------------
1 | let a = {};
2 | let b = {
3 | "1": "one",
4 | "2": "two",
5 | "3": "three"
6 | };
7 | let c = {
8 | [42]: "answer",
9 | [7]: "lucky"
10 | };
11 | let d = {
12 | a: 1,
13 | b: 2,
14 | c: 3
15 | };
16 | let e = d.a;
17 | let f = d["c"];
18 | let g = {
19 | m() {},
20 | ['n'](a) {},
21 | o(a) {
22 | return a;
23 | }
24 | };
25 | let h = ({}).toString();
26 | let i = {
27 | ...d,
28 | a
29 | };
30 | a?.['b']?.[0]?.(1);
31 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/operators.js:
--------------------------------------------------------------------------------
1 | let a, b, c, d;
2 | a = a || (a = 2);
3 | a ||= 2;
4 | a = a && (a = 2);
5 | a &&= 2;
6 | a = a ?? (a = 2);
7 | a ??= 2;
8 | b = 1 + 2;
9 | b = 1 - 2;
10 | b = 1 / 2;
11 | b = 1 * 2;
12 | c = 2 ** 1;
13 | c = 1 % 2;
14 | d = 1 << 2;
15 | d = 1 >> 2;
16 | d = 1 >>> 2;
17 | d = 1 & 3;
18 | d = 1 ^ 3;
19 | d = 1 | 3;
20 | a == b;
21 | a === b;
22 | a != b;
23 | a !== b;
24 | a > b;
25 | a >= b;
26 | a < b;
27 | a <= b;
28 | ++a;
29 | a++;
30 | +a;
31 | -a;
32 | --a;
33 | a--;
34 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/loop.js:
--------------------------------------------------------------------------------
1 | for (let a in b) {}
2 | for (let [a, b] in c) {}
3 | for (let {a, b} in c) {}
4 | for (let {a: b, c} in d) {}
5 | for (let a of b) {}
6 | for (var [a, b] of c) {}
7 | for (let {a, b} in c) {}
8 | for (let {a: b, c} in d) {}
9 | for (let i = 0, {length} = list; i < length; i++) {}
10 | for (; ; ) {}
11 | for (function () {
12 | const i = 0;
13 | }; ; ) {}
14 | for (() => {
15 | const i = 0;
16 | }; ; ) {}
17 | async function test() {
18 | for await (const x of xs) {
19 | x();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/control.js:
--------------------------------------------------------------------------------
1 | if (a > b) {} else {}
2 | if (c != d) {}
3 | var a = b > c ? d : e;
4 | let b = (c = 1) ? d : e;
5 | switch (a) {
6 | case b:
7 | break;
8 | case "c":
9 | break;
10 | case 42:
11 | break;
12 | case d:
13 | if (a < b) {}
14 | break;
15 | default:
16 | break;
17 | }
18 | while (a > b) {
19 | if (c == d) {
20 | break;
21 | }
22 | }
23 | do {
24 | if (e === f) {
25 | continue;
26 | }
27 | } while (g < h);
28 | label: if (a === b) {
29 | if (b = c) {
30 | break label;
31 | }
32 | }
33 | if (a != b) {}
34 | endingLabel: {}
35 |
--------------------------------------------------------------------------------
/.github/workflows/dependencies.yml:
--------------------------------------------------------------------------------
1 | name: Dependencies
2 |
3 | on:
4 | pull_request_target:
5 | types: [opened, synchronize, reopened, ready_for_review]
6 | branches: [master]
7 |
8 | workflow_dispatch:
9 |
10 | jobs:
11 | merge:
12 | name: Merge
13 | runs-on: ubuntu-latest
14 | if: ${{ github.actor == 'dependabot[bot]' }}
15 |
16 | steps:
17 | - uses: actions/checkout@v2
18 | - uses: ahmadnassri/action-dependabot-auto-merge@v2
19 | with:
20 | target: minor
21 | github-token: ${{ secrets.ACCESS_TOKEN }}
22 | config: .github/dependencies.yml
23 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | branches: [main]
8 |
9 | jobs:
10 | test:
11 | name: Test
12 | runs-on: ubuntu-latest
13 | strategy:
14 | matrix:
15 | node-version: [14, 16]
16 | steps:
17 | - uses: actions/checkout@v2
18 | - name: Use Node.js ${{ matrix.node-version }}
19 | uses: actions/setup-node@v2
20 | with:
21 | node-version: ${{ matrix.node-version }}
22 | cache: 'npm'
23 | - run: npm ci
24 | - run: npm test
25 | - run: npx codecov --file=./coverage/lcov.info
26 |
--------------------------------------------------------------------------------
/src/tests/fixtures/comment/block.js:
--------------------------------------------------------------------------------
1 | /*
2 | Block comment for this script file.
3 | */
4 | /*
5 | Block comment for function f.
6 | */
7 | function f() {}
8 | /**
9 | * JSDoc block comment.
10 | * @param {string} a - Some string to test
11 | */
12 | function g(a) {
13 | return a;
14 | }
15 | /*
16 | Block comment for function g.
17 | */
18 | function h() {
19 | /*
20 | Block comment inside of function g.
21 | */
22 | instruction1;
23 | /*
24 | Block comment for instruction 2 with a quote:
25 | > What was that?
26 | */
27 | instruction2;
28 | /*
29 | Block comment for instruction 3…
30 | …with several indented lines.
31 | */
32 | instruction3;
33 | /*
34 | Trailing block comment inside of function g.
35 | */
36 | }
37 | /*
38 | Trailing block comment of this script file.
39 | */
40 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = (api) => {
2 | api.cache.never()
3 | switch (process.env.BABEL_MODE) {
4 | case 'minified':
5 | return {
6 | presets: [
7 | [
8 | '@babel/preset-env',
9 | {
10 | forceAllTransforms: true,
11 | modules: 'umd',
12 | },
13 | ],
14 | [
15 | 'minify',
16 | {
17 | mangle: {
18 | blacklist: {
19 | generate: true,
20 | baseGenerator: true,
21 | },
22 | },
23 | },
24 | ],
25 | ],
26 | }
27 | default:
28 | return {
29 | presets: [
30 | [
31 | '@babel/preset-env',
32 | {
33 | forceAllTransforms: true,
34 | },
35 | ],
36 | ],
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/astring.sublime-project:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": ".",
5 | "folder_exclude_patterns": ["node_modules", "coverage", ".nyc_output"]
6 | }
7 | ],
8 | "settings": {
9 | "folder_exclude_patterns": [".git", "node_modules", "dist"],
10 | "tab_size": 2,
11 | "js_prettier": {
12 | "auto_format_on_save": true,
13 | "auto_format_on_save_excludes": [
14 | "*/dist/*",
15 | "*/fixtures/*",
16 | "*/node_modules/*",
17 | "*/.git/*"
18 | ],
19 | "allow_inline_formatting": true,
20 | "custom_file_extensions": ["sublime-project"],
21 | "additional_cli_args": {
22 | "--config": "package.json",
23 | "--config-precedence": "prefer-file",
24 | "--parser": "babel"
25 | }
26 | }
27 | },
28 | "SublimeLinter": {
29 | "linters": {
30 | "eslint": {
31 | "disable": false
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/function.js:
--------------------------------------------------------------------------------
1 | function f(a, b, c) {
2 | return null;
3 | }
4 | var g = function (a, b, c) {
5 | return null;
6 | };
7 | function h(a, b = 1, c = 2) {
8 | return null;
9 | }
10 | function i(a = 1, b, c) {
11 | return null;
12 | }
13 | function j(...a) {}
14 | function k() {}
15 | var l = function () {};
16 | var m = function (a = 1, b, c) {};
17 | function* o() {
18 | yield 42;
19 | }
20 | function* p() {
21 | yield 42;
22 | yield 4 + 2;
23 | return "answer";
24 | }
25 | async function a() {}
26 | const b = async function () {};
27 | const c = {
28 | async f() {}
29 | };
30 | const d = async () => {};
31 | async function e() {
32 | await a + await b;
33 | return await f();
34 | }
35 | let q = function* () {};
36 | let r = a => a;
37 | let s = (a, b) => a + b;
38 | let t = (a, b = 0) => a + b;
39 | let u = (a, b) => {};
40 | let v = () => {};
41 | let w = () => ({});
42 | let x = () => {
43 | let a = 42;
44 | return a;
45 | };
46 | let y = () => ({
47 | a: 1,
48 | b: 2
49 | });
50 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, David Bonnet
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/src/tests/fixtures/comment/line.js:
--------------------------------------------------------------------------------
1 | function f() {}
2 | // Line comment for function g:
3 | function g() {
4 | // Line comment for instruction 1:
5 | instruction1;
6 | // Line comment for instruction 2:
7 | instruction2;
8 | instruction3;
9 | // Line comment for instruction 4:
10 | instruction4;
11 | instruction5;
12 | switch (value) {
13 | // Line comment for case 1:
14 | case 1:
15 | break;
16 | // Line comment for case 2:
17 | case 2:
18 | // Line comment for break:
19 | break;
20 | // Line comment for default:
21 | default:
22 | break;
23 | }
24 | // Comment above object
25 | var point = {
26 | // More line comments
27 | // Line comment for property x:
28 | x: 0,
29 | // Line comment for property y:
30 | y: 1
31 | // Trailing line comment of point object
32 | };
33 | // Comment after object
34 | // Comments before empty object
35 | var object = {};
36 | instruction6;
37 | // Several line comments
38 | // …for…
39 | // …instruction 7:
40 | instruction7;
41 | // Trailing line comment
42 | // Another comment
43 | }
44 | // Last line comment
45 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/class.js:
--------------------------------------------------------------------------------
1 | class A {
2 | constructor() {}
3 | }
4 | class B extends A {}
5 | class C extends A {
6 | method() {}
7 | get property() {
8 | return this._property;
9 | }
10 | set property(value) {
11 | this._property = value;
12 | }
13 | }
14 | class D extends class A {} {}
15 | class E extends class {
16 | constructor() {}
17 | } {}
18 | class F extends class {
19 | constructor() {}
20 | } {
21 | constructor() {}
22 | }
23 | class G {
24 | [Symbol.iterator]() {}
25 | ["method"]() {}
26 | }
27 | class H {
28 | static classMethod() {}
29 | method() {}
30 | }
31 | class I {
32 | static get property() {}
33 | static set property(value) {}
34 | }
35 | class J extends A {
36 | constructor() {
37 | super();
38 | }
39 | }
40 | class K extends (() => {}) {}
41 | class L extends (1 + 1) {}
42 | class M extends (-1) {}
43 | class N extends (c++) {}
44 | function* a() {
45 | class A extends (yield) {}
46 | }
47 | async function b() {
48 | class A extends (await a()) {}
49 | }
50 | const e = 'test';
51 | class O {
52 | #a = 42;
53 | #b() {}
54 | static #c = 'C';
55 | #d;
56 | static {
57 | const a = 3;
58 | }
59 | static [e] = 123;
60 | }
61 |
--------------------------------------------------------------------------------
/docs/demo/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Helvetica, sans-serif;
3 | }
4 |
5 | #console,
6 | #output {
7 | font-family: Menlo, "Bitstream Vera Sans Mono", Courier, monospace;
8 | font-size: 12px;
9 | -webkit-border-radius: 5px;
10 | -moz-border-radius: 5px;
11 | -o-border-radius: 5px;
12 | border-radius: 5px;
13 | border: 2px solid #c6c6c6;
14 | width: 90%;
15 | margin: 10px auto 10px auto;
16 | padding: 10px;
17 | }
18 |
19 | #console {
20 | background-color: #ffffe8;
21 | resize: vertical;
22 | outline: none;
23 | display: block;
24 | }
25 |
26 | .invalid {
27 | border-color: #f60016 !important;
28 | }
29 |
30 | #output {
31 | background-color: #eee;
32 | overflow: auto;
33 | }
34 |
35 | h1,
36 | p {
37 | text-align: center;
38 | }
39 |
40 | p {
41 | color: #696969;
42 | width: 90%;
43 | margin: 10px auto 10px auto;
44 | }
45 |
46 | p.settings {
47 | font-size: 0.8em;
48 | text-align: left;
49 | -webkit-touch-callout: none;
50 | -webkit-user-select: none;
51 | -khtml-user-select: none;
52 | -moz-user-select: none;
53 | -ms-user-select: none;
54 | user-select: none;
55 | }
56 |
57 | .settings label {
58 | padding-right: 2em;
59 | }
60 |
61 | a {
62 | text-decoration: none;
63 | color: #155fce;
64 | }
65 |
66 | a:hover {
67 | text-decoration: underline;
68 | }
69 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["import"],
3 | "extends": ["eslint:recommended", "plugin:import/errors", "prettier"],
4 | "env": {
5 | "es6": true
6 | },
7 | "parserOptions": {
8 | "ecmaVersion": 13,
9 | "sourceType": "module"
10 | },
11 | "globals": {
12 | "console": true,
13 | "global": true,
14 | "module": true,
15 | "process": true,
16 | "require": true,
17 | "window": true,
18 | "__dirname": true
19 | },
20 | "overrides": [
21 | {
22 | "files": ["src/tests/fixtures/**/*.js"],
23 | "rules": {
24 | "constructor-super": 0,
25 | "getter-return": 0,
26 | "no-cond-assign": 0,
27 | "no-constant-condition": 0,
28 | "no-control-regex": 0,
29 | "no-debugger": 0,
30 | "no-dupe-class-members": 0,
31 | "no-dupe-keys": 0,
32 | "no-duplicate-case": 0,
33 | "no-empty": 0,
34 | "no-inner-declarations": 0,
35 | "no-irregular-whitespace": 0,
36 | "no-redeclare": 0,
37 | "no-sparse-arrays": 0,
38 | "no-undef": 0,
39 | "no-unreachable": 0,
40 | "no-unsafe-negation": 0,
41 | "no-unused-labels": 0,
42 | "no-unused-vars": 0,
43 | "no-useless-escape": 0,
44 | "no-var": 0,
45 | "no-loss-of-precision": 0
46 | }
47 | }
48 | ]
49 | }
50 |
--------------------------------------------------------------------------------
/src/tests/fixtures/syntax/precedence.js:
--------------------------------------------------------------------------------
1 | var a, b, c, d, e, f, g, x, y, z;
2 | a = 1 + 2 * 3 / 5;
3 | b = (1 + 2) * 3 / 5;
4 | c = (1 + 2) * (3 - 5);
5 | d = x | y ^ z;
6 | e = (x | y) ^ z;
7 | f = "a" + (1 + 2) + "b";
8 | g = "a" + (1 - 2) + "b";
9 | a = true || false && null;
10 | b = c == d || e != f;
11 | b = (c || d) ?? e;
12 | b = c || (d ?? e);
13 | b = c && (d || e);
14 | b = c && (d ?? e);
15 | b = (c && d) ?? e;
16 | c = x instanceof y || x instanceof z;
17 | d = x == y && y != z;
18 | a = !false;
19 | b = !x instanceof Number;
20 | c = !(x instanceof Number);
21 | d = typeof a === 'boolean';
22 | e = !typeof a === 'boolean';
23 | f = !(typeof a === 'boolean');
24 | f = typeof (() => {});
25 | a = (1.1).toString();
26 | b = new A().toString();
27 | c = new x.A().toString();
28 | d = new x.y().z();
29 | var r = (/ab+c/i).exec('abc');
30 | a = b ** 2 * 3;
31 | c = (d ** 2) ** 3;
32 | e = f ** 2 ** 3;
33 | e = (+2) ** 3;
34 | e = 2 ** +3;
35 | f = a + (b = 3);
36 | g = 1 && (() => {});
37 | g = (() => {}) && 1;
38 | g = (1, + +2);
39 | g = (1, + +(2 + 3));
40 | a = - --i;
41 | b = - - --i;
42 | c = + + ++j;
43 | d = !!a;
44 | e = !+-+!a;
45 | f = -+-a++;
46 | g = b + -+-a++;
47 | (async function* () {
48 | await a + b;
49 | await a + await b;
50 | await (a = b);
51 | (await f()).a;
52 | await f().a;
53 | yield 1;
54 | yield 1 + 2;
55 | (yield 1) + (yield 2);
56 | yield a = b;
57 | const c = yield 3;
58 | });
59 | (function* () {
60 | !(yield 1);
61 | });
62 | !(() => {});
63 | (() => {}) ? a : b;
64 | ({}) ? a : b;
65 | (({}) ? a : b) ? c : d;
66 | (function () {}) ? a : b;
67 | (class {}) ? a : b;
68 |
--------------------------------------------------------------------------------
/src/tests/performance.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 |
3 | import test from 'ava'
4 |
5 | import { benchmark } from './benchmark'
6 | import { readFile } from './tools'
7 |
8 | const FIXTURES_FOLDER = path.join(__dirname, 'fixtures')
9 |
10 | test('Performance tiny code', (assert) => {
11 | const result = benchmark('var a = 2;', 'tiny code')
12 | assert.true(
13 | result['astring'].speed > result['escodegen'].speed,
14 | 'astring is faster than escodegen',
15 | )
16 | assert.true(
17 | result['astring'].speed > 10 * result['babel'].speed,
18 | 'astring is at least 10x faster than babel',
19 | )
20 | assert.true(
21 | result['astring'].speed > 10 * result['prettier'].speed,
22 | 'astring is at least 10x faster than prettier',
23 | )
24 | assert.true(
25 | result['acorn + astring'].speed > result['buble'].speed,
26 | 'astring is faster than buble',
27 | )
28 | })
29 |
30 | test('Performance with everything', (assert) => {
31 | const result = benchmark(
32 | readFile(path.join(FIXTURES_FOLDER, 'tree', 'es6.js')),
33 | 'everything',
34 | )
35 | assert.true(
36 | result['astring'].speed > result['escodegen'].speed,
37 | 'astring is faster than escodegen',
38 | )
39 | assert.true(
40 | result['astring'].speed > 10 * result['babel'].speed,
41 | 'astring is at least 10x faster than babel',
42 | )
43 | assert.true(
44 | result['astring'].speed > 10 * result['prettier'].speed,
45 | 'astring is at least 10x faster than prettier',
46 | )
47 | assert.true(
48 | result['acorn + astring'].speed > result['buble'].speed,
49 | 'astring is faster than buble',
50 | )
51 | })
52 |
--------------------------------------------------------------------------------
/docs/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Astring
2 |
3 | Suggestions and changes are welcome and can be made through issue tickets and pull requests.
4 |
5 | ## Code
6 |
7 | Formatted code is written out through `state.write(code)` calls. The `code` can end with a `state.lineEnd`, but no use of `state.lineEnd` must be made within. In that case, multiple `state.write()` calls should be made.
8 |
9 | ## Tests
10 |
11 | Code fixtures are used to assert that the generated code matches the expected content. These fixtures are located in `src/tests/fixtures/syntax`. Testing new syntax extensions simply consists of updating the files within that folder or by adding new files.
12 |
13 | ## Scripts
14 |
15 | - `npm run build`: Produces a JavaScript 5-compliant modules at `dist/astring.js`.
16 | - `npm run build:minified`: Produces a JavaScript 5-compliant modules at `dist/astring.min.js` along with a source map at `dist/astring.min.js.map`.
17 | - `npm start`: Generates `dist/astring.js` and `dist/astring.js.map` at each change detected on `src/astring.js`.
18 | - `npm test`: Runs tests.
19 | - `npm run dev`: Runs tests and watches for changes.
20 | - `npm run test:coverage`: Runs tests with coverage.
21 | - `npm run test:scripts`: Runs tests over a large array of script files.
22 | - `npm run test:performance`: Runs performance tests to ensure thresholds are met.
23 | - `npm run benchmark`: Runs benchmarks against other code generators. Requires to run `npm install escodegen@1.8 uglify-js@2 babel-generator@6 buble@0.15` first.
24 |
25 | ## Roadmap
26 |
27 | Planned features and releases are outlined on the [milestones page](https://github.com/davidbonnet/astring/milestones).
28 |
--------------------------------------------------------------------------------
/src/tests/scripts.js:
--------------------------------------------------------------------------------
1 | import test from 'ava'
2 | import fs from 'fs'
3 | import path from 'path'
4 | import normalizeNewline from 'normalize-newline'
5 | import { Parser } from 'acorn'
6 | import { importAttributes } from 'acorn-import-attributes'
7 | import * as astravel from 'astravel'
8 | import glob from 'glob'
9 |
10 | import { generate } from '../astring'
11 |
12 | const pattern = path.join(__dirname, '../../node_modules/@babel/**/*.js')
13 | const options = {
14 | ecmaVersion: 8,
15 | sourceType: 'module',
16 | allowHashBang: true,
17 | }
18 |
19 | const stripLocation = astravel.makeTraveler({
20 | go: function (node, state) {
21 | delete node.start
22 | delete node.end
23 | this[node.type](node, state)
24 | },
25 | // TODO: Remove once Astravel is updated
26 | Property(node, state) {
27 | this.go(node.key, state)
28 | if (node.value != null) {
29 | this.go(node.value, state)
30 | }
31 | },
32 | })
33 |
34 | const files = glob.sync(pattern, {
35 | nodir: true,
36 | })
37 |
38 | test('Script tests', (assert) => {
39 | files.forEach((fileName) => {
40 | try {
41 | const code = normalizeNewline(fs.readFileSync(fileName, 'utf8'))
42 | let ast
43 | try {
44 | ast = Parser.extend(importAttributes).parse(code, options)
45 | } catch (error) {
46 | return
47 | }
48 | stripLocation.go(ast)
49 | const formattedAst = Parser.extend(importAttributes).parse(
50 | generate(ast),
51 | options,
52 | )
53 | stripLocation.go(formattedAst)
54 | assert.deepEqual(formattedAst, ast, fileName)
55 | } catch (error) {
56 | assert.fail(error)
57 | }
58 | })
59 | })
60 |
--------------------------------------------------------------------------------
/astring.d.ts:
--------------------------------------------------------------------------------
1 | import type { Node as EstreeNode } from 'estree'
2 | import type { Mapping, SourceMapGenerator } from 'source-map'
3 | import type { Writable } from 'stream'
4 |
5 | /**
6 | * State object passed to generator functions.
7 | */
8 | export interface State {
9 | output: string
10 | write(code: string, node?: EstreeNode): void
11 | writeComments: boolean
12 | indent: string
13 | lineEnd: string
14 | indentLevel: number
15 | line?: number
16 | column?: number
17 | lineEndSize?: number
18 | mapping?: Mapping
19 | }
20 |
21 | /**
22 | * Code generator for each node type.
23 | */
24 | export type Generator = {
25 | [T in EstreeNode['type']]: (
26 | node: EstreeNode & { type: T },
27 | state: State,
28 | ) => void
29 | }
30 |
31 | /**
32 | * Code generator options.
33 | */
34 | export interface Options