From 17c3c98f07628b43b941b84831e8e1f9bcd7ca46 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Fri, 19 Mar 2021 17:07:45 -0400 Subject: [PATCH] Initial tests set up (#10) * Begin debugging * Initial tests set up This adds tests using uvu (we can switch if people want) and restructures things a bit so that it's easier to test. Like in snowpack you set up a little project. In our tests you can say: ```js const result = await runtime.load('/blog/hello-world') ``` And analyze the result. I included a `test-helpers.js` which has a function that will turn HTML into a cheerio instance, for inspecting the result HTML. * Add CI * Remove extra console logs * Formatting --- .github/workflows/nodejs.yml | 26 + package-lock.json | 289 +- package.json | 9 +- src/cli.ts | 23 +- src/codegen/index.ts | 11 +- src/compiler/Stats.ts | 119 +- src/compiler/index.ts | 2 +- src/compiler/parse/acorn.ts | 22 +- src/compiler/parse/index.ts | 388 +- src/compiler/parse/read/context.ts | 124 +- src/compiler/parse/read/expression.ts | 55 +- src/compiler/parse/read/script.ts | 73 +- src/compiler/parse/state/fragment.ts | 14 +- src/compiler/parse/state/mustache.ts | 673 +-- src/compiler/parse/state/tag.ts | 854 ++-- src/compiler/parse/state/text.ts | 30 +- src/compiler/parse/utils/bracket.ts | 21 +- src/compiler/parse/utils/entities.ts | 4062 ++++++++--------- src/compiler/parse/utils/html.ts | 214 +- src/compiler/parse/utils/node.ts | 52 +- src/compiler/utils/error.ts | 59 +- src/compiler/utils/full_char_code_at.ts | 8 +- src/compiler/utils/fuzzymatch.ts | 354 +- src/compiler/utils/get_code_frame.ts | 40 +- src/compiler/utils/link.ts | 4 +- src/compiler/utils/list.ts | 6 +- src/compiler/utils/names.ts | 234 +- src/compiler/utils/namespaces.ts | 17 +- src/compiler/utils/nodes_match.ts | 44 +- src/compiler/utils/trim.ts | 12 +- src/config.ts | 22 + src/dev.ts | 107 +- src/logger.ts | 51 +- src/runtime.ts | 137 + src/style-stuff.ts | 0 test/fixtures/hmx-basic/astro.config.mjs | 6 + test/fixtures/hmx-basic/astro/pages/index.hmx | 15 + test/fixtures/hmx-basic/snowpack.config.js | 5 + test/hmx-basic.test.js | 38 + test/test-utils.js | 5 + 40 files changed, 4199 insertions(+), 4026 deletions(-) create mode 100644 .github/workflows/nodejs.yml create mode 100644 src/config.ts create mode 100644 src/runtime.ts create mode 100644 src/style-stuff.ts create mode 100644 test/fixtures/hmx-basic/astro.config.mjs create mode 100644 test/fixtures/hmx-basic/astro/pages/index.hmx create mode 100644 test/fixtures/hmx-basic/snowpack.config.js create mode 100644 test/hmx-basic.test.js create mode 100644 test/test-utils.js diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml new file mode 100644 index 000000000..b89978663 --- /dev/null +++ b/.github/workflows/nodejs.yml @@ -0,0 +1,26 @@ +name: Node CI + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [14.x] + + steps: + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: npm install, build, and test + run: | + npm ci + npm run build + npm test + env: + CI: true \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7850aa248..1d4c4cb77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -536,7 +536,8 @@ "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true }, "boxen": { "version": "4.2.0", @@ -684,67 +685,31 @@ "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==" }, "cheerio": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", - "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", + "version": "1.0.0-rc.5", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.5.tgz", + "integrity": "sha512-yoqps/VCaZgN4pfXtenwHROTp8NG6/Hlt4Jpz2FEP0ZJQ+ZUkVDd0hAPDNKhj3nakpfPt/CNs57yEtxD1bXQiw==", + "dev": true, "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash.assignin": "^4.0.9", - "lodash.bind": "^4.1.4", - "lodash.defaults": "^4.0.1", - "lodash.filter": "^4.4.0", - "lodash.flatten": "^4.2.0", - "lodash.foreach": "^4.3.0", - "lodash.map": "^4.4.0", - "lodash.merge": "^4.4.0", - "lodash.pick": "^4.2.1", - "lodash.reduce": "^4.4.0", - "lodash.reject": "^4.4.0", - "lodash.some": "^4.4.0" - }, - "dependencies": { - "domhandler": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", - "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", - "requires": { - "domelementtype": "1" - } - }, - "htmlparser2": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", - "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", - "requires": { - "domelementtype": "^1.3.1", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^3.1.1" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - } + "cheerio-select-tmp": "^0.1.0", + "dom-serializer": "~1.2.0", + "domhandler": "^4.0.0", + "entities": "~2.1.0", + "htmlparser2": "^6.0.0", + "parse5": "^6.0.0", + "parse5-htmlparser2-tree-adapter": "^6.0.0" + } + }, + "cheerio-select-tmp": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/cheerio-select-tmp/-/cheerio-select-tmp-0.1.1.tgz", + "integrity": "sha512-YYs5JvbpU19VYJyj+F7oYrIE2BOll1/hRU7rEy/5+v9BzkSo3bK81iAeeQEMI92vRIxz677m72UmJUiVwwgjfQ==", + "dev": true, + "requires": { + "css-select": "^3.1.2", + "css-what": "^4.0.0", + "domelementtype": "^2.1.0", + "domhandler": "^4.0.0", + "domutils": "^2.4.4" } }, "chokidar": { @@ -950,14 +915,16 @@ "dev": true }, "css-select": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", - "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-3.1.2.tgz", + "integrity": "sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==", + "dev": true, "requires": { - "boolbase": "~1.0.0", - "css-what": "2.1", - "domutils": "1.5.1", - "nth-check": "~1.0.1" + "boolbase": "^1.0.0", + "css-what": "^4.0.0", + "domhandler": "^4.0.0", + "domutils": "^2.4.3", + "nth-check": "^2.0.0" } }, "css-tree": { @@ -970,9 +937,10 @@ } }, "css-what": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", - "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-4.0.0.tgz", + "integrity": "sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A==", + "dev": true }, "cssesc": { "version": "3.0.0", @@ -1019,6 +987,11 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + }, "default-browser-id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-2.0.0.tgz", @@ -1045,6 +1018,18 @@ "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", "dev": true }, + "dequal": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz", + "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==", + "dev": true + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1064,18 +1049,21 @@ } }, "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz", + "integrity": "sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==", + "dev": true, "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "entities": "^2.0.0" } }, "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz", + "integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w==", + "dev": true }, "domhandler": { "version": "4.0.0", @@ -1093,12 +1081,14 @@ } }, "domutils": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", - "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.5.0.tgz", + "integrity": "sha512-Ho16rzNMOFk2fPwChGh3D2D9OEHAfG19HgmRR2l+WLSsIstNsAYBzePH412bL0y5T44ejABIVfTHQ8nqi/tBCg==", + "dev": true, "requires": { - "dom-serializer": "0", - "domelementtype": "1" + "dom-serializer": "^1.0.1", + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0" } }, "dot-prop": { @@ -1151,9 +1141,10 @@ } }, "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true }, "error-ex": { "version": "1.3.2", @@ -1806,7 +1797,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "ini": { "version": "1.3.7", @@ -2073,71 +2065,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "lodash.assignin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", - "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" - }, - "lodash.bind": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", - "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" - }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" - }, - "lodash.filter": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", - "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" - }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" - }, - "lodash.foreach": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", - "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" - }, - "lodash.map": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", - "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" - }, - "lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" - }, - "lodash.reduce": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", - "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" - }, - "lodash.reject": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", - "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" - }, - "lodash.some": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", - "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" - }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -2292,6 +2224,12 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true }, + "mri": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz", + "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2390,11 +2328,12 @@ "dev": true }, "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz", + "integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==", + "dev": true, "requires": { - "boolbase": "~1.0.0" + "boolbase": "^1.0.0" } }, "object-assign": { @@ -2499,6 +2438,21 @@ "lines-and-columns": "^1.1.6" } }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "requires": { + "parse5": "^6.0.1" + } + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -2873,10 +2827,14 @@ "tslib": "^1.9.0" } }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "sade": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz", + "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==", + "dev": true, + "requires": { + "mri": "^1.1.0" + } }, "sass": { "version": "1.32.8", @@ -3216,6 +3174,12 @@ "is-number": "^7.0.0" } }, + "totalist": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-2.0.0.tgz", + "integrity": "sha512-+Y17F0YzxfACxTyjfhnJQEe7afPA0GSpYlFkl2VFMxYP7jshQf9gXV7cH47EfToBumFThfKBvfAcoUn6fdNeRQ==", + "dev": true + }, "touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -3366,6 +3330,19 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "uvu": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.1.tgz", + "integrity": "sha512-JGxttnOGDFs77FaZ0yMUHIzczzQ5R1IlDeNW6Wymw6gAscwMdAffVOP6TlxLIfReZyK8tahoGwWZaTCJzNFDkg==", + "dev": true, + "requires": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3", + "totalist": "^2.0.0" + } + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", diff --git a/package.json b/package.json index 5b596f104..0dd8b04f9 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "dev": "concurrently 'tsc --watch' 'npm run copy-js:watch'", "format": "prettier -w 'src/**/*.{js,ts}'", "copy-js": "copyfiles -u 1 src/*.js lib/", - "copy-js:watch": "nodemon -w src --ext js --exec 'npm run copy-js'" + "copy-js:watch": "nodemon -w src --ext js --exec 'npm run copy-js'", + "test": "uvu test -i fixtures -i test-utils.js" }, "dependencies": { "@types/estree": "0.0.46", @@ -31,8 +32,8 @@ "acorn-jsx": "^5.3.1", "astring": "^1.7.0", "autoprefixer": "^10.2.5", - "cheerio": "^0.22.0", "css-tree": "^1.1.2", + "deepmerge": "^4.2.2", "domhandler": "^4.0.0", "es-module-lexer": "^0.4.1", "gray-matter": "^4.0.2", @@ -57,6 +58,7 @@ "@types/yargs-parser": "^20.2.0", "@typescript-eslint/eslint-plugin": "^4.18.0", "@typescript-eslint/parser": "^4.18.0", + "cheerio": "^1.0.0-rc.5", "concurrently": "^6.0.0", "copyfiles": "^2.4.1", "eslint": "^7.22.0", @@ -67,6 +69,7 @@ "preact": "^10.5.12", "preact-render-to-string": "^5.1.14", "prettier": "^2.2.1", - "typescript": "^4.2.3" + "typescript": "^4.2.3", + "uvu": "^0.5.1" } } diff --git a/src/cli.ts b/src/cli.ts index 62e50f3eb..0a5c9612d 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,10 +1,10 @@ import type { AstroConfig } from './@types/astro'; import * as colors from 'kleur/colors'; -import { join as pathJoin, resolve as pathResolve } from 'path'; -import { existsSync, promises as fsPromises } from 'fs'; +import { promises as fsPromises } from 'fs'; import yargs from 'yargs-parser'; +import { loadConfig } from './config.js'; import generate from './generate.js'; import devServer from './dev.js'; @@ -49,25 +49,6 @@ async function printVersion() { console.error(pkg.version); } -async function loadConfig(rawRoot: string | undefined): Promise { - if (typeof rawRoot === 'undefined') { - rawRoot = process.cwd(); - } - - const root = pathResolve(rawRoot); - const fileProtocolRoot = `file://${root}/`; - const astroConfigPath = pathJoin(root, 'astro.config.mjs'); - - if (!existsSync(astroConfigPath)) { - return undefined; - } - - const astroConfig: AstroConfig = (await import(astroConfigPath)).default; - astroConfig.projectRoot = new URL(astroConfig.projectRoot + '/', fileProtocolRoot); - astroConfig.hmxRoot = new URL(astroConfig.hmxRoot + '/', fileProtocolRoot); - return astroConfig; -} - async function runCommand(rawRoot: string, cmd: (a: AstroConfig) => Promise) { const astroConfig = await loadConfig(rawRoot); if (typeof astroConfig === 'undefined') { diff --git a/src/codegen/index.ts b/src/codegen/index.ts index 9b3104f0a..662d63858 100644 --- a/src/codegen/index.ts +++ b/src/codegen/index.ts @@ -1,6 +1,6 @@ import type { CompileOptions } from '../@types/compiler'; import type { Ast, TemplateNode } from '../compiler/interfaces'; -import type { JsxItem, TransformResult } from '../@types/astro.js'; +import type { JsxItem, TransformResult } from '../@types/astro'; import eslexer from 'es-module-lexer'; import esbuild from 'esbuild'; @@ -61,7 +61,6 @@ function getAttributes(attrs: Attribute[]): Record { result[attr.name] = JSON.stringify(getTextFromAttribute(val)); continue; default: - console.log(val); throw new Error('UNKNOWN V'); } } @@ -75,7 +74,6 @@ function getTextFromAttribute(attr: any): string { if (attr.data !== undefined) { return attr.data; } - console.log(attr); throw new Error('UNKNOWN attr'); } @@ -169,12 +167,11 @@ function compileScriptSafe(raw: string, loader: 'jsx' | 'tsx'): string { export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Promise { await eslexer.init; - const script = compileScriptSafe(ast.instance ? ast.instance.content : '', 'tsx'); // Compile scripts as TypeScript, always + const script = compileScriptSafe(ast.instance ? ast.instance.content : '', 'tsx'); // Todo: Validate that `h` and `Fragment` aren't defined in the script - const [scriptImports] = eslexer.parse(script, 'optional-sourcename'); const components = Object.fromEntries( scriptImports.map((imp) => { @@ -193,7 +190,6 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro walk(ast.html, { enter(node: TemplateNode) { - // console.log("enter", node.type); switch (node.type) { case 'MustacheTag': let code = compileScriptSafe(node.expression, 'jsx'); @@ -238,7 +234,6 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro case 'Element': const name: string = node.name; if (!name) { - console.log(node); throw new Error('AHHHH'); } const attributes = getAttributes(node.attributes); @@ -298,12 +293,10 @@ export async function codegen(ast: Ast, { compileOptions }: CodeGenOptions): Pro return; } default: - console.log(node); throw new Error('Unexpected node type: ' + node.type); } }, leave(node, parent, prop, index) { - // console.log("leave", node.type); switch (node.type) { case 'Text': case 'MustacheTag': diff --git a/src/compiler/Stats.ts b/src/compiler/Stats.ts index 81c7f92ef..33802a42b 100644 --- a/src/compiler/Stats.ts +++ b/src/compiler/Stats.ts @@ -1,75 +1,82 @@ // @ts-nocheck -const now = (typeof process !== 'undefined' && process.hrtime) - ? () => { - const t = process.hrtime(); - return t[0] * 1e3 + t[1] / 1e6; - } - : () => self.performance.now(); +const now = + typeof process !== 'undefined' && process.hrtime + ? () => { + const t = process.hrtime(); + return t[0] * 1e3 + t[1] / 1e6; + } + : () => self.performance.now(); interface Timing { - label: string; - start: number; - end: number; - children: Timing[]; + label: string; + start: number; + end: number; + children: Timing[]; } function collapse_timings(timings) { - const result = {}; - timings.forEach(timing => { - result[timing.label] = Object.assign({ - total: timing.end - timing.start - }, timing.children && collapse_timings(timing.children)); - }); - return result; + const result = {}; + timings.forEach((timing) => { + result[timing.label] = Object.assign( + { + total: timing.end - timing.start, + }, + timing.children && collapse_timings(timing.children) + ); + }); + return result; } export default class Stats { - start_time: number; - current_timing: Timing; - current_children: Timing[]; - timings: Timing[]; - stack: Timing[]; + start_time: number; + current_timing: Timing; + current_children: Timing[]; + timings: Timing[]; + stack: Timing[]; - constructor() { - this.start_time = now(); - this.stack = []; - this.current_children = this.timings = []; - } + constructor() { + this.start_time = now(); + this.stack = []; + this.current_children = this.timings = []; + } - start(label) { - const timing = { - label, - start: now(), - end: null, - children: [] - }; + start(label) { + const timing = { + label, + start: now(), + end: null, + children: [], + }; - this.current_children.push(timing); - this.stack.push(timing); + this.current_children.push(timing); + this.stack.push(timing); - this.current_timing = timing; - this.current_children = timing.children; - } + this.current_timing = timing; + this.current_children = timing.children; + } - stop(label) { - if (label !== this.current_timing.label) { - throw new Error(`Mismatched timing labels (expected ${this.current_timing.label}, got ${label})`); - } + stop(label) { + if (label !== this.current_timing.label) { + throw new Error(`Mismatched timing labels (expected ${this.current_timing.label}, got ${label})`); + } - this.current_timing.end = now(); - this.stack.pop(); - this.current_timing = this.stack[this.stack.length - 1]; - this.current_children = this.current_timing ? this.current_timing.children : this.timings; - } + this.current_timing.end = now(); + this.stack.pop(); + this.current_timing = this.stack[this.stack.length - 1]; + this.current_children = this.current_timing ? this.current_timing.children : this.timings; + } - render() { - const timings = Object.assign({ - total: now() - this.start_time - }, collapse_timings(this.timings)); + render() { + const timings = Object.assign( + { + total: now() - this.start_time, + }, + collapse_timings(this.timings) + ); - return { - timings - }; - } + return { + timings, + }; + } } diff --git a/src/compiler/index.ts b/src/compiler/index.ts index 3c70d347b..718199c94 100644 --- a/src/compiler/index.ts +++ b/src/compiler/index.ts @@ -1 +1 @@ -export { default as parse } from './parse/index.js'; \ No newline at end of file +export { default as parse } from './parse/index.js'; diff --git a/src/compiler/parse/acorn.ts b/src/compiler/parse/acorn.ts index f017b6dec..966a13792 100644 --- a/src/compiler/parse/acorn.ts +++ b/src/compiler/parse/acorn.ts @@ -5,14 +5,16 @@ import jsx from 'acorn-jsx'; const acornJsx = acorn.Parser.extend(jsx()); -export const parse = (source: string): Node => acorn.parse(source, { - sourceType: 'module', - ecmaVersion: 2020, - locations: true -}); +export const parse = (source: string): Node => + acorn.parse(source, { + sourceType: 'module', + ecmaVersion: 2020, + locations: true, + }); -export const parse_expression_at = (source: string, index: number): Node => acornJsx.parseExpressionAt(source, index, { - sourceType: 'module', - ecmaVersion: 2020, - locations: true -}); +export const parse_expression_at = (source: string, index: number): Node => + acornJsx.parseExpressionAt(source, index, { + sourceType: 'module', + ecmaVersion: 2020, + locations: true, + }); diff --git a/src/compiler/parse/index.ts b/src/compiler/parse/index.ts index f5c5a592b..eab2c42c5 100644 --- a/src/compiler/parse/index.ts +++ b/src/compiler/parse/index.ts @@ -8,245 +8,257 @@ import full_char_code_at from '../utils/full_char_code_at.js'; import { TemplateNode, Ast, ParserOptions, Fragment, Style, Script } from '../interfaces.js'; import error from '../utils/error.js'; -type ParserState = (parser: Parser) => (ParserState | void); +type ParserState = (parser: Parser) => ParserState | void; interface LastAutoClosedTag { - tag: string; - reason: string; - depth: number; + tag: string; + reason: string; + depth: number; } export class Parser { - readonly template: string; - readonly filename?: string; - readonly customElement: boolean; + readonly template: string; + readonly filename?: string; + readonly customElement: boolean; - index = 0; - stack: TemplateNode[] = []; + index = 0; + stack: TemplateNode[] = []; - html: Fragment; - css: Style[] = []; - js: Script[] = []; - meta_tags = {}; - last_auto_closed_tag?: LastAutoClosedTag; + html: Fragment; + css: Style[] = []; + js: Script[] = []; + meta_tags = {}; + last_auto_closed_tag?: LastAutoClosedTag; - constructor(template: string, options: ParserOptions) { - if (typeof template !== 'string') { - throw new TypeError('Template must be a string'); - } + constructor(template: string, options: ParserOptions) { + if (typeof template !== 'string') { + throw new TypeError('Template must be a string'); + } - this.template = template.replace(/\s+$/, ''); - this.filename = options.filename; - this.customElement = options.customElement; + this.template = template.replace(/\s+$/, ''); + this.filename = options.filename; + this.customElement = options.customElement; - this.html = { - start: null, - end: null, - type: 'Fragment', - children: [] - }; + this.html = { + start: null, + end: null, + type: 'Fragment', + children: [], + }; - this.stack.push(this.html); + this.stack.push(this.html); - let state: ParserState = fragment; + let state: ParserState = fragment; - while (this.index < this.template.length) { - state = state(this) || fragment; - } + while (this.index < this.template.length) { + state = state(this) || fragment; + } - if (this.stack.length > 1) { - const current = this.current(); + if (this.stack.length > 1) { + const current = this.current(); - const type = current.type === 'Element' ? `<${current.name}>` : 'Block'; - const slug = current.type === 'Element' ? 'element' : 'block'; + const type = current.type === 'Element' ? `<${current.name}>` : 'Block'; + const slug = current.type === 'Element' ? 'element' : 'block'; - this.error({ - code: `unclosed-${slug}`, - message: `${type} was left open` - }, current.start); - } + this.error( + { + code: `unclosed-${slug}`, + message: `${type} was left open`, + }, + current.start + ); + } - if (state !== fragment) { - this.error({ - code: 'unexpected-eof', - message: 'Unexpected end of input' - }); - } + if (state !== fragment) { + this.error({ + code: 'unexpected-eof', + message: 'Unexpected end of input', + }); + } - if (this.html.children.length) { - let start = this.html.children[0].start; - while (whitespace.test(template[start])) start += 1; + if (this.html.children.length) { + let start = this.html.children[0].start; + while (whitespace.test(template[start])) start += 1; - let end = this.html.children[this.html.children.length - 1].end; - while (whitespace.test(template[end - 1])) end -= 1; + let end = this.html.children[this.html.children.length - 1].end; + while (whitespace.test(template[end - 1])) end -= 1; - this.html.start = start; - this.html.end = end; - } else { - this.html.start = this.html.end = null; - } - } + this.html.start = start; + this.html.end = end; + } else { + this.html.start = this.html.end = null; + } + } - current() { - return this.stack[this.stack.length - 1]; - } + current() { + return this.stack[this.stack.length - 1]; + } - acorn_error(err: any) { - this.error({ - code: 'parse-error', - message: err.message.replace(/ \(\d+:\d+\)$/, '') - }, err.pos); - } + acorn_error(err: any) { + this.error( + { + code: 'parse-error', + message: err.message.replace(/ \(\d+:\d+\)$/, ''), + }, + err.pos + ); + } - error({ code, message }: { code: string; message: string }, index = this.index) { - error(message, { - name: 'ParseError', - code, - source: this.template, - start: index, - filename: this.filename - }); - } + error({ code, message }: { code: string; message: string }, index = this.index) { + error(message, { + name: 'ParseError', + code, + source: this.template, + start: index, + filename: this.filename, + }); + } - eat(str: string, required?: boolean, message?: string) { - if (this.match(str)) { - this.index += str.length; - return true; - } + eat(str: string, required?: boolean, message?: string) { + if (this.match(str)) { + this.index += str.length; + return true; + } - if (required) { - this.error({ - code: `unexpected-${this.index === this.template.length ? 'eof' : 'token'}`, - message: message || `Expected ${str}` - }); - } + if (required) { + this.error({ + code: `unexpected-${this.index === this.template.length ? 'eof' : 'token'}`, + message: message || `Expected ${str}`, + }); + } - return false; - } + return false; + } - match(str: string) { - return this.template.slice(this.index, this.index + str.length) === str; - } + match(str: string) { + return this.template.slice(this.index, this.index + str.length) === str; + } - match_regex(pattern: RegExp) { - const match = pattern.exec(this.template.slice(this.index)); - if (!match || match.index !== 0) return null; + match_regex(pattern: RegExp) { + const match = pattern.exec(this.template.slice(this.index)); + if (!match || match.index !== 0) return null; - return match[0]; - } + return match[0]; + } - allow_whitespace() { - while ( - this.index < this.template.length && - whitespace.test(this.template[this.index]) - ) { - this.index++; - } - } + allow_whitespace() { + while (this.index < this.template.length && whitespace.test(this.template[this.index])) { + this.index++; + } + } - read(pattern: RegExp) { - const result = this.match_regex(pattern); - if (result) this.index += result.length; - return result; - } + read(pattern: RegExp) { + const result = this.match_regex(pattern); + if (result) this.index += result.length; + return result; + } - read_identifier(allow_reserved = false) { - const start = this.index; + read_identifier(allow_reserved = false) { + const start = this.index; - let i = this.index; + let i = this.index; - const code = full_char_code_at(this.template, i); - if (!isIdentifierStart(code, true)) return null; + const code = full_char_code_at(this.template, i); + if (!isIdentifierStart(code, true)) return null; - i += code <= 0xffff ? 1 : 2; + i += code <= 0xffff ? 1 : 2; - while (i < this.template.length) { - const code = full_char_code_at(this.template, i); + while (i < this.template.length) { + const code = full_char_code_at(this.template, i); - if (!isIdentifierChar(code, true)) break; - i += code <= 0xffff ? 1 : 2; - } + if (!isIdentifierChar(code, true)) break; + i += code <= 0xffff ? 1 : 2; + } - const identifier = this.template.slice(this.index, this.index = i); + const identifier = this.template.slice(this.index, (this.index = i)); - if (!allow_reserved && reserved.has(identifier)) { - this.error({ - code: 'unexpected-reserved-word', - message: `'${identifier}' is a reserved word in JavaScript and cannot be used here` - }, start); - } + if (!allow_reserved && reserved.has(identifier)) { + this.error( + { + code: 'unexpected-reserved-word', + message: `'${identifier}' is a reserved word in JavaScript and cannot be used here`, + }, + start + ); + } - return identifier; - } + return identifier; + } - read_until(pattern: RegExp) { - if (this.index >= this.template.length) { - this.error({ - code: 'unexpected-eof', - message: 'Unexpected end of input' - }); - } + read_until(pattern: RegExp) { + if (this.index >= this.template.length) { + this.error({ + code: 'unexpected-eof', + message: 'Unexpected end of input', + }); + } - const start = this.index; - const match = pattern.exec(this.template.slice(start)); + const start = this.index; + const match = pattern.exec(this.template.slice(start)); - if (match) { - this.index = start + match.index; - return this.template.slice(start, this.index); - } + if (match) { + this.index = start + match.index; + return this.template.slice(start, this.index); + } - this.index = this.template.length; - return this.template.slice(start); - } + this.index = this.template.length; + return this.template.slice(start); + } - require_whitespace() { - if (!whitespace.test(this.template[this.index])) { - this.error({ - code: 'missing-whitespace', - message: 'Expected whitespace' - }); - } + require_whitespace() { + if (!whitespace.test(this.template[this.index])) { + this.error({ + code: 'missing-whitespace', + message: 'Expected whitespace', + }); + } - this.allow_whitespace(); - } + this.allow_whitespace(); + } } -export default function parse( - template: string, - options: ParserOptions = {} -): Ast { - const parser = new Parser(template, options); +export default function parse(template: string, options: ParserOptions = {}): Ast { + const parser = new Parser(template, options); - // TODO we may want to allow multiple