Merge branch 'master' into feat/css-none

pull/7914/head
Maximiliano Ruani 4 years ago committed by GitHub
commit ba441d19b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,9 +1,47 @@
name: CI
on: [push, pull_request]
on:
push:
branches: [ master ]
pull_request:
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
Setup:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
cache: npm
- run: npm install
env:
SKIP_PREPARE: true
- run: npm run build
env:
PUBLISH: true
- name: Upload build assets
id: upload-artifact
uses: actions/upload-artifact@v3
with:
name: build-assets
path: |
index.*
compiler.*
ssr.*
action/
animate/
easing/
internal/
motion/
store/
transition/
types/
Tests:
needs: Setup
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
@ -11,21 +49,28 @@ jobs:
node-version: [8, 10, 12, 14, 16]
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Download build assets
uses: actions/download-artifact@v3
id: download-artifact
with:
name: build-assets
- run: npm install
- run: npm test
env:
SKIP_PREPARE: true
- run: npm run test:integration
env:
CI: true
Lint:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
cache: npm
- run: 'npm i && npm run lint'
@ -36,8 +81,11 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
cache: npm
- run: 'npm i && npm run test:unit'
- run: npm install
env:
SKIP_PREPARE: true
- run: npm run test:unit

@ -1,7 +1,7 @@
const is_unit_test = process.env.UNIT_TEST;
module.exports = {
file: [
'test/test.ts'
],
file: is_unit_test ? [] : ['test/test.ts'],
require: [
'sucrase/register'
]

@ -0,0 +1,15 @@
module.exports = {
spec: [
'src/**/__test__.ts',
],
require: [
'sucrase/register'
],
recursive: true,
};
// add coverage options when running 'npx c8 mocha'
if (process.env.NODE_V8_COVERAGE) {
module.exports.fullTrace = true;
module.exports.require.push('source-map-support/register');
}

@ -1,25 +1,36 @@
# Svelte changelog
## Unreleased
* Upgraded `css-tree` to `2.1.0` ([#7572](https://github.com/sveltejs/svelte/pull/7572))
## 3.52.0
* Throw compile-time error when attempting to update `const` variable ([#4895](https://github.com/sveltejs/svelte/issues/4895))
* Warn when using `<a target="_blank">` without `rel="noreferrer"` ([#6188](https://github.com/sveltejs/svelte/issues/6188))
* Support `style:foo|important` modifier ([#7365](https://github.com/sveltejs/svelte/issues/7365))
* Fix hydration regression with `{@html}` and components in `<svelte:head>` ([#7941](https://github.com/sveltejs/svelte/pull/7941))
## 3.51.0
* Fix hydration issue with using `{@html}` and components in `svelte:head` ([#4533](https://github.com/sveltejs/svelte/issues/4533), [#6463](https://github.com/sveltejs/svelte/issues/6463), [#7444](https://github.com/sveltejs/svelte/issues/7444))
* Warn instead of throwing error if `<svelte:element>` is void tag ([#7566](https://github.com/sveltejs/svelte/issues/7566))
* Treat `inert` as boolean attribute ([#7785](https://github.com/sveltejs/svelte/pull/7785))
* Supporting scoped style for `<svelte:element>` ([#7443](https://github.com/sveltejs/svelte/issues/7443))
* Supports SVG elements with `<svelte:element>`([#7613](https://github.com/sveltejs/svelte/issues/7613))
* Warn user when binding on a `{...rest}` object in `{#each}` block ([#6860](https://github.com/sveltejs/svelte/issues/6860))
* Supports `--style-props` for `<svelte:component>` ([#7461](https://github.com/sveltejs/svelte/issues/7461))
* Supports `--style-props` for SVG components ([#7808](https://github.com/sveltejs/svelte/issues/7808))
* Add a11y warnings:
* `a11y-no-noninteractive-tabindex`: check for tabindex on non-interactive elements ([#6693](https://github.com/sveltejs/svelte/pull/6693))
* `a11y-click-events-have-key-events`: check if click event is accompanied by key events ([#5073](https://github.com/sveltejs/svelte/pull/5073))
* `a11y-role-has-required-aria-props` do not warn when elements matched their semantic role ([#7838](https://github.com/sveltejs/svelte/pull/7838))
* Supports custom element in `<svelte:element>` ([#7766](https://github.com/sveltejs/svelte/pull/7766))
* Improve performance of custom element data setting in `<svelte:element>` ([#7869](https://github.com/sveltejs/svelte/pull/7869))
* `a11y-no-noninteractive-tabindex`: check for tabindex on non-interactive elements ([#6693](https://github.com/sveltejs/svelte/pull/6693))
* Warn when two-way binding to `{...rest}` object in `{#each}` block ([#6860](https://github.com/sveltejs/svelte/issues/6860))
* Support `--style-props` on `<svelte:component>` ([#7461](https://github.com/sveltejs/svelte/issues/7461))
* Supports nullish values for component event handlers ([#7568](https://github.com/sveltejs/svelte/issues/7568))
* Better error message when specifying an invalid value for `<svelte:component this={...}>` ([#7550](https://github.com/sveltejs/svelte/issues/7550))
* Fix to call component unmount if a component is mounted and then immediately unmounted ([#7817](https://github.com/sveltejs/svelte/issues/7817))
* Fix false positive warnings about props binding in dev mode ([#4457](https://github.com/sveltejs/svelte/issues/4457))
* Supports SVG elements with `<svelte:element>`([#7613](https://github.com/sveltejs/svelte/issues/7613))
* Treat `inert` as boolean attribute ([#7785](https://github.com/sveltejs/svelte/pull/7785))
* Support `--style-props` for SVG components ([#7808](https://github.com/sveltejs/svelte/issues/7808))
* Fix false positive dev warnings about unset props when they are bound ([#4457](https://github.com/sveltejs/svelte/issues/4457))
* Fix hydration with `{@html}` and components in `<svelte:head>` ([#4533](https://github.com/sveltejs/svelte/issues/4533), [#6463](https://github.com/sveltejs/svelte/issues/6463), [#7444](https://github.com/sveltejs/svelte/issues/7444))
* Support scoped style for `<svelte:element>` ([#7443](https://github.com/sveltejs/svelte/issues/7443))
* Improve error message for invalid value for `<svelte:component this={...}>` ([#7550](https://github.com/sveltejs/svelte/issues/7550))
* Improve error message when using logic blocks or tags at invalid location ([#7552](https://github.com/sveltejs/svelte/issues/7552))
* Warn instead of throwing error if `<svelte:element>` is a void tag ([#7566](https://github.com/sveltejs/svelte/issues/7566))
* Supports custom elements in `<svelte:element>` ([#7733](https://github.com/sveltejs/svelte/issues/7733))
* Fix calling component unmount if a component is mounted and then immediately unmounted ([#7817](https://github.com/sveltejs/svelte/issues/7817))
* Do not generate `a11y-role-has-required-aria-props` warning when elements match their semantic role ([#7837](https://github.com/sveltejs/svelte/issues/7837))
* Improve performance of custom element data setting in `<svelte:element>` ([#7869](https://github.com/sveltejs/svelte/pull/7869))
## 3.50.1

185
package-lock.json generated

@ -1,18 +1,18 @@
{
"name": "svelte",
"version": "3.50.1",
"version": "3.52.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "svelte",
"version": "3.49.0",
"version": "3.52.0",
"license": "MIT",
"devDependencies": {
"@ampproject/remapping": "^0.3.0",
"@rollup/plugin-commonjs": "^11.0.0",
"@rollup/plugin-json": "^4.0.1",
"@rollup/plugin-node-resolve": "^6.0.0",
"@rollup/plugin-node-resolve": "^11.2.1",
"@rollup/plugin-replace": "^2.3.0",
"@rollup/plugin-sucrase": "^3.1.0",
"@rollup/plugin-typescript": "^2.0.1",
@ -28,7 +28,7 @@
"aria-query": "^5.0.0",
"axobject-query": "^3.0.1",
"code-red": "^0.2.5",
"css-tree": "^1.1.2",
"css-tree": "^2.1.0",
"eslint": "^8.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-svelte3": "^4.0.0",
@ -209,21 +209,54 @@
}
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-6.0.0.tgz",
"integrity": "sha512-GqWz1CfXOsqpeVMcoM315+O7zMxpRsmhWyhJoxLFHVSp9S64/u02i7len/FnbTNbmgYs+sZyilasijH8UiuboQ==",
"version": "11.2.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz",
"integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^3.0.0",
"@types/resolve": "0.0.8",
"@rollup/pluginutils": "^3.1.0",
"@types/resolve": "1.17.1",
"builtin-modules": "^3.1.0",
"deepmerge": "^4.2.2",
"is-module": "^1.0.0",
"resolve": "^1.11.1"
"resolve": "^1.19.0"
},
"engines": {
"node": ">= 10.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0"
}
},
"node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
"integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
"dev": true,
"dependencies": {
"@types/estree": "0.0.39",
"estree-walker": "^1.0.1",
"picomatch": "^2.2.2"
},
"engines": {
"node": ">= 8.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0"
}
},
"node_modules/@rollup/plugin-node-resolve/node_modules/@types/estree": {
"version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
"dev": true
},
"node_modules/@rollup/plugin-node-resolve/node_modules/estree-walker": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
"dev": true
},
"node_modules/@rollup/plugin-replace": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.3.0.tgz",
@ -331,9 +364,9 @@
"dev": true
},
"node_modules/@types/resolve": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz",
"integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==",
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
"integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
"dev": true,
"dependencies": {
"@types/node": "*"
@ -1077,25 +1110,17 @@
"dev": true
},
"node_modules/css-tree": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz",
"integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
"integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
"dev": true,
"dependencies": {
"mdn-data": "2.0.14",
"source-map": "^0.6.1"
"mdn-data": "2.0.28",
"source-map-js": "^1.0.1"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/css-tree/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
"npm": ">=7.0.0"
}
},
"node_modules/cssom": {
@ -1177,6 +1202,15 @@
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true
},
"node_modules/deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/define-properties": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
@ -3108,9 +3142,9 @@
}
},
"node_modules/mdn-data": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
"version": "2.0.28",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
"dev": true
},
"node_modules/merge2": {
@ -4055,6 +4089,15 @@
"node": ">= 8"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.13",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
@ -4884,16 +4927,42 @@
}
},
"@rollup/plugin-node-resolve": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-6.0.0.tgz",
"integrity": "sha512-GqWz1CfXOsqpeVMcoM315+O7zMxpRsmhWyhJoxLFHVSp9S64/u02i7len/FnbTNbmgYs+sZyilasijH8UiuboQ==",
"version": "11.2.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz",
"integrity": "sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^3.0.0",
"@types/resolve": "0.0.8",
"@rollup/pluginutils": "^3.1.0",
"@types/resolve": "1.17.1",
"builtin-modules": "^3.1.0",
"deepmerge": "^4.2.2",
"is-module": "^1.0.0",
"resolve": "^1.11.1"
"resolve": "^1.19.0"
},
"dependencies": {
"@rollup/pluginutils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
"integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
"dev": true,
"requires": {
"@types/estree": "0.0.39",
"estree-walker": "^1.0.1",
"picomatch": "^2.2.2"
}
},
"@types/estree": {
"version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
"dev": true
},
"estree-walker": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
"integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
"dev": true
}
}
},
"@rollup/plugin-replace": {
@ -4997,9 +5066,9 @@
"dev": true
},
"@types/resolve": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz",
"integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==",
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
"integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
"dev": true,
"requires": {
"@types/node": "*"
@ -5545,21 +5614,13 @@
"dev": true
},
"css-tree": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz",
"integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==",
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
"integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
"dev": true,
"requires": {
"mdn-data": "2.0.14",
"source-map": "^0.6.1"
},
"dependencies": {
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true
}
"mdn-data": "2.0.28",
"source-map-js": "^1.0.1"
}
},
"cssom": {
@ -5626,6 +5687,12 @@
"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==",
"dev": true
},
"define-properties": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
@ -7103,9 +7170,9 @@
}
},
"mdn-data": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
"integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==",
"version": "2.0.28",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
"dev": true
},
"merge2": {
@ -7837,6 +7904,12 @@
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"dev": true
},
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"dev": true
},
"source-map-support": {
"version": "0.5.13",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",

@ -1,6 +1,6 @@
{
"name": "svelte",
"version": "3.50.1",
"version": "3.52.0",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",
@ -86,15 +86,15 @@
},
"types": "types/runtime/index.d.ts",
"scripts": {
"test": "mocha --exit",
"test:unit": "mocha --require sucrase/register --recursive src/**/__test__.ts --exit",
"quicktest": "mocha",
"test": "npm run test:unit && npm run test:integration",
"test:integration": "mocha --exit",
"test:unit": "mocha --config .mocharc.unit.js --exit",
"quicktest": "mocha --exit",
"build": "rollup -c && npm run tsd",
"prepare": "npm run build",
"prepare": "node scripts/skip_in_ci.js npm run build",
"dev": "rollup -cw",
"pretest": "npm run build",
"posttest": "agadoo internal/index.mjs",
"prepublishOnly": "node check_publish_env.js && npm run lint && npm test",
"prepublishOnly": "node check_publish_env.js && npm run lint && npm run build && npm test",
"tsd": "node ./generate-type-definitions.js",
"lint": "eslint \"{src,test}/**/*.{ts,js}\""
},
@ -118,7 +118,7 @@
"@ampproject/remapping": "^0.3.0",
"@rollup/plugin-commonjs": "^11.0.0",
"@rollup/plugin-json": "^4.0.1",
"@rollup/plugin-node-resolve": "^6.0.0",
"@rollup/plugin-node-resolve": "^11.2.1",
"@rollup/plugin-replace": "^2.3.0",
"@rollup/plugin-sucrase": "^3.1.0",
"@rollup/plugin-typescript": "^2.0.1",
@ -134,7 +134,7 @@
"aria-query": "^5.0.0",
"axobject-query": "^3.0.1",
"code-red": "^0.2.5",
"css-tree": "^1.1.2",
"css-tree": "^2.1.0",
"eslint": "^8.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-svelte3": "^4.0.0",

@ -0,0 +1,7 @@
if (process.env.SKIP_PREPARE) {
console.log('Skipped "prepare" script');
} else {
const { execSync } = require("child_process");
const command = process.argv.slice(2).join(" ");
execSync(command, { stdio: "inherit" });
}

@ -7,6 +7,7 @@ title: Getting started
To try Svelte in an interactive online environment you can try [the REPL](https://svelte.dev/repl) or [StackBlitz](https://node.new/svelte).
To create a project locally, run:
```
npm create vite@latest myapp -- --template svelte
cd myapp
@ -14,8 +15,8 @@ npm install
npm run dev
```
Or use [SvelteKit](https://kit.svelte.dev/), the official application framework from the Svelte team (currently in beta).
Or use [SvelteKit](https://kit.svelte.dev/), the official application framework from the Svelte team (currently in release candidate status).
See the SvelteSociety website run by the Svelte community for a list of integrations with various [tooling and editots](https://sveltesociety.dev/tools).
The Svelte team maintains a [VS Code extension](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode) and the Svelte community has created a list of integrations with various other [tooling and editors](https://sveltesociety.dev/tools).
If you're having trouble, get help on [Discord](https://svelte.dev/chat) or [StackOverflow](https://stackoverflow.com/questions/tagged/svelte).

@ -209,7 +209,7 @@ It is important to note that the reactive blocks are ordered via simple static a
</script>
```
Moving the line `$: yDependent = y` bellow `$: setY(x)` will cause `yDependent` to be updated when `x` is updated.
Moving the line `$: yDependent = y` below `$: setY(x)` will cause `yDependent` to be updated when `x` is updated.
---

@ -158,7 +158,7 @@ You can use HTML comments inside components.
---
Comments beginning with `svelte-ignore` disable warnings for the next block of markup. Usually these are accessibility warnings; make sure that you're disabling them for a good reason.
Comments beginning with `svelte-ignore` disable warnings for the next block of markup. Usually, these are accessibility warnings; make sure that you're disabling them for a good reason.
```sv
<!-- svelte-ignore a11y-autofocus -->
@ -593,7 +593,7 @@ The simplest bindings reflect the value of a property, such as `input.value`.
---
If the name matches the value, you can use a shorthand.
If the name matches the value, you can use shorthand.
```sv
<!-- These are equivalent -->
@ -750,7 +750,7 @@ Videos additionally have readonly `videoWidth` and `videoHeight` bindings.
---
Block-level elements have 4 readonly bindings, measured using a technique similar to [this one](http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/):
Block-level elements have 4 read-only bindings, measured using a technique similar to [this one](http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/):
* `clientWidth`
* `clientHeight`
@ -874,6 +874,9 @@ The `style:` directive provides a shorthand for setting multiple styles on an el
<!-- Multiple styles can be included -->
<div style:color style:width="12rem" style:background-color={darkMode ? "black" : "white"}>...</div>
<!-- Styles can be marked as important -->
<div style:color|important="red">...</div>
```
---
@ -981,7 +984,7 @@ transition = (node: HTMLElement, params: any) => {
A transition is triggered by an element entering or leaving the DOM as a result of a state change.
When a block is transitioning out, all elements inside the block, including those that do not have their own transitions, are kept in the DOM until every transition in the block has completed.
When a block is transitioning out, all elements inside the block, including those that do not have their own transitions, are kept in the DOM until every transition in the block has been completed.
The `transition:` directive indicates a *bidirectional* transition, which means it can be smoothly reversed while the transition is in progress.
@ -1241,7 +1244,7 @@ As with actions and transitions, animations can have parameters.
---
Animations can use custom functions that provide the `node`, an `animation` object and any `parameters` as arguments. The `animation` parameter is an object containing `from` and `to` properties each containing a [DOMRect](https://developer.mozilla.org/en-US/docs/Web/API/DOMRect#Properties) describing the geometry of the element in its `start` and `end` positions. The `from` property is the DOMRect of the element in its starting position, the `to` property is the DOMRect of the element in its final position after the list has been reordered and the DOM updated.
Animations can use custom functions that provide the `node`, an `animation` object and any `parameters` as arguments. The `animation` parameter is an object containing `from` and `to` properties each containing a [DOMRect](https://developer.mozilla.org/en-US/docs/Web/API/DOMRect#Properties) describing the geometry of the element in its `start` and `end` positions. The `from` property is the DOMRect of the element in its starting position, and the `to` property is the DOMRect of the element in its final position after the list has been reordered and the DOM updated.
If the returned object has a `css` method, Svelte will create a CSS animation that plays on the element.
@ -1390,7 +1393,7 @@ For SVG namespace, the example above desugars into using `<g>` instead:
---
Svelte's CSS Variables support allows for easily themable components:
Svelte's CSS Variables support allows for easily themeable components:
```sv
<!-- Slider.svelte -->
@ -1654,7 +1657,7 @@ If `this` is falsy, no component is rendered.
The `<svelte:element>` element lets you render an element of a dynamically specified type. This is useful for example when displaying rich text content from a CMS. Any properties and event listeners present will be applied to the element.
The only supported binding is `bind:this`, since the element type specific bindings that Svelte does at build time (e.g. `bind:value` for input elements) does not work with a dynamic tag type.
The only supported binding is `bind:this`, since the element type specific bindings that Svelte does at build time (e.g. `bind:value` for input elements) do not work with a dynamic tag type.
If `this` has a nullish value, the element and its children will not be rendered.

@ -268,7 +268,7 @@ Some HTML elements have default ARIA roles. Giving these elements an ARIA role t
Tab key navigation should be limited to elements on the page that can be interacted with.
```sv
<!-- A11y: not interactive element cannot have positive tabIndex value -->
<!-- A11y: noninteractive element cannot have positive tabIndex value -->
<div tabindex='0' />
```

@ -11,7 +11,7 @@
<ul>
{#each cats as { id, name }, i}
<li>
<a target="_blank" href="https://www.youtube.com/watch?v={id}">
<a target="_blank" rel="noreferrer" href="https://www.youtube.com/watch?v={id}">
{i + 1}: {name}
</a>
</li>

@ -43,4 +43,8 @@
background-color: #ff3e00;
color: white;
}
</style>
p {
pointer-events: none;
}
</style>

@ -47,6 +47,10 @@ interface ComponentOptions {
preserveWhitespace?: boolean;
}
const regex_leading_directory_separator = /^[/\\]/;
const regex_starts_with_term_export = /^Export/;
const regex_contains_term_function = /Function/;
export default class Component {
stats: Stats;
warnings: Warning[];
@ -136,7 +140,7 @@ export default class Component {
(typeof process !== 'undefined'
? compile_options.filename
.replace(process.cwd(), '')
.replace(/^[/\\]/, '')
.replace(regex_leading_directory_separator, '')
: compile_options.filename);
this.locate = getLocator(this.source, { offsetLine: 1 });
@ -638,7 +642,7 @@ export default class Component {
body.splice(i, 1);
}
if (/^Export/.test(node.type)) {
if (regex_starts_with_term_export.test(node.type)) {
const replacement = this.extract_exports(node, true);
if (replacement) {
body[i] = replacement;
@ -788,6 +792,42 @@ export default class Component {
scope = map.get(node);
}
let deep = false;
let names: string[] | undefined;
if (node.type === 'AssignmentExpression') {
deep = node.left.type === 'MemberExpression';
names = deep
? [get_object(node.left).name]
: extract_names(node.left);
} else if (node.type === 'UpdateExpression') {
deep = node.argument.type === 'MemberExpression';
const { name } = get_object(node.argument);
names = [name];
}
if (names) {
names.forEach(name => {
let current_scope = scope;
let declaration;
while (current_scope) {
if (current_scope.declarations.has(name)) {
declaration = current_scope.declarations.get(name);
break;
}
current_scope = current_scope.parent;
}
if (declaration && declaration.kind === 'const' && !deep) {
component.error(node as any, {
code: 'assignment-to-const',
message: 'You are assigning to a const'
});
}
});
}
if (node.type === 'ImportDeclaration') {
component.extract_imports(node);
// TODO: to use actual remove
@ -795,7 +835,7 @@ export default class Component {
return this.skip();
}
if (/^Export/.test(node.type)) {
if (regex_starts_with_term_export.test(node.type)) {
const replacement = component.extract_exports(node);
if (replacement) {
this.replace(replacement);
@ -918,7 +958,7 @@ export default class Component {
}
if (name[1] !== '$' && scope.has(name.slice(1)) && scope.find_owner(name.slice(1)) !== this.instance_scope) {
if (!((/Function/.test(parent.type) && prop === 'params') || (parent.type === 'VariableDeclarator' && prop === 'id'))) {
if (!((regex_contains_term_function.test(parent.type) && prop === 'params') || (parent.type === 'VariableDeclarator' && prop === 'id'))) {
return this.error(node as any, compiler_errors.contextual_store);
}
}
@ -965,7 +1005,7 @@ export default class Component {
walk(this.ast.instance.content, {
enter(node: Node) {
if (/Function/.test(node.type)) {
if (regex_contains_term_function.test(node.type)) {
return this.skip();
}
@ -1089,7 +1129,7 @@ export default class Component {
this.replace(b`
${node.declarations.length ? node : null}
${ props.length > 0 && b`let { ${ props } } = $$props;`}
${ props.length > 0 && b`let { ${props} } = $$props;`}
${inserts}
` as any);
return this.skip();
@ -1460,6 +1500,8 @@ export default class Component {
}
}
const regex_valid_tag_name = /^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/;
function process_component_options(component: Component, nodes) {
const component_options: ComponentOptions = {
immutable: component.compile_options.immutable || false,
@ -1473,7 +1515,7 @@ function process_component_options(component: Component, nodes) {
const node = nodes.find(node => node.name === 'svelte:options');
function get_value(attribute, {code, message}) {
function get_value(attribute, { code, message }) {
const { value } = attribute;
const chunk = value[0];
@ -1505,7 +1547,7 @@ function process_component_options(component: Component, nodes) {
return component.error(attribute, compiler_errors.invalid_tag_attribute);
}
if (tag && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) {
if (tag && !regex_valid_tag_name.test(tag)) {
return component.error(attribute, compiler_errors.invalid_tag_property);
}

@ -281,5 +281,9 @@ export default {
invalid_component_style_directive: {
code: 'invalid-component-style-directive',
message: 'Style directives cannot be used on components'
}
},
invalid_style_directive_modifier: (valid: string) => ({
code: 'invalid-style-directive-modifier',
message: `Valid modifiers for style directives are: ${valid}`
})
};

@ -185,7 +185,7 @@ export default {
}),
a11y_no_noninteractive_tabindex: {
code: 'a11y-no-noninteractive-tabindex',
message: 'A11y: not interactive element cannot have positive tabIndex value'
message: 'A11y: noninteractive element cannot have positive tabIndex value'
},
redundant_event_modifier_for_touch: {
code: 'redundant-event-modifier',

@ -9,6 +9,7 @@ import EachBlock from '../nodes/EachBlock';
import IfBlock from '../nodes/IfBlock';
import AwaitBlock from '../nodes/AwaitBlock';
import compiler_errors from '../compiler_errors';
import { regex_starts_with_whitespace, regex_ends_with_whitespace } from '../../utils/patterns';
enum BlockAppliesToNode {
NotPossible,
@ -25,6 +26,8 @@ const whitelist_attribute_selector = new Map([
['dialog', new Set(['open'])]
]);
const regex_is_single_css_selector = /[^\\],(?!([^([]+[^\\]|[^([\\])[)\]])/;
export default class Selector {
node: CssNode;
stylesheet: Stylesheet;
@ -157,7 +160,7 @@ export default class Selector {
for (const block of this.blocks) {
for (const selector of block.selectors) {
if (selector.type === 'PseudoClassSelector' && selector.name === 'global') {
if (/[^\\],(?!([^([]+[^\\]|[^([\\])[)\]])/.test(selector.children[0].value)) {
if (regex_is_single_css_selector.test(selector.children[0].value)) {
component.error(selector, compiler_errors.css_invalid_global_selector);
}
}
@ -209,7 +212,7 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{
}
if (block.combinator) {
if (block.combinator.type === 'WhiteSpace') {
if (block.combinator.type === 'Combinator' && block.combinator.name === ' ') {
for (const ancestor_block of blocks) {
if (ancestor_block.global) {
continue;
@ -281,12 +284,14 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: Array<{
return true;
}
const regex_backslash_and_following_character = /\\(.)/g;
function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToNode {
let i = block.selectors.length;
while (i--) {
const selector = block.selectors[i];
const name = typeof selector.name === 'string' && selector.name.replace(/\\(.)/g, '$1');
const name = typeof selector.name === 'string' && selector.name.replace(regex_backslash_and_following_character, '$1');
if (selector.type === 'PseudoClassSelector' && (name === 'host' || name === 'root')) {
return BlockAppliesToNode.NotPossible;
@ -371,7 +376,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
const start_with_space = [];
const remaining = [];
current_possible_values.forEach((current_possible_value: string) => {
if (/^\s/.test(current_possible_value)) {
if (regex_starts_with_whitespace.test(current_possible_value)) {
start_with_space.push(current_possible_value);
} else {
remaining.push(current_possible_value);
@ -392,7 +397,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
prev_values = combined;
start_with_space.forEach((value: string) => {
if (/\s$/.test(value)) {
if (regex_ends_with_whitespace.test(value)) {
possible_values.add(value);
} else {
prev_values.push(value);
@ -406,7 +411,7 @@ function attribute_matches(node: CssNode, name: string, expected_value: string,
}
current_possible_values.forEach((current_possible_value: string) => {
if (/\s$/.test(current_possible_value)) {
if (regex_ends_with_whitespace.test(current_possible_value)) {
possible_values.add(current_possible_value);
} else {
prev_values.push(current_possible_value);

@ -9,9 +9,12 @@ import hash from '../utils/hash';
import compiler_warnings from '../compiler_warnings';
import { extract_ignores_above_position } from '../../utils/extract_svelte_ignore';
import { push_array } from '../../utils/push_array';
import { regex_only_whitespaces, regex_whitespace } from '../../utils/patterns';
const regex_css_browser_prefix = /^-((webkit)|(moz)|(o)|(ms))-/;
function remove_css_prefix(name: string): string {
return name.replace(/^-((webkit)|(moz)|(o)|(ms))-/, '');
return name.replace(regex_css_browser_prefix, '');
}
const is_keyframes_node = (node: CssNode) =>
@ -147,10 +150,10 @@ class Declaration {
// Don't minify whitespace in custom properties, since some browsers (Chromium < 99)
// treat --foo: ; and --foo:; differently
if (first.type === 'Raw' && /^\s+$/.test(first.value)) return;
if (first.type === 'Raw' && regex_only_whitespaces.test(first.value)) return;
let start = first.start;
while (/\s/.test(code.original[start])) start += 1;
while (regex_whitespace.test(code.original[start])) start += 1;
if (start - c > 1) {
code.overwrite(c, start, ':');

@ -43,6 +43,9 @@ const valid_css_values = [
'none'
];
const regex_valid_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
const regex_starts_with_lowercase_character = /^[a-z]/;
function validate_options(options: CompileOptions, warnings: Warning[]) {
const { name, filename, loopGuardTimeout, dev, namespace, css } = options;
@ -56,11 +59,11 @@ function validate_options(options: CompileOptions, warnings: Warning[]) {
}
});
if (name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test(name)) {
if (name && !regex_valid_identifier.test(name)) {
throw new Error(`options.name must be a valid identifier (got '${name}')`);
}
if (name && /^[a-z]/.test(name)) {
if (name && regex_starts_with_lowercase_character.test(name)) {
const message = 'options.name should be capitalised';
warnings.push({
code: 'options-lowercase-name',

@ -3,7 +3,7 @@ import get_object from '../utils/get_object';
import Expression from './shared/Expression';
import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
import {dimensions} from '../../utils/patterns';
import { regex_dimensions } from '../../utils/patterns';
import { Node as ESTreeNode } from 'estree';
import { TemplateNode } from '../../interfaces';
import Element from './Element';
@ -88,7 +88,7 @@ export default class Binding extends Node {
const type = parent.get_static_attribute_value('type');
this.is_readonly =
dimensions.test(this.name) ||
regex_dimensions.test(this.name) ||
(isElement(parent) &&
((parent.is_media_node() && read_only_media_attributes.has(this.name)) ||
(parent.name === 'input' && type === 'file')) /* TODO others? */);

@ -11,7 +11,7 @@ import StyleDirective from './StyleDirective';
import Text from './Text';
import { namespaces } from '../../utils/namespaces';
import map_children from './shared/map_children';
import { dimensions, start_newline } from '../../utils/patterns';
import { regex_dimensions, regex_starts_with_newline, regex_non_whitespace_character } from '../../utils/patterns';
import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list';
import Let from './Let';
@ -203,6 +203,10 @@ function is_valid_aria_attribute_value(schema: ARIAPropertyDefinition, value: st
}
}
const regex_any_repeated_whitespaces = /[\s]+/g;
const regex_heading_tags = /^h[1-6]$/;
const regex_illegal_attribute_character = /(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/;
export default class Element extends Node {
type: 'Element';
name: string;
@ -253,7 +257,7 @@ export default class Element extends Node {
// places if there's another newline afterwards.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
first.data = first.data.replace(start_newline, '');
first.data = first.data.replace(regex_starts_with_newline, '');
}
}
@ -398,7 +402,7 @@ export default class Element extends Node {
// Errors
if (/(^[0-9-.])|[\^$@%&#?!|()[\]{}^*+~;]/.test(name)) {
if (regex_illegal_attribute_character.test(name)) {
return component.error(attribute, compiler_errors.illegal_attribute(name));
}
@ -464,7 +468,7 @@ export default class Element extends Node {
component.warn(attribute, compiler_warnings.a11y_unknown_aria_attribute(type, match));
}
if (name === 'aria-hidden' && /^h[1-6]$/.test(this.name)) {
if (name === 'aria-hidden' && regex_heading_tags.test(this.name)) {
component.warn(attribute, compiler_warnings.a11y_hidden(this.name));
}
@ -609,6 +613,26 @@ export default class Element extends Node {
const href_attribute = attribute_map.get('href') || attribute_map.get('xlink:href');
const id_attribute = attribute_map.get('id');
const name_attribute = attribute_map.get('name');
const target_attribute = attribute_map.get('target');
if (target_attribute && target_attribute.get_static_value() === '_blank' && href_attribute) {
const href_static_value = href_attribute.get_static_value() ? href_attribute.get_static_value().toLowerCase() : null;
if (href_static_value === null || href_static_value.match(/^(https?:)?\/\//i)) {
const rel = attribute_map.get('rel');
const rel_values = rel ? rel.get_static_value().split(' ') : [];
const expected_values = ['noreferrer'];
expected_values.forEach(expected_value => {
if (!rel || rel && rel_values.indexOf(expected_value) < 0) {
component.warn(this, {
code: `security-anchor-rel-${expected_value}`,
message: `Security: Anchor with "target=_blank" should have rel attribute containing the value "${expected_value}"`
});
}
});
}
}
if (href_attribute) {
const href_value = href_attribute.get_static_value();
@ -729,7 +753,7 @@ export default class Element extends Node {
if (this.name === 'figure') {
const children = this.children.filter(node => {
if (node.type === 'Comment') return false;
if (node.type === 'Text') return /\S/.test(node.data);
if (node.type === 'Text') return regex_non_whitespace_character.test(node.data);
return true;
});
@ -861,7 +885,7 @@ export default class Element extends Node {
if (this.name !== 'video') {
return component.error(binding, compiler_errors.invalid_binding_element_with('<video>', name));
}
} else if (dimensions.test(name)) {
} else if (regex_dimensions.test(name)) {
if (this.name === 'svg' && (name === 'offsetWidth' || name === 'offsetHeight')) {
return component.error(binding, compiler_errors.invalid_binding_on(binding.name, `<svg>. Use '${name.replace('offset', 'client')}' instead`));
} else if (is_svg(this.name)) {
@ -988,7 +1012,7 @@ export default class Element extends Node {
if (attribute && !attribute.is_true) {
attribute.chunks.forEach((chunk, index) => {
if (chunk.type === 'Text') {
let data = chunk.data.replace(/[\s\n\t]+/g, ' ');
let data = chunk.data.replace(regex_any_repeated_whitespaces, ' ');
if (index === 0) {
data = data.trimLeft();
} else if (index === attribute.chunks.length - 1) {
@ -1002,12 +1026,14 @@ export default class Element extends Node {
}
}
const regex_starts_with_vovel = /^[aeiou]/;
function should_have_attribute(
node,
attributes: string[],
name = node.name
) {
const article = /^[aeiou]/.test(attributes[0]) ? 'an' : 'a';
const article = regex_starts_with_vovel.test(attributes[0]) ? 'an' : 'a';
const sequence = attributes.length > 1 ?
attributes.slice(0, -1).join(', ') + ` or ${attributes[attributes.length - 1]}` :
attributes[0];
@ -1015,10 +1041,12 @@ function should_have_attribute(
node.component.warn(node, compiler_warnings.a11y_missing_attribute(name, article, sequence));
}
const regex_minus_sign = /-/;
function within_custom_element(parent: INode) {
while (parent) {
if (parent.type === 'InlineComponent') return false;
if (parent.type === 'Element' && /-/.test(parent.name)) return true;
if (parent.type === 'Element' && regex_minus_sign.test(parent.name)) return true;
parent = parent.parent;
}
return false;

@ -6,6 +6,8 @@ import { Identifier } from 'estree';
import TemplateScope from './shared/TemplateScope';
import { TemplateNode } from '../../interfaces';
const regex_contains_term_function_expression = /FunctionExpression/;
export default class EventHandler extends Node {
type: 'EventHandler';
name: string;
@ -25,7 +27,7 @@ export default class EventHandler extends Node {
this.expression = new Expression(component, this, template_scope, info.expression);
this.uses_context = this.expression.uses_context;
if (/FunctionExpression/.test(info.expression.type) && info.expression.params.length === 0) {
if (regex_contains_term_function_expression.test(info.expression.type) && info.expression.params.length === 0) {
// TODO make this detection more accurate — if `event.preventDefault` isn't called, and
// `event` is passed to another function, we can make it passive
this.can_make_passive = true;
@ -55,7 +57,7 @@ export default class EventHandler extends Node {
}
const node = this.expression.node;
if (/FunctionExpression/.test(node.type)) {
if (regex_contains_term_function_expression.test(node.type)) {
return false;
}

@ -5,6 +5,7 @@ import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
import { TemplateNode } from '../../interfaces';
import compiler_errors from '../compiler_errors';
import { regex_non_whitespace_character } from '../../utils/patterns';
export default class Head extends Node {
type: 'Head';
@ -20,7 +21,7 @@ export default class Head extends Node {
}
this.children = map_children(component, parent, scope, info.children.filter(child => {
return (child.type !== 'Text' || /\S/.test(child.data));
return (child.type !== 'Text' || regex_non_whitespace_character.test(child.data));
}));
if (this.children.length > 0) {

@ -10,6 +10,7 @@ import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
import { TemplateNode } from '../../interfaces';
import compiler_errors from '../compiler_errors';
import { regex_only_whitespaces } from '../../utils/patterns';
export default class InlineComponent extends Node {
type: 'InlineComponent';
@ -73,10 +74,10 @@ export default class InlineComponent extends Node {
case 'Transition':
return component.error(node, compiler_errors.invalid_transition);
case 'StyleDirective':
return component.error(node, compiler_errors.invalid_component_style_directive);
default:
throw new Error(`Not implemented: ${node.type}`);
}
@ -165,7 +166,7 @@ export default class InlineComponent extends Node {
}
function not_whitespace_text(node) {
return !(node.type === 'Text' && /^\s+$/.test(node.data));
return !(node.type === 'Text' && regex_only_whitespaces.test(node.data));
}
function get_namespace(parent: Node, explicit_namespace: string) {

@ -1,20 +1,42 @@
import { TemplateNode } from '../../interfaces';
import list from '../../utils/list';
import compiler_errors from '../compiler_errors';
import Component from '../Component';
import { nodes_to_template_literal } from '../utils/nodes_to_template_literal';
import Expression from './shared/Expression';
import Node from './shared/Node';
import TemplateScope from './shared/TemplateScope';
const valid_modifiers = new Set(['important']);
export default class StyleDirective extends Node {
type: 'StyleDirective';
name: string;
modifiers: Set<string>;
expression: Expression;
should_cache: boolean;
constructor(component: Component, parent: Node, scope: TemplateScope, info: TemplateNode) {
constructor(
component: Component,
parent: Node,
scope: TemplateScope,
info: TemplateNode
) {
super(component, parent, scope, info);
this.name = info.name;
this.modifiers = new Set(info.modifiers);
for (const modifier of this.modifiers) {
if (!valid_modifiers.has(modifier)) {
component.error(
this,
compiler_errors.invalid_style_directive_modifier(
list([...valid_modifiers])
)
);
}
}
// Convert the value array to an expression so it's easier to handle
// the StyleDirective going forward.
@ -34,6 +56,9 @@ export default class StyleDirective extends Node {
this.expression = new Expression(component, this, scope, raw_expression);
this.should_cache = raw_expression.expressions.length > 0;
}
}
get important() {
return this.modifiers.has('important');
}
}

@ -3,6 +3,7 @@ import Component from '../Component';
import TemplateScope from './shared/TemplateScope';
import { INode } from './interfaces';
import { TemplateNode } from '../../interfaces';
import { regex_non_whitespace_character } from '../../utils/patterns';
// Whitespace inside one of these elements will not result in
// a whitespace node being created in any circumstances. (This
@ -16,6 +17,8 @@ const elements_without_text = new Set([
'video'
]);
const regex_ends_with_svg = /svg$/;
export default class Text extends Node {
type: 'Text';
data: string;
@ -28,7 +31,7 @@ export default class Text extends Node {
}
should_skip() {
if (/\S/.test(this.data)) return false;
if (regex_non_whitespace_character.test(this.data)) return false;
const parent_element = this.find_nearest(/(?:Element|InlineComponent|SlotTemplate|Head)/);
if (!parent_element) return false;
@ -37,7 +40,7 @@ export default class Text extends Node {
if (parent_element.type === 'InlineComponent') return parent_element.children.length === 1 && this === parent_element.children[0];
// svg namespace exclusions
if (/svg$/.test(parent_element.namespace)) {
if (regex_ends_with_svg.test(parent_element.namespace)) {
if (this.prev && this.prev.type === 'Element' && this.prev.name === 'tspan') return false;
}

@ -4,6 +4,8 @@ import Node from './Node';
import { INode } from '../interfaces';
import compiler_warnings from '../../compiler_warnings';
const regex_non_whitespace_characters = /[^ \r\n\f\v\t]/;
export default class AbstractBlock extends Node {
block: Block;
children: INode[];
@ -17,7 +19,7 @@ export default class AbstractBlock extends Node {
const child = this.children[0];
if (!child || (child.type === 'Text' && !/[^ \r\n\f\v\t]/.test(child.data))) {
if (!child || (child.type === 'Text' && !regex_non_whitespace_characters.test(child.data))) {
this.component.warn(this, compiler_warnings.empty_block);
}
}

@ -21,6 +21,8 @@ import compiler_errors from '../../compiler_errors';
type Owner = INode;
const regex_contains_term_function_expression = /FunctionExpression/;
export default class Expression {
type: 'Expression' = 'Expression';
component: Component;
@ -72,7 +74,7 @@ export default class Expression {
scope = map.get(node);
}
if (!function_expression && /FunctionExpression/.test(node.type)) {
if (!function_expression && regex_contains_term_function_expression.test(node.type)) {
function_expression = node;
}
@ -126,6 +128,7 @@ export default class Expression {
deep = node.left.type === 'MemberExpression';
names = extract_names(deep ? get_object(node.left) : node.left);
} else if (node.type === 'UpdateExpression') {
deep = node.argument.type === 'MemberExpression';
names = extract_names(get_object(node.argument));
}
}
@ -147,7 +150,26 @@ export default class Expression {
component.add_reference(node, name);
const variable = component.var_lookup.get(name);
if (variable) variable[deep ? 'mutated' : 'reassigned'] = true;
if (variable) {
variable[deep ? 'mutated' : 'reassigned'] = true;
}
const declaration: any = scope.find_owner(name)?.declarations.get(name);
if (declaration) {
if (declaration.kind === 'const' && !deep) {
component.error(node, {
code: 'assignment-to-const',
message: 'You are assigning to a const'
});
}
} else if (variable && variable.writable === false && !deep) {
component.error(node, {
code: 'assignment-to-const',
message: 'You are assigning to a const'
});
}
}
});
}

@ -3,6 +3,7 @@ import Wrapper from './wrappers/shared/Wrapper';
import { b, x } from 'code-red';
import { Node, Identifier, ArrayPattern } from 'estree';
import { is_head } from './wrappers/shared/is_head';
import { regex_double_quotes } from '../../utils/patterns';
export interface Bindings {
object: Identifier;
@ -415,7 +416,7 @@ export default class Block {
block: ${block},
id: ${this.name || 'create_fragment'}.name,
type: "${this.type}",
source: "${this.comment ? this.comment.replace(/"/g, '\\"') : ''}",
source: "${this.comment ? this.comment.replace(regex_double_quotes, '\\"') : ''}",
ctx: #ctx
});
return ${block};`

@ -168,7 +168,7 @@ export default class Renderer {
return member;
}
invalidate(name: string, value?, main_execution_context: boolean = false) {
invalidate(name: string, value?: unknown, main_execution_context: boolean = false) {
return renderer_invalidate(this, name, value, main_execution_context);
}

@ -12,6 +12,7 @@ import { RawSourceMap, DecodedSourceMap } from '@ampproject/remapping/dist/types
import { flatten } from '../../utils/flatten';
import check_enable_sourcemap from '../utils/check_enable_sourcemap';
import { push_array } from '../../utils/push_array';
import { regex_backslashes } from '../../utils/patterns';
export default function dom(
component: Component,
@ -82,7 +83,7 @@ export default function dom(
}
const uses_slots = component.var_lookup.has('$$slots');
let compute_slots;
let compute_slots: Node[] | undefined;
if (uses_slots) {
compute_slots = b`
const $$slots = @compute_slots(#slots);
@ -395,7 +396,7 @@ export default function dom(
if (has_definition) {
const reactive_declarations: (Node | Node[]) = [];
const fixed_reactive_declarations = []; // not really 'reactive' but whatever
const fixed_reactive_declarations: Node[] = []; // not really 'reactive' but whatever
component.reactive_declarations.forEach(d => {
const dependencies = Array.from(d.dependencies);
@ -440,7 +441,7 @@ export default function dom(
return b`let ${$name};`;
});
let unknown_props_check;
let unknown_props_check: Node[] | undefined;
if (component.compile_options.dev && !(uses_props || uses_rest)) {
unknown_props_check = b`
const writable_props = [${writable_props.map(prop => x`'${prop.export_name}'`)}];
@ -530,7 +531,7 @@ export default function dom(
constructor(options) {
super();
${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${css_sourcemap_enabled && options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(regex_backslashes, '\\\\')}${css_sourcemap_enabled && options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
@init(this, { target: this.shadowRoot, props: ${init_props}, customElement: true }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, null, ${dirty});

@ -80,7 +80,7 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names:
return invalidate;
}
export function renderer_invalidate(renderer: Renderer, name: string, value?, main_execution_context: boolean = false) {
export function renderer_invalidate(renderer: Renderer, name: string, value?: unknown, main_execution_context: boolean = false) {
const variable = renderer.component.var_lookup.get(name);
if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) {

@ -582,7 +582,7 @@ export default class EachBlockWrapper extends Wrapper {
const start = this.block.has_update_method ? 0 : '#old_length';
let remove_old_blocks;
let remove_old_blocks: Node[];
if (this.block.has_outros) {
const out = block.get_unique_name('out');

@ -10,6 +10,7 @@ import handle_select_value_binding from './handle_select_value_binding';
import { Identifier, Node } from 'estree';
import { namespaces } from '../../../../utils/namespaces';
import { boolean_attributes } from '../../../../../shared/boolean_attributes';
import { regex_double_quotes } from '../../../../utils/patterns';
const non_textlike_input_types = new Set([
'button',
@ -42,9 +43,12 @@ export class BaseAttributeWrapper {
}
}
render(_block: Block) {}
render(_block: Block) { }
}
const regex_minus_sign = /-/;
const regex_invalid_variable_identifier_characters = /[^a-zA-Z_$]/g;
export default class AttributeWrapper extends BaseAttributeWrapper {
node: Attribute;
parent: ElementWrapper;
@ -115,7 +119,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
// xlink is a special case... we could maybe extend this to generic
// namespaced attributes but I'm not sure that's applicable in
// HTML5?
const method = /-/.test(element.node.name)
const method = regex_minus_sign.test(element.node.name)
? '@set_custom_element_data'
: name.slice(0, 6) === 'xlink:'
? '@xlink_attr'
@ -126,7 +130,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
const dependencies = this.get_dependencies();
const value = this.get_value(block);
let updater;
let updater: Node[];
const init = this.get_init(block, value);
if (is_legacy_input_type) {
@ -196,7 +200,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
get_init(block: Block, value) {
this.last = this.should_cache && block.get_unique_name(
`${this.parent.var.name}_${this.name.replace(/[^a-zA-Z_$]/g, '_')}_value`
`${this.parent.var.name}_${this.name.replace(regex_invalid_variable_identifier_characters, '_')}_value`
);
if (this.should_cache) block.add_variable(this.last);
@ -254,7 +258,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
return metadata;
}
get_value(block) {
get_value(block: Block) {
if (this.node.is_true) {
if (this.metadata && boolean_attributes.has(this.metadata.property_name.toLowerCase())) {
return x`true`;
@ -283,7 +287,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
return value;
}
get_class_name_text(block) {
get_class_name_text(block: Block) {
const scoped_css = this.node.chunks.some((chunk: Text) => chunk.synthetic);
const rendered = this.render_chunks(block);
@ -313,7 +317,7 @@ export default class AttributeWrapper extends BaseAttributeWrapper {
return `="${value.map(chunk => {
return chunk.type === 'Text'
? chunk.data.replace(/"/g, '\\"')
? chunk.data.replace(regex_double_quotes, '\\"')
: `\${${chunk.manipulate()}}`;
}).join('')}"`;
}
@ -381,13 +385,14 @@ function should_cache(attribute: AttributeWrapper) {
return attribute.is_src || attribute.node.should_cache();
}
const regex_contains_checked_or_group = /checked|group/;
function is_indirectly_bound_value(attribute: AttributeWrapper) {
const element = attribute.parent;
return attribute.name === 'value' &&
(element.node.name === 'option' || // TODO check it's actually bound
(element.node.name === 'input' &&
element.node.bindings.some(
(binding) =>
/checked|group/.test(binding.name)
(binding) => regex_contains_checked_or_group.test(binding.name)
)));
}

@ -299,8 +299,8 @@ function get_binding_group(renderer: Renderer, value: Binding, block: Block) {
for (const dep of contextual_dependencies) {
const context = block.bindings.get(dep);
let key;
let name;
let key: string;
let name: string;
if (context) {
key = context.object.name;
name = context.property.name;
@ -364,7 +364,7 @@ function get_event_handler(
const contextual_dependencies = new Set<string>(binding.node.expression.contextual_dependencies);
const context = block.bindings.get(name);
let set_store;
let set_store: Node[] | undefined;
if (context) {
const { object, property, store, snippet } = context;

@ -26,7 +26,7 @@ export default class EventHandlerWrapper {
}
}
get_snippet(block) {
get_snippet(block: Block) {
const snippet = this.node.expression ? this.node.expression.manipulate(block) : block.renderer.reference(this.node.handler_name);
if (this.node.reassigned) {

@ -69,6 +69,8 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
}
}
const regex_style_prop_key = /^\s*([\w-]+):\s*/;
function optimize_style(value: Array<Text | Expression>) {
const props: StyleProp[] = [];
let chunks = value.slice();
@ -78,7 +80,7 @@ function optimize_style(value: Array<Text | Expression>) {
if (chunk.type !== 'Text') return null;
const key_match = /^\s*([\w-]+):\s*/.exec(chunk.data);
const key_match = regex_style_prop_key.exec(chunk.data);
if (!key_match) return null;
const key = key_match[1];
@ -106,6 +108,9 @@ function optimize_style(value: Array<Text | Expression>) {
return props;
}
const regex_important_flag = /\s*!important\s*$/;
const regex_semicolon_or_whitespace = /[;\s]/;
function get_style_value(chunks: Array<Text | Expression>) {
const value: Array<Text | Expression> = [];
@ -151,7 +156,7 @@ function get_style_value(chunks: Array<Text | Expression>) {
} as Text);
}
while (/[;\s]/.test(chunk.data[c])) c += 1;
while (regex_semicolon_or_whitespace.test(chunk.data[c])) c += 1;
const remaining_data = chunk.data.slice(c);
if (remaining_data) {
@ -172,9 +177,9 @@ function get_style_value(chunks: Array<Text | Expression>) {
let important = false;
const last_chunk = value[value.length - 1];
if (last_chunk && last_chunk.type === 'Text' && /\s*!important\s*$/.test(last_chunk.data)) {
if (last_chunk && last_chunk.type === 'Text' && regex_important_flag.test(last_chunk.data)) {
important = true;
last_chunk.data = last_chunk.data.replace(/\s*!important\s*$/, '');
last_chunk.data = last_chunk.data.replace(regex_important_flag, '');
if (!last_chunk.data) value.pop();
}

@ -6,7 +6,7 @@ svg_attributes.forEach(name => {
svg_attribute_lookup.set(name.toLowerCase(), name);
});
export default function fix_attribute_casing(name) {
export default function fix_attribute_casing(name: string) {
name = name.toLowerCase();
return svg_attribute_lookup.get(name) || name;
}

@ -12,14 +12,14 @@ import { namespaces } from '../../../../utils/namespaces';
import AttributeWrapper from './Attribute';
import StyleAttributeWrapper from './StyleAttribute';
import SpreadAttributeWrapper from './SpreadAttribute';
import { dimensions, start_newline } from '../../../../utils/patterns';
import { regex_dimensions, regex_starts_with_newline, regex_backslashes } from '../../../../utils/patterns';
import Binding from './Binding';
import add_to_set from '../../../utils/add_to_set';
import { add_event_handler } from '../shared/add_event_handlers';
import { add_action } from '../shared/add_actions';
import bind_this from '../shared/bind_this';
import { is_head } from '../shared/is_head';
import { Identifier, ExpressionStatement, CallExpression } from 'estree';
import { Identifier, ExpressionStatement, CallExpression, Node } from 'estree';
import EventHandler from './EventHandler';
import { extract_names } from 'periscopic';
import Action from '../../../nodes/Action';
@ -34,12 +34,16 @@ interface BindingGroup {
bindings: Binding[];
}
const regex_contains_radio_or_checkbox_or_file = /radio|checkbox|file/;
const regex_contains_radio_or_checkbox_or_range_or_file = /radio|checkbox|range|file/;
const events = [
{
event_names: ['input'],
filter: (node: Element, _name: string) =>
node.name === 'textarea' ||
node.name === 'input' && !/radio|checkbox|range|file/.test(node.get_static_attribute_value('type') as string)
node.name === 'input' &&
!regex_contains_radio_or_checkbox_or_range_or_file.test(node.get_static_attribute_value('type') as string)
},
{
event_names: ['input'],
@ -51,7 +55,8 @@ const events = [
event_names: ['change'],
filter: (node: Element, _name: string) =>
node.name === 'select' ||
node.name === 'input' && /radio|checkbox|file/.test(node.get_static_attribute_value('type') as string)
node.name === 'input' &&
regex_contains_radio_or_checkbox_or_file.test(node.get_static_attribute_value('type') as string)
},
{
event_names: ['change', 'input'],
@ -62,7 +67,7 @@ const events = [
{
event_names: ['elementresize'],
filter: (_node: Element, name: string) =>
dimensions.test(name)
regex_dimensions.test(name)
},
// media events
@ -136,6 +141,8 @@ const events = [
];
const CHILD_DYNAMIC_ELEMENT_BLOCK = 'child_dynamic_element';
const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g;
const regex_minus_signs = /-/g;
export default class ElementWrapper extends Wrapper {
node: Element;
@ -182,7 +189,7 @@ export default class ElementWrapper extends Wrapper {
this.var = {
type: 'Identifier',
name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_')
name: node.name.replace(regex_invalid_variable_identifier_characters, '_')
};
this.void = is_void(node.name);
@ -527,7 +534,7 @@ export default class ElementWrapper extends Wrapper {
.filter((attr) => !(attr instanceof SpreadAttributeWrapper) && !attr.property_name)
.map((attr) => p`${(attr as StyleAttributeWrapper | AttributeWrapper).name}: true`);
let reference;
let reference: string | ReturnType<typeof x>;
if (this.node.tag_expr.node.type === 'Literal') {
if (this.node.namespace) {
reference = `"${this.node.tag_expr.node.value}"`;
@ -547,7 +554,7 @@ export default class ElementWrapper extends Wrapper {
}
}
add_directives_in_order (block: Block) {
add_directives_in_order(block: Block) {
type OrderedAttribute = EventHandler | BindingGroup | Binding | Action;
const binding_groups = events
@ -561,7 +568,7 @@ export default class ElementWrapper extends Wrapper {
const this_binding = this.bindings.find(b => b.node.name === 'this');
function getOrder (item: OrderedAttribute) {
function getOrder(item: OrderedAttribute) {
if (item instanceof EventHandler) {
return item.node.start;
} else if (item instanceof Binding) {
@ -627,7 +634,7 @@ export default class ElementWrapper extends Wrapper {
// media bindings — awkward special case. The native timeupdate events
// fire too infrequently, so we need to take matters into our
// own hands
let animation_frame;
let animation_frame: Identifier | undefined;
if (binding_group.events[0] === 'timeupdate') {
animation_frame = block.get_unique_name(`${this.var.name}_animationframe`);
block.add_variable(animation_frame);
@ -867,9 +874,7 @@ export default class ElementWrapper extends Wrapper {
}
}
add_transitions(
block: Block
) {
add_transitions(block: Block) {
const { intro, outro } = this.node;
if (!intro && !outro) return;
@ -926,7 +931,7 @@ export default class ElementWrapper extends Wrapper {
const fn = this.renderer.reference(intro.name);
let intro_block;
let intro_block: Node[];
if (outro) {
intro_block = b`
@ -1025,7 +1030,7 @@ export default class ElementWrapper extends Wrapper {
${outro && b`@add_transform(${this.var}, ${rect});`}
`);
let params;
let params: Node | ReturnType<typeof x>;
if (this.node.animation.expression) {
params = this.node.animation.expression.manipulate(block);
@ -1053,8 +1058,8 @@ export default class ElementWrapper extends Wrapper {
const has_spread = this.node.attributes.some(attr => attr.is_spread);
this.node.classes.forEach(class_directive => {
const { expression, name } = class_directive;
let snippet;
let dependencies;
let snippet: Node | string;
let dependencies: Set<string>;
if (expression) {
snippet = expression.manipulate(block);
dependencies = expression.dependencies;
@ -1095,16 +1100,16 @@ export default class ElementWrapper extends Wrapper {
add_styles(block: Block) {
const has_spread = this.node.attributes.some(attr => attr.is_spread);
this.node.styles.forEach((style_directive) => {
const { name, expression, should_cache } = style_directive;
const { name, expression, should_cache, important } = style_directive;
const snippet = expression.manipulate(block);
let cached_snippet;
let cached_snippet: Identifier | undefined;
if (should_cache) {
cached_snippet = block.get_unique_name(`style_${name.replace(/-/g, '_')}`);
cached_snippet = block.get_unique_name(`style_${name.replace(regex_minus_signs, '_')}`);
block.add_variable(cached_snippet, snippet);
}
const updater = b`@set_style(${this.var}, "${name}", ${should_cache ? cached_snippet : snippet}, false)`;
const updater = b`@set_style(${this.var}, "${name}", ${should_cache ? cached_snippet : snippet}, ${important ? 1 : null})`;
block.chunks.hydrate.push(updater);
@ -1129,7 +1134,7 @@ export default class ElementWrapper extends Wrapper {
});
}
add_manual_style_scoping(block) {
add_manual_style_scoping(block: Block) {
if (this.node.needs_manual_style_scoping) {
const updater = b`@toggle_class(${this.var}, "${this.node.component.stylesheet.id}", true);`;
block.chunks.hydrate.push(updater);
@ -1138,10 +1143,13 @@ export default class ElementWrapper extends Wrapper {
}
}
const regex_backticks = /`/g;
const regex_dollar_signs = /\$/g;
function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapper | RawMustacheTagWrapper>, block: Block, literal: any, state: any, can_use_raw_text?: boolean) {
wrappers.forEach(wrapper => {
if (wrapper instanceof TextWrapper) {
// Don't add the <pre>/<textare> newline logic here because pre/textarea.innerHTML
// Don't add the <pre>/<textarea> newline logic here because pre/textarea.innerHTML
// would keep the leading newline, too, only someParent.innerHTML = '..<pre/textarea>..' won't
if ((wrapper as TextWrapper).use_space()) state.quasi.value.raw += ' ';
@ -1155,9 +1163,9 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapp
);
state.quasi.value.raw += (raw ? wrapper.data : escape_html(wrapper.data))
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`')
.replace(/\$/g, '\\$');
.replace(regex_backslashes, '\\\\')
.replace(regex_backticks, '\\`')
.replace(regex_dollar_signs, '\\$');
} else if (wrapper instanceof MustacheTagWrapper || wrapper instanceof RawMustacheTagWrapper) {
literal.quasis.push(state.quasi);
literal.expressions.push(wrapper.node.expression.manipulate(block));
@ -1192,7 +1200,7 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapp
// Two or more leading newlines are required to restore the leading newline immediately after `<pre>`.
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
const first = wrapper.fragment.nodes[0];
if (first && first.node.type === 'Text' && start_newline.test(first.node.data)) {
if (first && first.node.type === 'Text' && regex_starts_with_newline.test(first.node.data)) {
state.quasi.value.raw += '\n';
}
}
@ -1204,7 +1212,7 @@ function to_html(wrappers: Array<ElementWrapper | TextWrapper | MustacheTagWrapp
// Two or more leading newlines are required to restore the leading newline immediately after `<textarea>`.
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
const first = value_attribute.node.chunks[0];
if (first && first.type === 'Text' && start_newline.test(first.data)) {
if (first && first.type === 'Text' && regex_starts_with_newline.test(first.data)) {
state.quasi.value.raw += '\n';
}
to_html_for_attr_value(value_attribute, block, literal, state);

@ -21,6 +21,7 @@ import Block from '../Block';
import { trim_start, trim_end } from '../../../utils/trim';
import { link } from '../../../utils/link';
import { Identifier } from 'estree';
import { regex_starts_with_whitespace } from '../../../utils/patterns';
const wrappers = {
AwaitBlock,
@ -64,7 +65,7 @@ export default class FragmentWrapper {
this.nodes = [];
let last_child: Wrapper;
let window_wrapper;
let window_wrapper: Window | undefined;
let i = nodes.length;
while (i--) {
@ -92,7 +93,7 @@ export default class FragmentWrapper {
// *unless* there is no whitespace between this node and its next sibling
if (this.nodes.length === 0) {
const should_trim = (
next_sibling ? (next_sibling.node.type === 'Text' && /^\s/.test(next_sibling.node.data) && trimmable_at(child, next_sibling)) : !child.has_ancestor('EachBlock')
next_sibling ? (next_sibling.node.type === 'Text' && regex_starts_with_whitespace.test(next_sibling.node.data) && trimmable_at(child, next_sibling)) : !child.has_ancestor('EachBlock')
);
if (should_trim && !child.keep_space()) {

@ -33,7 +33,7 @@ export default class HeadWrapper extends Wrapper {
}
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
let nodes;
let nodes: Identifier;
if (this.renderer.options.hydratable && this.fragment.nodes.length) {
nodes = block.get_unique_name('head_nodes');
block.chunks.claim.push(b`const ${nodes} = @head_selector('${this.node.id}', @_document.head);`);

@ -13,6 +13,8 @@ import { Identifier, Node } from 'estree';
import { push_array } from '../../../utils/push_array';
import { add_const_tags, add_const_tags_context } from './shared/add_const_tags';
type DetachingOrNull = 'detaching' | null;
function is_else_if(node: ElseBlock) {
return (
node && node.children.length === 1 && node.children[0].type === 'IfBlock'
@ -213,7 +215,7 @@ export default class IfBlockWrapper extends Wrapper {
const vars = { name, anchor, if_exists_condition, has_else, has_transitions };
const detaching = parent_node && !is_head(parent_node) ? null : 'detaching';
const detaching: DetachingOrNull = parent_node && !is_head(parent_node) ? null : 'detaching';
if (this.node.else) {
this.branches.forEach(branch => {
@ -275,9 +277,9 @@ export default class IfBlockWrapper extends Wrapper {
block: Block,
parent_node: Identifier,
_parent_nodes: Identifier,
dynamic,
dynamic: boolean,
{ name, anchor, has_else, if_exists_condition, has_transitions },
detaching
detaching: DetachingOrNull
) {
const select_block_type = this.renderer.component.get_unique_name('select_block_type');
const current_block_type = block.get_unique_name('current_block_type');
@ -414,9 +416,9 @@ export default class IfBlockWrapper extends Wrapper {
block: Block,
parent_node: Identifier,
_parent_nodes: Identifier,
dynamic,
dynamic: boolean,
{ name, anchor, has_else, has_transitions, if_exists_condition },
detaching
detaching: DetachingOrNull
) {
const select_block_type = this.renderer.component.get_unique_name('select_block_type');
const current_block_type_index = block.get_unique_name('current_block_type_index');
@ -428,8 +430,8 @@ export default class IfBlockWrapper extends Wrapper {
const if_ctx = select_block_ctx ? x`${select_block_ctx}(#ctx, ${current_block_type_index})` : x`#ctx`;
const if_current_block_type_index = has_else
? nodes => nodes
: nodes => b`if (~${current_block_type_index}) { ${nodes} }`;
? (nodes: Node[]) => nodes
: (nodes: Node[]) => b`if (~${current_block_type_index}) { ${nodes} }`;
block.add_variable(current_block_type_index);
block.add_variable(name);
@ -593,9 +595,9 @@ export default class IfBlockWrapper extends Wrapper {
block: Block,
parent_node: Identifier,
_parent_nodes: Identifier,
dynamic,
dynamic: boolean,
{ name, anchor, if_exists_condition, has_transitions },
detaching
detaching: DetachingOrNull
) {
const branch = this.branches[0];
const if_ctx = branch.get_ctx_name ? x`${branch.get_ctx_name}(#ctx)` : x`#ctx`;

@ -24,6 +24,8 @@ import { namespaces } from '../../../../utils/namespaces';
type SlotDefinition = { block: Block; scope: TemplateScope; get_context?: Node; get_changes?: Node };
const regex_invalid_variable_identifier_characters = /[^a-zA-Z_$]/g;
export default class InlineComponentWrapper extends Wrapper {
var: Identifier;
slots: Map<string, SlotDefinition> = new Map();
@ -137,7 +139,7 @@ export default class InlineComponentWrapper extends Wrapper {
child.render(block, null, x`#nodes` as Identifier);
});
let props;
let props: Identifier | undefined;
const name_changes = block.get_unique_name(`${name.name}_changes`);
const uses_spread = !!this.node.attributes.find(a => a.is_spread);
@ -232,7 +234,7 @@ export default class InlineComponentWrapper extends Wrapper {
: null;
const unchanged = dependencies.size === 0;
let change_object;
let change_object: Node | ReturnType<typeof x>;
if (attr.is_spread) {
const value = attr.expression.manipulate(block);
initial_props.push(value);
@ -596,7 +598,7 @@ export default class InlineComponentWrapper extends Wrapper {
this.node.css_custom_properties.forEach((attr) => {
const dependencies = attr.get_dependencies();
const should_cache = attr.should_cache();
const last = should_cache && block.get_unique_name(`${attr.name.replace(/[^a-zA-Z_$]/g, '_')}_last`);
const last = should_cache && block.get_unique_name(`${attr.name.replace(regex_invalid_variable_identifier_characters, '_')}_last`);
if (should_cache) block.add_variable(last);
const value = attr.get_value(block);
const init = should_cache ? x`${last} = ${value}` : value;

@ -8,7 +8,7 @@ import Element from '../../nodes/Element';
import MustacheTag from '../../nodes/MustacheTag';
import RawMustacheTag from '../../nodes/RawMustacheTag';
import { is_head } from './shared/is_head';
import { Identifier } from 'estree';
import { Identifier, Node } from 'estree';
export default class RawMustacheTagWrapper extends Tag {
var: Identifier = { type: 'Identifier', name: 'raw' };
@ -30,7 +30,7 @@ export default class RawMustacheTagWrapper extends Tag {
const can_use_innerhtml = !in_head && parent_node && !this.prev && !this.next;
if (can_use_innerhtml) {
const insert = content => b`${parent_node}.innerHTML = ${content};`[0];
const insert = (content: Node) => b`${parent_node}.innerHTML = ${content};`[0];
const { init } = this.rename_this_method(
block,

@ -9,7 +9,7 @@ import add_to_set from '../../utils/add_to_set';
import get_slot_data from '../../utils/get_slot_data';
import { is_reserved_keyword } from '../../utils/reserved_keywords';
import is_dynamic from './shared/is_dynamic';
import { Identifier, ObjectExpression } from 'estree';
import { Identifier, ObjectExpression, Node } from 'estree';
import create_debugging_comment from './shared/create_debugging_comment';
export default class SlotWrapper extends Wrapper {
@ -75,9 +75,9 @@ export default class SlotWrapper extends Wrapper {
block = this.slot_block;
}
let get_slot_changes_fn;
let get_slot_spread_changes_fn;
let get_slot_context_fn;
let get_slot_changes_fn: Identifier | 'null';
let get_slot_spread_changes_fn: Identifier | undefined;
let get_slot_context_fn: Identifier | 'null';
if (this.node.values.size > 0) {
get_slot_changes_fn = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_changes`);
@ -176,7 +176,7 @@ export default class SlotWrapper extends Wrapper {
].filter(Boolean);
const all_dirty_condition = all_dirty_conditions.length ? all_dirty_conditions.reduce((condition1, condition2) => x`${condition1} || ${condition2}`) : null;
let slot_update;
let slot_update: Node[];
if (all_dirty_condition) {
const dirty = x`${all_dirty_condition} ? @get_all_dirty_from_scope(${renderer.reference('$$scope')}) : @get_slot_changes(${slot_definition}, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn})`;

@ -5,6 +5,8 @@ import Wrapper from './shared/Wrapper';
import { x } from 'code-red';
import { Identifier } from 'estree';
const regex_non_whitespace_characters = /[\S\u00A0]/;
export default class TextWrapper extends Wrapper {
node: Text;
data: string;
@ -27,7 +29,7 @@ export default class TextWrapper extends Wrapper {
use_space() {
if (this.renderer.component.component_options.preserveWhitespace) return false;
if (/[\S\u00A0]/.test(this.data)) return false;
if (regex_non_whitespace_characters.test(this.data)) return false;
return !this.node.within_pre();
}

@ -45,7 +45,7 @@ export default class WindowWrapper extends Wrapper {
const { renderer } = this;
const { component } = renderer;
const events = {};
const events: Record<string, Array<{ name: string; value: string }>> = {};
const bindings: Record<string, string> = {};
add_actions(block, '@_window', this.node.actions);

@ -1,7 +1,7 @@
import { b, x } from 'code-red';
import Block from '../../Block';
import Action from '../../../nodes/Action';
import { Expression } from 'estree';
import { Expression, Node } from 'estree';
import is_contextual from '../../../nodes/shared/is_contextual';
export default function add_actions(
@ -12,10 +12,12 @@ export default function add_actions(
actions.forEach(action => add_action(block, target, action));
}
const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g;
export function add_action(block: Block, target: string | Expression, action: Action) {
const { expression, template_scope } = action;
let snippet;
let dependencies;
let snippet: Node | undefined;
let dependencies: string[] | undefined;
if (expression) {
snippet = expression.manipulate(block);
@ -23,7 +25,7 @@ export function add_action(block: Block, target: string | Expression, action: Ac
}
const id = block.get_unique_name(
`${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action`
`${action.name.replace(regex_invalid_variable_identifier_characters, '_')}_action`
);
block.add_variable(id);

@ -1,5 +1,7 @@
import Component from '../../../Component';
import { INode } from '../../../nodes/interfaces';
import { regex_whitespace_characters } from '../../../../utils/patterns';
export default function create_debugging_comment(
node: INode,
@ -13,7 +15,7 @@ export default function create_debugging_comment(
while (source[c - 1] === '{') c -= 1;
}
let d;
let d: number;
if (node.type === 'InlineComponent' || node.type === 'Element' || node.type === 'SlotTemplate') {
if (node.children.length) {
@ -36,5 +38,5 @@ export default function create_debugging_comment(
const start = locate(c);
const loc = `(${start.line}:${start.column})`;
return `${loc} ${source.slice(c, d)}`.replace(/\s/g, ' ');
return `${loc} ${source.slice(c, d)}`.replace(regex_whitespace_characters, ' ');
}

@ -2,7 +2,7 @@ import Let from '../../../nodes/Let';
import { x, p } from 'code-red';
import Block from '../../Block';
import TemplateScope from '../../../nodes/shared/TemplateScope';
import { BinaryExpression } from 'estree';
import { BinaryExpression, Identifier } from 'estree';
export function get_slot_definition(block: Block, scope: TemplateScope, lets: Let[]) {
if (lets.length === 0) return { block, scope };
@ -21,7 +21,7 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
const value_map = new Map();
lets.forEach(l => {
let value;
let value: Identifier;
if (l.names.length > 1) {
// more than one, probably destructuring
const unique_name = block.get_unique_name(l.names.join('_')).name;
@ -86,7 +86,7 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
elements[g] = grouped[g]
? grouped[g]
.map(({ name, n }) => x`${name} ? ${1 << n} : 0`)
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`)
.reduce((lhs: ReturnType<typeof x>, rhs: ReturnType<typeof x>) => x`${lhs} | ${rhs}`)
: x`0`;
}

@ -1,3 +1,5 @@
export function is_head(node) {
return node && node.type === 'MemberExpression' && node.object.name === '@_document' && node.property.name === 'head';
import { Node } from 'estree';
export function is_head(node: Node) {
return node && node.type === 'MemberExpression' && node.object['name'] === '@_document' && node.property['name'] === 'head';
}

@ -8,7 +8,7 @@ import Expression from '../../nodes/shared/Expression';
import remove_whitespace_children from './utils/remove_whitespace_children';
import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing';
import { namespaces } from '../../../utils/namespaces';
import { start_newline } from '../../../utils/patterns';
import { regex_starts_with_newline } from '../../../utils/patterns';
import { Node, Expression as ESExpression } from 'estree';
export default function (node: Element, renderer: Renderer, options: RenderOptions) {
@ -44,7 +44,10 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
class_expression_list.reduce((lhs, rhs) => x`${lhs} + ' ' + ${rhs}`);
const style_expression_list = node.styles.map(style_directive => {
const { name, expression: { node: expression } } = style_directive;
let { name, important, expression: { node: expression } } = style_directive;
if (important) {
expression = x`${expression} + ' !important'`;
}
return p`"${name}": ${expression}`;
});
@ -173,7 +176,7 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
const value_attribute = node.attributes.find(({ name }) => name === 'value');
if (value_attribute) {
const first = value_attribute.chunks[0];
if (first && first.type === 'Text' && start_newline.test(first.data)) {
if (first && first.type === 'Text' && regex_starts_with_newline.test(first.data)) {
renderer.add_string('\n');
}
}
@ -188,7 +191,7 @@ export default function (node: Element, renderer: Renderer, options: RenderOptio
// see https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
// see https://html.spec.whatwg.org/multipage/syntax.html#element-restrictions
const first = children[0];
if (first && first.type === 'Text' && start_newline.test(first.data)) {
if (first && first.type === 'Text' && regex_starts_with_newline.test(first.data)) {
renderer.add_string('\n');
}
}

@ -1,6 +1,7 @@
import Renderer, { RenderOptions } from '../Renderer';
import Head from '../../nodes/Head';
import { x } from 'code-red';
import { Node } from 'estree';
export default function(node: Head, renderer: Renderer, options: RenderOptions) {
const head_options = {
@ -11,6 +12,12 @@ export default function(node: Head, renderer: Renderer, options: RenderOptions)
renderer.push();
renderer.render(node.children, head_options);
const result = renderer.pop();
let expression: Node = result;
if (options.hydratable) {
const start_comment = `HEAD_${node.id}_START`;
const end_comment = `HEAD_${node.id}_END`;
expression = x`'<!-- ${start_comment} -->' + ${expression} + '<!-- ${end_comment} -->'`;
}
renderer.add_expression(x`$$result.head += '<!-- HEAD_${node.id}_START -->' + ${result} + '<!-- HEAD_${node.id}_END -->', ""`);
renderer.add_expression(x`$$result.head += ${expression}, ""`);
}

@ -4,6 +4,7 @@ import Text from '../../../nodes/Text';
import { x } from 'code-red';
import Expression from '../../../nodes/shared/Expression';
import { Expression as ESTreeExpression } from 'estree';
import { regex_double_quotes } from '../../../../utils/patterns';
export function get_class_attribute_value(attribute: Attribute): ESTreeExpression {
// handle special case — `class={possiblyUndefined}` with scoped CSS
@ -21,7 +22,7 @@ export function get_attribute_value(attribute: Attribute): ESTreeExpression {
return attribute.chunks
.map((chunk) => {
return chunk.type === 'Text'
? string_literal(chunk.data.replace(/"/g, '&quot;')) as ESTreeExpression
? string_literal(chunk.data.replace(regex_double_quotes, '&quot;')) as ESTreeExpression
: x`@escape(${chunk.node}, true)`;
})
.reduce((lhs, rhs) => x`${lhs} + ${rhs}`);

@ -1,6 +1,7 @@
import { INode } from '../../../nodes/interfaces';
import { trim_end, trim_start } from '../../../../utils/trim';
import { link } from '../../../../utils/link';
import { regex_starts_with_whitespace } from '../../../../utils/patterns';
// similar logic from `compile/render_dom/wrappers/Fragment`
// We want to remove trailing whitespace inside an element/component/block,
@ -22,7 +23,7 @@ export default function remove_whitespace_children(children: INode[], next?: INo
if (nodes.length === 0) {
const should_trim = next
? next.type === 'Text' &&
/^\s/.test(next.data) &&
regex_starts_with_whitespace.test(next.data) &&
trimmable_at(child, next)
: !child.has_ancestor('EachBlock');

@ -1,3 +1,10 @@
import { regex_starts_with_underscore, regex_ends_with_underscore } from '../../utils/patterns';
const regex_percentage_characters = /%/g;
const regex_file_ending = /\.[^.]+$/;
const regex_repeated_invalid_variable_identifier_characters = /[^a-zA-Z_$0-9]+/g;
const regex_starts_with_digit = /^(\d)/;
export default function get_name_from_filename(filename: string) {
if (!filename) return null;
@ -12,12 +19,12 @@ export default function get_name_from_filename(filename: string) {
}
const base = parts.pop()
.replace(/%/g, 'u')
.replace(/\.[^.]+$/, '')
.replace(/[^a-zA-Z_$0-9]+/g, '_')
.replace(/^_/, '')
.replace(/_$/, '')
.replace(/^(\d)/, '_$1');
.replace(regex_percentage_characters, 'u')
.replace(regex_file_ending, '')
.replace(regex_repeated_invalid_variable_identifier_characters, '_')
.replace(regex_starts_with_underscore, '')
.replace(regex_ends_with_underscore, '')
.replace(regex_starts_with_digit, '_$1');
if (!base) {
throw new Error(`Could not derive component name from file ${filename}`);

@ -1,6 +1,9 @@
// https://github.com/darkskyapp/string-hash/blob/master/index.js
const regex_return_characters = /\r/g;
export default function hash(str: string): string {
str = str.replace(/\r/g, '');
str = str.replace(regex_return_characters, '');
let hash = 5381;
let i = str.length;

@ -19,10 +19,14 @@ const escaped = {
'>': '&gt;'
};
const regex_html_characters_to_escape = /["'&<>]/g;
export function escape_html(html) {
return String(html).replace(/["'&<>]/g, match => escaped[match]);
return String(html).replace(regex_html_characters_to_escape, match => escaped[match]);
}
const regex_template_characters_to_escape = /(\${|`|\\)/g;
export function escape_template(str) {
return str.replace(/(\${|`|\\)/g, '\\$1');
return str.replace(regex_template_characters_to_escape, '\\$1');
}

@ -107,6 +107,14 @@ export default {
code: `invalid-${slug}-placement`,
message: `<${name}> tags cannot be inside elements or blocks`
}),
invalid_logic_block_placement: (location: string, name: string) => ({
code: 'invalid-logic-block-placement',
message: `{#${name}} logic block cannot be ${location}`
}),
invalid_tag_placement: (location: string, name: string) => ({
code: 'invalid-tag-placement',
message: `{@${name}} tag cannot be ${location}`
}),
invalid_ref_directive: (name: string) => ({
code: 'invalid-ref-directive',
message: `The ref directive is no longer supported — use \`bind:this={${name}}\` instead`

@ -1,6 +1,6 @@
import { isIdentifierStart, isIdentifierChar } from 'acorn';
import fragment from './state/fragment';
import { whitespace } from '../utils/patterns';
import { regex_whitespace } from '../utils/patterns';
import { reserved } from '../utils/names';
import full_char_code_at from '../utils/full_char_code_at';
import { TemplateNode, Ast, ParserOptions, Fragment, Style, Script } from '../interfaces';
@ -15,6 +15,8 @@ interface LastAutoClosedTag {
depth: number;
}
const regex_position_indicator = / \(\d+:\d+\)$/;
export class Parser {
readonly template: string;
readonly filename?: string;
@ -76,10 +78,10 @@ export class Parser {
if (this.html.children.length) {
let start = this.html.children[0].start;
while (whitespace.test(template[start])) start += 1;
while (regex_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;
while (regex_whitespace.test(template[end - 1])) end -= 1;
this.html.start = start;
this.html.end = end;
@ -95,7 +97,7 @@ export class Parser {
acorn_error(err: any) {
this.error({
code: 'parse-error',
message: err.message.replace(/ \(\d+:\d+\)$/, '')
message: err.message.replace(regex_position_indicator, '')
}, err.pos);
}
@ -140,7 +142,7 @@ export class Parser {
allow_whitespace() {
while (
this.index < this.template.length &&
whitespace.test(this.template[this.index])
regex_whitespace.test(this.template[this.index])
) {
this.index++;
}
@ -202,7 +204,7 @@ export class Parser {
}
require_whitespace() {
if (!whitespace.test(this.template[this.index])) {
if (!regex_whitespace.test(this.template[this.index])) {
this.error({
code: 'missing-whitespace',
message: 'Expected whitespace'

@ -10,6 +10,7 @@ import {
import { parse_expression_at } from '../acorn';
import { Pattern } from 'estree';
import parser_errors from '../errors';
import { regex_not_newline_characters } from '../../utils/patterns';
export default function read_context(
parser: Parser
@ -65,7 +66,7 @@ export default function read_context(
// so we offset it by removing 1 character in the `space_with_newline`
// to achieve that, we remove the 1st space encountered,
// so it will not affect the `column` of the node
let space_with_newline = parser.template.slice(0, start).replace(/[^\n]/g, ' ');
let space_with_newline = parser.template.slice(0, start).replace(regex_not_newline_characters, ' ');
const first_space = space_with_newline.indexOf(' ');
space_with_newline = space_with_newline.slice(0, first_space) + space_with_newline.slice(first_space + 1);

@ -1,7 +1,7 @@
import { parse_expression_at } from '../acorn';
import { Parser } from '../index';
import { Node } from 'estree';
import { whitespace } from '../../utils/patterns';
import { regex_whitespace } from '../../utils/patterns';
import parser_errors from '../errors';
export default function read_expression(parser: Parser): Node {
@ -20,7 +20,7 @@ export default function read_expression(parser: Parser): Node {
if (char === ')') {
num_parens -= 1;
} else if (!whitespace.test(char)) {
} else if (!regex_whitespace.test(char)) {
parser.error(parser_errors.unexpected_token(')'), index);
}

@ -3,6 +3,9 @@ import { Parser } from '../index';
import { Script } from '../../interfaces';
import { Node, Program } from 'estree';
import parser_errors from '../errors';
import { regex_not_newline_characters } from '../../utils/patterns';
const regex_closing_script_tag = /<\/script\s*>/;
function get_context(parser: Parser, attributes: any[], start: number): string {
const context = attributes.find(attribute => attribute.name === 'context');
@ -23,13 +26,13 @@ function get_context(parser: Parser, attributes: any[], start: number): string {
export default function read_script(parser: Parser, start: number, attributes: Node[]): Script {
const script_start = parser.index;
const data = parser.read_until(/<\/script\s*>/, parser_errors.unclosed_script);
const data = parser.read_until(regex_closing_script_tag, parser_errors.unclosed_script);
if (parser.index >= parser.template.length) {
parser.error(parser_errors.unclosed_script);
}
const source = parser.template.slice(0, script_start).replace(/[^\n]/g, ' ') + data;
parser.read(/<\/script\s*>/);
const source = parser.template.slice(0, script_start).replace(regex_not_newline_characters, ' ') + data;
parser.read(regex_closing_script_tag);
let ast: Program;

@ -1,15 +1,17 @@
import parse from 'css-tree/lib/parser/index.js';
// @ts-ignore
import parse from 'css-tree/parser';
import { walk } from 'estree-walker';
import { Parser } from '../index';
import { Node } from 'estree';
import { Style } from '../../interfaces';
import parser_errors from '../errors';
const regex_closing_style_tag = /<\/style\s*>/;
export default function read_style(parser: Parser, start: number, attributes: Node[]): Style {
const content_start = parser.index;
const tag_close_pattern = /<\/style\s*>/;
const styles = parser.read_until(tag_close_pattern, parser_errors.unclosed_style);
const styles = parser.read_until(regex_closing_style_tag, parser_errors.unclosed_style);
if (parser.index >= parser.template.length) {
parser.error(parser_errors.unclosed_style);
@ -19,7 +21,7 @@ export default function read_style(parser: Parser, start: number, attributes: No
// discard styles when css is disabled
if (parser.css_mode === 'none') {
parser.read(tag_close_pattern);
parser.read(regex_closing_style_tag);
return null;
}
@ -74,7 +76,8 @@ export default function read_style(parser: Parser, start: number, attributes: No
}
});
parser.read(tag_close_pattern);
parser.read(regex_closing_style_tag);
const end = parser.index;
return {

@ -1,7 +1,7 @@
import read_context from '../read/context';
import read_expression from '../read/expression';
import { closing_tag_omitted } from '../utils/html';
import { whitespace } from '../../utils/patterns';
import { regex_whitespace } from '../../utils/patterns';
import { trim_start, trim_end } from '../../utils/trim';
import { to_string } from '../utils/node';
import { Parser } from '../index';
@ -33,6 +33,8 @@ function trim_whitespace(block: TemplateNode, trim_before: boolean, trim_after:
}
}
const regex_whitespace_with_closing_curly_brace = /\s*}/;
export default function mustache(parser: Parser) {
const start = parser.index;
parser.index += 1;
@ -87,8 +89,8 @@ export default function mustache(parser: Parser) {
// strip leading/trailing whitespace as necessary
const char_before = parser.template[block.start - 1];
const char_after = parser.template[parser.index];
const trim_before = !char_before || whitespace.test(char_before);
const trim_after = !char_after || whitespace.test(char_after);
const trim_before = !char_before || regex_whitespace.test(char_before);
const trim_after = !char_after || regex_whitespace.test(char_after);
trim_whitespace(block, trim_before, trim_after);
@ -106,8 +108,8 @@ export default function mustache(parser: Parser) {
const block = parser.current();
if (block.type !== 'IfBlock') {
parser.error(
parser.stack.some(block => block.type === 'IfBlock')
? parser_errors.invalid_elseif_placement_unclosed_block(to_string(block))
parser.stack.some(block => block.type === 'IfBlock')
? parser_errors.invalid_elseif_placement_unclosed_block(to_string(block))
: parser_errors.invalid_elseif_placement_outside_if
);
}
@ -141,8 +143,8 @@ export default function mustache(parser: Parser) {
const block = parser.current();
if (block.type !== 'IfBlock' && block.type !== 'EachBlock') {
parser.error(
parser.stack.some(block => block.type === 'IfBlock' || block.type === 'EachBlock')
? parser_errors.invalid_else_placement_unclosed_block(to_string(block))
parser.stack.some(block => block.type === 'IfBlock' || block.type === 'EachBlock')
? parser_errors.invalid_else_placement_unclosed_block(to_string(block))
: parser_errors.invalid_else_placement_outside_if
);
}
@ -167,15 +169,15 @@ export default function mustache(parser: Parser) {
if (block.type !== 'PendingBlock') {
parser.error(
parser.stack.some(block => block.type === 'PendingBlock')
? parser_errors.invalid_then_placement_unclosed_block(to_string(block))
: parser_errors.invalid_then_placement_without_await
? parser_errors.invalid_then_placement_unclosed_block(to_string(block))
: parser_errors.invalid_then_placement_without_await
);
}
} else {
if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') {
parser.error(parser.stack.some(block => block.type === 'ThenBlock' || block.type === 'PendingBlock')
? parser_errors.invalid_catch_placement_unclosed_block(to_string(block))
: parser_errors.invalid_catch_placement_without_await
? parser_errors.invalid_catch_placement_unclosed_block(to_string(block))
: parser_errors.invalid_catch_placement_without_await
);
}
}
@ -290,7 +292,7 @@ export default function mustache(parser: Parser) {
const await_block_shorthand = type === 'AwaitBlock' && parser.eat('then');
if (await_block_shorthand) {
if (parser.match_regex(/\s*}/)) {
if (parser.match_regex(regex_whitespace_with_closing_curly_brace)) {
parser.allow_whitespace();
} else {
parser.require_whitespace();
@ -301,7 +303,7 @@ export default function mustache(parser: Parser) {
const await_block_catch_shorthand = !await_block_shorthand && type === 'AwaitBlock' && parser.eat('catch');
if (await_block_catch_shorthand) {
if (parser.match_regex(/\s*}/)) {
if (parser.match_regex(regex_whitespace_with_closing_curly_brace)) {
parser.allow_whitespace();
} else {
parser.require_whitespace();
@ -350,7 +352,7 @@ export default function mustache(parser: Parser) {
let identifiers;
// Implies {@debug} which indicates "debug all"
if (parser.read(/\s*}/)) {
if (parser.read(regex_whitespace_with_closing_curly_brace)) {
identifiers = [];
} else {
const expression = read_expression(parser);

@ -53,13 +53,17 @@ function parent_is_head(stack) {
return false;
}
const regex_closing_textarea_tag = /^<\/textarea(\s[^>]*)?>/i;
const regex_closing_comment = /-->/;
const regex_capital_letter = /[A-Z]/;
export default function tag(parser: Parser) {
const start = parser.index++;
let parent = parser.current();
if (parser.eat('!--')) {
const data = parser.read_until(/-->/);
const data = parser.read_until(regex_closing_comment);
parser.eat('-->', true, parser_errors.unclosed_comment);
parser.current().children.push({
@ -104,7 +108,7 @@ export default function tag(parser: Parser) {
const type = meta_tags.has(name)
? meta_tags.get(name)
: (/[A-Z]/.test(name[0]) || name === 'svelte:self' || name === 'svelte:component') ? 'InlineComponent'
: (regex_capital_letter.test(name[0]) || name === 'svelte:self' || name === 'svelte:component') ? 'InlineComponent'
: name === 'svelte:fragment' ? 'SlotTemplate'
: name === 'title' && parent_is_head(parser.stack) ? 'Title'
: name === 'slot' && !parser.customElement ? 'Slot' : 'Element';
@ -218,10 +222,10 @@ export default function tag(parser: Parser) {
// special case
element.children = read_sequence(
parser,
() =>
/^<\/textarea(\s[^>]*)?>/i.test(parser.template.slice(parser.index))
() => regex_closing_textarea_tag.test(parser.template.slice(parser.index)),
'inside <textarea>'
);
parser.read(/^<\/textarea(\s[^>]*)?>/i);
parser.read(regex_closing_textarea_tag);
element.end = parser.index;
} else if (name === 'script' || name === 'style') {
// special case
@ -236,6 +240,8 @@ export default function tag(parser: Parser) {
}
}
const regex_whitespace_or_slash_or_closing_tag = /(\s|\/|>)/;
function read_tag_name(parser: Parser) {
const start = parser.index;
@ -265,7 +271,7 @@ function read_tag_name(parser: Parser) {
if (parser.read(SLOT)) return 'svelte:fragment';
const name = parser.read_until(/(\s|\/|>)/);
const name = parser.read_until(regex_whitespace_or_slash_or_closing_tag);
if (meta_tags.has(name)) return name;
@ -285,6 +291,10 @@ function read_tag_name(parser: Parser) {
return name;
}
// eslint-disable-next-line no-useless-escape
const regex_token_ending_character = /[\s=\/>"']/;
const regex_quote_characters = /["']/;
function read_attribute(parser: Parser, unique_names: Set<string>) {
const start = parser.index;
@ -343,8 +353,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
}
}
// eslint-disable-next-line no-useless-escape
const name = parser.read_until(/[\s=\/>"']/);
const name = parser.read_until(regex_token_ending_character);
if (!name) return null;
let end = parser.index;
@ -359,7 +368,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
parser.allow_whitespace();
value = read_attribute_value(parser);
end = parser.index;
} else if (parser.match_regex(/["']/)) {
} else if (parser.match_regex(regex_quote_characters)) {
parser.error(parser_errors.unexpected_token('='), parser.index);
}
@ -386,6 +395,7 @@ function read_attribute(parser: Parser, unique_names: Set<string>) {
end,
type,
name: directive_name,
modifiers,
value
};
}
@ -473,7 +483,7 @@ function read_attribute_value(parser: Parser) {
let value;
try {
value = read_sequence(parser, () => !!parser.match_regex(regex));
value = read_sequence(parser, () => !!parser.match_regex(regex), 'in attribute value');
} catch (error) {
if (error.code === 'parse-error') {
// if the attribute value didn't close + self-closing tag
@ -495,7 +505,7 @@ function read_attribute_value(parser: Parser) {
return value;
}
function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] {
function read_sequence(parser: Parser, done: () => boolean, location: string): TemplateNode[] {
let current_chunk: Text = {
start: parser.index,
end: null,
@ -521,6 +531,18 @@ function read_sequence(parser: Parser, done: () => boolean): TemplateNode[] {
flush(parser.index);
return chunks;
} else if (parser.eat('{')) {
if (parser.match('#')) {
const index = parser.index - 1;
parser.eat('#');
const name = parser.read_until(/[^a-z]/);
parser.error(parser_errors.invalid_logic_block_placement(location, name), index);
} else if (parser.match('@')) {
const index = parser.index - 1;
parser.eat('@');
const name = parser.read_until(/[^a-z]/);
parser.error(parser_errors.invalid_tag_placement(location, name), index);
}
flush(parser.index - 1);
parser.allow_whitespace();

@ -4,6 +4,7 @@ import { MappedCode, SourceLocation, parse_attached_sourcemap, sourcemap_add_off
import { decode_map } from './decode_sourcemap';
import { replace_in_code, slice_source } from './replace_in_code';
import { MarkupPreprocessor, Source, Preprocessor, PreprocessorGroup, Processed } from './types';
import { regex_whitespaces } from '../utils/patterns';
export * from './types';
@ -13,8 +14,10 @@ interface SourceUpdate {
dependencies?: string[];
}
const regex_filepath_separator = /[/\\]/;
function get_file_basename(filename: string) {
return filename.split(/[/\\]/).pop();
return filename.split(regex_filepath_separator).pop();
}
/**
@ -120,20 +123,26 @@ function processed_tag_to_code(
return tag_open_code.concat(content_code).concat(tag_close_code);
}
const regex_quoted_value = /^['"](.*)['"]$/;
function parse_tag_attributes(str: string) {
// note: won't work with attribute values containing spaces.
return str
.split(/\s+/)
.split(regex_whitespaces)
.filter(Boolean)
.reduce((attrs, attr) => {
const i = attr.indexOf('=');
const [key, value] = i > 0 ? [attr.slice(0, i), attr.slice(i + 1)] : [attr];
const [, unquoted] = (value && value.match(/^['"](.*)['"]$/)) || [];
const [, unquoted] = (value && value.match(regex_quoted_value)) || [];
return { ...attrs, [key]: unquoted ?? value ?? true };
}, {});
}
const regex_style_tags = /<!--[^]*?-->|<style(\s[^]*?)?(?:>([^]*?)<\/style>|\/>)/gi;
const regex_script_tags = /<!--[^]*?-->|<script(\s[^]*?)?(?:>([^]*?)<\/script>|\/>)/gi;
/**
* Calculate the updates required to process all instances of the specified tag.
*/
@ -143,10 +152,7 @@ async function process_tag(
source: Source
): Promise<SourceUpdate> {
const { filename, source: markup } = source;
const tag_regex =
tag_name === 'style'
? /<!--[^]*?-->|<style(\s[^]*?)?(?:>([^]*?)<\/style>|\/>)/gi
: /<!--[^]*?-->|<script(\s[^]*?)?(?:>([^]*?)<\/script>|\/>)/gi;
const tag_regex = tag_name === 'style' ? regex_style_tags : regex_script_tags;
const dependencies: string[] = [];
@ -190,7 +196,7 @@ async function process_markup(process: MarkupPreprocessor, source: Source) {
string: processed.code,
map: processed.map
? // TODO: can we use decode_sourcemap?
typeof processed.map === 'string'
typeof processed.map === 'string'
? JSON.parse(processed.map)
: processed.map
: undefined,

@ -1,11 +1,12 @@
import { TemplateNode } from '../interfaces';
import { flatten } from './flatten';
import { regex_whitespace } from './patterns';
const pattern = /^\s*svelte-ignore\s+([\s\S]+)\s*$/m;
const regex_svelte_ignore = /^\s*svelte-ignore\s+([\s\S]+)\s*$/m;
export function extract_svelte_ignore(text: string): string[] {
const match = pattern.exec(text);
return match ? match[1].split(/[^\S]/).map(x => x.trim()).filter(Boolean) : [];
const match = regex_svelte_ignore.exec(text);
return match ? match[1].split(regex_whitespace).map(x => x.trim()).filter(Boolean) : [];
}
export function extract_svelte_ignore_from_comments<Node extends { leadingComments?: Array<{value: string}> }>(node: Node): string[] {

@ -1,5 +1,7 @@
const regex_tabs = /^\t+/;
function tabs_to_spaces(str: string) {
return str.replace(/^\t+/, match => match.split('\t').join(' '));
return str.replace(regex_tabs, match => match.split('\t').join(' '));
}
export default function get_code_frame(

@ -61,6 +61,8 @@ function merge_tables<T>(this_table: T[], other_table: T[]): [T[], number[], boo
return [new_table, idx_map, val_changed, idx_changed];
}
const regex_line_token = /([^\d\w\s]|\s+)/g;
export class MappedCode {
string: string;
map: DecodedSourceMap;
@ -195,7 +197,7 @@ export class MappedCode {
const line_list = source.split('\n');
for (let line = 0; line < line_list.length; line++) {
map.mappings.push([]);
const token_list = line_list[line].split(/([^\d\w\s]|\s+)/g);
const token_list = line_list[line].split(regex_line_token);
for (let token = 0, column = 0; token < token_list.length; token++) {
if (token_list[token] == '') continue;
map.mappings[line].push([column, 0, offset.line + line, column]);
@ -245,7 +247,7 @@ export function combine_sourcemaps(
if (!map.file) delete map.file; // skip optional field `file`
// When source maps are combined and the leading map is empty, sources is not set.
// Add the filename to the empty array in this case.
// Add the filename to the empty array in this case.
// Further improvements to remapping may help address this as well https://github.com/ampproject/remapping/issues/116
if (!map.sources.length) map.sources = [filename];
@ -289,6 +291,8 @@ export function apply_preprocessor_sourcemap(filename: string, svelte_map: Sourc
return result_map as SourceMap;
}
const regex_data_uri = /data:(?:application|text)\/json;(?:charset[:=]\S+?;)?base64,(\S*)/;
// parse attached sourcemap in processed.code
export function parse_attached_sourcemap(processed: Processed, tag_name: 'script' | 'style'): void {
const r_in = '[#@]\\s*sourceMappingURL\\s*=\\s*(\\S*)';
@ -302,7 +306,7 @@ export function parse_attached_sourcemap(processed: Processed, tag_name: 'script
}
processed.code = processed.code.replace(regex, (_, match1, match2) => {
const map_url = (tag_name == 'script') ? (match1 || match2) : match1;
const map_data = (map_url.match(/data:(?:application|text)\/json;(?:charset[:=]\S+?;)?base64,(\S*)/) || [])[1];
const map_data = (map_url.match(regex_data_uri) || [])[1];
if (map_data) {
// sourceMappingURL is data URL
if (processed.map) {

@ -1,5 +1,6 @@
import { isIdentifierStart, isIdentifierChar } from 'acorn';
import full_char_code_at from './full_char_code_at';
import { regex_starts_with_underscore, regex_ends_with_underscore } from './patterns';
export const reserved = new Set([
'arguments',
@ -65,10 +66,13 @@ export function is_valid(str: string): boolean {
return true;
}
const regex_non_standard_characters = /[^a-zA-Z0-9_]+/g;
const regex_starts_with_number = /^[0-9]/;
export function sanitize(name: string) {
return name
.replace(/[^a-zA-Z0-9_]+/g, '_')
.replace(/^_/, '')
.replace(/_$/, '')
.replace(/^[0-9]/, '_$&');
.replace(regex_non_standard_characters, '_')
.replace(regex_starts_with_underscore, '')
.replace(regex_ends_with_underscore, '')
.replace(regex_starts_with_number, '_$&');
}

@ -1,6 +1,24 @@
export const whitespace = /[ \t\r\n]/;
export const start_whitespace = /^[ \t\r\n]*/;
export const end_whitespace = /[ \t\r\n]*$/;
export const start_newline = /^\r?\n/;
export const regex_whitespace = /\s/;
export const regex_whitespaces = /\s+/;
export const regex_starts_with_whitespace = /^\s/;
export const regex_starts_with_whitespaces = /^[ \t\r\n]*/;
export const regex_ends_with_whitespace = /\s$/;
export const regex_ends_with_whitespaces = /[ \t\r\n]*$/;
export const regex_only_whitespaces = /^\s+$/;
export const dimensions = /^(?:offset|client)(?:Width|Height)$/;
export const regex_whitespace_characters = /\s/g;
export const regex_non_whitespace_character = /\S/;
export const regex_starts_with_newline = /^\r?\n/;
export const regex_not_newline_characters = /[^\n]/g;
export const regex_double_quotes = /"/g;
export const regex_backslashes = /\\/g;
export const regex_starts_with_underscore = /^_/;
export const regex_ends_with_underscore = /_$/;
export const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g;
export const regex_dimensions = /^(?:offset|client)(?:Width|Height)$/;

@ -1,9 +1,9 @@
import { start_whitespace, end_whitespace } from './patterns';
import { regex_starts_with_whitespaces, regex_ends_with_whitespaces } from './patterns';
export function trim_start(str: string) {
return str.replace(start_whitespace, '');
return str.replace(regex_starts_with_whitespaces, '');
}
export function trim_end(str: string) {
return str.replace(end_whitespace, '');
return str.replace(regex_ends_with_whitespaces, '');
}

@ -11,18 +11,47 @@ export function get_current_component() {
return current_component;
}
/**
* Schedules a callback to run immediately before the component is updated after any state change.
*
* The first time the callback runs will be before the initial `onMount`
*
* https://svelte.dev/docs#run-time-svelte-beforeupdate
*/
export function beforeUpdate(fn: () => any) {
get_current_component().$$.before_update.push(fn);
}
/**
* The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM.
* It must be called during the component's initialisation (but doesn't need to live *inside* the component;
* it can be called from an external module).
*
* `onMount` does not run inside a [server-side component](/docs#run-time-server-side-component-api).
*
* https://svelte.dev/docs#run-time-svelte-onmount
*/
export function onMount(fn: () => any) {
get_current_component().$$.on_mount.push(fn);
}
/**
* Schedules a callback to run immediately after the component has been updated.
*
* The first time the callback runs will be after the initial `onMount`
*/
export function afterUpdate(fn: () => any) {
get_current_component().$$.after_update.push(fn);
}
/**
* Schedules a callback to run immediately before the component is unmounted.
*
* Out of `onMount`, `beforeUpdate`, `afterUpdate` and `onDestroy`, this is the
* only one that runs inside a server-side component.
*
* https://svelte.dev/docs#run-time-svelte-ondestroy
*/
export function onDestroy(fn: () => any) {
get_current_component().$$.on_destroy.push(fn);
}
@ -31,6 +60,18 @@ export interface DispatchOptions {
cancelable?: boolean;
}
/**
* Creates an event dispatcher that can be used to dispatch [component events](/docs#template-syntax-component-directives-on-eventname).
* Event dispatchers are functions that can take two arguments: `name` and `detail`.
*
* Component events created with `createEventDispatcher` create a
* [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent).
* These events do not [bubble](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture).
* The `detail` argument corresponds to the [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail)
* property and can contain any type of data.
*
* https://svelte.dev/docs#run-time-svelte-createeventdispatcher
*/
export function createEventDispatcher<EventMap extends {} = any>(): <
EventKey extends Extract<keyof EventMap, string>
>(
@ -57,19 +98,47 @@ export function createEventDispatcher<EventMap extends {} = any>(): <
};
}
/**
* Associates an arbitrary `context` object with the current component and the specified `key`
* and returns that object. The context is then available to children of the component
* (including slotted content) with `getContext`.
*
* Like lifecycle functions, this must be called during component initialisation.
*
* https://svelte.dev/docs#run-time-svelte-setcontext
*/
export function setContext<T>(key, context: T): T {
get_current_component().$$.context.set(key, context);
return context;
}
/**
* Retrieves the context that belongs to the closest parent component with the specified `key`.
* Must be called during component initialisation.
*
* https://svelte.dev/docs#run-time-svelte-getcontext
*/
export function getContext<T>(key): T {
return get_current_component().$$.context.get(key);
}
/**
* Retrieves the whole context map that belongs to the closest parent component.
* Must be called during component initialisation. Useful, for example, if you
* programmatically create a component and want to pass the existing context to it.
*
* https://svelte.dev/docs#run-time-svelte-getallcontexts
*/
export function getAllContexts<T extends Map<any, any> = Map<any, any>>(): T {
return get_current_component().$$.context;
}
/**
* Checks whether a given `key` has been set in the context of a parent component.
* Must be called during component initialisation.
*
* https://svelte.dev/docs#run-time-svelte-hascontext
*/
export function hasContext(key): boolean {
return get_current_component().$$.context.has(key);
}

@ -73,7 +73,7 @@ let world1 = 'world';
let world2 = 'world';
function instance($$self, $$props, $$invalidate) {
const world3 = 'world';
let world3 = 'world';
function foo() {
$$invalidate(0, world3 = 'svelte');

@ -1,7 +1,7 @@
<script>
let world1 = 'world';
let world2 = 'world';
const world3 = 'world';
let world3 = 'world';
function foo() {
world3 = 'svelte';
}

@ -0,0 +1 @@
<div style:color|important={myColor}></div>

@ -0,0 +1,50 @@
{
"html": {
"start": 0,
"end": 43,
"type": "Fragment",
"children": [
{
"start": 0,
"end": 43,
"type": "Element",
"name": "div",
"attributes": [
{
"start": 5,
"end": 36,
"type": "StyleDirective",
"name": "color",
"modifiers": [
"important"
],
"value": [
{
"start": 27,
"end": 36,
"type": "MustacheTag",
"expression": {
"type": "Identifier",
"start": 28,
"end": 35,
"loc": {
"start": {
"line": 1,
"column": 28
},
"end": {
"line": 1,
"column": 35
}
},
"name": "myColor"
}
}
]
}
],
"children": []
}
]
}
}

@ -15,6 +15,7 @@
"end": 16,
"type": "StyleDirective",
"name": "color",
"modifiers": [],
"value": true
}
],
@ -22,4 +23,4 @@
}
]
}
}
}

@ -15,6 +15,7 @@
"end": 22,
"type": "StyleDirective",
"name": "color",
"modifiers": [],
"value": [
{
"start": 18,
@ -45,6 +46,7 @@
"start": 35,
"end": 52,
"type": "StyleDirective",
"modifiers": [],
"name": "color",
"value": [
{
@ -77,6 +79,7 @@
"end": 80,
"type": "StyleDirective",
"name": "color",
"modifiers": [],
"value": [
{
"start": 77,
@ -108,6 +111,7 @@
"end": 120,
"type": "StyleDirective",
"name": "color",
"modifiers": [],
"value": [
{
"start": 106,
@ -160,6 +164,7 @@
"end": 160,
"type": "StyleDirective",
"name": "color",
"modifiers": [],
"value": [
{
"start": 146,
@ -212,6 +217,7 @@
"end": 198,
"type": "StyleDirective",
"name": "color",
"modifiers": [],
"value": [
{
"start": 185,
@ -264,6 +270,7 @@
"end": 245,
"type": "StyleDirective",
"name": "color",
"modifiers": [],
"value": [
{
"start": 223,
@ -352,4 +359,4 @@
}
]
}
}
}

@ -15,6 +15,7 @@
"end": 26,
"type": "StyleDirective",
"name": "color",
"modifiers": [],
"value": [
{
"start": 17,

@ -1,6 +1,6 @@
{
"code": "css-syntax-error",
"message": "LeftCurlyBracket is expected",
"message": "\"{\" is expected",
"start": {
"line": 2,
"column": 16,

@ -0,0 +1,22 @@
export default {
html: `
<h1 class="svelte-szzkfu" style="background-color: red;">hello</h1>
<h1 class="svelte-szzkfu" style="background-color: red !important;">hello</h1>
`,
ssrHtml: `
<h1 class="svelte-szzkfu" style="background-color: red;">hello</h1>
<h1 class="svelte-szzkfu" style="background-color: red !important;">hello</h1>
`,
test({ assert, target, window, component }) {
const h1s = target.querySelectorAll('h1');
assert.equal(window.getComputedStyle(h1s[0])['backgroundColor'], 'rgb(0, 0, 255)');
assert.equal(window.getComputedStyle(h1s[1])['backgroundColor'], 'rgb(255, 0, 0)');
component.color = 'yellow';
assert.equal(window.getComputedStyle(h1s[0])['backgroundColor'], 'rgb(0, 0, 255)');
assert.equal(window.getComputedStyle(h1s[1])['backgroundColor'], 'rgb(255, 255, 0)');
}
};

@ -0,0 +1,12 @@
<script>
export let color = 'red'
</script>
<h1 style:background-color={color} >hello</h1>
<h1 style:background-color|important="{color}">hello</h1>
<style>
h1 {
background-color: blue !important;
}
</style>

@ -117,7 +117,9 @@ describe('ssr', () => {
fs.writeFileSync(`${dir}/_actual-head.html`, head);
try {
assert.htmlEqual(
(compileOptions.hydratable
? assert.htmlEqualWithComments
: assert.htmlEqual)(
head,
fs.readFileSync(`${dir}/_expected-head.html`, 'utf-8')
);

@ -1,5 +1,6 @@
export default {
compileOptions: {
hydratable: true
}
},
withoutNormalizeHtml: true
};

@ -6,7 +6,7 @@
"column": 20,
"line": 11
},
"message": "A11y: not interactive element cannot have positive tabIndex value",
"message": "A11y: noninteractive element cannot have positive tabIndex value",
"pos": 221,
"start": {
"character": 221,
@ -21,7 +21,7 @@
"column": 35,
"line": 12
},
"message": "A11y: not interactive element cannot have positive tabIndex value",
"message": "A11y: noninteractive element cannot have positive tabIndex value",
"pos": 242,
"start": {
"character": 242,
@ -36,7 +36,7 @@
"column": 24,
"line": 13
},
"message": "A11y: not interactive element cannot have positive tabIndex value",
"message": "A11y: noninteractive element cannot have positive tabIndex value",
"pos": 278,
"start": {
"character": 278,
@ -51,7 +51,7 @@
"column": 26,
"line": 14
},
"message": "A11y: not interactive element cannot have positive tabIndex value",
"message": "A11y: noninteractive element cannot have positive tabIndex value",
"pos": 303,
"start": {
"character": 303,

@ -0,0 +1,17 @@
[
{
"code": "assignment-to-const",
"message": "You are assigning to a const",
"start": {
"line": 13,
"column": 24,
"character": 282
},
"end": {
"line": 13,
"column": 35,
"character": 293
},
"pos": 282
}
]

@ -0,0 +1,15 @@
<script>
const immutable = 0;
const obj1 = { prop: true };
const obj2 = { prop: 0 }
</script>
<!-- should not error -->
<button on:click={() => obj1.prop = false}>click</button>
<button on:click={() => obj2.prop++}>click</button>
<!-- should error -->
<button on:click={() => immutable++}>click</button>

@ -0,0 +1,17 @@
[
{
"code": "assignment-to-const",
"message": "You are assigning to a const",
"start": {
"line": 14,
"column": 3,
"character": 172
},
"end": {
"line": 14,
"column": 10,
"character": 179
},
"pos": 172
}
]

@ -0,0 +1,20 @@
<script>
const foo = 'hello';
function shouldNotError() {
let foo = 0;
function inner() {
foo = 1;
}
}
function shouldError() {
function inner() {
foo = 1;
}
}
</script>
<button on:click={shouldNotError}>click</button>
<button on:click={shouldError}>click</button>

@ -0,0 +1,17 @@
[
{
"code": "assignment-to-const",
"message": "You are assigning to a const",
"start": {
"line": 17,
"column": 2,
"character": 189
},
"end": {
"line": 17,
"column": 9,
"character": 196
},
"pos": 189
}
]

@ -0,0 +1,21 @@
<script>
const foo = 'hello';
</script>
<button on:click={() => {
let foo = 0;
function inner() {
foo = 1;
}
}}>
click
</button>
<button on:click={() => {
function inner() {
foo = 1;
}
}}>
click
</button>

@ -0,0 +1,17 @@
[
{
"code": "assignment-to-const",
"message": "You are assigning to a const",
"start": {
"line": 16,
"column": 2,
"character": 225
},
"end": {
"line": 16,
"column": 18,
"character": 241
},
"pos": 225
}
]

@ -0,0 +1,24 @@
<script>
const immutable = false;
const obj1 = { prop: true };
const obj2 = { prop: 0 };
function shouldNotError() {
obj1.prop = false;
}
function shouldNotError2() {
obj2.prop++;
}
function shouldError() {
immutable = true
}
</script>
<button on:click={shouldNotError}>click</button>
<button on:click={shouldNotError2}>click</button>
<button on:click={shouldError}>click</button>

@ -0,0 +1,9 @@
[
{
"code": "invalid-tag-placement",
"message": "{@html} tag cannot be in attribute value",
"start": { "line": 1, "column": 12, "character": 12 },
"end": { "line": 1, "column": 12, "character": 12 },
"pos": 12
}
]

@ -0,0 +1,9 @@
[
{
"code": "invalid-tag-placement",
"message": "{@html} tag cannot be inside <textarea>",
"start": { "line": 2, "column": 1, "character": 12 },
"end": { "line": 2, "column": 1, "character": 12 },
"pos": 12
}
]

@ -0,0 +1,3 @@
<textarea>
{@html fruit}
</textarea>

@ -0,0 +1,9 @@
[
{
"code": "invalid-logic-block-placement",
"message": "{#if} logic block cannot be in attribute value",
"start": { "line": 1, "column": 12, "character": 12 },
"end": { "line": 1, "column": 12, "character": 12 },
"pos": 12
}
]

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save