Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vscode.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoão Moreno <joao.moreno@microsoft.com>2022-07-19 15:25:52 +0300
committerGitHub <noreply@github.com>2022-07-19 15:25:52 +0300
commitdc755925909f87f6fc44c2d29f3334192eba505c (patch)
treef1f67973a9a395f9b1687fa8b024df361fe4dac8 /src
parent4b95a2d3edef09299f0f1626b08d8049a91f85dd (diff)
fix type issues in h() (#155600)
- improve regex with named capture groups - drop $ in favor of inline id - add tests Co-authored-by: Henning Dieterichs <notify.henning.dieterichs@live.de> Co-authored-by: Henning Dieterichs <notify.henning.dieterichs@live.de>
Diffstat (limited to 'src')
-rw-r--r--src/vs/base/browser/dom.ts113
-rw-r--r--src/vs/base/browser/ui/tree/abstractTree.ts8
-rw-r--r--src/vs/base/test/browser/dom.test.ts157
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts12
4 files changed, 231 insertions, 59 deletions
diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts
index 4db47cd947a..4019ea90b61 100644
--- a/src/vs/base/browser/dom.ts
+++ b/src/vs/base/browser/dom.ts
@@ -1738,56 +1738,81 @@ type HTMLElementAttributeKeys<T> = Partial<{ [K in keyof T]: T[K] extends Functi
type ElementAttributes<T> = HTMLElementAttributeKeys<T> & Record<string, any>;
type RemoveHTMLElement<T> = T extends HTMLElement ? never : T;
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
-type ArrayToObj<T extends any[]> = UnionToIntersection<RemoveHTMLElement<T[number]>>;
-
-type TagToElement<T> = T extends `.${string}`
- ? HTMLDivElement
- : T extends `#${string}`
- ? HTMLDivElement
- : T extends `${infer TStart}#${string}`
- ? TStart extends keyof HTMLElementTagNameMap
- ? HTMLElementTagNameMap[TStart]
+type ArrayToObj<T extends readonly any[]> = UnionToIntersection<RemoveHTMLElement<T[number]>>;
+type HHTMLElementTagNameMap = HTMLElementTagNameMap & { '': HTMLDivElement };
+
+type TagToElement<T> = T extends `${infer TStart}#${string}`
+ ? TStart extends keyof HHTMLElementTagNameMap
+ ? HHTMLElementTagNameMap[TStart]
: HTMLElement
: T extends `${infer TStart}.${string}`
- ? TStart extends keyof HTMLElementTagNameMap
- ? HTMLElementTagNameMap[TStart]
+ ? TStart extends keyof HHTMLElementTagNameMap
+ ? HHTMLElementTagNameMap[TStart]
: HTMLElement
: T extends keyof HTMLElementTagNameMap
? HTMLElementTagNameMap[T]
: HTMLElement;
+type TagToElementAndId<TTag> = TTag extends `${infer TTag}@${infer TId}`
+ ? { element: TagToElement<TTag>; id: TId }
+ : { element: TagToElement<TTag>; id: 'root' };
+
+type TagToRecord<TTag> = TagToElementAndId<TTag> extends { element: infer TElement; id: infer TId }
+ ? Record<(TId extends string ? TId : never) | 'root', TElement>
+ : never;
+
+type Child = HTMLElement | string | Record<string, HTMLElement>;
+type Children = []
+ | [Child]
+ | [Child, Child]
+ | [Child, Child, Child]
+ | [Child, Child, Child, Child]
+ | [Child, Child, Child, Child, Child]
+ | [Child, Child, Child, Child, Child, Child]
+ | [Child, Child, Child, Child, Child, Child, Child]
+ | [Child, Child, Child, Child, Child, Child, Child, Child]
+ | [Child, Child, Child, Child, Child, Child, Child, Child, Child]
+ | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]
+ | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]
+ | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]
+ | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]
+ | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]
+ | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child]
+ | [Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child, Child];
+
+const H_REGEX = /(?<tag>[\w\-]+)?(?:#(?<id>[\w\-]+))?(?<class>(?:\.(?:[\w\-]+))*)(?:@(?<name>(?:[\w\_])+))?/;
+
/**
* A helper function to create nested dom nodes.
*
*
* ```ts
- * private readonly htmlElements = h('div.code-view', [
- * h('div.title', { $: 'title' }),
+ * const elements = h('div.code-view', [
+ * h('div.title@title'),
* h('div.container', [
- * h('div.gutter', { $: 'gutterDiv' }),
- * h('div', { $: 'editor' }),
+ * h('div.gutter@gutterDiv'),
+ * h('div@editor'),
* ]),
* ]);
- * private readonly editor = createEditor(this.htmlElements.editor);
+ * const editor = createEditor(elements.editor);
* ```
*/
-export function h<TTag extends string>(
- tag: TTag
-): (Record<'root', TagToElement<TTag>>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
-export function h<TTag extends string, TId extends string>(
- tag: TTag,
- attributes: { $: TId } & Partial<ElementAttributes<TagToElement<TTag>>>
-): Record<TId | 'root', TagToElement<TTag>>;
-export function h<TTag extends string, T extends (HTMLElement | string | Record<string, HTMLElement>)[]>(
- tag: TTag,
- children: T
-): (ArrayToObj<T> & Record<'root', TagToElement<TTag>>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
-export function h<TTag extends string>(tag: TTag, attributes: Partial<ElementAttributes<TagToElement<TTag>>>): Record<'root', TagToElement<TTag>>;
-export function h<TTag extends string, TId extends string, T extends (HTMLElement | string | Record<string, HTMLElement>)[]>(
- tag: TTag,
- attributes: { $: TId } & Partial<ElementAttributes<TagToElement<TTag>>>,
- children: T
-): (ArrayToObj<T> & Record<TId, TagToElement<TTag>>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
+export function h<TTag extends string>
+ (tag: TTag):
+ TagToRecord<TTag> extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
+
+export function h<TTag extends string, T extends Children>
+ (tag: TTag, children: T):
+ (ArrayToObj<T> & TagToRecord<TTag>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
+
+export function h<TTag extends string>
+ (tag: TTag, attributes: Partial<ElementAttributes<TagToElement<TTag>>>):
+ TagToRecord<TTag> extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
+
+export function h<TTag extends string, T extends Children>
+ (tag: TTag, attributes: Partial<ElementAttributes<TagToElement<TTag>>>, children: T):
+ (ArrayToObj<T> & TagToRecord<TTag>) extends infer Y ? { [TKey in keyof Y]: Y[TKey] } : never;
+
export function h(tag: string, ...args: [] | [attributes: { $: string } & Partial<ElementAttributes<HTMLElement>> | Record<string, any>, children?: any[]] | [children: any[]]): Record<string, HTMLElement> {
let attributes: { $?: string } & Partial<ElementAttributes<HTMLElement>>;
let children: (Record<string, HTMLElement> | HTMLElement)[] | undefined;
@@ -1800,25 +1825,29 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia
children = args[1];
}
- const match = SELECTOR_REGEX.exec(tag);
+ const match = H_REGEX.exec(tag);
- if (!match) {
+ if (!match || !match.groups) {
throw new Error('Bad use of h');
}
- const tagName = match[1] || 'div';
+ const tagName = match.groups['tag'] || 'div';
const el = document.createElement(tagName);
- if (match[3]) {
- el.id = match[3];
+ if (match.groups['id']) {
+ el.id = match.groups['id'];
}
- if (match[4]) {
- el.className = match[4].replace(/\./g, ' ').trim();
+ if (match.groups['class']) {
+ el.className = match.groups['class'].replace(/\./g, ' ').trim();
}
const result: Record<string, HTMLElement> = {};
+ if (match.groups['name']) {
+ result[match.groups['name']] = el;
+ }
+
if (children) {
for (const c of children) {
if (c instanceof HTMLElement) {
@@ -1833,10 +1862,6 @@ export function h(tag: string, ...args: [] | [attributes: { $: string } & Partia
}
for (const [key, value] of Object.entries(attributes)) {
- if (key === '$') {
- result[value] = el;
- continue;
- }
if (key === 'style') {
for (const [cssKey, cssValue] of Object.entries(value)) {
el.style.setProperty(
diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts
index ba4c6ec3af1..95032ae5ee4 100644
--- a/src/vs/base/browser/ui/tree/abstractTree.ts
+++ b/src/vs/base/browser/ui/tree/abstractTree.ts
@@ -684,10 +684,10 @@ export enum TreeFindMode {
class FindWidget<T, TFilterData> extends Disposable {
- private readonly elements = h('div.monaco-tree-type-filter', [
- h('div.monaco-tree-type-filter-grab.codicon.codicon-debug-gripper', { $: 'grab' }),
- h('div.monaco-tree-type-filter-input', { $: 'findInput' }),
- h('div.monaco-tree-type-filter-actionbar', { $: 'actionbar' }),
+ private readonly elements = h('.monaco-tree-type-filter', [
+ h('.monaco-tree-type-filter-grab.codicon.codicon-debug-gripper@grab'),
+ h('.monaco-tree-type-filter-input@findInput'),
+ h('.monaco-tree-type-filter-actionbar@actionbar'),
]);
set mode(mode: TreeFindMode) {
diff --git a/src/vs/base/test/browser/dom.test.ts b/src/vs/base/test/browser/dom.test.ts
index 435f0066b9c..1ad53561927 100644
--- a/src/vs/base/test/browser/dom.test.ts
+++ b/src/vs/base/test/browser/dom.test.ts
@@ -4,8 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
-import * as dom from 'vs/base/browser/dom';
-const $ = dom.$;
+import { $, h, multibyteAwareBtoa } from 'vs/base/browser/dom';
suite('dom', () => {
test('hasClass', () => {
@@ -73,9 +72,9 @@ suite('dom', () => {
});
test('multibyteAwareBtoa', () => {
- assert.ok(dom.multibyteAwareBtoa('hello world').length > 0);
- assert.ok(dom.multibyteAwareBtoa('平仮名').length > 0);
- assert.ok(dom.multibyteAwareBtoa(new Array(100000).fill('vs').join('')).length > 0); // https://github.com/microsoft/vscode/issues/112013
+ assert.ok(multibyteAwareBtoa('hello world').length > 0);
+ assert.ok(multibyteAwareBtoa('平仮名').length > 0);
+ assert.ok(multibyteAwareBtoa(new Array(100000).fill('vs').join('')).length > 0); // https://github.com/microsoft/vscode/issues/112013
});
suite('$', () => {
@@ -129,4 +128,152 @@ suite('dom', () => {
assert.strictEqual(firstChild.textContent, 'foobar');
});
});
+
+ suite('h', () => {
+ test('should build simple nodes', () => {
+ const div = h('div');
+ assert(div.root instanceof HTMLElement);
+ assert.strictEqual(div.root.tagName, 'DIV');
+
+ const span = h('span');
+ assert(span.root instanceof HTMLElement);
+ assert.strictEqual(span.root.tagName, 'SPAN');
+
+ const img = h('img');
+ assert(img.root instanceof HTMLElement);
+ assert.strictEqual(img.root.tagName, 'IMG');
+ });
+
+ test('should handle ids and classes', () => {
+ const divId = h('div#myid');
+ assert.strictEqual(divId.root.tagName, 'DIV');
+ assert.strictEqual(divId.root.id, 'myid');
+
+ const divClass = h('div.a');
+ assert.strictEqual(divClass.root.tagName, 'DIV');
+ assert.strictEqual(divClass.root.classList.length, 1);
+ assert(divClass.root.classList.contains('a'));
+
+ const divClasses = h('div.a.b.c');
+ assert.strictEqual(divClasses.root.tagName, 'DIV');
+ assert.strictEqual(divClasses.root.classList.length, 3);
+ assert(divClasses.root.classList.contains('a'));
+ assert(divClasses.root.classList.contains('b'));
+ assert(divClasses.root.classList.contains('c'));
+
+ const divAll = h('div#myid.a.b.c');
+ assert.strictEqual(divAll.root.tagName, 'DIV');
+ assert.strictEqual(divAll.root.id, 'myid');
+ assert.strictEqual(divAll.root.classList.length, 3);
+ assert(divAll.root.classList.contains('a'));
+ assert(divAll.root.classList.contains('b'));
+ assert(divAll.root.classList.contains('c'));
+
+ const spanId = h('span#myid');
+ assert.strictEqual(spanId.root.tagName, 'SPAN');
+ assert.strictEqual(spanId.root.id, 'myid');
+
+ const spanClass = h('span.a');
+ assert.strictEqual(spanClass.root.tagName, 'SPAN');
+ assert.strictEqual(spanClass.root.classList.length, 1);
+ assert(spanClass.root.classList.contains('a'));
+
+ const spanClasses = h('span.a.b.c');
+ assert.strictEqual(spanClasses.root.tagName, 'SPAN');
+ assert.strictEqual(spanClasses.root.classList.length, 3);
+ assert(spanClasses.root.classList.contains('a'));
+ assert(spanClasses.root.classList.contains('b'));
+ assert(spanClasses.root.classList.contains('c'));
+
+ const spanAll = h('span#myid.a.b.c');
+ assert.strictEqual(spanAll.root.tagName, 'SPAN');
+ assert.strictEqual(spanAll.root.id, 'myid');
+ assert.strictEqual(spanAll.root.classList.length, 3);
+ assert(spanAll.root.classList.contains('a'));
+ assert(spanAll.root.classList.contains('b'));
+ assert(spanAll.root.classList.contains('c'));
+ });
+
+ test('should implicitly handle ids and classes', () => {
+ const divId = h('#myid');
+ assert.strictEqual(divId.root.tagName, 'DIV');
+ assert.strictEqual(divId.root.id, 'myid');
+
+ const divClass = h('.a');
+ assert.strictEqual(divClass.root.tagName, 'DIV');
+ assert.strictEqual(divClass.root.classList.length, 1);
+ assert(divClass.root.classList.contains('a'));
+
+ const divClasses = h('.a.b.c');
+ assert.strictEqual(divClasses.root.tagName, 'DIV');
+ assert.strictEqual(divClasses.root.classList.length, 3);
+ assert(divClasses.root.classList.contains('a'));
+ assert(divClasses.root.classList.contains('b'));
+ assert(divClasses.root.classList.contains('c'));
+
+ const divAll = h('#myid.a.b.c');
+ assert.strictEqual(divAll.root.tagName, 'DIV');
+ assert.strictEqual(divAll.root.id, 'myid');
+ assert.strictEqual(divAll.root.classList.length, 3);
+ assert(divAll.root.classList.contains('a'));
+ assert(divAll.root.classList.contains('b'));
+ assert(divAll.root.classList.contains('c'));
+ });
+
+ test('should handle @ identifiers', () => {
+ const implicit = h('@el');
+ assert.strictEqual(implicit.root, implicit.el);
+ assert.strictEqual(implicit.el.tagName, 'DIV');
+
+ const explicit = h('div@el');
+ assert.strictEqual(explicit.root, explicit.el);
+ assert.strictEqual(explicit.el.tagName, 'DIV');
+
+ const implicitId = h('#myid@el');
+ assert.strictEqual(implicitId.root, implicitId.el);
+ assert.strictEqual(implicitId.el.tagName, 'DIV');
+ assert.strictEqual(implicitId.root.id, 'myid');
+
+ const explicitId = h('div#myid@el');
+ assert.strictEqual(explicitId.root, explicitId.el);
+ assert.strictEqual(explicitId.el.tagName, 'DIV');
+ assert.strictEqual(explicitId.root.id, 'myid');
+
+ const implicitClass = h('.a@el');
+ assert.strictEqual(implicitClass.root, implicitClass.el);
+ assert.strictEqual(implicitClass.el.tagName, 'DIV');
+ assert.strictEqual(implicitClass.root.classList.length, 1);
+ assert(implicitClass.root.classList.contains('a'));
+
+ const explicitClass = h('div.a@el');
+ assert.strictEqual(explicitClass.root, explicitClass.el);
+ assert.strictEqual(explicitClass.el.tagName, 'DIV');
+ assert.strictEqual(explicitClass.root.classList.length, 1);
+ assert(explicitClass.root.classList.contains('a'));
+ });
+ });
+
+ test('should recurse', () => {
+ const result = h('div.code-view', [
+ h('div.title@title'),
+ h('div.container', [
+ h('div.gutter@gutterDiv'),
+ h('span@editor'),
+ ]),
+ ]);
+
+ assert.strictEqual(result.root.tagName, 'DIV');
+ assert.strictEqual(result.root.className, 'code-view');
+ assert.strictEqual(result.root.childElementCount, 2);
+ assert.strictEqual(result.root.firstElementChild, result.title);
+ assert.strictEqual(result.title.tagName, 'DIV');
+ assert.strictEqual(result.title.className, 'title');
+ assert.strictEqual(result.title.childElementCount, 0);
+ assert.strictEqual(result.gutterDiv.tagName, 'DIV');
+ assert.strictEqual(result.gutterDiv.className, 'gutter');
+ assert.strictEqual(result.gutterDiv.childElementCount, 0);
+ assert.strictEqual(result.editor.tagName, 'SPAN');
+ assert.strictEqual(result.editor.className, '');
+ assert.strictEqual(result.editor.childElementCount, 0);
+ });
});
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts
index 321d1594dd5..d1988a2cfd9 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts
@@ -24,14 +24,14 @@ export abstract class CodeEditorView extends Disposable {
readonly model = this._viewModel.map(m => /** @description model */ m?.model);
protected readonly htmlElements = h('div.code-view', [
- h('div.title', { $: 'header' }, [
- h('span.title', { $: 'title' }),
- h('span.description', { $: 'description' }),
- h('span.detail', { $: 'detail' }),
+ h('div.title@header', [
+ h('span.title@title'),
+ h('span.description@description'),
+ h('span.detail@detail'),
]),
h('div.container', [
- h('div.gutter', { $: 'gutterDiv' }),
- h('div', { $: 'editor' }),
+ h('div.gutter@gutterDiv'),
+ h('div@editor'),
]),
]);