Revert "ESLint for great justice: Episode I"

This commit is contained in:
Josh Abernathy 2017-09-20 14:10:29 -04:00 committed by GitHub
parent 0c22a1fde5
commit 45e7b216ea
70 changed files with 484 additions and 587 deletions

View file

@ -1,87 +0,0 @@
root: true
parser: typescript-eslint-parser
plugins:
- typescript
- babel
- react
- prettier
extends:
- prettier
- prettier/react
rules:
##########
# CUSTOM #
##########
insecure-random: error
###########
# PLUGINS #
###########
# TYPESCRIPT
typescript/interface-name-prefix:
- error
- always
typescript/no-angle-bracket-type-assertion: error
typescript/explicit-member-accessibility: error
typescript/no-unused-vars: error
typescript/no-use-before-define:
- error
- functions: false
variables: false
typedefs: false
## blocked by https://github.com/nzakas/eslint-plugin-typescript/pull/23
# typescript/member-ordering: error
##
## blocked by https://github.com/nzakas/eslint-plugin-typescript/issues/41
# typescript/type-annotation-spacing: error
##
# Babel
babel/no-invalid-this: error
# React
react/jsx-boolean-value:
- error
- always
react/jsx-key: error
react/jsx-no-bind: error
react/no-string-refs: error
###########
# BUILTIN #
###########
curly: error
no-new-wrappers: error
no-redeclare:
- error
- builtinGlobals: true
no-eval: error
no-sync: error
no-unused-expressions: error
no-var: error
prefer-const: error
eqeqeq:
- error
- smart
###########
# SPECIAL #
###########
prettier/prettier:
- error
- singleQuote: true
trailingComma: es5
semi: false
parser: typescript
no-restricted-syntax:
- error
# no-default-export
- selector: ExportDefaultDeclaration
message: Use of default exports is forbidden
parserOptions:
sourceType: module
ecmaFeatures:
jsx: true

3
.gitignore vendored
View file

@ -5,5 +5,4 @@ npm-debug.log
app/node_modules/
.DS_Store
.awcache
.idea/
.eslintcache
.idea/

View file

@ -20,7 +20,6 @@ cache:
directories:
- node_modules
- $HOME/.electron
- .eslintcache
install:
- npm install -g npm@4.6.1
@ -31,6 +30,7 @@ install:
- npm ls --dev
script:
- npm run check-prettiness
- npm run lint
- npm run build:prod
- npm run test:setup

162
app/npm-shrinkwrap.json generated
View file

