diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff0ec4f633..4e04386fcc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,7 @@ on: [push, pull_request] jobs: Tests: runs-on: ${{ matrix.os }} + timeout-minutes: 10 strategy: matrix: node-version: [8, 10, 12, 14] @@ -18,12 +19,14 @@ jobs: CI: true Lint: runs-on: ubuntu-latest + timeout-minutes: 2 steps: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 - run: 'npm i && npm run lint' Unit: runs-on: ${{ matrix.os }} + timeout-minutes: 5 strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] diff --git a/CHANGELOG.md b/CHANGELOG.md index 82d0d58a15..ac39b41ee3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,31 @@ ## Unreleased -* Fix placement of `{@html}` when used at the root of a slot or the root of a component ([#5012](https://github.com/sveltejs/svelte/issues/5012)) +* Prevent duplicate invalidation with certain two-way component bindings ([#3180](https://github.com/sveltejs/svelte/issues/3180), [#5117](https://github.com/sveltejs/svelte/issues/5117), [#5144](https://github.com/sveltejs/svelte/issues/5144)) +* Fix reactivity when passing `$$props` to a `` ([#3364](https://github.com/sveltejs/svelte/issues/3364)) +* Fix transitions on `{#each}` `{:else}` ([#4970](https://github.com/sveltejs/svelte/issues/4970)) +* Fix unneeded invalidation of `$$props` and `$$restProps` ([#4993](https://github.com/sveltejs/svelte/issues/4993), [#5118](https://github.com/sveltejs/svelte/issues/5118)) +* Provide better compiler error message when mismatched tags are due to autoclosing of tags ([#5049](https://github.com/sveltejs/svelte/issues/5049)) +* Add `a11y-label-has-associated-control` warning ([#5074](https://github.com/sveltejs/svelte/pull/5074)) +* Fix `bind:group` when using contextual reference ([#5174](https://github.com/sveltejs/svelte/issues/5174)) + +## 3.24.0 + +* Support nullish coalescing (`??`) and optional chaining (`?.`) operators ([#1972](https://github.com/sveltejs/svelte/issues/1972)) +* Support `import.meta` ([#4379](https://github.com/sveltejs/svelte/issues/4379)) +* Fix only setting `` values when they're changed when there are spread attributes ([#4418](https://github.com/sveltejs/svelte/issues/4418)) +* Fix placement of `{@html}` when used at the root of a slot, at the root of a component, or in `` ([#5012](https://github.com/sveltejs/svelte/issues/5012), [#5071](https://github.com/sveltejs/svelte/pull/5071)) +* Fix certain handling of two-way bound `contenteditable` elements ([#5018](https://github.com/sveltejs/svelte/issues/5018)) * Fix handling of `import`ed value that is used as a store and is also mutated ([#5019](https://github.com/sveltejs/svelte/issues/5019)) * Do not display `a11y-missing-content` warning on elements with `contenteditable` bindings ([#5020](https://github.com/sveltejs/svelte/issues/5020)) * Fix handling of `this` in inline function expressions in the template ([#5033](https://github.com/sveltejs/svelte/issues/5033)) * Fix collapsing HTML with static content ([#5040](https://github.com/sveltejs/svelte/issues/5040)) +* Prevent use of `$store` at compile time when top-level `store` has been shadowed ([#5048](https://github.com/sveltejs/svelte/issues/5048)) * Update ` {/each} diff --git a/site/content/faq/100-im-new-to-svelte.md b/site/content/faq/100-im-new-to-svelte.md index 18e4b6742f..0f1df9cff5 100644 --- a/site/content/faq/100-im-new-to-svelte.md +++ b/site/content/faq/100-im-new-to-svelte.md @@ -2,6 +2,6 @@ question: I'm new to Svelte. Where should I start? --- -We think the best way to get started is playing through the interactive [Tutorial](https://svelte.dev/tutorial). Each step there is mainly focused on one specific aspect and is easy to follow. You'll be editing and running real Svelte components right in your browser. +We think the best way to get started is playing through the interactive [Tutorial](tutorial). Each step there is mainly focused on one specific aspect and is easy to follow. You'll be editing and running real Svelte components right in your browser. Five to ten minutes should be enough to get you up and running. An hour and a half should get you through the entire tutorial. \ No newline at end of file diff --git a/site/content/faq/400-how-can-i-get-syntax-highlighting.md b/site/content/faq/400-how-can-i-get-syntax-highlighting.md index 90f10b254f..96a80e3c32 100644 --- a/site/content/faq/400-how-can-i-get-syntax-highlighting.md +++ b/site/content/faq/400-how-can-i-get-syntax-highlighting.md @@ -1,5 +1,5 @@ --- -question: How can I get VSCode to syntax-highlight my .svelte files? +question: How can I get VS Code to syntax-highlight my .svelte files? --- -There is an [official VSCode extension for Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode), however it is still in the **beta** testing stage, and not all issues have been ironed out. \ No newline at end of file +There is an [official VS Code extension for Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). diff --git a/site/content/faq/450-how-do-i-document-my-components.md b/site/content/faq/450-how-do-i-document-my-components.md new file mode 100644 index 0000000000..78177abd54 --- /dev/null +++ b/site/content/faq/450-how-do-i-document-my-components.md @@ -0,0 +1,32 @@ +--- +question: How do I document my components? +--- + +In editors which use the Svelte Language Server you can document Components, functions and exports using specially formatted comments. + +````svelte + + + +
+

