eslint-plugin-prettier

Also, get rid of the Prettier-related scripts in package.json
This commit is contained in:
Jed Fox 2017-08-09 12:59:42 -04:00
parent 22c663b01e
commit 8f48ce41c5
10 changed files with 139 additions and 125 deletions

View file

@ -3,6 +3,8 @@ plugins:
- typescript
- babel
- react
- prettier
extends:
- prettier
- prettier/react
@ -62,6 +64,12 @@ rules:
###########
# SPECIAL #
###########
prettier/prettier:
- error
- singleQuote: true
trailingComma: es5
semi: false
parser: typescript
no-restricted-syntax:
- error
# no-default-export

View file

@ -28,7 +28,6 @@ install:
- npm install
script:
- npm run check-prettiness
- npm run lint
- npm run build:prod
- npm run test:setup

View file

@ -28,7 +28,6 @@ install:
- npm install
build_script:
- npm run check-prettiness
- npm run lint
- npm run build:prod

View file

@ -1,34 +1,40 @@
// 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";
'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";
'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);
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"
(isMemberExpression &&
callee.property.name === 'pseudoRandomBytes') ||
callee.name === 'pseudoRandomBytes'
) {
context.report(node, NODE_FAIL_STRING);
context.report(node, NODE_FAIL_STRING)
}
}
},
}
}
},
}

View file

@ -25,13 +25,10 @@
"tslint": "tslint ./tslint-rules/*.ts ./app/{src,typings,test}/**/*.{ts,tsx}",
"eslint": "eslint --rulesdir=./eslint-rules --ext=.js,.ts,.jsx,.tsx ./eslint-rules ./tslint-rules/*.ts ./app/{src,typings,test}",
"eslint-check": "eslint --print-config .eslintrc.* | eslint-config-prettier-check",
"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",
"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)}\""
"rebuild-hard:prod": "npm run clean-slate && npm run build:prod"
},
"author": {
"name": "GitHub, Inc.",
@ -62,6 +59,7 @@
"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.3.0",
"express": "^4.15.0",

View file

@ -1,47 +0,0 @@
'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

@ -27,7 +27,6 @@ import * as ts from 'typescript'
import * as Lint from 'tslint'
class ButtonGroupOrderWalker extends Lint.RuleWalker {
/**
* Visit the node and ensure any button children are in the correct order.
*/
@ -83,26 +82,34 @@ 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}`
@ -112,11 +119,13 @@ 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 []
}
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

@ -24,7 +24,6 @@ import * as Lint from 'tslint'
// 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)
@ -43,8 +42,9 @@ 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?
@ -54,18 +54,25 @@ class ReactNoUnboundDispatcherPropsWalker extends Lint.RuleWalker {
//
// 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
}
@ -76,7 +83,8 @@ class ReactNoUnboundDispatcherPropsWalker extends Lint.RuleWalker {
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}`
@ -87,11 +95,13 @@ 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 []
}
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

@ -16,14 +16,16 @@ interface IExpectedParameter {
}
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()
@ -44,7 +46,10 @@ 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':
@ -76,7 +81,10 @@ 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()
@ -84,7 +92,9 @@ 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
}
@ -92,14 +102,19 @@ 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]
@ -110,7 +125,9 @@ 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
}
@ -122,7 +139,9 @@ 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()
@ -130,7 +149,9 @@ 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
@ -141,7 +162,9 @@ 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) {
@ -169,18 +192,21 @@ 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 []
}
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

@ -16,20 +16,22 @@ import * as Lint from 'tslint'
// 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
@ -46,7 +48,9 @@ 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
@ -57,11 +61,13 @@ class ReactReadonlyPropsAndStateWalker 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 ReactReadonlyPropsAndStateWalker(sourceFile, this.getOptions()))
} else {
return []
}
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (sourceFile.languageVariant === ts.LanguageVariant.JSX) {
return this.applyWithWalker(
new ReactReadonlyPropsAndStateWalker(sourceFile, this.getOptions())
)
} else {
return []
}
}
}