@ -10,7 +10,7 @@
},
"accessibility-developer-tools": {
"version": "2.12.0",
"from": "accessibility-developer-tools@^2.11.0",
"from": "accessibility-developer-tools@>=2.11.0 <3.0.0",
"resolved": "https://registry.npmjs.org/accessibility-developer-tools/-/accessibility-developer-tools-2.12.0.tgz",
"dev": true
},
@ -25,9 +25,15 @@
"resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",
"dev": true
},
"ansi-regex": {
"version": "2.1.1",
"from": "ansi-regex@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"dev": true
},
"app-path": {
"version": "2.2.0",
"from": "app-path@>=2.2.0 <3.0.0",
"from": "app-path@latest",
"resolved": "https://registry.npmjs.org/app-path/-/app-path-2.2.0.tgz"
},
"argparse": {
@ -36,9 +42,9 @@
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz"
},
"asap": {
"version": "2.0.6",
"version": "2.0.5",
"from": "asap@>=2.0.3 <2.1.0",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz"
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz"
},
"asn1": {
"version": "0.2.3",
@ -95,7 +101,7 @@
},
"big.js": {
"version": "3.1.3",
"from": "big.js@^3.1.3",
"from": "big.js@>=3.1.3 <4.0.0",
"resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz",
"dev": true
},
@ -212,12 +218,6 @@
}
}
},
"debug": {
"version": "2.6.8",
"from": "debug@^2.6.8",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
"dev": true
},
"deep-equal": {
"version": "1.0.1",
"from": "deep-equal@>=1.0.1 <2.0.0",
@ -230,7 +230,7 @@
},
"devtron": {
"version": "1.4.0",
"from": "devtron@^1.4.0",
"from": "devtron@>=1.4.0 <2.0.0",
"resolved": "https://registry.npmjs.org/devtron/-/devtron-1.4.0.tgz",
"dev": true
},
@ -266,33 +266,27 @@
"optional": true
},
"electron-debug": {
"version": "1.3.0",
"from": "electron-debug@^1.1.0",
"resolved": "https://registry.npmjs.org/electron-debug/-/electron-debug-1.3.0.tgz",
"version": "1.1.0",
"from": "electron-debug@>=1.1.0 <2.0.0",
"resolved": "https://registry.npmjs.org/electron-debug/-/electron-debug-1.1.0.tgz",
"dev": true
},
"electron-devtools-installer": {
"version": "2.2.0",
"from": "electron-devtools-installer@^2.1.0",
"from": "electron-devtools-installer@>=2.1.0 <3.0.0",
"resolved": "https://registry.npmjs.org/electron-devtools-installer/-/electron-devtools-installer-2.2.0.tgz",
"dev": true
},
"electron-is-accelerator": {
"version": "0.1.2",
"from": "electron-is-accelerator@^0.1.0",
"resolved": "https://registry.npmjs.org/electron-is-accelerator/-/electron-is-accelerator-0.1.2.tgz",
"dev": true
},
"electron-is-dev": {
"version": "0.3.0",
"from": "electron-is-dev@^0.3.0",
"resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-0.3.0.tgz",
"version": "0.1.2",
"from": "electron-is-dev@>=0.1.0 <0.2.0",
"resolved": "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-0.1.2.tgz",
"dev": true
},
"electron-localshortcut": {
"version": "2.0.2",
"from": "electron-localshortcut@^2.0.0",
"resolved": "https://registry.npmjs.org/electron-localshortcut/-/electron-localshortcut-2.0.2.tgz",
"version": "0.6.1",
"from": "electron-localshortcut@>=0.6.0 <0.7.0",
"resolved": "https://registry.npmjs.org/electron-localshortcut/-/electron-localshortcut-0.6.1.tgz",
"dev": true
},
"electron-window-state": {
@ -302,7 +296,7 @@
},
"emojis-list": {
"version": "2.1.0",
"from": "emojis-list@^2.0.0",
"from": "emojis-list@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
"dev": true
},
@ -342,13 +336,13 @@
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz"
},
"fbjs": {
"version": "0.8.14",
"version": "0.8.12",
"from": "fbjs@>=0.8.9 <0.9.0",
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.14.tgz"
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.12.tgz"
},
"file-uri-to-path": {
"version": "0.0.2",
"from": "file-uri-to-path@>=0.0.0 <1.0.0",
"from": "file-uri-to-path@latest",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-0.0.2.tgz"
},
"forever-agent": {
@ -376,6 +370,11 @@
"from": "fs.realpath@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
},
"fstream": {
"version": "1.0.11",
"from": "fstream@>=1.0.2 <2.0.0",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz"
},
"getpass": {
"version": "0.1.7",
"from": "getpass@>=0.1.1 <0.2.0",
@ -414,9 +413,9 @@
"resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz"
},
"highlight.js": {
"version": "9.12.0",
"from": "highlight.js@^9.3.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.12.0.tgz",
"version": "9.11.0",
"from": "highlight.js@>=9.3.0 <10.0.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.11.0.tgz",
"dev": true
},
"hoek": {
@ -426,20 +425,25 @@
},
"html-entities": {
"version": "1.2.1",
"from": "html-entities@^1.2.0",
"from": "html-entities@>=1.2.0 <2.0.0",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz",
"dev": true
},
"http-signature": {
"version": "1.1.1",
"from": "http-signature@>=1.1.0 <1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz"
},
"humanize-plus": {
"version": "1.8.2",
"from": "humanize-plus@^1.8.1",
"from": "humanize-plus@>=1.8.1 <2.0.0",
"resolved": "https://registry.npmjs.org/humanize-plus/-/humanize-plus-1.8.2.tgz",
"dev": true
},
"iconv-lite": {
"version": "0.4.18",
"version": "0.4.17",
"from": "iconv-lite@>=0.4.13 <0.5.0",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz"
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.17.tgz"
},
"inflight": {
"version": "1.0.6",
@ -477,9 +481,9 @@
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz"
},
"js-tokens": {
"version": "3.0.2",
"version": "3.0.1",
"from": "js-tokens@>=3.0.0 <4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz"
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz"
},
"js-yaml": {
"version": "3.8.3",
@ -509,7 +513,7 @@
},
"json5": {
"version": "0.5.1",
"from": "json5@^0.5.0",
"from": "json5@>=0.5.0 <0.6.0",
"resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
"dev": true
},
@ -542,7 +546,7 @@
},
"loader-utils": {
"version": "1.1.0",
"from": "loader-utils@^1.0.2",
"from": "loader-utils@>=1.0.2 <2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz",
"dev": true
},
@ -591,21 +595,15 @@
"from": "moment@>=2.17.1 <3.0.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz"
},
"ms": {
"version": "2.0.0",
"from": "ms@2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"dev": true
},
"nan": {
"version": "2.5.1",
"from": "nan@2.5.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.5.1.tgz"
},
"node-fetch": {
"version": "1.7.1",
"version": "1.6.3",
"from": "node-fetch@>=1.0.1 <2.0.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.1.tgz"
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.6.3.tgz"
},
"npm-run-path": {
"version": "1.0.0",
@ -663,9 +661,9 @@
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz"
},
"promise": {
"version": "7.3.1",
"version": "7.1.1",
"from": "promise@>=7.1.1 <8.0.0",
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz"
"resolved": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz"
},
"prop-types": {
"version": "15.5.10",
@ -689,7 +687,7 @@
},
"querystring": {
"version": "0.2.0",
"from": "querystring@^0.2.0",
"from": "querystring@>=0.2.0 <0.3.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"dev": true
},
@ -700,7 +698,7 @@
},
"react-addons-perf": {
"version": "15.4.2",
"from": "react-addons-perf@15.4.2",
"from": "react-addons-perf@>=15.4.2 <16.0.0",
"resolved": "https://registry.npmjs.org/react-addons-perf/-/react-addons-perf-15.4.2.tgz",
"dev": true
},
@ -735,6 +733,11 @@
"from": "regenerator-runtime@>=0.10.0 <0.11.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz"
},
"request": {
"version": "2.81.0",
"from": "request@>=2.79.0 <3.0.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz"
},
"rimraf": {
"version": "2.6.1",
"from": "rimraf@>=2.5.4 <3.0.0",
@ -751,9 +754,9 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz"
},
"semver": {
"version": "5.4.1",
"from": "semver@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0||>=4.0.0 <5.0.0||>=5.0.0 <6.0.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
"version": "5.3.0",
"from": "semver@>=5.3.0 <6.0.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
"dev": true
},
"setimmediate": {
@ -803,6 +806,12 @@
"from": "stringstream@>=0.0.4 <0.1.0",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz"
},
"strip-ansi": {
"version": "3.0.1",
"from": "strip-ansi@>=3.0.0 <4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"dev": true
},
"strip-eof": {
"version": "1.0.0",
"from": "strip-eof@>=1.0.0 <2.0.0",
@ -810,31 +819,24 @@
},
"style-loader": {
"version": "0.13.2",
"from": "style-loader@^0.13.2",
"from": "style-loader@>=0.13.2 <0.14.0",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.13.2.tgz",
"dev": true
},
"tar": {
"version": "2.2.1",
"from": "tar@>=2.2.1 <3.0.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
"dependencies": {
"fstream": {
"version": "1.0.11",
"from": "fstream@https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz"
}
}
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz"
},
"temp": {
"version": "0.8.3",
"from": "temp@^0.8.3",
"from": "temp@>=0.8.3 <0.9.0",
"resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz",
"dev": true,
"dependencies": {
"rimraf": {
"version": "2.2.8",
"from": "rimraf@~2.2.6",
"from": "rimraf@>=2.2.6 <2.3.0",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
"dev": true
}
@ -899,24 +901,10 @@
"resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz"
},
"webpack-hot-middleware": {
"version": "2.18.2",
"from": "webpack-hot-middleware@^2.10.0",
"resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.18.2.tgz",
"dev": true,
"dependencies": {
"ansi-regex": {
"version": "2.1.1",
"from": "ansi-regex@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"dev": true
},
"strip-ansi": {
"version": "3.0.1",
"from": "strip-ansi@>=3.0.0 <4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"dev": true
}
}
"version": "2.18.0",
"from": "webpack-hot-middleware@>=2.10.0 <3.0.0",
"resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.18.0.tgz",
"dev": true
},
"whatwg-fetch": {
"version": "2.0.3",

View file

@ -147,7 +147,7 @@ function skewInterval(): number {
// We don't need cryptographically secure random numbers for
// the skew. Pseudo-random should be just fine.
// eslint-disable-next-line insecure-random
// tslint:disable-next-line:insecure-random
const skew = Math.ceil(Math.random() * SkewUpperBound)
_skewInterval = skew
return skew

View file

@ -62,7 +62,7 @@ declare type DOMHighResTimeStamp = number
*
* https://developer.mozilla.org/en-US/docs/Web/API/IdleDeadline
*/
interface IIdleDeadline {
interface IdleDeadline {
readonly didTimeout: boolean
readonly timeRemaining: () => DOMHighResTimeStamp
}
@ -73,7 +73,7 @@ interface IIdleDeadline {
*
* See https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback
*/
interface IIdleCallbackOptions {
interface IdleCallbackOptions {
/**
* If timeout is specified and has a positive value, and the callback has not
* already been called by the time timeout milliseconds have passed, the
@ -99,8 +99,8 @@ interface IIdleCallbackOptions {
* timeout:
*/
declare function requestIdleCallback(
fn: (deadline: IIdleDeadline) => void,
options?: IIdleCallbackOptions
fn: (deadline: IdleDeadline) => void,
options?: IdleCallbackOptions
): number
interface IDesktopLogger {
@ -177,7 +177,7 @@ declare const log: IDesktopLogger
// these changes should be pushed into the Electron declarations
declare namespace NodeJS {
// eslint-disable-next-line typescript/interface-name-prefix
// tslint:disable-next-line:interface-name
interface Process extends EventEmitter {
once(event: 'uncaughtException', listener: (error: Error) => void): this
on(event: 'uncaughtException', listener: (error: Error) => void): this
@ -187,7 +187,7 @@ declare namespace NodeJS {
}
declare namespace Electron {
// eslint-disable-next-line typescript/interface-name-prefix
// tslint:disable-next-line:interface-name
interface MenuItem {
readonly accelerator?: Electron.Accelerator
readonly submenu?: Electron.Menu
@ -195,7 +195,7 @@ declare namespace Electron {
readonly type: 'normal' | 'separator' | 'submenu' | 'checkbox' | 'radio'
}
// eslint-disable-next-line typescript/interface-name-prefix
// tslint:disable-next-line:interface-name
interface RequestOptions {
readonly method: string
readonly url: string
@ -204,7 +204,7 @@ declare namespace Electron {
type AppleActionOnDoubleClickPref = 'Maximize' | 'Minimize' | 'None'
// eslint-disable-next-line typescript/interface-name-prefix
// tslint:disable-next-line:interface-name
interface SystemPreferences {
getUserDefault(
key: 'AppleActionOnDoubleClick',

View file

@ -3,7 +3,7 @@ import { formatLogMessage } from '../format-log-message'
const g = global as any
g.log = {
g.log = <IDesktopLogger>{
error(message: string, error?: Error) {
log('error', '[main] ' + formatLogMessage(message, error))
},
@ -16,4 +16,4 @@ g.log = {
debug(message: string, error?: Error) {
log('debug', '[main] ' + formatLogMessage(message, error))
},
} as IDesktopLogger
}

View file

@ -17,7 +17,7 @@ function log(level: LogLevel, message: string, error?: Error) {
)
}
g.log = {
g.log = <IDesktopLogger>{
error(message: string, error?: Error) {
log('error', message, error)
console.error(formatLogMessage(message, error))
@ -34,4 +34,4 @@ g.log = {
log('debug', message, error)
console.debug(formatLogMessage(message, error))
},
} as IDesktopLogger
}

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import * as ChildProcess from 'child_process'
import * as os from 'os'

View file

@ -48,13 +48,13 @@ function retrieveSourceMap(source: string) {
// https://github.com/v8/v8/wiki/Stack-Trace-API#customizing-stack-traces
// This happens on-demand when someone accesses the stack
// property on an error object and has to be synchronous :/
// eslint-disable-next-line no-sync
// tslint:disable-next-line:no-sync-functions
if (!Fs.existsSync(path)) {
return
}
try {
// eslint-disable-next-line no-sync
// tslint:disable-next-line:no-sync-functions
const map = Fs.readFileSync(path, 'utf8')
return { url: Path.basename(path), map }
} catch (error) {
@ -125,6 +125,7 @@ function sourceMappedStackTrace(error: Error): string | undefined {
// in our weak map. In order to get around that we'll eagerly access the
// stack, forcing our handler to run which should ensure that the native
// frames are stored in our weak map.
// tslint:disable-next-line:whitespace
;(error.stack || '').toString()
frames = stackFrameMap.get(error)
}

View file

@ -6,45 +6,6 @@ import { assertNever } from '../lib/fatal-error'
*/
export const maximumDiffStringSize = 268435441
/**
* A container for holding an image for display in the application
*/
export class Image {
/**
* The base64 encoded contents of the image
*/
public readonly contents: string
/**
* The data URI media type, so the browser can render the image correctly
*/
public readonly mediaType: string
}
/** each diff is made up of a number of hunks */
export class DiffHunk {
/** details from the diff hunk header about the line start and patch length */
public readonly header: DiffHunkHeader
/** the contents - context and changes - of the diff setion */
public readonly lines: ReadonlyArray<DiffLine>
/** the diff hunk's start position in the overall file diff */
public readonly unifiedDiffStart: number
/** the diff hunk's end position in the overall file diff */
public readonly unifiedDiffEnd: number
public constructor(
header: DiffHunkHeader,
lines: ReadonlyArray<DiffLine>,
unifiedDiffStart: number,
unifiedDiffEnd: number
) {
this.header = header
this.unifiedDiffStart = unifiedDiffStart
this.unifiedDiffEnd = unifiedDiffEnd
this.lines = lines
}
}
export enum DiffType {
/** changes to a text file, which may be partially selected for commit */
Text,
@ -201,6 +162,45 @@ export class DiffHunkHeader {
}
}
/** each diff is made up of a number of hunks */
export class DiffHunk {
/** details from the diff hunk header about the line start and patch length */
public readonly header: DiffHunkHeader
/** the contents - context and changes - of the diff setion */
public readonly lines: ReadonlyArray<DiffLine>
/** the diff hunk's start position in the overall file diff */
public readonly unifiedDiffStart: number
/** the diff hunk's end position in the overall file diff */
public readonly unifiedDiffEnd: number
public constructor(
header: DiffHunkHeader,
lines: ReadonlyArray<DiffLine>,
unifiedDiffStart: number,
unifiedDiffEnd: number
) {
this.header = header
this.unifiedDiffStart = unifiedDiffStart
this.unifiedDiffEnd = unifiedDiffEnd
this.lines = lines
}
}
/**
* A container for holding an image for display in the application
*/
export class Image {
/**
* The base64 encoded contents of the image
*/
public readonly contents: string
/**
* The data URI media type, so the browser can render the image correctly
*/
public readonly mediaType: string
}
export class FileSummary {
/**
* The number of lines added as part of this change.

View file

@ -13,7 +13,7 @@ import { LinkButton } from '../lib/link-button'
import { PopupType } from '../../lib/app-state'
import * as Path from 'path'
import untildify = require('untildify')
const untildify: (str: string) => string = require('untildify')
interface IAddExistingRepositoryProps {
readonly dispatcher: Dispatcher

View file

@ -1,13 +1,9 @@
import * as React from 'react'
import {
IMenu,
ISubmenuItem,
findItemByAccessKey,
itemIsSelectable,
} from '../../models/app-menu'
import { IMenu, ISubmenuItem } from '../../models/app-menu'
import { AppMenuBarButton } from './app-menu-bar-button'
import { Dispatcher } from '../../lib/dispatcher'
import { AppMenuFoldout, FoldoutType } from '../../lib/app-state'
import { findItemByAccessKey, itemIsSelectable } from '../../models/app-menu'
interface IAppMenuBarProps {
readonly appMenu: ReadonlyArray<IMenu>
@ -31,7 +27,7 @@ interface IAppMenuBarProps {
/**
* An optional function that's called when the menubar loses focus.
*
*
* Note that this function will only be called once no descendant element
* of the menu bar has keyboard focus. In other words this differs
* from the traditional onBlur event.
@ -51,7 +47,7 @@ interface IAppMenuBarState {
* Creates menu bar state given props. This is intentionally not
* an instance member in order to avoid mistakenly using any other
* input data or state than the received props.
*
*
* The state consists of a list of visible top-level menu items which have
* child menus of their own (ie submenu items).
*/
@ -99,12 +95,12 @@ export class AppMenuBar extends React.Component<
* element which had focus prior to the component receiving it. We do so in
* order to be able to restore focus to that element when we decide to
* _programmatically_ give up our focus.
*
*
* A good example of this is when the user is focused on a text box and hits
* the Alt key. Focus will then move to the first menu item in the menu bar.
* If the user then hits Enter we relinquish our focus and return it back to
* the text box again.
*
*
* As long as we hold on to this reference we might be preventing GC from
* collecting a potentially huge subtree of the DOM so we need to make sure
* to clear it out as soon as we're done with it.

View file

@ -1285,11 +1285,9 @@ export class App extends React.Component<IAppProps, IAppState> {
}
private onRepositoryDropdownStateChanged = (newState: DropdownState) => {
if (newState === 'open') {
this.props.dispatcher.showFoldout({ type: FoldoutType.Repository })
} else {
this.props.dispatcher.closeFoldout(FoldoutType.Repository)
}
newState === 'open'
? this.props.dispatcher.showFoldout({ type: FoldoutType.Repository })
: this.props.dispatcher.closeFoldout(FoldoutType.Repository)
}
private renderRepositoryToolbarButton() {
@ -1437,11 +1435,9 @@ export class App extends React.Component<IAppProps, IAppState> {
}
private onBranchDropdownStateChanged = (newState: DropdownState) => {
if (newState === 'open') {
this.props.dispatcher.showFoldout({ type: FoldoutType.Branch })
} else {
this.props.dispatcher.closeFoldout(FoldoutType.Branch)
}
newState === 'open'
? this.props.dispatcher.showFoldout({ type: FoldoutType.Branch })
: this.props.dispatcher.closeFoldout(FoldoutType.Branch)
}
private renderBranchToolbarButton(): JSX.Element | null {

View file

@ -4,12 +4,20 @@ import { IAutocompletionProvider } from './index'
import { fatalError } from '../../lib/fatal-error'
import * as classNames from 'classnames'
interface IPosition {
readonly top: number
readonly left: number
}
interface IRange {
readonly start: number
readonly length: number
}
import getCaretCoordinates = require('textarea-caret')
const getCaretCoordinates: (
element: HTMLElement,
position: number
) => IPosition = require('textarea-caret')
interface IAutocompletingTextInputProps<ElementType> {
/**
@ -124,7 +132,6 @@ export abstract class AutocompletingTextInput<
const element = this.element!
let coordinates = getCaretCoordinates(element, state.range.start)
coordinates = {
...coordinates,
top: coordinates.top - element.scrollTop,
left: coordinates.left - element.scrollLeft,
}

View file

@ -36,7 +36,7 @@ export class EmojiAutocompletionProvider
}
public getRegExp(): RegExp {
return /(?:^|\n| )(?::)([a-z\d\\+-][a-z\d_]*)?/g
return /(?:^|\n| )(?::)([a-z0-9\\+\\-][a-z0-9_]*)?/g
}
public async getAutocompletionItems(

View file

@ -1,7 +1,8 @@
import * as React from 'react'
import { IAutocompletionProvider } from './index'
import { Dispatcher, IssuesStore } from '../../lib/dispatcher'
import { IssuesStore } from '../../lib/dispatcher'
import { GitHubRepository } from '../../models/github-repository'
import { Dispatcher } from '../../lib/dispatcher'
import { ThrottledScheduler } from '../lib/throttled-scheduler'
/** The interval we should use to throttle the issues update. */
@ -44,7 +45,7 @@ export class IssuesAutocompletionProvider
}
public getRegExp(): RegExp {
return /(?:^|\n| )(?:#)([a-z\d\\+-][a-z\d_]*)?/g
return /(?:^|\n| )(?:#)([a-z0-9\\+\\-][a-z0-9_]*)?/g
}
public getAutocompletionItems(

View file

@ -30,7 +30,7 @@ export class UserAutocompletionProvider
}
public getRegExp(): RegExp {
return /(?:^|\n| )(?:@)([a-z\d\\+-][a-z\d_-]*)?/g
return /(?:^|\n| )(?:@)([a-z0-9\\+\\-][a-z0-9_\-]*)?/g
}
public async getAutocompletionItems(

View file

@ -11,8 +11,9 @@ import { DiffSelectionType } from '../../models/diff'
import { CommitIdentity } from '../../models/commit-identity'
import { Checkbox, CheckboxValue } from '../lib/checkbox'
import { ICommitMessage } from '../../lib/app-state'
import { Dispatcher, IGitHubUser } from '../../lib/dispatcher'
import { IGitHubUser } from '../../lib/dispatcher'
import { IAutocompletionProvider } from '../autocompletion'
import { Dispatcher } from '../../lib/dispatcher'
import { Repository } from '../../models/repository'
import { showContextualMenu, IMenuItem } from '../main-process-proxy'

View file

@ -6,7 +6,8 @@ import {
} from '../autocompletion'
import { CommitIdentity } from '../../models/commit-identity'
import { ICommitMessage } from '../../lib/app-state'
import { Dispatcher, IGitHubUser } from '../../lib/dispatcher'
import { Dispatcher } from '../../lib/dispatcher'
import { IGitHubUser } from '../../lib/dispatcher'
import { Repository } from '../../models/repository'
import { Button } from '../lib/button'
import { Avatar } from '../lib/avatar'

View file

@ -3,7 +3,7 @@ import * as React from 'react'
import { ChangesList } from './changes-list'
import { DiffSelectionType } from '../../models/diff'
import { ICommitMessage, IChangesState, PopupType } from '../../lib/app-state'
import { IChangesState, PopupType } from '../../lib/app-state'
import { Repository } from '../../models/repository'
import {
Dispatcher,
@ -20,6 +20,7 @@ import {
IssuesAutocompletionProvider,
UserAutocompletionProvider,
} from '../autocompletion'
import { ICommitMessage } from '../../lib/app-state'
import { ClickSource } from '../list'
import { WorkingDirectoryFileChange } from '../../models/status'
import { CSSTransitionGroup } from 'react-transition-group'

View file

@ -192,6 +192,7 @@ export class Dialog extends React.Component<IDialogProps, IDialogState> {
public componentDidMount() {
// This cast to any is necessary since React doesn't know about the
// dialog element yet.
// tslint:disable-next-line:whitespace
;(this.dialogElement as any).showModal()
this.setState({ isAppearing: true })

View file

@ -1,14 +1,14 @@
import * as React from 'react'
import * as CodeMirror from 'codemirror'
// Required for us to be able to customize the foreground color of selected text
import 'codemirror/addon/selection/mark-selection'
if (__DARWIN__) {
// This has to be required to support the `simple` scrollbar style.
require('codemirror/addon/scroll/simplescrollbars')
}
// Required for us to be able to customize the foreground color of selected text
require('codemirror/addon/selection/mark-selection')
interface ICodeMirrorHostProps {
/**
* An optional class name for the wrapper element around the

View file

@ -510,7 +510,7 @@ export class Diff extends React.Component<IDiffProps, {}> {
//
// The only way to unsubscribe is to pass the exact same function given to the
// 'on' function to the 'off' so we need a reference to ourselves, basically.
let deleteHandler: () => void // eslint-disable-line prefer-const
let deleteHandler: () => void
// Since we manually render a react component we have to take care of unmounting
// it or else we'll leak memory. This disposable will unmount the component.

View file

@ -5,12 +5,13 @@ import { FileList } from './file-list'
import { Repository } from '../../models/repository'
import { FileChange } from '../../models/status'
import { Commit } from '../../models/commit'
import { Dispatcher, IGitHubUser } from '../../lib/dispatcher'
import { Dispatcher } from '../../lib/dispatcher'
import {
IHistoryState as IAppHistoryState,
ImageDiffType,
} from '../../lib/app-state'
import { ThrottledScheduler } from '../lib/throttled-scheduler'
import { IGitHubUser } from '../../lib/dispatcher'
import { Resizable } from '../resizable'
// At some point we'll make index.tsx only be exports

View file

@ -81,7 +81,7 @@ export class Checkbox extends React.Component<ICheckboxProps, ICheckboxState> {
const label = this.props.label
const inputId = this.state.inputId
return label ? <label htmlFor={inputId}>{label}</label> : null
return !!label ? <label htmlFor={inputId}>{label}</label> : null
}
public render() {

View file

@ -47,7 +47,7 @@ export class Select extends React.Component<ISelectProps, ISelectState> {
const label = this.props.label
const inputId = this.state.inputId
return label ? <label htmlFor={inputId}>{label}</label> : null
return !!label ? <label htmlFor={inputId}>{label}</label> : null
}
public render() {

View file

@ -177,7 +177,7 @@ class UpdateStore {
public quitAndInstallUpdate() {
// This is synchronous so that we can ensure the app will let itself be quit
// before we call the function to quit.
// eslint-disable-next-line no-sync
// tslint:disable-next-line:no-sync-functions
sendWillQuitSync()
autoUpdater.quitAndInstall()
}

View file

@ -478,12 +478,14 @@ export class List extends React.Component<IListProps, IListState> {
className={className}
tabIndex={tabIndex}
ref={ref}
/* eslint-disable react/jsx-no-bind */
// tslint:disable-next-line jsx-no-lambda
onMouseOver={e => this.onRowMouseOver(rowIndex, e)}
// tslint:disable-next-line jsx-no-lambda
onMouseDown={e => this.handleMouseDown(rowIndex, e)}
// tslint:disable-next-line jsx-no-lambda
onClick={e => this.onRowClick(rowIndex, e)}
// tslint:disable-next-line jsx-no-lambda
onKeyDown={e => this.handleRowKeyDown(rowIndex, e)}
/* eslint-enable react/jsx-no-bind */
style={style}
>
{element}

View file

@ -39,7 +39,7 @@ export function showCertificateTrustDialog(
* that would tell the app to quit.
*/
export function sendWillQuitSync() {
// eslint-disable-next-line no-sync
// tslint:disable-next-line:no-sync-functions
ipcRenderer.sendSync('will-quit')
}

View file

@ -1,5 +1,38 @@
import * as React from 'react'
export interface IResizableProps extends React.Props<Resizable> {
readonly width: number
/** The maximum width the panel can be resized to.
*
* @default 350
*/
readonly maximumWidth?: number
/**
* The minimum width the panel can be resized to.
*
* @default 150
*/
readonly minimumWidth?: number
/** The optional ID for the root element. */
readonly id?: string
/**
* Handler called when the width of the component has changed
* through an explicit resize event (dragging the handle).
*/
readonly onResize?: (newWidth: number) => void
/**
* Handler called when the resizable component has been
* reset (ie restored to its original width by double clicking
* on the resize handle).
*/
readonly onReset?: () => void
}
/**
* Component abstracting a resizable panel.
*
@ -106,36 +139,3 @@ export class Resizable extends React.Component<IResizableProps, {}> {
)
}
}
export interface IResizableProps extends React.Props<Resizable> {
readonly width: number
/** The maximum width the panel can be resized to.
*
* @default 350
*/
readonly maximumWidth?: number
/**
* The minimum width the panel can be resized to.
*
* @default 150
*/
readonly minimumWidth?: number
/** The optional ID for the root element. */
readonly id?: string
/**
* Handler called when the width of the component has changed
* through an explicit resize event (dragging the handle).
*/
readonly onResize?: (newWidth: number) => void
/**
* Handler called when the resizable component has been
* reset (ie restored to its original width by double clicking
* on the resize handle).
*/
readonly onReset?: () => void
}

View file

@ -1,10 +1,12 @@
import * as React from 'react'
import { ToolbarButton, ToolbarButtonStyle } from './button'
import { IAheadBehind, Progress } from '../../lib/app-state'
import { ToolbarButton } from './button'
import { ToolbarButtonStyle } from './button'
import { IAheadBehind } from '../../lib/app-state'
import { Dispatcher } from '../../lib/dispatcher'
import { Octicon, OcticonSymbol } from '../octicons'
import { Repository } from '../../models/repository'
import { RelativeTime } from '../relative-time'
import { Progress } from '../../lib/app-state'
interface IPushPullButtonProps {
/**

View file

@ -54,11 +54,9 @@ export class TitleBar extends React.Component<ITitleBarProps, ITitleBarState> {
switch (actionOnDoubleClick) {
case 'Maximize':
if (mainWindow.isMaximized()) {
mainWindow.unmaximize()
} else {
mainWindow.maximize()
}
mainWindow.isMaximized()
? mainWindow.unmaximize()
: mainWindow.maximize()
break
case 'Minimize':
mainWindow.minimize()

View file

@ -1,3 +0,0 @@
rules:
# throws with Chai
no-unused-expressions: off

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import * as Path from 'path'
import * as FSE from 'fs-extra'

View file

@ -16,9 +16,9 @@ g['__RELEASE_CHANNEL__'] = 'development'
g['__UPDATES_URL__'] = ''
g['__SHA__'] = 'test'
g['log'] = {
g['log'] = <IDesktopLogger>{
error: () => {},
warn: () => {},
info: () => {},
debug: () => {},
} as IDesktopLogger
}

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import { IAppShell } from '../src/lib/dispatcher/app-shell'

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import * as chai from 'chai'
const expect = chai.expect

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import * as chai from 'chai'
const expect = chai.expect

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import * as path from 'path'
import { expect } from 'chai'

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import { expect } from 'chai'

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import { expect } from 'chai'
import { GitError } from 'dugite'

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import * as chai from 'chai'
const expect = chai.expect

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import { expect } from 'chai'

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import { expect } from 'chai'

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import { expect } from 'chai'

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import * as path from 'path'
import * as Fs from 'fs'

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import * as path from 'path'
import { expect } from 'chai'

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import * as chai from 'chai'
const expect = chai.expect

View file

@ -67,57 +67,59 @@ describe('GitProgressParser', () => {
expect(result.kind).to.equal('context')
})
})
describe('progress parser', () => {
it('parses progress with no total', () => {
const result = parse('remote: Counting objects: 167587')
expect(result).to.deep.equal({
expect(result).to.deep.equal(<IGitProgressInfo>{
title: 'remote: Counting objects',
text: 'remote: Counting objects: 167587',
value: 167587,
done: false,
percent: undefined,
total: undefined,
} as IGitProgressInfo)
})
})
it('parses final progress with no total', () => {
const result = parse('remote: Counting objects: 167587, done.')
expect(result).to.deep.equal({
expect(result).to.deep.equal(<IGitProgressInfo>{
title: 'remote: Counting objects',
text: 'remote: Counting objects: 167587, done.',
value: 167587,
done: true,
percent: undefined,
total: undefined,
} as IGitProgressInfo)
})
})
it('parses progress with total', () => {
const result = parse('remote: Compressing objects: 72% (16/22)')
expect(result).to.deep.equal({
expect(result).to.deep.equal(<IGitProgressInfo>{
title: 'remote: Compressing objects',
text: 'remote: Compressing objects: 72% (16/22)',
value: 16,
done: false,
percent: 72,
total: 22,
} as IGitProgressInfo)
})
})
it('parses final with total', () => {
const result = parse('remote: Compressing objects: 100% (22/22), done.')
expect(result).to.deep.equal({
expect(result).to.deep.equal(<IGitProgressInfo>{
title: 'remote: Compressing objects',
text: 'remote: Compressing objects: 100% (22/22), done.',
value: 22,
done: true,
percent: 100,
total: 22,
} as IGitProgressInfo)
})
})
it('parses with total and throughput', () => {
@ -125,14 +127,14 @@ describe('GitProgressParser', () => {
'Receiving objects: 99% (166741/167587), 267.24 MiB | 2.40 MiB/s'
)
expect(result).to.deep.equal({
expect(result).to.deep.equal(<IGitProgressInfo>{
title: 'Receiving objects',
text: 'Receiving objects: 99% (166741/167587), 267.24 MiB | 2.40 MiB/s',
value: 166741,
done: false,
percent: 99,
total: 167587,
} as IGitProgressInfo)
})
})
it('parses final with total and throughput', () => {
@ -140,7 +142,7 @@ describe('GitProgressParser', () => {
'Receiving objects: 100% (167587/167587), 279.67 MiB | 2.43 MiB/s, done.'
)
expect(result).to.deep.equal({
expect(result).to.deep.equal(<IGitProgressInfo>{
title: 'Receiving objects',
text:
'Receiving objects: 100% (167587/167587), 279.67 MiB | 2.43 MiB/s, done.',
@ -148,7 +150,7 @@ describe('GitProgressParser', () => {
done: true,
percent: 100,
total: 167587,
} as IGitProgressInfo)
})
})
it("does not parse things that aren't progress", () => {

View file

@ -1,4 +1,4 @@
/* eslint-disable no-sync */
/* tslint:disable:no-sync-functions */
import * as chai from 'chai'
const expect = chai.expect

View file

@ -1,10 +0,0 @@
declare module 'textarea-caret' {
interface ICaret {
top: number
left: number
height: number
}
function getCaretCoordinates(element: HTMLElement, position: number): ICaret
export = getCaretCoordinates
}

View file

@ -1,4 +0,0 @@
declare module 'untildify' {
function untildify(path: string): string
export = untildify
}

View file

@ -7,7 +7,6 @@ environment:
nodejs_version: "7"
cache:
- .eslintcache
- node_modules
- '%USERPROFILE%\.electron'
@ -31,6 +30,7 @@ install:
- npm ls --dev
build_script:
- npm run check-prettiness
- npm run lint
- npm run build:prod

View file

@ -11,7 +11,6 @@ machine:
dependencies:
cache_directories:
- ".eslintcache"
- "node_modules"
- "~/.electron"
@ -31,6 +30,7 @@ dependencies:
compile:
override:
- npm run check-prettiness
- npm run lint
- npm run build:prod

View file

@ -1,40 +0,0 @@
// strings from https://github.com/Microsoft/tslint-microsoft-contrib/blob/b720cd9/src/insecureRandomRule.ts
const MATH_FAIL_STRING =
'Math.random produces insecure random numbers. ' +
'Use crypto.randomBytes() or window.crypto.getRandomValues() instead'
const NODE_FAIL_STRING =
'crypto.pseudoRandomBytes produces insecure random numbers. ' +
'Use crypto.randomBytes() instead'
module.exports = {
meta: {
docs: {
description: 'Do not use insecure sources for random bytes',
category: 'Best Practices',
},
},
create(context) {
return {
CallExpression(node) {
const { callee } = node
const isMemberExpression = callee.type === 'MemberExpression'
if (
isMemberExpression &&
callee.object.name === 'Math' &&
callee.property.name === 'random'
) {
context.report(node, MATH_FAIL_STRING)
}
if (
(isMemberExpression &&
callee.property.name === 'pseudoRandomBytes') ||
callee.name === 'pseudoRandomBytes'
) {
context.report(node, NODE_FAIL_STRING)
}
},
}
},
}

View file

@ -21,15 +21,14 @@
"package": "node script/package",
"clean:tslint": "rimraf tslint-rules/*.js",
"compile:tslint": "tsc -p tslint-rules",
"lint": "npm run compile:tslint && npm run tslint && npm run eslint-check && npm run eslint",
"tslint": "tslint ./tslint-rules/*.ts ./app/{src,typings,test}/**/*.{ts,tsx}",
"eslint": "ts-node script/eslint.ts",
"eslint:fix": "ts-node script/eslint.ts --fix",
"eslint-check": "eslint --print-config .eslintrc.* | eslint-config-prettier-check",
"lint": "npm run compile:tslint && tslint \"./app/{src,test}/**/*.{ts,tsx}\"",
"check-prettiness": "node script/is-it-pretty",
"publish": "node script/publish",
"clean-slate": "rimraf out node_modules app/node_modules && npm install",
"rebuild-hard:dev": "npm run clean-slate && npm run build:dev",
"rebuild-hard:prod": "npm run clean-slate && npm run build:prod"
"rebuild-hard:prod": "npm run clean-slate && npm run build:prod",
"prettier:base": "prettier --single-quote --trailing-comma es5 --no-semi --write",
"prettify": "npm run prettier:base \"{app/{src,test}/**/*.{ts,tsx,js},app/webpack.*.js,script/!(*.ps1|*.bat|setup-macos-keychain)}\""
},
"author": {
"name": "GitHub, Inc.",
@ -57,12 +56,6 @@
"electron-mocha": "^4.0.0",
"electron-packager": "^8.7.2",
"electron-winstaller": "2.5.2",
"eslint": "^4.3.0",
"eslint-config-prettier": "^2.3.0",
"eslint-plugin-babel": "^4.1.2",
"eslint-plugin-prettier": "^2.1.2",
"eslint-plugin-react": "^7.1.0",
"eslint-plugin-typescript": "^0.4.0",
"express": "^4.15.0",
"extract-text-webpack-plugin": "^3.0.0",
"fs-extra": "^2.1.2",
@ -85,7 +78,6 @@
"tslint": "^4.5.1",
"tslint-microsoft-contrib": "^4.0.1",
"typescript": "2.5.2",
"typescript-eslint-parser": "^8.0.0",
"vrsource-tslint-rules": "^0.12.0",
"webpack": "^3.5.5",
"webpack-dev-middleware": "^1.12.0",
@ -114,7 +106,6 @@
"@types/ua-parser-js": "^0.7.30",
"@types/uuid": "^3.4.0",
"@types/winston": "^2.2.0",
"eslint_d": "^5.1.0",
"prettier": "~1.7.0",
"tslint-config-prettier": "^1.1.0",
"tslint-react": "~3.0.0"

View file

@ -1,4 +0,0 @@
rules:
unicorn/no-process-exit: off
import/no-commonjs: off
no-sync: off

View file

@ -8,7 +8,6 @@ import * as packager from 'electron-packager'
const legalEagle: LegalEagle = require('legal-eagle')
const distInfo = require('./dist-info')
const getReleaseChannel: () => string = distInfo.getReleaseChannel
const getVersion: () => string = distInfo.getVersion
const getExecutableName: () => string = distInfo.getExecutableName
@ -48,6 +47,7 @@ updateLicenseDump(err => {
if (isPublishableBuild) {
process.exit(1)
return
}
}
@ -172,14 +172,12 @@ function copyStaticResources() {
}
function copyDependencies() {
// eslint-disable-next-line import/no-dynamic-require
const originalPackage: Package = require(path.join(
projectRoot,
'app',
'package.json'
))
// eslint-disable-next-line import/no-dynamic-require
const commonConfig = require(path.resolve(__dirname, '../app/webpack.common'))
const externals = commonConfig.externals
const oldDependencies = originalPackage.dependencies

View file

@ -5,7 +5,6 @@ const os = require('os')
const fs = require('fs')
const projectRoot = path.join(__dirname, '..')
// eslint-disable-next-line import/no-dynamic-require
const appPackage = require(path.join(projectRoot, 'app', 'package.json'))
function getDistPath() {

View file

@ -1,33 +0,0 @@
#!/usr/bin/env ts-node
interface IClient {
// theres other stuff; Im not including it
// because its not needed here.
// If you want to add to it, heres the source:
// https://github.com/mantoni/eslint_d.js/blob/v5.1.0/lib/client.js
lint(args: string[], text?: string): void
}
const client: IClient = require('eslint_d/lib/client')
const ESLINT_ARGS = [
'--cache',
'--rulesdir=./eslint-rules',
'./{script,eslint-rules}/**/*.{j,t}s?(x)',
'./tslint-rules/**/*.ts',
'./app/{src,typings,test}/**/*.{j,t}s?(x)',
...process.argv.slice(2),
]
client.lint(ESLINT_ARGS)
type ProcessOnExit = (cb: (code: number) => void) => void
const onExit = process.on.bind(process, 'exit') as ProcessOnExit
onExit(code => {
if (code && ESLINT_ARGS.indexOf('--fix') === -1) {
console.error(
'\x1b[1m\x1b[32m→ To fix some of these errors, run \x1b[4mnpm run eslint:fix\x1b[24\x1b[39m\x1b[22m\n'
)
}
})

47
script/is-it-pretty.js Normal file
View file

@ -0,0 +1,47 @@
'use strict'
const prettier = require('prettier')
const glob = require('glob')
const fs = require('fs')
const globPattern =
'{app/{src,test}/**/*.{ts,tsx,js},app/webpack.*.js,script/!(*.ps1|*.bat|setup-macos-keychain)}'
const prettierOptions = {
parser: 'typescript',
singleQuote: true,
trailingComma: 'es5',
semi: false,
printWidth: 80,
}
glob(globPattern, (err, matches) => {
if (err) {
console.error(err)
process.exit(1)
}
const uglyFiles = []
const matchCount = matches.length
for (const match of matches) {
const fileContents = fs.readFileSync(match, 'utf8')
const isPretty = prettier.check(fileContents, prettierOptions)
if (!isPretty) {
uglyFiles.push(match)
}
}
if (uglyFiles.length === 0) {
console.log('This is some pretty code')
} else {
console.log(
`${uglyFiles.length} out of ${matchCount} code files are ugly. Please run 'npm run prettify' to make the following files pretty:`
)
for (const file of uglyFiles) {
console.log(`\t${file}`)
}
process.exit(1)
}
})

View file

@ -2,9 +2,7 @@
import * as fs from 'fs'
import * as path from 'path'
const distInfo = require('./dist-info')
const getUserDataPath: () => string = distInfo.getUserDataPath
export function getLogFiles(): ReadonlyArray<string> {

View file

@ -3,7 +3,6 @@
import * as fs from 'fs'
import * as cp from 'child_process'
import { getLogFiles } from './review-logs'
const distInfo = require('./dist-info')
const getDistPath: () => string = distInfo.getDistPath

View file

@ -26,7 +26,18 @@
import * as ts from 'typescript'
import * as Lint from 'tslint'
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
return this.applyWithWalker(new ButtonGroupOrderWalker(sourceFile, this.getOptions()))
} else {
return []
}
}
}
class ButtonGroupOrderWalker extends Lint.RuleWalker {
/**
* Visit the node and ensure any button children are in the correct order.
*/
@ -82,34 +93,26 @@ class ButtonGroupOrderWalker extends Lint.RuleWalker {
}
const buttonsWithTypeAttr = buttons.map(b => {
const typeAttr = b.attributes.properties.find(
a =>
a.kind === ts.SyntaxKind.JsxAttribute && a.name.getText() === 'type'
const typeAttr = b.attributes.properties.find(a =>
a.kind === ts.SyntaxKind.JsxAttribute && a.name.getText() === 'type'
) as ts.JsxAttribute | undefined
let value = undefined
if (
typeAttr &&
typeAttr.initializer &&
typeAttr.initializer.kind === ts.SyntaxKind.StringLiteral
) {
if (typeAttr && typeAttr.initializer && typeAttr.initializer.kind === ts.SyntaxKind.StringLiteral) {
value = typeAttr.initializer.text
}
return [b, value]
return [ b, value ]
})
const primaryButtonIx = buttonsWithTypeAttr.findIndex(
x => x[1] === 'submit'
)
const primaryButtonIx = buttonsWithTypeAttr.findIndex(x => x[1] === 'submit')
if (primaryButtonIx !== -1 && primaryButtonIx !== 0) {
const start = node.getStart()
const width = node.getWidth()
const error = `Wrong button order in ButtonGroup.`
const explanation =
'ButtonGroups should have the primary button as its first child'
const explanation = 'ButtonGroups should have the primary button as its first child'
const message = `${error} ${explanation}`
@ -117,15 +120,3 @@ class ButtonGroupOrderWalker extends Lint.RuleWalker {
}
}
}
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
return this.applyWithWalker(
new ButtonGroupOrderWalker(sourceFile, this.getOptions())
)
} else {
return []
}
}
}

View file

@ -0,0 +1,39 @@
/**
* no-sync-functions
*
* Don't allow calling any functions that end in 'Sync'.
*/
import * as ts from 'typescript'
import * as Lint from 'tslint'
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new NoSyncFunctionsWalker(sourceFile, this.getOptions()))
}
}
// The walker takes care of all the work.
class NoSyncFunctionsWalker extends Lint.RuleWalker {
protected visitCallExpression(node: ts.CallExpression): void {
const functionName = this.getFunctionName(node)
if (functionName && functionName.endsWith('Sync')) {
const start = node.getStart()
const width = node.getWidth()
const error = `Synchronous functions shouldn't be used. Find an asynchronous alternative.`
this.addFailure(this.createFailure(start, width, error))
} else {
super.visitCallExpression(node)
}
}
// Taken from https://github.com/Microsoft/tslint-microsoft-contrib/blob/051abda5bafffd8068c42bdc9da7afc488cfab76/src/utils/AstUtils.ts#L16-L23.
private getFunctionName(node: ts.CallExpression): string {
const expression: ts.Expression = node.expression
let functionName: string = (expression as any).text
if (functionName === undefined && (expression as any).name) {
functionName = (expression as any).name.text
}
return functionName
}
}

View file

@ -22,8 +22,19 @@
import * as ts from 'typescript'
import * as Lint from 'tslint'
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
return this.applyWithWalker(new ReactNoUnboundDispatcherPropsWalker(sourceFile, this.getOptions()))
} else {
return []
}
}
}
// The walker takes care of all the work.
class ReactNoUnboundDispatcherPropsWalker extends Lint.RuleWalker {
protected visitJsxElement(node: ts.JsxElement): void {
this.visitJsxOpeningLikeElement(node.openingElement)
super.visitJsxElement(node)
@ -42,49 +53,40 @@ class ReactNoUnboundDispatcherPropsWalker extends Lint.RuleWalker {
private visitJsxOpeningLikeElement(node: ts.JsxOpeningLikeElement): void {
// create violations if the listener is a reference to a class method that was not bound to 'this' in the constructor
node.attributes.properties.forEach(attributeLikeElement => {
if (attributeLikeElement.kind !== ts.SyntaxKind.JsxAttribute) {
return
}
if (attributeLikeElement.kind !== ts.SyntaxKind.JsxAttribute) { return }
// This is some weak sauce, why doesn't JsxAttribute specify a literal kind
// so that it can be narrowed automatically?
const attribute: ts.JsxAttribute = attributeLikeElement
const attribute: ts.JsxAttribute = <ts.JsxAttribute>attributeLikeElement
// This means that the attribute is an inferred boolean true value. See:
//
// https://github.com/Microsoft/TypeScript/blob/52ec508/src/compiler/types.ts#L1483
// https://facebook.github.io/react/docs/jsx-in-depth.html#props-default-to-true
if (!attribute.initializer) {
return
}
if (!attribute.initializer) { return }
// This likely means that the attribute is a string literal
// ie <foo className='foo' />
if (attribute.initializer.kind !== ts.SyntaxKind.JsxExpression) {
return
}
if (attribute.initializer.kind !== ts.SyntaxKind.JsxExpression) { return }
const jsxExpression: ts.JsxExpression = attribute.initializer
// We only care about property accesses, direct method invocation on
// dispatcher is still okay. This excludes things like
// <A foo={1} />, <B foo={this.method()} />, <C foo={{ foo: 'bar' }} etc.
if (
!jsxExpression.expression ||
jsxExpression.expression.kind !== ts.SyntaxKind.PropertyAccessExpression
) {
if (!jsxExpression.expression || jsxExpression.expression.kind !== ts.SyntaxKind.PropertyAccessExpression) {
return
}
const propAccess: ts.PropertyAccessExpression = jsxExpression.expression as ts.PropertyAccessExpression
const propAccess: ts.PropertyAccessExpression = <ts.PropertyAccessExpression>jsxExpression.expression
const propAccessText = propAccess.getText()
if (/^this\.props\.dispatcher\./.test(propAccessText)) {
const start = propAccess.getStart()
const width = propAccess.getWidth()
const error = `Use of unbound dispatcher method: ${propAccessText}.`
const explanation =
'Consider extracting the method call to a bound instance method.'
const explanation = 'Consider extracting the method call to a bound instance method.'
const message = `${error} ${explanation}`
@ -93,15 +95,3 @@ class ReactNoUnboundDispatcherPropsWalker extends Lint.RuleWalker {
})
}
}
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
return this.applyWithWalker(
new ReactNoUnboundDispatcherPropsWalker(sourceFile, this.getOptions())
)
} else {
return []
}
}
}

View file

@ -15,17 +15,25 @@ interface IExpectedParameter {
readonly type: string
}
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
return this.applyWithWalker(new ReactProperLifecycleMethodsWalker(sourceFile, this.getOptions()))
} else {
return []
}
}
}
class ReactProperLifecycleMethodsWalker extends Lint.RuleWalker {
private propsTypeName: string
private stateTypeName: string
protected visitClassDeclaration(node: ts.ClassDeclaration): void {
if (node.heritageClauses && node.heritageClauses.length) {
for (const heritageClause of node.heritageClauses) {
if (
heritageClause.token === ts.SyntaxKind.ExtendsKeyword &&
heritageClause.types
) {
if (heritageClause.token === ts.SyntaxKind.ExtendsKeyword && heritageClause.types) {
for (const type of heritageClause.types) {
const inheritedName = type.expression.getText()
@ -46,10 +54,7 @@ class ReactProperLifecycleMethodsWalker extends Lint.RuleWalker {
protected visitMethodDeclaration(node: ts.MethodDeclaration): void {
const methodName = node.name.getText()
if (
methodName.startsWith('component') ||
methodName.startsWith('shouldComponent')
) {
if (methodName.startsWith('component') || methodName.startsWith('shouldComponent')) {
switch (methodName) {
case 'componentWillMount':
case 'componentDidMount':
@ -81,10 +86,7 @@ class ReactProperLifecycleMethodsWalker extends Lint.RuleWalker {
}
}
private verifyParameter(
node: ts.ParameterDeclaration,
expectedParameter: IExpectedParameter
): boolean {
private verifyParameter(node: ts.ParameterDeclaration, expectedParameter: IExpectedParameter): boolean {
const parameterName = node.name.getText()
const parameterStart = node.getStart()
@ -92,9 +94,7 @@ class ReactProperLifecycleMethodsWalker extends Lint.RuleWalker {
if (parameterName !== expectedParameter.name) {
const message = `parameter should be named ${expectedParameter.name}.`
this.addFailure(
this.createFailure(parameterStart, parameterWidth, message)
)
this.addFailure(this.createFailure(parameterStart, parameterWidth, message))
return false
}
@ -102,19 +102,14 @@ class ReactProperLifecycleMethodsWalker extends Lint.RuleWalker {
if (parameterTypeName !== expectedParameter.type) {
const message = `parameter should be of type ${expectedParameter.type}.`
this.addFailure(
this.createFailure(parameterStart, parameterWidth, message)
)
this.addFailure(this.createFailure(parameterStart, parameterWidth, message))
return false
}
return true
}
private verifyParameters(
node: ts.MethodDeclaration,
expectedParameters: ReadonlyArray<IExpectedParameter>
): boolean {
private verifyParameters(node: ts.MethodDeclaration, expectedParameters: ReadonlyArray<IExpectedParameter>): boolean {
// It's okay to omit parameters
for (let i = 0; i < node.parameters.length; i++) {
const parameter = node.parameters[i]
@ -125,9 +120,7 @@ class ReactProperLifecycleMethodsWalker extends Lint.RuleWalker {
const parameterWidth = parameter.getWidth()
const message = `unknown parameter ${parameterName}`
this.addFailure(
this.createFailure(parameterStart, parameterWidth, message)
)
this.addFailure(this.createFailure(parameterStart, parameterWidth, message))
return false
}
@ -139,9 +132,7 @@ class ReactProperLifecycleMethodsWalker extends Lint.RuleWalker {
// Remove trailing unused void parameters
for (let i = node.parameters.length - 1; i >= 0; i--) {
const parameter = node.parameters[i]
const parameterTypeName = parameter.type
? parameter.type.getText()
: undefined
const parameterTypeName = parameter.type ? parameter.type.getText() : undefined
if (parameterTypeName === 'void') {
const parameterName = parameter.getText()
@ -149,9 +140,7 @@ class ReactProperLifecycleMethodsWalker extends Lint.RuleWalker {
const parameterWidth = parameter.getWidth()
const message = `remove unused void parameter ${parameterName}.`
this.addFailure(
this.createFailure(parameterStart, parameterWidth, message)
)
this.addFailure(this.createFailure(parameterStart, parameterWidth, message))
return false
} else {
break
@ -162,9 +151,7 @@ class ReactProperLifecycleMethodsWalker extends Lint.RuleWalker {
}
private verifyComponentWillReceiveProps(node: ts.MethodDeclaration) {
this.verifyParameters(node, [
{ name: 'nextProps', type: this.propsTypeName },
])
this.verifyParameters(node, [ { name: 'nextProps', type: this.propsTypeName } ])
}
private verifyComponentWillUpdate(node: ts.MethodDeclaration) {
@ -192,21 +179,9 @@ class ReactProperLifecycleMethodsWalker extends Lint.RuleWalker {
const start = node.name.getStart()
const width = node.name.getWidth()
const message =
'Method names starting with component or shouldComponent ' +
const message = 'Method names starting with component or shouldComponent ' +
'are prohibited since they can be confused with React lifecycle methods.'
this.addFailure(this.createFailure(start, width, message))
}
}
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
return this.applyWithWalker(
new ReactProperLifecycleMethodsWalker(sourceFile, this.getOptions())
)
} else {
return []
}
}
}

View file

@ -13,25 +13,33 @@
import * as ts from 'typescript'
import * as Lint from 'tslint'
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
return this.applyWithWalker(new ReactReadonlyPropsAndStateWalker(sourceFile, this.getOptions()))
} else {
return []
}
}
}
// The walker takes care of all the work.
class ReactReadonlyPropsAndStateWalker extends Lint.RuleWalker {
protected visitInterfaceDeclaration(node: ts.InterfaceDeclaration): void {
if (node.name.text.endsWith('Props')) {
this.ensureReadOnly(node.members)
}
if (node.name.text.endsWith('Props')) {
this.ensureReadOnly(node.members)
}
if (node.name.text.endsWith('State')) {
this.ensureReadOnly(node.members)
}
if (node.name.text.endsWith('State')) {
this.ensureReadOnly(node.members)
}
super.visitInterfaceDeclaration(node)
super.visitInterfaceDeclaration(node)
}
private ensureReadOnly(members: ts.NodeArray<ts.TypeElement>) {
members.forEach(member => {
if (member.kind !== ts.SyntaxKind.PropertySignature) {
return
}
if (member.kind !== ts.SyntaxKind.PropertySignature) { return }
const propertySignature = member as ts.PropertySignature
@ -48,9 +56,7 @@ class ReactReadonlyPropsAndStateWalker extends Lint.RuleWalker {
private isReadOnly(propertySignature: ts.PropertySignature): boolean {
const modifiers = propertySignature.modifiers
if (!modifiers) {
return false
}
if (!modifiers) { return false }
if (modifiers.find(m => m.kind === ts.SyntaxKind.ReadonlyKeyword)) {
return true
@ -59,15 +65,3 @@ class ReactReadonlyPropsAndStateWalker extends Lint.RuleWalker {
return false
}
}
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
return this.applyWithWalker(
new ReactReadonlyPropsAndStateWalker(sourceFile, this.getOptions())
)
} else {
return []
}
}
}

View file

@ -1,9 +1,9 @@
{
"extends": [
"tslint-react",
"tslint-config-prettier"
],
"rulesDirectory": [
"node_modules/tslint-react/rules",
"node_modules/vrsource-tslint-rules/rules",
"node_modules/tslint-microsoft-contrib/",
"tslint-rules/"
@ -11,13 +11,50 @@
"rules": {
"button-group-order": true,
"class-name": true,
"curly": true,
"indent": [
true,
"spaces"
],
"insecure-random": [ true ],
"interface-name": [ true, "always-prefix"],
"jsdoc-format": true,
"literal-spacing": [
false,
{
"array": ["never"],
"object": ["always"]
}
],
"member-access": [
true,
"check-accessor",
"check-constructor"
],
"member-ordering": [
true,
"static-before-instance",
"variables-before-functions"
],
"no-construct": true,
"no-default-export": true,
"no-duplicate-variable": true,
"no-eval": true,
"no-internal-module": true,
"no-invalid-this": true,
"no-stateless-class": true,
"no-sync-functions": true,
"no-trailing-whitespace": [ true, "ignore-comments", "ignore-jsdoc" ],
"no-unused-expression": true,
"no-unused-variable": [true, "react"],
"no-use-before-declare": true,
"no-var-keyword": true,
"one-line": [
true,
"check-open-brace",
"check-whitespace"
],
"prefer-const": true,
"promise-must-complete": true,
"react-no-unbound-dispatcher-props": true,
"react-proper-lifecycle-methods": true,
@ -29,6 +66,21 @@
}
],
"react-this-binding-issue": true,
"semicolon": [
true,
"never"
],
"trailing-comma": [
false,
{
"multiline": "always",
"singleline": "never"
}
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
@ -42,6 +94,14 @@
"variable-name": [
true,
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}