+ Hello, {name} +

+
+```` + +Note: The `@component` is necessary in the HTML comment which describes your component. diff --git a/site/content/faq/500-what-about-typescript-support.md b/site/content/faq/500-what-about-typescript-support.md index 7cd0ccbe54..adfd63764e 100644 --- a/site/content/faq/500-what-about-typescript-support.md +++ b/site/content/faq/500-what-about-typescript-support.md @@ -1,10 +1,11 @@ --- -question: What about Typescript support? +question: What about TypeScript support? --- -You need to install a preprocessor such as [svelte-preprocess](https://github.com/sveltejs/svelte-preprocess). Work is ongoing to improve [IDE support](https://github.com/sveltejs/language-tools/issues/83). You can also run type checking from the command line with [svelte-check](https://www.npmjs.com/package/svelte-check). +You need to install a preprocessor such as [svelte-preprocess](https://github.com/sveltejs/svelte-preprocess). You can run type checking from the command line with [svelte-check](https://www.npmjs.com/package/svelte-check). + +To declare the type of a reactive variable in a Svelte template, you should use the following syntax: -To declare the type of a reactive variable in a Svelte template, you can use the following syntax: ``` let x: number; $: x = count + 1; diff --git a/site/content/tutorial/16-special-elements/01-svelte-self/text.md b/site/content/tutorial/16-special-elements/01-svelte-self/text.md index 773dded83e..c7946f6c6d 100644 --- a/site/content/tutorial/16-special-elements/01-svelte-self/text.md +++ b/site/content/tutorial/16-special-elements/01-svelte-self/text.md @@ -14,7 +14,7 @@ It's useful for things like this folder tree view, where folders can contain *ot {/if} ``` -...but that's impossible, because a file can't import itself. Instead, we use ``: +...but that's impossible, because a module can't import itself. Instead, we use ``: ```html {#if file.type === 'folder'} @@ -22,4 +22,4 @@ It's useful for things like this folder tree view, where folders can contain *ot {:else} {/if} -``` \ No newline at end of file +``` diff --git a/site/package-lock.json b/site/package-lock.json index 33dfa629a1..bde0e87f82 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -1281,9 +1281,9 @@ } }, "@sveltejs/site-kit": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@sveltejs/site-kit/-/site-kit-1.1.5.tgz", - "integrity": "sha512-Rs2quQ/H00DAN/ZTFa+unLefL335UW3Yo4I2rTocW5JwW73Kvi5++d7BcY8LsjhMCbG1PkwQmJE2RVrIIxQcOw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@sveltejs/site-kit/-/site-kit-1.2.1.tgz", + "integrity": "sha512-5AhOCBcproHF5UbmuOIB9CiyAmG6BXOr2G4EjeeDz/tyo9myfnRKIgJSy26q8Zlw6TOlwxhzcAgvL5sgZF+IsA==", "dev": true, "requires": { "@sindresorhus/slugify": "^0.9.1", @@ -1291,9 +1291,9 @@ } }, "@sveltejs/svelte-repl": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.1.19.tgz", - "integrity": "sha512-35R94X6uYgy6PHLQnQCsKcZ4zb6rGGQXjBYqjuCkoCykIlSLx8/avq6BGqudmE5pzVWDP3kk4063cHgccy1xYg==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@sveltejs/svelte-repl/-/svelte-repl-0.2.0.tgz", + "integrity": "sha512-2vLQnOVrsmn2d2K4a6urGm8OulGGSPhZCGNySSb1H8nOPsgKrdcTt5qoaxNYXgcyVp55Yow2SvXYXsyJKd4KEQ==", "dev": true, "requires": { "codemirror": "^5.49.2", @@ -1601,9 +1601,9 @@ "dev": true }, "codemirror": { - "version": "5.53.2", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.53.2.tgz", - "integrity": "sha512-wvSQKS4E+P8Fxn/AQ+tQtJnF1qH5UOlxtugFLpubEZ5jcdH2iXTVinb+Xc/4QjshuOxRm4fUsU2QPF1JJKiyXA==", + "version": "5.55.0", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.55.0.tgz", + "integrity": "sha512-TumikSANlwiGkdF/Blnu/rqovZ0Y3Jh8yy9TqrPbSM0xxSucq3RgnpVDQ+mD9q6JERJEIT2FMuF/fBGfkhIR/g==", "dev": true }, "color-convert": { @@ -2404,9 +2404,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", "dev": true }, "lodash.deburr": { diff --git a/site/package.json b/site/package.json index ffcf2b9473..2564fce2c6 100644 --- a/site/package.json +++ b/site/package.json @@ -36,8 +36,8 @@ "@babel/preset-env": "^7.6.0", "@babel/runtime": "^7.6.0", "@sindresorhus/slugify": "^0.9.1", - "@sveltejs/site-kit": "^1.1.5", - "@sveltejs/svelte-repl": "^0.1.19", + "@sveltejs/site-kit": "^1.2.1", + "@sveltejs/svelte-repl": "^0.2.0", "degit": "^2.1.4", "dotenv": "^8.1.0", "esm": "^3.2.25", diff --git a/site/src/routes/blog/[slug].svelte b/site/src/routes/blog/[slug].svelte index d5fd6c0676..b97b5ef136 100644 --- a/site/src/routes/blog/[slug].svelte +++ b/site/src/routes/blog/[slug].svelte @@ -137,6 +137,15 @@ top: calc((var(--h3) - 24px) / 2); } + .post :global(a) { + padding: 0; + transition: none; + } + + .post :global(a):not(:hover) { + border: none; + } + @media (max-width: 768px) { .post :global(.anchor) { transform: scale(0.6); diff --git a/site/src/routes/blog/rss.xml.js b/site/src/routes/blog/rss.xml.js index 974806ad44..544474b274 100644 --- a/site/src/routes/blog/rss.xml.js +++ b/site/src/routes/blog/rss.xml.js @@ -8,6 +8,18 @@ function formatPubdate(str) { return `${d} ${months[+m]} ${y} 12:00 +0000`; } +function escapeHTML(html) { + const chars = { + '"' : 'quot', + "'": '#39', + '&': 'amp', + '<' : 'lt', + '>' : 'gt' + }; + + return html.replace(/["'&<>]/g, c => `&${chars[c]};`); +} + const rss = ` @@ -23,9 +35,9 @@ const rss = ` ${get_posts().filter(post => !post.metadata.draft).map(post => ` - ${post.metadata.title} + ${escapeHTML(post.metadata.title)} https://svelte.dev/blog/${post.slug} - ${post.metadata.description} + ${escapeHTML(post.metadata.description)} ${formatPubdate(post.metadata.pubdate)} `).join('')} diff --git a/site/src/routes/docs/_sections.js b/site/src/routes/docs/_sections.js index bb081a050b..3657ba85ac 100644 --- a/site/src/routes/docs/_sections.js +++ b/site/src/routes/docs/_sections.js @@ -85,7 +85,7 @@ export default function() { renderer.heading = (text, level, rawtext) => { let slug; - const match = /(.+)<\/a>/.exec(text); + const match = /]*>(.+)<\/a>/.exec(text); if (match) { slug = match[1]; text = match[2]; diff --git a/site/src/routes/repl/[id]/_components/AppControls/index.svelte b/site/src/routes/repl/[id]/_components/AppControls/index.svelte index 10639e91c0..1d4e6b3f03 100644 --- a/site/src/routes/repl/[id]/_components/AppControls/index.svelte +++ b/site/src/routes/repl/[id]/_components/AppControls/index.svelte @@ -227,6 +227,7 @@ export default app;` }); padding: .6rem var(--side-nav); background-color: var(--second); color: white; + white-space: nowrap; } .icon { diff --git a/site/src/utils/highlight.js b/site/src/utils/highlight.js index d9e4fe5ec8..124af2e6be 100644 --- a/site/src/utils/highlight.js +++ b/site/src/utils/highlight.js @@ -1,6 +1,7 @@ import { langs } from '@sveltejs/site-kit/utils/markdown.js'; import PrismJS from 'prismjs'; import 'prismjs/components/prism-bash'; +import 'prismjs/components/prism-diff'; import 'prism-svelte'; export function highlight(source, lang) { diff --git a/site/static/media/svelte-ts.png b/site/static/media/svelte-ts.png new file mode 100644 index 0000000000..ed562822c3 Binary files /dev/null and b/site/static/media/svelte-ts.png differ diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 5f06de8e21..88fe197993 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -836,7 +836,7 @@ export default class Component { }); } - warn_on_undefined_store_value_references(node, parent, scope) { + warn_on_undefined_store_value_references(node, parent, scope: Scope) { if ( node.type === 'LabeledStatement' && node.label.name === '$' && @@ -852,8 +852,17 @@ export default class Component { const object = get_object(node); const { name } = object; - if (name[0] === '$' && !scope.has(name)) { - this.warn_if_undefined(name, object, null); + if (name[0] === '$') { + if (!scope.has(name)) { + this.warn_if_undefined(name, object, null); + } + + if (name[1] !== '$' && scope.has(name.slice(1)) && scope.find_owner(name.slice(1)) !== this.instance_scope) { + this.error(node, { + code: `contextual-store`, + message: `Stores must be declared at the top level of the component (this may change in a future version of Svelte)` + }); + } } } } diff --git a/src/compiler/compile/nodes/Binding.ts b/src/compiler/compile/nodes/Binding.ts index bcb07280b0..c561e72b99 100644 --- a/src/compiler/compile/nodes/Binding.ts +++ b/src/compiler/compile/nodes/Binding.ts @@ -41,7 +41,8 @@ export default class Binding extends Node { this.raw_expression = JSON.parse(JSON.stringify(info.expression)); const { name } = get_object(this.expression.node); - this.is_contextual = scope.names.has(name); + + this.is_contextual = Array.from(this.expression.references).some(name => scope.names.has(name)); // make sure we track this as a mutable ref if (scope.is_let(name)) { @@ -49,7 +50,7 @@ export default class Binding extends Node { code: 'invalid-binding', message: 'Cannot bind to a variable declared with the let: directive' }); - } else if (this.is_contextual) { + } else if (scope.names.has(name)) { if (scope.is_await(name)) { component.error(this, { code: 'invalid-binding', diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 0f68eeb846..145866cf4f 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -61,6 +61,17 @@ const a11y_no_onchange = new Set([ 'option' ]); +const a11y_labelable = new Set([ + "button", + "input", + "keygen", + "meter", + "output", + "progress", + "select", + "textarea" +]); + const invisible_elements = new Set(['meta', 'html', 'script', 'style']); const valid_modifiers = new Set([ @@ -507,6 +518,15 @@ export default class Element extends Node { } } + if (this.name === 'label') { + const has_input_child = this.children.some(i => (i instanceof Element && a11y_labelable.has(i.name) )); + if (!attribute_map.has('for') && !has_input_child) { + component.warn(this, { + code: `a11y-label-has-associated-control`, + message: `A11y: A form label must be associated with a control.` + }); + } + } if (this.is_media_node()) { if (attribute_map.has('muted')) { diff --git a/src/compiler/compile/nodes/shared/Context.ts b/src/compiler/compile/nodes/shared/Context.ts index 797405b0fe..bcc0521ffa 100644 --- a/src/compiler/compile/nodes/shared/Context.ts +++ b/src/compiler/compile/nodes/shared/Context.ts @@ -1,5 +1,5 @@ import { x } from 'code-red'; -import { Node, Identifier, RestElement, Property } from 'estree'; +import { Node, Identifier } from 'estree'; export interface Context { key: Identifier; @@ -34,12 +34,10 @@ export function unpack_destructuring(contexts: Context[], node: Node, modifier: const used_properties = []; node.properties.forEach((property) => { - const props: (RestElement | Property) = (property as any); - - if (props.type === 'RestElement') { + if (property.type === 'RestElement') { unpack_destructuring( contexts, - props.argument, + property.argument, node => x`@object_without_properties(${modifier(node)}, [${used_properties}])` as Node ); } else { diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index c7c66940fd..bbbc1b2f2d 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -24,7 +24,7 @@ export default class Expression { component: Component; owner: Owner; node: any; - references: Set; + references: Set = new Set(); dependencies: Set = new Set(); contextual_dependencies: Set = new Set(); @@ -50,7 +50,7 @@ export default class Expression { this.template_scope = template_scope; this.owner = owner; - const { dependencies, contextual_dependencies } = this; + const { dependencies, contextual_dependencies, references } = this; let { map, scope } = create_scopes(info); this.scope = scope; @@ -75,14 +75,18 @@ export default class Expression { if (is_reference(node, parent)) { const { name, nodes } = flatten_reference(node); + references.add(name); if (scope.has(name)) return; - if (name[0] === '$' && template_scope.names.has(name.slice(1))) { - component.error(node, { - code: `contextual-store`, - message: `Stores must be declared at the top level of the component (this may change in a future version of Svelte)` - }); + if (name[0] === '$') { + const store_name = name.slice(1); + if (template_scope.names.has(store_name) || scope.has(store_name)) { + component.error(node, { + code: `contextual-store`, + message: `Stores must be declared at the top level of the component (this may change in a future version of Svelte)` + }); + } } if (template_scope.is_let(name)) { @@ -198,7 +202,7 @@ export default class Expression { scope = map.get(node); } - if (is_reference(node, parent)) { + if (node.type === 'Identifier' && is_reference(node, parent)) { const { name } = flatten_reference(node); if (scope.has(name)) return; diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 3b5001d483..7d0dce8315 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -420,7 +420,7 @@ export default function dom( ${component.partly_hoisted} - ${set && b`$$self.$set = ${set};`} + ${set && b`$$self.$$set = ${set};`} ${capture_state && b`$$self.$capture_state = ${capture_state};`} diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index bd981a0603..126b114487 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -298,6 +298,19 @@ export default class EachBlockWrapper extends Wrapper { } `); + const has_transitions = !!(this.else.block.has_intro_method || this.else.block.has_outro_method); + + const destroy_block_else = this.else.block.has_outro_method + ? b` + @group_outros(); + @transition_out(${each_block_else}, 1, 1, () => { + ${each_block_else} = null; + }); + @check_outros();` + : b` + ${each_block_else}.d(1); + ${each_block_else} = null;`; + if (this.else.block.has_update_method) { this.updates.push(b` if (!${this.vars.data_length} && ${each_block_else}) { @@ -305,22 +318,22 @@ export default class EachBlockWrapper extends Wrapper { } else if (!${this.vars.data_length}) { ${each_block_else} = ${this.else.block.name}(#ctx); ${each_block_else}.c(); + ${has_transitions && b`@transition_in(${each_block_else}, 1);`} ${each_block_else}.m(${update_mount_node}, ${update_anchor_node}); } else if (${each_block_else}) { - ${each_block_else}.d(1); - ${each_block_else} = null; + ${destroy_block_else}; } `); } else { this.updates.push(b` if (${this.vars.data_length}) { if (${each_block_else}) { - ${each_block_else}.d(1); - ${each_block_else} = null; + ${destroy_block_else}; } } else if (!${each_block_else}) { ${each_block_else} = ${this.else.block.name}(#ctx); ${each_block_else}.c(); + ${has_transitions && b`@transition_in(${each_block_else}, 1);`} ${each_block_else}.m(${update_mount_node}, ${update_anchor_node}); } `); diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts index 86b67ca47e..ec281648b8 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts @@ -7,8 +7,9 @@ import { b, x } from 'code-red'; import Expression from '../../../nodes/shared/Expression'; import Text from '../../../nodes/Text'; import handle_select_value_binding from './handle_select_value_binding'; +import { Identifier, Node } from 'estree'; -export default class AttributeWrapper { +export class BaseAttributeWrapper { node: Attribute; parent: ElementWrapper; @@ -21,7 +22,29 @@ export default class AttributeWrapper { parent.not_static_content(); block.add_dependencies(node.dependencies); + } + } + + render(_block: Block) {} +} +export default class AttributeWrapper extends BaseAttributeWrapper { + node: Attribute; + parent: ElementWrapper; + metadata: any; + name: string; + property_name: string; + is_indirectly_bound_value: boolean; + is_src: boolean; + is_select_value_attribute: boolean; + is_input_value: boolean; + should_cache: boolean; + last: Identifier; + + constructor(parent: ElementWrapper, block: Block, node: Attribute) { + super(parent, block, node); + + if (node.dependencies.size > 0) { // special case —