From 4960650579213235d8cff611bcb4e11213fdb3f7 Mon Sep 17 00:00:00 2001 From: Michael Hutchison Date: Wed, 17 Mar 2021 19:16:35 +1100 Subject: [PATCH] ESLint Rule Set Changes: Use warnings instead of errors to indicate code style issues, expansion of linting rules (with the required changes to the codebase). --- .eslintrc.json | 119 ++++++++++++++++++++++++---------- package.json | 4 +- src/avatarManager.ts | 2 +- src/commands.ts | 2 +- src/dataSource.ts | 8 +-- src/gitGraphView.ts | 4 +- src/life-cycle/startup.ts | 2 +- src/life-cycle/uninstall.ts | 2 +- src/life-cycle/utils.ts | 2 +- src/logger.ts | 4 +- src/repoManager.ts | 4 +- src/statusBarItem.ts | 4 +- tests/repoFileWatcher.test.ts | 2 +- web/contextMenu.ts | 2 +- web/dialog.ts | 4 +- web/findWidget.ts | 10 +-- web/global.d.ts | 6 +- web/graph.ts | 2 +- web/main.ts | 28 ++++---- web/textFormatter.ts | 2 +- 20 files changed, 132 insertions(+), 81 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 20b839af..09d82a90 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,85 +2,123 @@ "root": true, "parser": "@typescript-eslint/parser", "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module" + "project": [ + "./src/tsconfig.json", + "./tests/tsconfig.json", + "./web/tsconfig.json" + ] }, "plugins": [ "@typescript-eslint" ], "rules": { + "arrow-spacing": [ + "warn", + { + "before": true, + "after": true + } + ], "brace-style": [ - "error", + "warn", "1tbs", { "allowSingleLine": true } ], - "comma-dangle": "error", - "comma-spacing": "error", - "comma-style": "error", + "comma-dangle": "warn", + "comma-spacing": "warn", + "comma-style": "warn", "dot-location": [ - "error", + "warn", "property" ], - "eol-last": "error", - "eqeqeq": "error", - "func-call-spacing": "error", + "eol-last": "warn", + "eqeqeq": "warn", + "func-call-spacing": "warn", "indent": [ - "error", + "warn", "tab", { "SwitchCase": 1 } ], - "key-spacing": "error", + "key-spacing": "warn", "linebreak-style": [ - "error", + "warn", "windows" ], - "new-cap": "error", - "new-parens": "error", - "no-console": "warn", + "new-cap": "warn", + "new-parens": "warn", + "no-alert": "error", + "no-console": "error", "no-eval": "error", + "no-extra-boolean-cast": "warn", + "no-implied-eval": "error", + "no-irregular-whitespace": "warn", "no-labels": "error", - "no-multi-spaces": "error", + "no-multi-spaces": "warn", + "no-proto": "error", + "no-prototype-builtins": "error", "no-redeclare": "error", + "no-global-assign": "error", + "no-return-await": "warn", "no-shadow-restricted-names": "error", - "no-throw-literal": "error", - "no-unused-expressions": "error", - "no-whitespace-before-property": "error", + "no-script-url": "error", + "no-sparse-arrays": "warn", + "no-throw-literal": "warn", + "no-trailing-spaces": "warn", + "no-unneeded-ternary": "warn", + "no-unsafe-negation": "warn", + "no-unused-expressions": "warn", + "no-var": "warn", + "no-whitespace-before-property": "warn", + "no-with": "error", + "padded-blocks": [ + "warn", + { + "classes": "never", + "switches": "never" + } + ], "quotes": [ - "error", + "warn", "single" ], - "rest-spread-spacing": "error", - "semi": "error", + "rest-spread-spacing": "warn", + "semi": "warn", "sort-imports": [ - "error", + "warn", { "allowSeparatedGroups": true, "ignoreDeclarationSort": true } ], "space-before-function-paren": [ - "error", + "warn", { "anonymous": "always", "named": "never", "asyncArrow": "always" } ], - "space-before-blocks": "error", - "space-infix-ops": "error", - "spaced-comment": "error", - "template-curly-spacing": "error", + "space-before-blocks": "warn", + "space-infix-ops": "warn", + "spaced-comment": "warn", + "template-curly-spacing": "warn", "wrap-iife": [ - "error", + "warn", "inside" ], - "yoda": "error", + "yoda": "warn", + "@typescript-eslint/await-thenable": "warn", + "@typescript-eslint/ban-ts-comment": "error", + "@typescript-eslint/class-literal-property-style": [ + "warn", + "fields" + ], "@typescript-eslint/explicit-member-accessibility": [ - "error", + "warn", { "overrides": { "accessors": "off", @@ -88,8 +126,12 @@ } } ], + "@typescript-eslint/method-signature-style": [ + "warn", + "property" + ], "@typescript-eslint/naming-convention": [ - "error", + "warn", { "selector": "class", "format": [ @@ -102,7 +144,10 @@ "camelCase" ] } - ] + ], + "@typescript-eslint/no-misused-new": "warn", + "@typescript-eslint/no-this-alias": "warn", + "@typescript-eslint/no-unnecessary-boolean-literal-compare": "warn" }, "overrides": [ { @@ -111,6 +156,12 @@ "no-console": "off", "spaced-comment": "off" } + }, + { + "files": "./tests/mocks/*.ts", + "rules": { + "no-global-assign": "off" + } } ] } \ No newline at end of file diff --git a/package.json b/package.json index 1505422a..233faed8 100644 --- a/package.json +++ b/package.json @@ -1466,8 +1466,8 @@ "compile-src": "tsc -p ./src && node ./.vscode/package-src.js", "compile-web": "tsc -p ./web && node ./.vscode/package-web.js", "compile-web-debug": "tsc -p ./web && node ./.vscode/package-web.js debug", - "lint": "eslint -c .eslintrc.json --ext .ts ./src ./tests ./web", - "package": "npm run clean && vsce package", + "lint": "eslint -c .eslintrc.json --max-warnings 0 --ext .ts ./src ./tests ./web", + "package": "vsce package", "package-and-install": "npm run package && node ./.vscode/install-package.js", "test": "jest --verbose", "test-and-report-coverage": "jest --verbose --coverage" diff --git a/src/avatarManager.ts b/src/avatarManager.ts index 82d2dd14..0572bfac 100644 --- a/src/avatarManager.ts +++ b/src/avatarManager.ts @@ -530,7 +530,7 @@ class AvatarRequestQueue { * @param item The avatar request item. */ private insertItem(item: AvatarRequestItem) { - var l = 0, r = this.queue.length - 1, c, prevLength = this.queue.length; + let l = 0, r = this.queue.length - 1, c, prevLength = this.queue.length; while (l <= r) { c = l + r >> 1; if (this.queue[c].checkAfter <= item.checkAfter) { diff --git a/src/commands.ts b/src/commands.ts index 25b01518..87c8def4 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -317,7 +317,7 @@ export class CommandManager extends Disposable { /** * Opens a file in Visual Studio Code, based on a Git Graph URI (from the Diff View). * The method run when the `git-graph.openFile` command is invoked. - * @param arg The Git Graph URI. + * @param arg The Git Graph URI. */ private openFile(arg?: vscode.Uri) { const uri = arg || vscode.window.activeTextEditor?.document.uri; diff --git a/src/dataSource.ts b/src/dataSource.ts index a9d4cf87..92bd6b6e 100644 --- a/src/dataSource.ts +++ b/src/dataSource.ts @@ -624,8 +624,8 @@ export class DataSource extends Disposable { /** * Edit an existing remote of a repository. * @param repo The path of the repository. - * @param nameOld The old name of the remote. - * @param nameNew The new name of the remote. + * @param nameOld The old name of the remote. + * @param nameNew The new name of the remote. * @param urlOld The old URL of the remote. * @param urlNew The new URL of the remote. * @param pushUrlOld The old Push URL of the remote. @@ -1703,7 +1703,7 @@ export class DataSource extends Disposable { /** * Spawn Git, with the return value resolved from `stdout` as a string. - * @param args The arguments to pass to Git. + * @param args The arguments to pass to Git. * @param repo The repository to run the command in. * @param resolveValue A callback invoked to resolve the data from `stdout`. */ @@ -1713,7 +1713,7 @@ export class DataSource extends Disposable { /** * Spawn Git, with the return value resolved from `stdout` as a buffer. - * @param args The arguments to pass to Git. + * @param args The arguments to pass to Git. * @param repo The repository to run the command in. * @param resolveValue A callback invoked to resolve the data from `stdout`. */ diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index 47f23ad2..3fa92f21 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -54,7 +54,7 @@ export class GitGraphView extends Disposable { GitGraphView.currentPanel.respondLoadRepos(repoManager.getRepos(), loadViewTo); } } else { - // If the Git Graph panel is not visible + // If the Git Graph panel is not visible GitGraphView.currentPanel.loadViewTo = loadViewTo; } GitGraphView.currentPanel.panel.reveal(column); @@ -150,7 +150,7 @@ export class GitGraphView extends Disposable { this.panel ); - // Instantiate a RepoFileWatcher that watches for file changes in the repository currently open in the Git Graph View + // Instantiate a RepoFileWatcher that watches for file changes in the repository currently open in the Git Graph View this.repoFileWatcher = new RepoFileWatcher(logger, () => { if (this.panel.visible) { this.sendMessage({ command: 'refresh' }); diff --git a/src/life-cycle/startup.ts b/src/life-cycle/startup.ts index eaab6cbc..0dcb7f40 100644 --- a/src/life-cycle/startup.ts +++ b/src/life-cycle/startup.ts @@ -1,7 +1,7 @@ /** * Git Graph generates an event when it is installed, updated, or uninstalled, that is anonymous, non-personal, and cannot be correlated. * - Each event only contains the Git Graph and Visual Studio Code version numbers, and a 256 bit cryptographically strong pseudo-random nonce. - * - The two version numbers recorded in these events only allow aggregate compatibility information to be generated (e.g. 50% of users are + * - The two version numbers recorded in these events only allow aggregate compatibility information to be generated (e.g. 50% of users are * using Visual Studio Code >= 1.41.0). These insights enable Git Graph to utilise the latest features of Visual Studio Code as soon as * the majority of users are using a compatible version. The data cannot, and will not, be used for any other purpose. * - Full details are available at: https://api.mhutchie.com/vscode-git-graph/about diff --git a/src/life-cycle/uninstall.ts b/src/life-cycle/uninstall.ts index f7b0656c..f463244b 100644 --- a/src/life-cycle/uninstall.ts +++ b/src/life-cycle/uninstall.ts @@ -1,7 +1,7 @@ /** * Git Graph generates an event when it is installed, updated, or uninstalled, that is anonymous, non-personal, and cannot be correlated. * - Each event only contains the Git Graph and Visual Studio Code version numbers, and a 256 bit cryptographically strong pseudo-random nonce. - * - The two version numbers recorded in these events only allow aggregate compatibility information to be generated (e.g. 50% of users are + * - The two version numbers recorded in these events only allow aggregate compatibility information to be generated (e.g. 50% of users are * using Visual Studio Code >= 1.41.0). These insights enable Git Graph to utilise the latest features of Visual Studio Code as soon as * the majority of users are using a compatible version. The data cannot, and will not, be used for any other purpose. * - Full details are available at: https://api.mhutchie.com/vscode-git-graph/about diff --git a/src/life-cycle/utils.ts b/src/life-cycle/utils.ts index 4be9b94c..032aefde 100644 --- a/src/life-cycle/utils.ts +++ b/src/life-cycle/utils.ts @@ -1,7 +1,7 @@ /** * Git Graph generates an event when it is installed, updated, or uninstalled, that is anonymous, non-personal, and cannot be correlated. * - Each event only contains the Git Graph and Visual Studio Code version numbers, and a 256 bit cryptographically strong pseudo-random nonce. - * - The two version numbers recorded in these events only allow aggregate compatibility information to be generated (e.g. 50% of users are + * - The two version numbers recorded in these events only allow aggregate compatibility information to be generated (e.g. 50% of users are * using Visual Studio Code >= 1.41.0). These insights enable Git Graph to utilise the latest features of Visual Studio Code as soon as * the majority of users are using a compatible version. The data cannot, and will not, be used for any other purpose. * - Full details are available at: https://api.mhutchie.com/vscode-git-graph/about diff --git a/src/logger.ts b/src/logger.ts index a240b549..bc9b1ccf 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -48,7 +48,7 @@ export class Logger extends Disposable { } } -/** +/** * Pad a number with a leading zero if it is less than two digits long. * @param n The number to be padded. * @returns The padded number. @@ -57,7 +57,7 @@ function pad2(n: number) { return (n > 9 ? '' : '0') + n; } -/** +/** * Pad a number with leading zeros if it is less than three digits long. * @param n The number to be padded. * @returns The padded number. diff --git a/src/repoManager.ts b/src/repoManager.ts index 29bae56e..230f1d8c 100644 --- a/src/repoManager.ts +++ b/src/repoManager.ts @@ -811,7 +811,7 @@ function readExternalConfigFile(repo: string) { /** * Writes the External Configuration File of a repository to the File System. * @param repo The path of the repository. - * @param file The file contents. + * @param file The file contents. * @returns A promise that resolves to a success message, or rejects to an error message. */ function writeExternalConfigFile(repo: string, file: ExternalRepoConfig.File) { @@ -910,7 +910,7 @@ function generateExternalConfigFile(state: GitRepoState): Readonly Value, String => The first field that is invalid. + * @returns NULL => Value, String => The first field that is invalid. */ function validateExternalConfigFile(file: Readonly) { if (typeof file.commitOrdering !== 'undefined' && file.commitOrdering !== RepoCommitOrdering.Date && file.commitOrdering !== RepoCommitOrdering.AuthorDate && file.commitOrdering !== RepoCommitOrdering.Topological) { diff --git a/src/statusBarItem.ts b/src/statusBarItem.ts index 79e5cac3..bab09245 100644 --- a/src/statusBarItem.ts +++ b/src/statusBarItem.ts @@ -44,7 +44,7 @@ export class StatusBarItem extends Disposable { this.setNumRepos(initialNumRepos); } - /** + /** * Sets the number of repositories known to Git Graph, before refreshing the Status Bar Item. * @param numRepos The number of repositories known to Git Graph. */ @@ -53,7 +53,7 @@ export class StatusBarItem extends Disposable { this.refresh(); } - /** + /** * Show or hide the Status Bar Item according to the configured value of `git-graph.showStatusBarItem`, and the number of repositories known to Git Graph. */ private refresh() { diff --git a/tests/repoFileWatcher.test.ts b/tests/repoFileWatcher.test.ts index b44507c7..af81b3ed 100644 --- a/tests/repoFileWatcher.test.ts +++ b/tests/repoFileWatcher.test.ts @@ -90,7 +90,7 @@ describe('RepoFileWatcher', () => { const onDidCreate = (>repoFileWatcher['fsWatcher']!.onDidCreate).mock.calls[0][0]; const onDidChange = (>repoFileWatcher['fsWatcher']!.onDidChange).mock.calls[0][0]; - // Run + // Run repoFileWatcher.mute(); repoFileWatcher.unmute(); onDidCreate(vscode.Uri.file('/path/to/repo/file')); diff --git a/web/contextMenu.ts b/web/contextMenu.ts index d52526d3..927d771c 100644 --- a/web/contextMenu.ts +++ b/web/contextMenu.ts @@ -140,7 +140,7 @@ class ContextMenu { } return; } else { - // ContextMenu is dependent on the commit and ref + // ContextMenu is dependent on the commit and ref const elems = >commitElem.querySelectorAll('[data-fullref]'); for (let i = 0; i < elems.length; i++) { if (elems[i].dataset.fullref! === this.target.ref) { diff --git a/web/dialog.ts b/web/dialog.ts index e1385a99..92d762d5 100644 --- a/web/dialog.ts +++ b/web/dialog.ts @@ -194,7 +194,7 @@ class Dialog { * @param target The target that the dialog was triggered on. * @param secondaryActionName An optional name for the secondary action. * @param secondaryActioned An optional callback to be invoked when the secondary action is selected by the user. - * @param includeLineBreak Should a line break be added between the message and form inputs. + * @param includeLineBreak Should a line break be added between the message and form inputs. */ public showForm(message: string, inputs: ReadonlyArray, actionName: string, actioned: (values: DialogInputValue[]) => void, target: DialogTarget | null, secondaryActionName: string = 'Cancel', secondaryActioned: ((values: DialogInputValue[]) => void) | null = null, includeLineBreak: boolean = true) { const multiElement = inputs.length > 1; @@ -419,7 +419,7 @@ class Dialog { } return; } else { - // Dialog is dependent on the commit and ref + // Dialog is dependent on the commit and ref const elems = >commitElem.querySelectorAll('[data-fullref]'); for (let i = 0; i < elems.length; i++) { if (elems[i].dataset.fullref! === this.target.ref) { diff --git a/web/findWidget.ts b/web/findWidget.ts index a79ad36c..ff0e0974 100644 --- a/web/findWidget.ts +++ b/web/findWidget.ts @@ -234,15 +234,16 @@ class FindWidget { for (let i = 0; i < commits.length; i++) { commit = commits[i]; let branchLabels = getBranchLabels(commit.heads, commit.remotes); - if (commit.hash !== UNCOMMITTED && ((colVisibility.author && findPattern.test(commit.author)) + if (commit.hash !== UNCOMMITTED && ( + (colVisibility.author && findPattern.test(commit.author)) || (colVisibility.commit && (commit.hash.search(findPattern) === 0 || findPattern.test(abbrevCommit(commit.hash)))) || findPattern.test(commit.message) || branchLabels.heads.some(head => findPattern!.test(head.name) || head.remotes.some(remote => findPattern!.test(remote))) || branchLabels.remotes.some(remote => findPattern!.test(remote.name)) || commit.tags.some(tag => findPattern!.test(tag.name)) || (colVisibility.date && findPattern.test(formatShortDate(commit.date).formatted)) - || (commit.stash !== null && findPattern.test(commit.stash.selector)))) { - + || (commit.stash !== null && findPattern.test(commit.stash.selector)) + )) { let idStr = i.toString(); while (j < commitElems.length && commitElems[j].dataset.id !== idStr) j++; if (j === commitElems.length) continue; @@ -388,7 +389,7 @@ class FindWidget { } /** - * If the Find Widget is configured to open the Commit Details View for the current find match, load the Commit Details View accordingly. + * If the Find Widget is configured to open the Commit Details View for the current find match, load the Commit Details View accordingly. */ private openCommitDetailsViewForCurrentMatchIfEnabled() { if (workspaceState.findOpenCommitDetailsView) { @@ -413,5 +414,4 @@ class FindWidget { span.innerHTML = text; return span; } - } diff --git a/web/global.d.ts b/web/global.d.ts index e8a5cf5f..5e1072ff 100644 --- a/web/global.d.ts +++ b/web/global.d.ts @@ -5,9 +5,9 @@ declare global { /* Visual Studio Code API Types */ function acquireVsCodeApi(): { - getState(): WebViewState | null, - postMessage(message: GG.RequestMessage): void, - setState(state: WebViewState): void + getState: () => WebViewState | null, + postMessage: (message: GG.RequestMessage) => void, + setState: (state: WebViewState) => void }; diff --git a/web/graph.ts b/web/graph.ts index 1d576723..415dfd2e 100644 --- a/web/graph.ts +++ b/web/graph.ts @@ -102,7 +102,7 @@ class Branch { lines.push({ p1: { x: x1, y: y1 }, p2: { x: x2, y: y2 }, isCommitted: i >= this.numUncommitted, lockedFirst: line.lockedFirst }); } - // Simplify consecutive lines that are straight by removing the 'middle' point + // Simplify consecutive lines that are straight by removing the 'middle' point i = 0; while (i < lines.length - 1) { line = lines[i]; diff --git a/web/main.ts b/web/main.ts index b8f6689f..1ef4b5c0 100644 --- a/web/main.ts +++ b/web/main.ts @@ -627,7 +627,7 @@ class GitGraphView { refreshState.hard = refreshState.hard || hard; refreshState.configChanges = refreshState.configChanges || configChanges; if (!skipRepoInfo) { - // This request will trigger a loadCommit request after the loadRepoInfo request has completed. + // This request will trigger a loadCommit request after the loadRepoInfo request has completed. // Invalidate any previous commit requests in progress. refreshState.loadCommitsRefreshId++; } @@ -1663,7 +1663,7 @@ class GitGraphView { } if (columnWidths[0] !== COLUMN_AUTO) { - // Table should have fixed layout + // Table should have fixed layout makeTableFixedLayout(); } else { // Table should have automatic layout @@ -2070,7 +2070,7 @@ class GitGraphView { const isExternalUrl = isExternalUrlElem(eventTarget), isInternalUrl = isInternalUrlElem(eventTarget); if (isExternalUrl || isInternalUrl) { const viewElem: HTMLElement | null = eventTarget.closest('#view'); - let eventElem; + let eventElem: HTMLElement | null; let target: (ContextMenuTarget & CommitTarget) | RepoTarget, isInDialog = false; if (this.expandedCommit !== null && eventTarget.closest('#cdv') !== null) { @@ -2083,7 +2083,7 @@ class GitGraphView { }; GitGraphView.closeCdvContextMenuIfOpen(this.expandedCommit); this.expandedCommit.contextMenuOpen.summary = true; - } else if ((eventElem = eventTarget.closest('.commit')) !== null) { + } else if ((eventElem = eventTarget.closest('.commit')) !== null) { // URL is in the Commits const commit = this.getCommitOfElem(eventElem); if (commit === null) return; @@ -2140,16 +2140,16 @@ class GitGraphView { if (e.target === null) return; const eventTarget = e.target; if (isUrlElem(eventTarget)) return; - let eventElem; + let eventElem: HTMLElement | null; - if ((eventElem = eventTarget.closest('.gitRef')) !== null) { + if ((eventElem = eventTarget.closest('.gitRef')) !== null) { // .gitRef was clicked e.stopPropagation(); if (contextMenu.isOpen()) { contextMenu.close(); } - } else if ((eventElem = eventTarget.closest('.commit')) !== null) { + } else if ((eventElem = eventTarget.closest('.commit')) !== null) { // .commit was clicked if (this.expandedCommit !== null) { const commit = this.getCommitOfElem(eventElem); @@ -2177,9 +2177,9 @@ class GitGraphView { if (e.target === null) return; const eventTarget = e.target; if (isUrlElem(eventTarget)) return; - let eventElem; + let eventElem: HTMLElement | null; - if ((eventElem = eventTarget.closest('.gitRef')) !== null) { + if ((eventElem = eventTarget.closest('.gitRef')) !== null) { // .gitRef was double clicked e.stopPropagation(); closeDialogAndContextMenu(); @@ -2214,9 +2214,9 @@ class GitGraphView { if (e.target === null) return; const eventTarget = e.target; if (isUrlElem(eventTarget)) return; - let eventElem; + let eventElem: HTMLElement | null; - if ((eventElem = eventTarget.closest('.gitRef')) !== null) { + if ((eventElem = eventTarget.closest('.gitRef')) !== null) { // .gitRef was right clicked handledEvent(e); const commitElem = eventElem.closest('.commit')!; @@ -2253,7 +2253,7 @@ class GitGraphView { contextMenu.show(actions, false, target, e, this.viewElem); - } else if ((eventElem = eventTarget.closest('.commit')) !== null) { + } else if ((eventElem = eventTarget.closest('.commit')) !== null) { // .commit was right clicked handledEvent(e); const commit = this.getCommitOfElem(eventElem); @@ -2706,10 +2706,10 @@ class GitGraphView { }; document.getElementById('cdvDivider')!.addEventListener('mousedown', () => { - let contentElem = document.getElementById('cdvContent')!; + const contentElem = document.getElementById('cdvContent'); if (contentElem === null) return; - let bounds = contentElem.getBoundingClientRect(); + const bounds = contentElem.getBoundingClientRect(); minX = bounds.left; width = bounds.width; eventOverlay.create('colResize', processDraggingCdvDivider, stopDraggingCdvDivider); diff --git a/web/textFormatter.ts b/web/textFormatter.ts index 70287642..6230693f 100644 --- a/web/textFormatter.ts +++ b/web/textFormatter.ts @@ -556,7 +556,7 @@ class TextFormatter { } /** - * Is a range included (partially or completely) within a tree. + * Is a range included (partially or completely) within a tree. * @param tree The tree to check. * @param start The index defining the start of the range. * @param end The index defining the end of the range.