diff --git a/package-lock.json b/package-lock.json index de991cc15d1..30e6bce4758 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,7 +79,7 @@ "sizzle": "^2.3.6", "terser": "5.6.1", "tslib": "^2.1.0", - "typescript": "4.2.3", + "typescript": "4.3.5", "webpack": "^4.46.0", "ws": "7.4.6" }, @@ -11640,9 +11640,9 @@ } }, "node_modules/typescript": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", - "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -22136,9 +22136,9 @@ } }, "typescript": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", - "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", "dev": true }, "uglify-js": { diff --git a/package.json b/package.json index 4f01aebccca..57bdf895ec1 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "sizzle": "^2.3.6", "terser": "5.6.1", "tslib": "^2.1.0", - "typescript": "4.2.3", + "typescript": "4.3.5", "webpack": "^4.46.0", "ws": "7.4.6" }, diff --git a/src/compiler/sys/dependencies.json b/src/compiler/sys/dependencies.json index 40cf3f70c34..333d6deecd3 100644 --- a/src/compiler/sys/dependencies.json +++ b/src/compiler/sys/dependencies.json @@ -50,6 +50,11 @@ "compiler/lib.es2020.sharedmemory.d.ts", "compiler/lib.es2020.string.d.ts", "compiler/lib.es2020.symbol.wellknown.d.ts", + "compiler/lib.es2021.d.ts", + "compiler/lib.es2021.full.d.ts", + "compiler/lib.es2021.promise.d.ts", + "compiler/lib.es2021.string.d.ts", + "compiler/lib.es2021.weakref.d.ts", "compiler/lib.es5.d.ts", "compiler/lib.es6.d.ts", "compiler/lib.esnext.d.ts", diff --git a/src/compiler/transformers/decorators-to-static/method-decorator.ts b/src/compiler/transformers/decorators-to-static/method-decorator.ts index d0d38306030..3b71b65fda2 100644 --- a/src/compiler/transformers/decorators-to-static/method-decorator.ts +++ b/src/compiler/transformers/decorators-to-static/method-decorator.ts @@ -5,6 +5,7 @@ import { createStaticGetter, getAttributeTypeInfo, isMemberPrivate, + mapJSDocTagInfo, serializeSymbol, typeToString, validateReferences, @@ -95,7 +96,7 @@ const parseMethodDecorator = ( }, docs: { text: ts.displayPartsToString(signature.getDocumentationComment(typeChecker)), - tags: signature.getJsDocTags(), + tags: mapJSDocTagInfo(signature.getJsDocTags()), }, }; validateReferences(diagnostics, methodMeta.complexType.references, method.type || method.name); diff --git a/src/compiler/transformers/test/transform-utils.spec.ts b/src/compiler/transformers/test/transform-utils.spec.ts new file mode 100644 index 00000000000..afcd0352437 --- /dev/null +++ b/src/compiler/transformers/test/transform-utils.spec.ts @@ -0,0 +1,41 @@ +import { mapJSDocTagInfo } from '../transform-utils'; + +describe('transform utils', () => { + it('flattens TypeScript JSDocTagInfo to Stencil JSDocTagInfo', () => { + // tags corresponds to the following JSDoc + /* + * @param foo the first parameter + * @param bar + * @returns + * @see {@link https://example.com} + */ + const tags = [ + { + name: 'param', + text: [ + { text: 'foo', kind: 'parameterName' }, + { text: ' ', kind: 'space' }, + { text: 'the first parameter', kind: 'text' }, + ], + }, + { name: 'param', text: [{ text: 'bar', kind: 'text' }] }, + { name: 'returns', text: undefined }, + { + name: 'see', + text: [ + { text: '', kind: 'text' }, + { text: '{@link ', kind: 'link' }, + { text: 'https://example.com', kind: 'linkText' }, + { text: '}', kind: 'link' }, + ], + }, + ]; + + expect(mapJSDocTagInfo(tags)).toEqual([ + { name: 'param', text: 'foo the first parameter' }, + { name: 'param', text: 'bar' }, + { name: 'returns', text: undefined }, + { name: 'see', text: '{@link https://example.com}' }, + ]); + }); +}); diff --git a/src/compiler/transformers/transform-utils.ts b/src/compiler/transformers/transform-utils.ts index ce69f21c0b4..3f42b9ea0c1 100644 --- a/src/compiler/transformers/transform-utils.ts +++ b/src/compiler/transformers/transform-utils.ts @@ -515,11 +515,23 @@ export const serializeSymbol = (checker: ts.TypeChecker, symbol: ts.Symbol): d.C }; } return { - tags: symbol.getJsDocTags().map((tag) => ({ text: tag.text, name: tag.name })), + tags: mapJSDocTagInfo(symbol.getJsDocTags()), text: ts.displayPartsToString(symbol.getDocumentationComment(checker)), }; }; +/** + * Maps a TypeScript 4.3+ JSDocTagInfo to a flattened Stencil CompilerJsDocTagInfo. + * @param tags A readonly array of JSDocTagInfo objects. + * @returns An array of CompilerJsDocTagInfo objects. + */ +export const mapJSDocTagInfo = (tags: readonly ts.JSDocTagInfo[]): d.CompilerJsDocTagInfo[] => { + // The text following a tag is split semantically by TS 4.3+, e.g. '@param foo the first parameter' -> + // [{text: 'foo', kind: 'parameterName'}, {text: ' ', kind: 'space'}, {text: 'the first parameter', kind: 'text'}], so + // we join the elements to reconstruct the text. + return tags.map((tag) => ({ ...tag, text: tag.text?.map((part) => part.text).join('') })); +}; + export const serializeDocsSymbol = (checker: ts.TypeChecker, symbol: ts.Symbol) => { const type = checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration); const set = new Set(); diff --git a/src/mock-doc/comment-node.ts b/src/mock-doc/comment-node.ts index a3451ff0d1d..2f14cfa5342 100644 --- a/src/mock-doc/comment-node.ts +++ b/src/mock-doc/comment-node.ts @@ -6,15 +6,15 @@ export class MockComment extends MockNode { super(ownerDocument, NODE_TYPES.COMMENT_NODE, NODE_NAMES.COMMENT_NODE, data); } - cloneNode(_deep?: boolean) { + override cloneNode(_deep?: boolean) { return new MockComment(null, this.nodeValue); } - get textContent() { + override get textContent() { return this.nodeValue; } - set textContent(text) { + override set textContent(text) { this.nodeValue = text; } } diff --git a/src/mock-doc/document-fragment.ts b/src/mock-doc/document-fragment.ts index 5f5a5039906..759865b7902 100644 --- a/src/mock-doc/document-fragment.ts +++ b/src/mock-doc/document-fragment.ts @@ -13,7 +13,7 @@ export class MockDocumentFragment extends MockHTMLElement { return getElementById(this, id); } - cloneNode(deep?: boolean) { + override cloneNode(deep?: boolean) { const cloned = new MockDocumentFragment(null); if (deep) { diff --git a/src/mock-doc/document.ts b/src/mock-doc/document.ts index adbdd16dbac..f5495fbd854 100644 --- a/src/mock-doc/document.ts +++ b/src/mock-doc/document.ts @@ -42,10 +42,10 @@ export class MockDocument extends MockHTMLElement { } } - get dir() { + override get dir() { return this.documentElement.dir; } - set dir(value: string) { + override set dir(value: string) { this.documentElement.dir = value; } @@ -166,7 +166,7 @@ export class MockDocument extends MockHTMLElement { } } - appendChild(newNode: MockElement) { + override appendChild(newNode: MockElement) { newNode.remove(); newNode.parentNode = this; this.childNodes.push(newNode); @@ -222,14 +222,14 @@ export class MockDocument extends MockHTMLElement { return getElementsByName(this, elmName.toLowerCase()); } - get title() { + override get title() { const title = this.head.childNodes.find((elm) => elm.nodeName === 'TITLE') as MockElement; if (title != null && typeof title.textContent === 'string') { return title.textContent.trim(); } return ''; } - set title(value: string) { + override set title(value: string) { const head = this.head; let title = head.childNodes.find((elm) => elm.nodeName === 'TITLE') as MockElement; if (title == null) { diff --git a/src/mock-doc/element.ts b/src/mock-doc/element.ts index 1dac29fcce4..c91158ac110 100644 --- a/src/mock-doc/element.ts +++ b/src/mock-doc/element.ts @@ -107,10 +107,10 @@ export class MockImageElement extends MockHTMLElement { super(ownerDocument, 'img'); } - get draggable() { + override get draggable() { return this.getAttributeNS(null, 'draggable') !== 'false'; } - set draggable(value: boolean) { + override set draggable(value: boolean) { this.setAttributeNS(null, 'draggable', value); } @@ -243,24 +243,24 @@ export class MockStyleElement extends MockHTMLElement { this.sheet = new MockCSSStyleSheet(this); } - get innerHTML() { + override get innerHTML() { return getStyleElementText(this); } - set innerHTML(value: string) { + override set innerHTML(value: string) { setStyleElementText(this, value); } - get innerText() { + override get innerText() { return getStyleElementText(this); } - set innerText(value: string) { + override set innerText(value: string) { setStyleElementText(this, value); } - get textContent() { + override get textContent() { return getStyleElementText(this); } - set textContent(value: string) { + override set textContent(value: string) { setStyleElementText(this, value); } } @@ -318,14 +318,14 @@ export class MockTemplateElement extends MockHTMLElement { this.content = new MockDocumentFragment(ownerDocument); } - get innerHTML() { + override get innerHTML() { return this.content.innerHTML; } - set innerHTML(html: string) { + override set innerHTML(html: string) { this.content.innerHTML = html; } - cloneNode(deep?: boolean) { + override cloneNode(deep?: boolean) { const cloned = new MockTemplateElement(null); cloned.attributes = cloneAttributes(this.attributes); diff --git a/src/mock-doc/node.ts b/src/mock-doc/node.ts index dd2ad7fb69e..3617e7a605b 100644 --- a/src/mock-doc/node.ts +++ b/src/mock-doc/node.ts @@ -66,7 +66,7 @@ export class MockNode { return -1; } - get firstChild() { + get firstChild(): MockNode | null { return this.childNodes[0] || null; } @@ -103,11 +103,11 @@ export class MockNode { return this === node; } - get lastChild() { + get lastChild(): MockNode | null { return this.childNodes[this.childNodes.length - 1] || null; } - get nextSibling() { + get nextSibling(): MockNode | null { if (this.parentNode != null) { const index = this.parentNode.childNodes.indexOf(this) + 1; return this.parentNode.childNodes[index] || null; @@ -129,7 +129,7 @@ export class MockNode { this.parentNode = value; } - get previousSibling() { + get previousSibling(): MockNode | null { if (this.parentNode != null) { const index = this.parentNode.childNodes.indexOf(this) - 1; return this.parentNode.childNodes[index] || null; @@ -273,7 +273,7 @@ export class MockElement extends MockNode { dispatchEvent(this, new MockEvent('click', { bubbles: true, cancelable: true, composed: true })); } - cloneNode(_deep?: boolean): MockElement { + override cloneNode(_deep?: boolean): MockElement { // implemented on MockElement.prototype from within element.ts return null; } @@ -304,7 +304,7 @@ export class MockElement extends MockNode { return dispatchEvent(this, ev); } - get firstElementChild() { + get firstElementChild(): MockElement | null { return this.children[0] || null; } @@ -481,7 +481,7 @@ export class MockElement extends MockNode { this.setAttributeNS(null, 'lang', value); } - get lastElementChild() { + get lastElementChild(): MockElement | null { const children = this.children; return children[children.length - 1] || null; } @@ -670,12 +670,12 @@ export class MockElement extends MockNode { this.nodeName = value; } - get textContent() { + override get textContent() { const text: string[] = []; getTextContent(this.childNodes, text); return text.join(''); } - set textContent(value: string) { + override set textContent(value: string) { setTextContent(this, value); } @@ -951,7 +951,7 @@ export class MockElement extends MockNode { /**/ } - toString(opts?: SerializeNodeToHtmlOptions) { + override toString(opts?: SerializeNodeToHtmlOptions) { return serializeNodeToHtml(this as any, opts); } } @@ -1011,26 +1011,26 @@ function insertBefore(parentNode: MockNode, newNode: MockNode, referenceNode: Mo } export class MockHTMLElement extends MockElement { - namespaceURI = 'http://www.w3.org/1999/xhtml'; + override namespaceURI = 'http://www.w3.org/1999/xhtml'; constructor(ownerDocument: any, nodeName: string) { super(ownerDocument, typeof nodeName === 'string' ? nodeName.toUpperCase() : null); } - get tagName() { + override get tagName() { return this.nodeName; } - set tagName(value: string) { + override set tagName(value: string) { this.nodeName = value; } - get attributes() { + override get attributes() { if (this.__attributeMap == null) { this.__attributeMap = createAttributeProxy(true); } return this.__attributeMap; } - set attributes(attrs: MockAttributeMap) { + override set attributes(attrs: MockAttributeMap) { this.__attributeMap = attrs; } } @@ -1040,14 +1040,14 @@ export class MockTextNode extends MockNode { super(ownerDocument, NODE_TYPES.TEXT_NODE, NODE_NAMES.TEXT_NODE, text); } - cloneNode(_deep?: boolean) { + override cloneNode(_deep?: boolean) { return new MockTextNode(null, this.nodeValue); } - get textContent() { + override get textContent() { return this.nodeValue; } - set textContent(text) { + override set textContent(text) { this.nodeValue = text; } diff --git a/src/screenshot/connector-local.ts b/src/screenshot/connector-local.ts index 89b59742859..2b839e35569 100644 --- a/src/screenshot/connector-local.ts +++ b/src/screenshot/connector-local.ts @@ -5,7 +5,7 @@ import { normalizePath } from '@utils'; import { ScreenshotConnector } from './connector-base'; export class ScreenshotLocalConnector extends ScreenshotConnector { - async publishBuild(results: d.ScreenshotBuildResults) { + override async publishBuild(results: d.ScreenshotBuildResults) { if (this.updateMaster || !results.masterBuild) { results.masterBuild = { id: 'master', @@ -64,7 +64,7 @@ export class ScreenshotLocalConnector extends ScreenshotConnector { return results; } - async getScreenshotCache() { + override async getScreenshotCache() { let screenshotCache: d.ScreenshotCache = null; try { @@ -74,7 +74,7 @@ export class ScreenshotLocalConnector extends ScreenshotConnector { return screenshotCache; } - async updateScreenshotCache(cache: d.ScreenshotCache, buildResults: d.ScreenshotBuildResults) { + override async updateScreenshotCache(cache: d.ScreenshotCache, buildResults: d.ScreenshotBuildResults) { cache = await super.updateScreenshotCache(cache, buildResults); await writeFile(this.screenshotCacheFilePath, JSON.stringify(cache, null, 2)); @@ -100,7 +100,7 @@ function createLocalCompareApp( - + diff --git a/src/testing/mock-fetch.ts b/src/testing/mock-fetch.ts index bf3d57d84a0..6b7306b99ee 100644 --- a/src/testing/mock-fetch.ts +++ b/src/testing/mock-fetch.ts @@ -133,9 +133,9 @@ export const mockFetch = { }; class MockResponse404 extends MockResponse { - ok = false; - status = 404; - statusText = 'Not Found'; + override ok = false; + override status = 404; + override statusText = 'Not Found'; constructor() { super('', { headers: new MockHeaders({ @@ -143,10 +143,10 @@ class MockResponse404 extends MockResponse { }), }); } - async json() { + override async json() { return { status: 404, statusText: 'Not Found' }; } - async text() { + override async text() { return 'Not Found'; } } diff --git a/src/testing/mocks.ts b/src/testing/mocks.ts index a152d10b176..197d5897dfe 100644 --- a/src/testing/mocks.ts +++ b/src/testing/mocks.ts @@ -137,12 +137,7 @@ export function mockLogger() { return new TestingLogger(); } -export interface TestingSystem extends CompilerSystem { - diskReads: number; - diskWrites: number; -} - -export function mockStencilSystem(): TestingSystem { +export function mockStencilSystem(): CompilerSystem { return createTestingSystem(); } diff --git a/src/testing/puppeteer/puppeteer-element.ts b/src/testing/puppeteer/puppeteer-element.ts index 53ade2cbe90..7582376396e 100644 --- a/src/testing/puppeteer/puppeteer-element.ts +++ b/src/testing/puppeteer/puppeteer-element.ts @@ -50,7 +50,7 @@ export class E2EElement extends MockHTMLElement implements pd.E2EElementInternal return eventSpy; } - async click(options?: puppeteer.ClickOptions) { + override async click(options?: puppeteer.ClickOptions) { await this._elmHandle.click(options); await this._page.waitForChanges(); } @@ -186,19 +186,19 @@ export class E2EElement extends MockHTMLElement implements pd.E2EElementInternal }); } - getAttribute(name: string) { + override getAttribute(name: string) { this._validate(); return super.getAttribute(name); } - setAttribute(name: string, value: any) { + override setAttribute(name: string, value: any) { this._queueAction({ setAttributeName: name, setAttributeValue: value, }); } - removeAttribute(name: string) { + override removeAttribute(name: string) { this._queueAction({ removeAttribute: name, }); @@ -211,7 +211,7 @@ export class E2EElement extends MockHTMLElement implements pd.E2EElementInternal }); } - get classList() { + override get classList() { const api: any = { add: (...classNames: string[]) => { classNames.forEach((className) => { @@ -240,60 +240,60 @@ export class E2EElement extends MockHTMLElement implements pd.E2EElementInternal return api; } - get className() { + override get className() { this._validate(); return super.className; } - set className(value: string) { + override set className(value: string) { this._queueAction({ setPropertyName: 'className', setPropertyValue: value, }); } - get id() { + override get id() { this._validate(); return super.id; } - set id(value: string) { + override set id(value: string) { this._queueAction({ setPropertyName: 'id', setPropertyValue: value, }); } - get innerHTML() { + override get innerHTML() { this._validate(); return super.innerHTML; } - set innerHTML(value: string) { + override set innerHTML(value: string) { this._queueAction({ setPropertyName: 'innerHTML', setPropertyValue: value, }); } - get innerText() { + override get innerText() { this._validate(); return super.innerText; } - set innerText(value: string) { + override set innerText(value: string) { this._queueAction({ setPropertyName: 'innerText', setPropertyValue: value, }); } - get nodeValue() { + override get nodeValue() { this._validate(); return super.nodeValue; } - set nodeValue(value: string) { + override set nodeValue(value: string) { if (typeof value === 'string') { this._queueAction({ setPropertyName: 'nodeValue', @@ -302,54 +302,54 @@ export class E2EElement extends MockHTMLElement implements pd.E2EElementInternal } } - get outerHTML() { + override get outerHTML() { this._validate(); return super.outerHTML; } - set outerHTML(_: any) { + override set outerHTML(_: any) { throw new Error(`outerHTML is read-only`); } - get shadowRoot() { + override get shadowRoot() { this._validate(); return super.shadowRoot; } - set shadowRoot(value: any) { + override set shadowRoot(value: any) { super.shadowRoot = value; } - get tabIndex() { + override get tabIndex() { this._validate(); return super.tabIndex; } - set tabIndex(value: number) { + override set tabIndex(value: number) { this._queueAction({ setPropertyName: 'tabIndex', setPropertyValue: value, }); } - get textContent() { + override get textContent() { this._validate(); return super.textContent; } - set textContent(value: string) { + override set textContent(value: string) { this._queueAction({ setPropertyName: 'textContent', setPropertyValue: value, }); } - get title() { + override get title() { this._validate(); return super.title; } - set title(value: string) { + override set title(value: string) { this._queueAction({ setPropertyName: 'title', setPropertyValue: value, diff --git a/src/testing/testing-sys.ts b/src/testing/testing-sys.ts index 0a4c54d0c6c..08a68a53ec6 100644 --- a/src/testing/testing-sys.ts +++ b/src/testing/testing-sys.ts @@ -1,8 +1,18 @@ +import type { CompilerSystem } from '@stencil/core/internal'; import { createSystem } from '../compiler/sys/stencil-sys'; import { createHash } from 'crypto'; import path from 'path'; -export const createTestingSystem = () => { +export interface TestingSystem extends CompilerSystem { + diskReads: number; + diskWrites: number; +} + +function isTestingSystem(sys: CompilerSystem): sys is TestingSystem { + return 'diskReads' in sys && 'diskWrites' in sys; +} + +export const createTestingSystem = (): TestingSystem => { let diskReads = 0; let diskWrites = 0; const sys = createSystem(); @@ -52,7 +62,7 @@ export const createTestingSystem = () => { sys.writeFile = wrapWrite(sys.writeFile); sys.writeFileSync = wrapWrite(sys.writeFileSync); - return Object.defineProperties(sys, { + Object.defineProperties(sys, { diskReads: { get() { return diskReads; @@ -70,4 +80,10 @@ export const createTestingSystem = () => { }, }, }); + + if (!isTestingSystem(sys)) { + throw new Error('could not generate TestingSystem'); + } + + return sys; }; diff --git a/tsconfig.json b/tsconfig.json index b72f10245a6..1e0c7ee1bba 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,6 +13,7 @@ "module": "esnext", "moduleResolution": "node", "noImplicitAny": true, + "noImplicitOverride": true, "noImplicitReturns": true, "noUnusedLocals": true, "noUnusedParameters": true,