-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e95d9a3
Showing
20 changed files
with
4,748 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
coverage | ||
dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
coverage | ||
dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// https://prettier.io/docs/en/options.html | ||
module.exports = { | ||
semi: false, | ||
useTabs: false, | ||
tabWidth: 2, | ||
singleQuote: true, | ||
trailingComma: 'es5', | ||
bracketSpacing: true, | ||
jsxBracketSameLine: true, | ||
arrowParens: 'always', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"editor.formatOnSave": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Changelog | ||
|
||
All notable changes to this project will be documented in this file. | ||
|
||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
||
## [Unreleased] | ||
|
||
## 0.0.1 - 2021-04-17 | ||
|
||
### Added | ||
|
||
- added `better-dates/ban-date-mutation` rule to prevent modifying Dates |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2021 Christopher Dignam | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# eslint-plugin-better-dates [![npm version](https://img.shields.io/npm/v/@typescript-eslint/eslint-plugin.svg)](https://www.npmjs.com/package/@typescript-eslint/eslint-plugin) | ||
|
||
> An ESlint plugin to prevent `Date`-related bugs. | ||
## Installation | ||
|
||
Make sure you have TypeScript and [`@typescript-eslint/parser`](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/README.md) installed: | ||
|
||
```console | ||
$ yarn add -D typescript @typescript-eslint/parser | ||
$ npm i --save-dev typescript @typescript-eslint/parser | ||
``` | ||
|
||
Then install the plugin: | ||
|
||
```console | ||
$ yarn add -D eslint-plugin-better-dates | ||
$ npm i --save-dev eslint-plugin-better-dates | ||
``` | ||
|
||
## Usage | ||
|
||
Add `@typescript-eslint/parser` to the `parser` field and `better-dates` to the plugins section of your `.eslintrc` configuration file, then configure the rules you want to use under the rules section. | ||
|
||
```json | ||
{ | ||
"parser": "@typescript-eslint/parser", | ||
"plugins": ["better-dates"], | ||
"rules": { | ||
"better-dates/ban-mutation": "error" | ||
} | ||
} | ||
``` | ||
|
||
**Note: Make sure to use `eslint --ext .js,.ts` since by [default](https://eslint.org/docs/user-guide/command-line-interface#--ext) `eslint` will only search for `.js` files.** | ||
|
||
## Rules | ||
|
||
**Key**: :wrench: = fixable, :thought_balloon: = requires type information | ||
|
||
| Name | Description | :wrench: | :thought_balloon: | | ||
| -------------------------------------------------------- | --------------------------------------------------------- | -------- | ----------------- | | ||
| [`ban-date-mutation`](./docs/rules/ban-date-mutation.md) | Bans calling methods on `Date` that will mutate the date. | | :thought_balloon: | | ||
|
||
> Project layout and configuration modified from [`typescript-eslint/eslint-plugin`](https://github.com/typescript-eslint/typescript-eslint/tree/26d71b57fbff013b9c9434c96e2ba98c6c541259/packages/eslint-plugin). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# Bans mutating dates (`ban-date-mutation`) | ||
|
||
Mutating dates can create confusing bugs. Instead, create new dates using a library like [`date-fns`](https://github.com/date-fns/date-fns). | ||
|
||
## Rule Details | ||
|
||
This rule bans methods that mutate `Date`. | ||
|
||
Banned `Date` methods: | ||
|
||
- `setDate` | ||
- `setFullYear` | ||
- `setHours` | ||
- `setMilliseconds` | ||
- `setMinutes` | ||
- `setMonth` | ||
- `setSeconds` | ||
- `setTime` | ||
- `setUTCDate` | ||
- `setUTCFullYear` | ||
- `setUTCHours` | ||
- `setUTCMilliseconds` | ||
- `setUTCMinutes` | ||
- `setUTCMonth` | ||
- `setUTCSeconds` | ||
- `setYear` | ||
|
||
### Examples | ||
|
||
Examples of **incorrect** code with the default options: | ||
|
||
```ts | ||
const expiration = new Date() | ||
|
||
const noon = expiration.setHours(12) | ||
const seventeenth = expiration.setDate(17) | ||
const twentyTen = expiration.setFullYear(2010, 05, 23) | ||
const thirtySeconds = expiration.setSeconds(30) | ||
``` | ||
|
||
Examples of **correct** code with the default options: | ||
|
||
```ts | ||
const expiration = new Date() | ||
|
||
import setHours from 'date-fns/setHours' | ||
const noon = setHours(setHours, 12) | ||
|
||
import setDate from 'date-fns/setDate' | ||
const seventeenth = setDate(expiration, 17) | ||
|
||
import setYear from 'date-fns/setYear' | ||
import setMonth from 'date-fns/setMonth' | ||
import setDate from 'date-fns/setDate' | ||
const twentyTen = setDate(setMonth(setYear(expiration, 2010), 05), 23) | ||
|
||
import setSeconds from 'date-fns/setSeconds' | ||
const thirtySeconds = setSeconds(expiration, 30) | ||
``` | ||
|
||
## When Not To Use It | ||
|
||
If you don't mind the risk of date mutations causing bugs in your system. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
'use strict' | ||
|
||
// @ts-check | ||
/** @type {import('@jest/types').Config.InitialOptions} */ | ||
module.exports = { | ||
globals: { | ||
'ts-jest': { | ||
isolatedModules: true, | ||
}, | ||
}, | ||
testEnvironment: 'node', | ||
transform: { | ||
'^.+\\.tsx?$': 'ts-jest', | ||
}, | ||
testRegex: './tests/.+\\.test\\.ts$', | ||
collectCoverage: false, | ||
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'], | ||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], | ||
coverageReporters: ['text-summary', 'lcov'], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
{ | ||
"name": "eslint-plugin-better-dates", | ||
"version": "0.0.1", | ||
"description": "ESLint plugin to prevent Date-related bugs.", | ||
"keywords": [ | ||
"eslint", | ||
"eslintplugin", | ||
"eslint-plugin", | ||
"typescript" | ||
], | ||
"engines": { | ||
"node": "^10.12.0 || >=12.0.0" | ||
}, | ||
"files": [ | ||
"dist", | ||
"docs", | ||
"package.json", | ||
"README.md", | ||
"LICENSE" | ||
], | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/chdsbd/eslint-plugin-better-dates.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/chdsbd/eslint-plugin-better-dates/issues" | ||
}, | ||
"license": "MIT", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"scripts": { | ||
"build": "tsc -b tsconfig.json", | ||
"clean": "tsc -b tsconfig.json --clean", | ||
"format": "prettier --write \"./**/*.{ts,js,json,md}\"", | ||
"lint": "eslint . --ext .js,.ts --ignore-path ../../.eslintignore", | ||
"test": "jest --coverage", | ||
"typecheck": "tsc -p tsconfig.json --noEmit" | ||
}, | ||
"dependencies": { | ||
"@typescript-eslint/experimental-utils": "^4.22.0" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^14.14.41", | ||
"@types/prettier": "*", | ||
"@typescript-eslint/parser": "^4.22.0", | ||
"eslint": "^7.24.0", | ||
"jest": "^26.6.3", | ||
"prettier": "*", | ||
"ts-jest": "^26.5.5", | ||
"typescript": "*" | ||
}, | ||
"peerDependencies": { | ||
"@typescript-eslint/parser": "^4.0.0", | ||
"eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" | ||
}, | ||
"peerDependenciesMeta": { | ||
"typescript": { | ||
"optional": true | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import rules from './rules' | ||
|
||
export = { | ||
rules, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import * as ts from 'typescript' | ||
import * as util from '../util' | ||
|
||
export type MessageIds = 'banDateMutation' | ||
|
||
const disallowedDateMethods = new Set([ | ||
'setDate', | ||
'setFullYear', | ||
'setHours', | ||
'setMilliseconds', | ||
'setMinutes', | ||
'setMonth', | ||
'setSeconds', | ||
'setTime', | ||
'setUTCDate', | ||
'setUTCFullYear', | ||
'setUTCHours', | ||
'setUTCMilliseconds', | ||
'setUTCMinutes', | ||
'setUTCMonth', | ||
'setUTCSeconds', | ||
'setYear', | ||
]) | ||
|
||
const methodToDateFns: Record<string, string | null> = { | ||
setDate: 'setDate', | ||
setFullYear: '{setYear,setMonth,setDate}', | ||
setHours: '{setHours,setMinutes,setSeconds,setMilliseconds}', | ||
setMilliseconds: 'setMilliseconds', | ||
setMinutes: 'setMinutes', | ||
setMonth: 'setMonth', | ||
setSeconds: 'setSeconds', | ||
setTime: 'fromUnixTime', | ||
setUTCDate: null, | ||
setUTCFullYear: null, | ||
setUTCHours: null, | ||
setUTCMilliseconds: null, | ||
setUTCMinutes: null, | ||
setUTCMonth: null, | ||
setUTCSeconds: null, | ||
setYear: 'setYear', | ||
} | ||
|
||
function formatSuggestion( | ||
methodName: string, | ||
suggestion: string | null | ||
): string { | ||
if (suggestion === null) { | ||
return '' | ||
} | ||
return ` Replace \`Date.${methodName}\` with \`date-fns/${suggestion}\`` | ||
} | ||
|
||
function isDate(symbol: ts.Symbol, typeChecker: ts.TypeChecker): boolean { | ||
return ( | ||
typeChecker.typeToString( | ||
typeChecker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration) | ||
) === 'DateConstructor' | ||
) | ||
} | ||
|
||
export default util.createRule<[], MessageIds>({ | ||
name: 'ban-date-mutation', | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
description: 'Bans mutating Date objects', | ||
category: 'Best Practices', | ||
recommended: 'error', | ||
}, | ||
messages: { | ||
banDateMutation: "Don't mutate a Date with `{{method}}`.{{suggestion}}", | ||
}, | ||
schema: [], | ||
}, | ||
defaultOptions: [], | ||
create(context) { | ||
return { | ||
MemberExpression(node): void { | ||
const parserServices = util.getParserServices(context) | ||
const typeChecker = parserServices.program.getTypeChecker() | ||
const objectType = typeChecker | ||
.getTypeAtLocation( | ||
parserServices.esTreeNodeToTSNodeMap.get(node.object) | ||
) | ||
.getSymbol() | ||
if (objectType != null && isDate(objectType, typeChecker)) { | ||
const dateMethod = typeChecker | ||
.getTypeAtLocation( | ||
parserServices.esTreeNodeToTSNodeMap.get(node.property) | ||
) | ||
.getSymbol() | ||
?.getEscapedName() | ||
if (dateMethod == null) { | ||
return | ||
} | ||
const dateMethodString = dateMethod.toString() | ||
if (disallowedDateMethods.has(dateMethodString)) { | ||
context.report({ | ||
node, | ||
data: { | ||
method: dateMethod, | ||
suggestion: formatSuggestion( | ||
dateMethodString, | ||
methodToDateFns[dateMethodString] | ||
), | ||
}, | ||
messageId: 'banDateMutation', | ||
}) | ||
} | ||
} | ||
}, | ||
} | ||
}, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import banDateMutation from './ban-date-mutation' | ||
|
||
export default { | ||
'ban-date-mutation': banDateMutation, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { ESLintUtils } from '@typescript-eslint/experimental-utils' | ||
|
||
const { getParserServices } = ESLintUtils | ||
|
||
const version = require('../package.json').version | ||
|
||
export const createRule = ESLintUtils.RuleCreator( | ||
(name) => | ||
`https://github.com/chdsbd/eslint-plugin-better-dates/blob/v${version}/packages/eslint-plugin/docs/rules/${name}.md` | ||
) | ||
|
||
export { getParserServices } |
Oops, something went wrong.