Skip to content

Commit

Permalink
Merge pull request #13807 from desktop/ts-4.5-linting
Browse files Browse the repository at this point in the history
TypeScript 4.5 linting
  • Loading branch information
niik committed Feb 3, 2022
2 parents 044b478 + 02a8032 commit de51c3f
Show file tree
Hide file tree
Showing 20 changed files with 779 additions and 591 deletions.
15 changes: 8 additions & 7 deletions .eslintrc.yml
Expand Up @@ -2,7 +2,6 @@ root: true
parser: '@typescript-eslint/parser'
plugins:
- '@typescript-eslint'
- babel
- react
- json
- jsdoc
Expand Down Expand Up @@ -100,9 +99,7 @@ rules:
'@typescript-eslint/no-explicit-any': off
'@typescript-eslint/no-inferrable-types': off
'@typescript-eslint/no-empty-function': off

# Babel
babel/no-invalid-this: error
'@typescript-eslint/no-redeclare': error

# React
react/jsx-boolean-value:
Expand All @@ -114,6 +111,10 @@ rules:
react/jsx-uses-vars: error
react/jsx-uses-react: error
react/no-unused-state: error
react/no-unused-prop-types: error
react/prop-types:
- error
- ignore: ['children']

# JSDoc
jsdoc/check-alignment: error
Expand All @@ -136,9 +137,9 @@ rules:
###########
curly: error
no-new-wrappers: error
no-redeclare:
- error
- builtinGlobals: true
# We'll use no-redeclare from @typescript/eslint-plugin instead as that
# supports overloads
no-redeclare: off
no-eval: error
no-sync: error
no-var: error
Expand Down
2 changes: 1 addition & 1 deletion app/src/lib/fuzzy-find.ts
Expand Up @@ -20,7 +20,7 @@ export interface IMatch<T> {

export type KeyFunction<T> = (item: T) => ReadonlyArray<string>

export function match<T, _K extends keyof T>(
export function match<T>(
query: string,
items: ReadonlyArray<T>,
getKey: KeyFunction<T>
Expand Down
3 changes: 0 additions & 3 deletions app/src/lib/oauth.ts
Expand Up @@ -23,9 +23,6 @@ let oauthState: IOAuthState | null = null
* flow.
*/
export function askUserToOAuth(endpoint: string) {
// Disable the lint warning since we're storing the `resolve` and `reject`
// functions.
// tslint:disable-next-line:promise-must-complete
return new Promise<Account>((resolve, reject) => {
oauthState = { state: uuid(), endpoint, resolve, reject }

Expand Down
2 changes: 1 addition & 1 deletion app/src/lib/stores/api-repositories-store.ts
Expand Up @@ -130,7 +130,7 @@ export class ApiRepositoriesStore extends BaseStore {
this.emitUpdate()
}

private updateAccount<T, K extends keyof IAccountRepositories>(
private updateAccount<K extends keyof IAccountRepositories>(
account: Account,
repositories: Pick<IAccountRepositories, K>
) {
Expand Down
1 change: 0 additions & 1 deletion app/src/lib/stores/app-store.ts
Expand Up @@ -5205,7 +5205,6 @@ export class AppStore extends TypedBaseStore<IAppState> {
* resolve when `_completeOpenInDesktop` is called.
*/
public _startOpenInDesktop(fn: () => void): Promise<Repository | null> {
// tslint:disable-next-line:promise-must-complete
const p = new Promise<Repository | null>(
resolve => (this.resolveOpenInDesktop = resolve)
)
Expand Down
2 changes: 0 additions & 2 deletions app/src/lib/stores/helpers/repository-indicator-updater.ts
Expand Up @@ -140,8 +140,6 @@ export class RepositoryIndicatorUpdater {

public pause() {
if (this.paused === false) {
// Disable the lint warning since we're storing the `resolve`
// tslint:disable-next-line:promise-must-complete
this.pausePromise = new Promise<void>(resolve => {
this.resolvePausePromise = resolve
})
Expand Down
3 changes: 0 additions & 3 deletions app/src/ui/lib/highlight-text.tsx
@@ -1,8 +1,5 @@
import * as React from 'react'

// broken for SFCs: https://github.com/Microsoft/tslint-microsoft-contrib/issues/339
/* tslint:disable react-unused-props-and-state */

interface IHighlightTextProps {
/** The text to render */
readonly text: string
Expand Down
68 changes: 68 additions & 0 deletions app/src/ui/tab-bar-item.tsx
@@ -0,0 +1,68 @@
import * as React from 'react'
import classNames from 'classnames'
import { TabBarType } from './tab-bar-type'

interface ITabBarItemProps {
readonly index: number
readonly selected: boolean
readonly onClick: (index: number) => void
readonly onMouseEnter: (index: number) => void
readonly onMouseLeave: () => void
readonly onSelectAdjacent: (
direction: 'next' | 'previous',
index: number
) => void
readonly onButtonRef: (
index: number,
button: HTMLButtonElement | null
) => void
readonly type?: TabBarType
}

export class TabBarItem extends React.Component<ITabBarItemProps, {}> {
private onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
this.props.onClick(this.props.index)
}

private onKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {
const { type, index } = this.props
const previousKey = type === TabBarType.Vertical ? 'ArrowUp' : 'ArrowLeft'
const nextKey = type === TabBarType.Vertical ? 'ArrowDown' : 'ArrowRight'
if (event.key === previousKey) {
this.props.onSelectAdjacent('previous', index)
event.preventDefault()
} else if (event.key === nextKey) {
this.props.onSelectAdjacent('next', index)
event.preventDefault()
}
}

private onButtonRef = (buttonRef: HTMLButtonElement | null) => {
this.props.onButtonRef(this.props.index, buttonRef)
}

private onMouseEnter = () => {
this.props.onMouseEnter(this.props.index)
}

public render() {
const selected = this.props.selected
const className = classNames('tab-bar-item', { selected })
return (
<button
ref={this.onButtonRef}
className={className}
onClick={this.onClick}
role="tab"
aria-selected={selected}
tabIndex={selected ? undefined : -1}
onKeyDown={this.onKeyDown}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.props.onMouseLeave}
type="button"
>
{this.props.children}
</button>
)
}
}
11 changes: 11 additions & 0 deletions app/src/ui/tab-bar-type.ts
@@ -0,0 +1,11 @@
/** The tab bar type. */
export enum TabBarType {
/** Standard tabs */
Tabs,

/** Simpler switch appearance */
Switch,

/** Vertical tabs */
Vertical,
}
81 changes: 3 additions & 78 deletions app/src/ui/tab-bar.tsx
@@ -1,22 +1,12 @@
import * as React from 'react'
import classNames from 'classnames'
import { dragAndDropManager } from '../lib/drag-and-drop-manager'
import { TabBarItem } from './tab-bar-item'
import { TabBarType } from './tab-bar-type'
export { TabBarType } from './tab-bar-type'

/** Time to wait for drag element hover before switching tabs */
const dragTabSwitchWaitTime = 500

/** The tab bar type. */
export enum TabBarType {
/** Standard tabs */
Tabs,

/** Simpler switch appearance */
Switch,

/** Vertical tabs */
Vertical,
}

interface ITabBarProps {
/** The currently selected tab. */
readonly selectedIndex: number
Expand Down Expand Up @@ -144,68 +134,3 @@ export class TabBar extends React.Component<ITabBarProps, {}> {
})
}
}

interface ITabBarItemProps {
readonly index: number
readonly selected: boolean
readonly onClick: (index: number) => void
readonly onMouseEnter: (index: number) => void
readonly onMouseLeave: () => void
readonly onSelectAdjacent: (
direction: 'next' | 'previous',
index: number
) => void
readonly onButtonRef: (
index: number,
button: HTMLButtonElement | null
) => void
readonly type?: TabBarType
}

class TabBarItem extends React.Component<ITabBarItemProps, {}> {
private onClick = (event: React.MouseEvent<HTMLButtonElement>) => {
this.props.onClick(this.props.index)
}

private onKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {
const { type, index } = this.props
const previousKey = type === TabBarType.Vertical ? 'ArrowUp' : 'ArrowLeft'
const nextKey = type === TabBarType.Vertical ? 'ArrowDown' : 'ArrowRight'
if (event.key === previousKey) {
this.props.onSelectAdjacent('previous', index)
event.preventDefault()
} else if (event.key === nextKey) {
this.props.onSelectAdjacent('next', index)
event.preventDefault()
}
}

private onButtonRef = (buttonRef: HTMLButtonElement | null) => {
this.props.onButtonRef(this.props.index, buttonRef)
}

private onMouseEnter = () => {
this.props.onMouseEnter(this.props.index)
}

public render() {
const selected = this.props.selected
const className = classNames('tab-bar-item', { selected })
return (
<button
ref={this.onButtonRef}
className={className}
onClick={this.onClick}
role="tab"
aria-selected={selected}
tabIndex={selected ? undefined : -1}
onKeyDown={this.onKeyDown}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.props.onMouseLeave}
type="button"
>
{this.props.children}
</button>
)
}
}
88 changes: 1 addition & 87 deletions app/src/ui/tutorial/tutorial-panel.tsx
Expand Up @@ -16,6 +16,7 @@ import { PopupType } from '../../models/popup'
import { PreferencesTab } from '../../models/preferences'
import { Ref } from '../lib/ref'
import { suggestedExternalEditor } from '../../lib/editors/shared'
import { TutorialStepInstructions } from './tutorial-step-instruction'

const TutorialPanelImage = encodePathAsUrl(
__dirname,
Expand Down Expand Up @@ -310,93 +311,6 @@ export class TutorialPanel extends React.Component<
}
}

interface ITutorialStepInstructionsProps {
/** Text displayed to summarize this step */
readonly summaryText: string
/** Used to find out if this step has been completed */
readonly isComplete: (step: ValidTutorialStep) => boolean
/** The step for this section */
readonly sectionId: ValidTutorialStep
/** Used to find out if this is the next step for the user to complete */
readonly isNextStepTodo: (step: ValidTutorialStep) => boolean

/** ID of the currently expanded tutorial step
* (used to determine if this step is expanded)
*/
readonly currentlyOpenSectionId: ValidTutorialStep

/** Skip button (if possible for this step) */
readonly skipLinkButton?: JSX.Element
/** Handler to open and close section */
readonly onSummaryClick: (id: ValidTutorialStep) => void
}

/** A step (summary and expandable description) in the tutorial side panel */
class TutorialStepInstructions extends React.Component<
ITutorialStepInstructionsProps
> {
public render() {
return (
<li key={this.props.sectionId} onClick={this.onSummaryClick}>
<details
open={this.props.sectionId === this.props.currentlyOpenSectionId}
onClick={this.onSummaryClick}
>
{this.renderSummary()}
<div className="contents">{this.props.children}</div>
</details>
</li>
)
}

private renderSummary = () => {
const shouldShowSkipLink =
this.props.skipLinkButton !== undefined &&
this.props.currentlyOpenSectionId === this.props.sectionId &&
this.props.isNextStepTodo(this.props.sectionId)
return (
<summary>
{this.renderTutorialStepIcon()}
<span className="summary-text">{this.props.summaryText}</span>
<span className="hang-right">
{shouldShowSkipLink ? (
this.props.skipLinkButton
) : (
<Octicon symbol={OcticonSymbol.chevronDown} />
)}
</span>
</summary>
)
}

private renderTutorialStepIcon() {
if (this.props.isComplete(this.props.sectionId)) {
return (
<div className="green-circle">
<Octicon symbol={OcticonSymbol.check} />
</div>
)
}

// ugh zero-indexing
const stepNumber = orderedTutorialSteps.indexOf(this.props.sectionId) + 1
return this.props.isNextStepTodo(this.props.sectionId) ? (
<div className="blue-circle">{stepNumber}</div>
) : (
<div className="empty-circle">{stepNumber}</div>
)
}

private onSummaryClick = (e: React.MouseEvent<HTMLElement>) => {
// prevents the default behavior of toggling on a `details` html element
// so we don't have to fight it with our react state
// for more info see:
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details#Events
e.preventDefault()
this.props.onSummaryClick(this.props.sectionId)
}
}

const SkipLinkButton: React.FunctionComponent<{
onClick: () => void
}> = props => <LinkButton onClick={props.onClick}>Skip</LinkButton>

0 comments on commit de51c3f

Please sign in to comment.