From c376fbc340b597d72af4102c899dc9bd12858095 Mon Sep 17 00:00:00 2001 From: Philipp Fritsche Date: Fri, 15 Jul 2022 08:48:58 +0000 Subject: [PATCH 1/2] chore: whitelist implicit globals --- eslint-local-rules/explicit-globals.js | 57 ++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/eslint-local-rules/explicit-globals.js b/eslint-local-rules/explicit-globals.js index 5f2aba44..663953f8 100644 --- a/eslint-local-rules/explicit-globals.js +++ b/eslint-local-rules/explicit-globals.js @@ -1,3 +1,53 @@ +// ignore standard built-in objects +const whitelist = [ + 'Infinity', + 'NaN', + 'undefined', + 'Object', + 'Function', + 'Boolean', + 'Symbol', + 'Error', + 'EvalError', + 'InternalError', + 'RangeError', + 'ReferenceError', + 'SyntaxError', + 'TypeError', + 'URIError', + 'Number', + 'Math', + 'Date', + 'String', + 'RegExp', + 'Array', + 'Int8Array', + 'Uint8Array', + 'Uint8ClampedArray', + 'Int16Array', + 'Uint16Array', + 'Int32Array', + 'Uint32Array', + 'Float32Array', + 'Float64Array', + 'Map', + 'Set', + 'WeakMap', + 'WeakSet', + 'ArrayBuffer', + 'DataView', + 'JSON', + 'Promise', + 'Generator', + 'GeneratorFunction', + 'Reflect', + 'Proxy', + 'Intl', + 'Intl.Collator', + 'Intl.DateTimeFormat', + 'Intl.NumberFormat', +] + module.exports = { meta: { type: 'suggestion', @@ -13,13 +63,12 @@ module.exports = { // `scope` is `GlobalScope` and `scope.variables` are the global variables scope.variables.forEach(variable => { - // ignore `undefined` - if (variable.name === 'undefined') { + if (whitelist.includes(variable.name)) { return } + variable.references.forEach(ref => { - // Ignore types and global standard variables like `Object` - if (ref.resolved.constructor.name === 'ImplicitLibVariable') { + if (ref.identifier.parent.type.startsWith('TS')) { return } From c88865d3fcb34c485f7f008a01e8eb7e5bfbfbbe Mon Sep 17 00:00:00 2001 From: Philipp Fritsche Date: Fri, 15 Jul 2022 09:04:06 +0000 Subject: [PATCH 2/2] fix: use `window.FileList` instead of implicit global --- src/utility/upload.ts | 10 ++- src/utils/dataTransfer/DataTransfer.ts | 94 +++++++++++++------------- src/utils/dataTransfer/FileList.ts | 9 ++- tests/utils/dataTransfer/FileList.ts | 2 +- tests/utils/edit/setFiles.ts | 8 +-- 5 files changed, 67 insertions(+), 56 deletions(-) diff --git a/src/utility/upload.ts b/src/utility/upload.ts index 72abedbd..84efc930 100644 --- a/src/utility/upload.ts +++ b/src/utility/upload.ts @@ -1,4 +1,10 @@ -import {createFileList, isDisabled, isElementType, setFiles} from '../utils' +import { + createFileList, + getWindow, + isDisabled, + isElementType, + setFiles, +} from '../utils' import {Config, Instance} from '../setup' export interface uploadInit { @@ -36,7 +42,7 @@ export async function upload( return } - setFiles(input, createFileList(files)) + setFiles(input, createFileList(getWindow(element), files)) this.dispatchUIEvent(input, 'input') this.dispatchUIEvent(input, 'change') } diff --git a/src/utils/dataTransfer/DataTransfer.ts b/src/utils/dataTransfer/DataTransfer.ts index fe622a12..fb13967a 100644 --- a/src/utils/dataTransfer/DataTransfer.ts +++ b/src/utils/dataTransfer/DataTransfer.ts @@ -74,63 +74,65 @@ function getTypeMatcher(type: string, exact: boolean) { } } -class DataTransferStub implements DataTransfer { - getData(format: string) { - const match = - this.items.find(getTypeMatcher(format, true)) ?? - this.items.find(getTypeMatcher(format, false)) - - let text = '' - match?.getAsString(t => { - text = t - }) - - return text - } - - setData(format: string, data: string) { - const matchIndex = this.items.findIndex(getTypeMatcher(format, true)) - - const item = new DataTransferItemStub(data, format) as DataTransferItem - if (matchIndex >= 0) { - this.items.splice(matchIndex, 1, item) - } else { - this.items.push(item) +function createDataTransferStub(window: Window & typeof globalThis) { + return new (class DataTransferStub implements DataTransfer { + getData(format: string) { + const match = + this.items.find(getTypeMatcher(format, true)) ?? + this.items.find(getTypeMatcher(format, false)) + + let text = '' + match?.getAsString(t => { + text = t + }) + + return text } - } - clearData(format?: string) { - if (format) { + setData(format: string, data: string) { const matchIndex = this.items.findIndex(getTypeMatcher(format, true)) + const item = new DataTransferItemStub(data, format) as DataTransferItem if (matchIndex >= 0) { - this.items.remove(matchIndex) + this.items.splice(matchIndex, 1, item) + } else { + this.items.push(item) } - } else { - this.items.clear() } - } - - dropEffect: DataTransfer['dropEffect'] = 'none' - effectAllowed: DataTransfer['effectAllowed'] = 'uninitialized' - readonly items = new DataTransferItemListStub() - readonly files = createFileList([]) + clearData(format?: string) { + if (format) { + const matchIndex = this.items.findIndex(getTypeMatcher(format, true)) - get types() { - const t = [] - if (this.files.length) { - t.push('Files') + if (matchIndex >= 0) { + this.items.remove(matchIndex) + } + } else { + this.items.clear() + } } - this.items.forEach(i => t.push(i.type)) - Object.freeze(t) + dropEffect: DataTransfer['dropEffect'] = 'none' + effectAllowed: DataTransfer['effectAllowed'] = 'uninitialized' - return t - } + readonly items = new DataTransferItemListStub() + readonly files = createFileList(window, []) - /* istanbul ignore next */ - setDragImage() {} + get types() { + const t = [] + if (this.files.length) { + t.push('Files') + } + this.items.forEach(i => t.push(i.type)) + + Object.freeze(t) + + return t + } + + /* istanbul ignore next */ + setDragImage() {} + })() } export function createDataTransfer( @@ -140,10 +142,10 @@ export function createDataTransfer( // Use real DataTransfer if available const dt = typeof window.DataTransfer === 'undefined' - ? (new DataTransferStub() as DataTransfer) + ? createDataTransferStub(window) : /* istanbul ignore next */ new window.DataTransfer() - Object.defineProperty(dt, 'files', {get: () => createFileList(files)}) + Object.defineProperty(dt, 'files', {get: () => createFileList(window, files)}) return dt } diff --git a/src/utils/dataTransfer/FileList.ts b/src/utils/dataTransfer/FileList.ts index 1de62055..ed5e3b5e 100644 --- a/src/utils/dataTransfer/FileList.ts +++ b/src/utils/dataTransfer/FileList.ts @@ -1,6 +1,9 @@ // FileList can not be created per constructor. -export function createFileList(files: File[]): FileList { +export function createFileList( + window: Window & typeof globalThis, + files: File[], +): FileList { const list: FileList & Iterable = { ...files, length: files.length, @@ -11,8 +14,8 @@ export function createFileList(files: File[]): FileList { } }, } - list.constructor = FileList - Object.setPrototypeOf(list, FileList.prototype) + list.constructor = window.FileList + Object.setPrototypeOf(list, window.FileList.prototype) Object.freeze(list) return list diff --git a/tests/utils/dataTransfer/FileList.ts b/tests/utils/dataTransfer/FileList.ts index e6ac8826..49d2f67a 100644 --- a/tests/utils/dataTransfer/FileList.ts +++ b/tests/utils/dataTransfer/FileList.ts @@ -2,7 +2,7 @@ import {createFileList} from '#src/utils' test('implement FileList', () => { const file = new File(['hello'], 'hello.png', {type: 'image/png'}) - const list = createFileList([file]) + const list = createFileList(window, [file]) expect(list).toBeInstanceOf(FileList) expect(list).toHaveLength(1) diff --git a/tests/utils/edit/setFiles.ts b/tests/utils/edit/setFiles.ts index 8168f18e..17c20a10 100644 --- a/tests/utils/edit/setFiles.ts +++ b/tests/utils/edit/setFiles.ts @@ -6,7 +6,7 @@ test('set files', () => { ``, ) - const list = createFileList([new File(['foo'], 'foo.txt')]) + const list = createFileList(window, [new File(['foo'], 'foo.txt')]) setFiles(element, list) expect(element).toHaveProperty('files', list) @@ -20,7 +20,7 @@ test('switching type resets value', () => { expect(element).toHaveValue('') - const list = createFileList([new File(['foo'], 'foo.txt')]) + const list = createFileList(window, [new File(['foo'], 'foo.txt')]) setFiles(element as HTMLInputElement & {type: 'file'}, list) element.type = 'file' @@ -38,7 +38,7 @@ test('setting value resets `files`', () => { ``, ) - const list = createFileList([new File(['foo'], 'foo.txt')]) + const list = createFileList(window, [new File(['foo'], 'foo.txt')]) setFiles(element, list) // Everything but an empty string throws an error in the browser @@ -58,7 +58,7 @@ test('is save to call multiple times', () => { ``, ) - const list = createFileList([new File(['foo'], 'foo.txt')]) + const list = createFileList(window, [new File(['foo'], 'foo.txt')]) setFiles(element, list) setFiles(element, list)