chore: add eslint package standard-with-typescript and apply rules

This commit is contained in:
Sándor Levcsák 2020-11-11 02:23:42 +02:00
parent 668e7dac73
commit adf3aefcc5
36 changed files with 237 additions and 247 deletions

29
.eslintrc Normal file
View File

@ -0,0 +1,29 @@
{
"root": true,
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"project": "./tsconfig.json",
"sourceType": "module",
"extraFileExtensions": [
".vue",
".d.ts"
]
},
"extends": [
"standard",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"standard-with-typescript",
"plugin:vue/vue3-recommended"
],
"rules": {
"no-undef": "off",
"comma-dangle": [
"warn",
"always-multiline"
],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/promise-function-async": "off"
}
}

16
cypress/.eslintrc Normal file
View File

@ -0,0 +1,16 @@
{
"root": true,
"env": {
"cypress/globals": true
},
"extends": [
"standard",
"plugin:cypress/recommended"
],
"rules": {
"comma-dangle": [
"warn",
"always-multiline"
]
}
}

View File

@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/// <reference types="cypress" /> /// <reference types="cypress" />
// *********************************************************** // ***********************************************************
// This example plugins/index.js can be used to load plugins // This example plugins/index.js can be used to load plugins

View File

@ -6,7 +6,7 @@
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"check-ts-errors": "yarn tsc && yarn vti diagnostics", "check-ts-errors": "yarn tsc && yarn vti diagnostics",
"lint": "eslint \"{src,cypress/integration}/**/*.{js,ts,vue}\"", "lint": "eslint \"{src,cypress}/**/*.{js,ts,vue}\"",
"test:unit": "jest --coverage", "test:unit": "jest --coverage",
"test:e2e": "cypress run", "test:e2e": "cypress run",
"test": "yarn check-ts-errors && yarn lint && yarn test:unit && yarn test:e2e" "test": "yarn check-ts-errors && yarn lint && yarn test:unit && yarn test:e2e"
@ -26,19 +26,20 @@
"@types/dompurify": "^2.0.4", "@types/dompurify": "^2.0.4",
"@types/jest": "^24.9.1", "@types/jest": "^24.9.1",
"@types/marked": "^1.1.0", "@types/marked": "^1.1.0",
"@typescript-eslint/eslint-plugin": "^4.6.1", "@typescript-eslint/eslint-plugin": "^4.7.0",
"@typescript-eslint/parser": "^4.6.1", "@typescript-eslint/parser": "^4.7.0",
"@vue/compiler-sfc": "^3.0.2", "@vue/compiler-sfc": "^3.0.2",
"@vue/test-utils": "^2.0.0-beta.8", "@vue/test-utils": "^2.0.0-beta.8",
"babel-jest": "^24.9.0", "babel-jest": "^24.9.0",
"cypress": "^5.3.0", "cypress": "^5.3.0",
"eslint": "^7.12.1", "eslint": "^7.13.0",
"eslint-config-standard": "^16.0.1", "eslint-config-standard": "^16.0.1",
"eslint-config-standard-with-typescript": "^19.0.1",
"eslint-plugin-cypress": "^2.11.2", "eslint-plugin-cypress": "^2.11.2",
"eslint-plugin-import": "^2.22.1", "eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.2", "eslint-plugin-standard": "^4.1.0",
"eslint-plugin-vue": "^7.1.0", "eslint-plugin-vue": "^7.1.0",
"husky": "^4.3.0", "husky": "^4.3.0",
"jest": "^24.9.0", "jest": "^24.9.0",
@ -60,30 +61,6 @@
"src/**/*.{ts,vue}": "eslint --fix", "src/**/*.{ts,vue}": "eslint --fix",
"cypress/**/*.js": "eslint --fix" "cypress/**/*.js": "eslint --fix"
}, },
"eslintConfig": {
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"sourceType": "module"
},
"extends": [
"standard",
"plugin:cypress/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:vue/vue3-recommended"
],
"rules": {
"no-undef": "off",
"comma-dangle": [
"warn",
"always-multiline"
]
}
},
"jest": { "jest": {
"preset": "ts-jest", "preset": "ts-jest",
"globals": { "globals": {

View File

@ -59,9 +59,9 @@ export function useArticles () {
const { active: articlesDownloading, run: runWrappedFetchArticles } = createAsyncProcess(fetchArticles) const { active: articlesDownloading, run: runWrappedFetchArticles } = createAsyncProcess(fetchArticles)
watch(metaChanged, () => { watch(metaChanged, async () => {
if (page.value !== 1) changePage(1) if (page.value !== 1) changePage(1)
else runWrappedFetchArticles() else await runWrappedFetchArticles()
}) })
watch(page, runWrappedFetchArticles) watch(page, runWrappedFetchArticles)

View File

@ -19,7 +19,7 @@ export function useFollow ({ username, following, onUpdate }: UseFollowProps) {
async function toggleFollow () { async function toggleFollow () {
let response: Either<AuthorizationError, Profile> let response: Either<AuthorizationError, Profile>
if (following.value === true) { if (following.value) {
response = await deleteFollowProfile(username.value) response = await deleteFollowProfile(username.value)
} else { } else {
response = await postFollowProfile(username.value) response = await postFollowProfile(username.value)

View File

@ -16,7 +16,7 @@ export function useProfile ({ username }: UseProfileProps) {
updateProfile(profileData) updateProfile(profileData)
} }
async function updateProfile (profileData: Profile | null) { function updateProfile (profileData: Profile | null) {
profile.value = profileData profile.value = profileData
} }

View File

@ -1,3 +1,4 @@
export const CONFIG = { export const CONFIG = {
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
API_HOST: import.meta.env.VITE_API_HOST || '', API_HOST: import.meta.env.VITE_API_HOST || '',
} }

View File

@ -3,5 +3,5 @@ import storage from '../utils/storage'
export default function (): void { export default function (): void {
const token = storage.get<User>('user')?.token const token = storage.get<User>('user')?.token
if (token) request.setAuthorizationHeader(token) if (token !== undefined) request.setAuthorizationHeader(token)
} }

View File

@ -2,16 +2,16 @@ import { createRouter, createWebHashHistory, RouteParams } from 'vue-router'
import Home from './pages/Home.vue' import Home from './pages/Home.vue'
export type AppRouteNames = 'global-feed' export type AppRouteNames = 'global-feed'
| 'my-feed' | 'my-feed'
| 'tag' | 'tag'
| 'article' | 'article'
| 'create-article' | 'create-article'
| 'edit-article' | 'edit-article'
| 'login' | 'login'
| 'register' | 'register'
| 'profile' | 'profile'
| 'profile-favorites' | 'profile-favorites'
| 'settings' | 'settings'
export const router = createRouter({ export const router = createRouter({
history: createWebHashHistory(), history: createWebHashHistory(),
@ -75,6 +75,6 @@ export const router = createRouter({
}) })
export function routerPush (name: AppRouteNames, params?: RouteParams): ReturnType<typeof router.push> { export function routerPush (name: AppRouteNames, params?: RouteParams): ReturnType<typeof router.push> {
if (params) return router.push({ name, params }) if (params !== undefined) return router.push({ name, params })
else return router.push({ name }) else return router.push({ name })
} }

View File

@ -1,5 +1,5 @@
import { request } from '../index' import { request } from '../index'
export async function deleteArticle (slug: string): Promise<void> { export function deleteArticle (slug: string): Promise<void> {
return request.delete(`/articles/${slug}`) return request.delete(`/articles/${slug}`)
} }

View File

@ -1,5 +1,5 @@
import { request } from '../index' import { request } from '../index'
export async function getArticle (slug: string): Promise<Article> { export function getArticle (slug: string): Promise<Article> {
return request.get<ArticleResponse>(`/articles/${slug}`).then(res => res.article) return request.get<ArticleResponse>(`/articles/${slug}`).then(res => res.article)
} }

View File

@ -1,26 +1,26 @@
import { limit, request } from '../index' import { limit, request } from '../index'
export async function getArticles (page = 1): Promise<ArticlesResponse> { export function getArticles (page = 1): Promise<ArticlesResponse> {
const params = { limit, offset: (page - 1) * limit } const params = { limit, offset: (page - 1) * limit }
return request.get<ArticlesResponse>('/articles', { params }) return request.get<ArticlesResponse>('/articles', { params })
} }
export async function getFavoritedArticles (username: string, page = 1): Promise<ArticlesResponse> { export function getFavoritedArticles (username: string, page = 1): Promise<ArticlesResponse> {
const params = { limit, offset: (page - 1) * limit, favorited: username } const params = { limit, offset: (page - 1) * limit, favorited: username }
return request.get<ArticlesResponse>('/articles', { params }) return request.get<ArticlesResponse>('/articles', { params })
} }
export async function getProfileArticles (username: string, page = 1): Promise<ArticlesResponse> { export function getProfileArticles (username: string, page = 1): Promise<ArticlesResponse> {
const params = { limit, offset: (page - 1) * limit, author: username } const params = { limit, offset: (page - 1) * limit, author: username }
return request.get<ArticlesResponse>('/articles', { params }) return request.get<ArticlesResponse>('/articles', { params })
} }
export async function getFeeds (page = 1): Promise<ArticlesResponse> { export function getFeeds (page = 1): Promise<ArticlesResponse> {
const params = { limit, offset: (page - 1) * limit } const params = { limit, offset: (page - 1) * limit }
return request.get<ArticlesResponse>('/articles/feed', { params }) return request.get<ArticlesResponse>('/articles/feed', { params })
} }
export async function getArticlesByTag (tagName: string, page = 1): Promise<ArticlesResponse> { export function getArticlesByTag (tagName: string, page = 1): Promise<ArticlesResponse> {
const params = { tag: tagName, limit, offset: (page - 1) * limit } const params = { tag: tagName, limit, offset: (page - 1) * limit }
return request.get<ArticlesResponse>('/articles', { params }) return request.get<ArticlesResponse>('/articles', { params })
} }

View File

@ -1,18 +1,18 @@
import { request } from '../index' import { request } from '../index'
interface PostArticleForm { interface PostArticleForm {
title: string; title: string
description: string; description: string
body: string; body: string
tagList: string[]; tagList: string[]
} }
export async function postArticle (form: PostArticleForm): Promise<Article> { export function postArticle (form: PostArticleForm): Promise<Article> {
return request.post<ArticleResponse>('/articles', { article: form }) return request.post<ArticleResponse>('/articles', { article: form })
.then(res => res.article) .then(res => res.article)
} }
export async function putArticle (slug: string, form: PostArticleForm): Promise<Article> { export function putArticle (slug: string, form: PostArticleForm): Promise<Article> {
return request.put<ArticleResponse>(`/articles/${slug}`, { article: form }) return request.put<ArticleResponse>(`/articles/${slug}`, { article: form })
.then(res => res.article) .then(res => res.article)
} }

View File

@ -6,8 +6,8 @@ import { mapValidationResponse } from '../../utils/map-checkable-response'
import { Either, fail, success } from '../../utils/either' import { Either, fail, success } from '../../utils/either'
export interface PostLoginForm { export interface PostLoginForm {
email: string; email: string
password: string; password: string
} }
export type PostLoginErrors = Partial<Record<keyof PostLoginForm, string[]>> export type PostLoginErrors = Partial<Record<keyof PostLoginForm, string[]>>

View File

@ -6,9 +6,9 @@ import { mapValidationResponse } from '../../utils/map-checkable-response'
import { Either, fail, success } from '../../utils/either' import { Either, fail, success } from '../../utils/either'
export interface PostRegisterForm { export interface PostRegisterForm {
email: string; email: string
password: string; password: string
username: string; username: string
} }
export type PostRegisterErrors = Partial<Record<keyof PostRegisterForm, string[]>> export type PostRegisterErrors = Partial<Record<keyof PostRegisterForm, string[]>>

View File

@ -1,5 +1,5 @@
import { request } from '../index' import { request } from '../index'
export async function getCommentsByArticle (slug: string): Promise<ArticleComment[]> { export function getCommentsByArticle (slug: string): Promise<ArticleComment[]> {
return request.get<CommentsResponse>(`/articles/${slug}/comments`).then(res => res.comments) return request.get<CommentsResponse>(`/articles/${slug}/comments`).then(res => res.comments)
} }

View File

@ -1,10 +1,10 @@
import { request } from '../index' import { request } from '../index'
export async function deleteComment (slug: string, commentId: number): Promise<Record<string, unknown>> { export function deleteComment (slug: string, commentId: number): Promise<Record<string, unknown>> {
return request.delete(`/articles/${slug}/comments/${commentId}`) return request.delete(`/articles/${slug}/comments/${commentId}`)
} }
export async function postComment (slug: string, body: string): Promise<ArticleComment> { export function postComment (slug: string, body: string): Promise<ArticleComment> {
return request.post<CommentResponse>(`/articles/${slug}/comments`, { comment: { body } }) return request.post<CommentResponse>(`/articles/${slug}/comments`, { comment: { body } })
.then(res => res.comment) .then(res => res.comment)
} }

View File

@ -1,5 +1,5 @@
import { request } from '../index' import { request } from '../index'
export async function getProfile (username: string): Promise<Profile> { export function getProfile (username: string): Promise<Profile> {
return request.get<ProfileResponse>(`/profiles/${username}`).then(res => res.profile) return request.get<ProfileResponse>(`/profiles/${username}`).then(res => res.profile)
} }

View File

@ -1,13 +1,13 @@
import { request } from '../index' import { request } from '../index'
export interface PutProfileForm { export interface PutProfileForm {
username?: string; username?: string
bio?: string; bio?: string
image?: string; image?: string
email?: string; email?: string
password?: string; password?: string
} }
export async function putProfile (form: PutProfileForm): Promise<User> { export function putProfile (form: PutProfileForm): Promise<User> {
return request.put<UserResponse>('/user', form).then(res => res.user) return request.put<UserResponse>('/user', form).then(res => res.user)
} }

View File

@ -1,5 +1,5 @@
import { request } from '../index' import { request } from '../index'
export async function getAllTags (): Promise<string[]> { export function getAllTags (): Promise<string[]> {
return request.get<TagsResponse>('/tags').then(res => res.tags) return request.get<TagsResponse>('/tags').then(res => res.tags)
} }

8
src/shimes-vue.d.ts vendored
View File

@ -5,8 +5,8 @@ declare module '*.vue' {
export default Component export default Component
} }
interface ImportMeta { interface ImportMeta {
env: { env: {
VITE_API_HOST: string VITE_API_HOST: string
}
} }
}

View File

@ -23,7 +23,7 @@ export const checkAuthorization = (user: ComputedRef<User | null>): user is Comp
} }
export const updateUser = mutation<User | null>('updateUser', (state, userData) => { export const updateUser = mutation<User | null>('updateUser', (state, userData) => {
if (!userData) { if (userData === undefined || userData === null) {
storage.remove('user') storage.remove('user')
request.deleteAuthorizationHeader() request.deleteAuthorizationHeader()
state.user = null state.user = null

View File

@ -1,12 +1,12 @@
declare interface Article { declare interface Article {
title: string; title: string
slug: string; slug: string
body: string; body: string
createdAt: string; createdAt: string
updatedAt: string; updatedAt: string
tagList: string[]; tagList: string[]
description: string; description: string
author: Profile; author: Profile
favorited: boolean; favorited: boolean
favoritesCount: number; favoritesCount: number
} }

View File

@ -1,7 +1,7 @@
declare interface ArticleComment { declare interface ArticleComment {
id: number; id: number
createdAt: string; createdAt: string
updatedAt: string; updatedAt: string
body: string; body: string
author: Profile; author: Profile
} }

View File

@ -1,28 +1,28 @@
declare interface UserResponse { declare interface UserResponse {
user: User; user: User
} }
declare interface TagsResponse { declare interface TagsResponse {
tags: string[]; tags: string[]
} }
declare interface ProfileResponse { declare interface ProfileResponse {
profile: Profile; profile: Profile
} }
declare interface ArticleResponse { declare interface ArticleResponse {
article: Article; article: Article
} }
declare interface ArticlesResponse { declare interface ArticlesResponse {
articles: Article[]; articles: Article[]
articlesCount: number; articlesCount: number
} }
declare interface CommentResponse { declare interface CommentResponse {
comment: ArticleComment; comment: ArticleComment
} }
declare interface CommentsResponse { declare interface CommentsResponse {
comments: ArticleComment[]; comments: ArticleComment[]
} }

20
src/types/user.d.ts vendored
View File

@ -1,15 +1,15 @@
declare interface Profile { declare interface Profile {
username: string; username: string
bio: string; bio: string
image: string; image: string
following: boolean; following: boolean
} }
declare interface User { declare interface User {
id: number; id: number
email: string; email: string
username: string; username: string
bio: string | undefined; bio: string | undefined
image: string | undefined; image: string | undefined
token: string; token: string
} }

View File

@ -2,7 +2,7 @@ import { isRef } from 'vue'
import createAsyncProcess from './create-async-process' import createAsyncProcess from './create-async-process'
describe('# Create async process', function () { describe('# Create async process', function () {
const someProcess = async () => Promise.resolve(null) const someProcess = () => Promise.resolve(null)
it('should expect active as Vue Ref type', function () { it('should expect active as Vue Ref type', function () {
const { active } = createAsyncProcess(someProcess) const { active } = createAsyncProcess(someProcess)

View File

@ -3,7 +3,7 @@ import { Ref, ref } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
interface CreateAsyncProcessReturn<T extends (...args: any[]) => any> { interface CreateAsyncProcessReturn<T extends (...args: any[]) => any> {
active: Ref<boolean> active: Ref<boolean>
run: (...args : Parameters<T>) => Promise<ReturnType<T>> run: (...args: Parameters<T>) => Promise<ReturnType<T>>
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@ -2,7 +2,7 @@ import { ValidationError, AuthorizationError, NetworkError } from 'src/types/err
import { Either, fail, isEither, success } from './either' import { Either, fail, isEither, success } from './either'
import { mapAuthorizationResponse, mapValidationResponse } from './map-checkable-response' import { mapAuthorizationResponse, mapValidationResponse } from './map-checkable-response'
const createCheckableResponse = (response: Partial<Response>): Either<NetworkError, Partial<Response>> => response.ok const createCheckableResponse = (response: Partial<Response>): Either<NetworkError, Partial<Response>> => response.ok === true
? success(response) ? success(response)
: fail(new NetworkError(response as Response)) : fail(new NetworkError(response as Response))

View File

@ -52,7 +52,7 @@ async function triggerMethod<T = unknown> (request: FetchRequest, method: Method
} }
} }
function forCorrectMethods (task: string, fn: (method: Method) => void): void { function forCorrectMethods (task: string, fn: (method: Method) => Promise<void>): void {
wrapTests<Method>({ wrapTests<Method>({
task, task,
fn, fn,
@ -61,7 +61,7 @@ function forCorrectMethods (task: string, fn: (method: Method) => void): void {
}) })
} }
function forCheckableMethods (task: string, fn: (method: CheckableMethod) => void): void { function forCheckableMethods (task: string, fn: (method: CheckableMethod) => Promise<void>): void {
wrapTests<CheckableMethod>({ wrapTests<CheckableMethod>({
task, task,
fn, fn,
@ -70,7 +70,7 @@ function forCheckableMethods (task: string, fn: (method: CheckableMethod) => voi
}) })
} }
function forAllMethods (task: string, fn: (method: Method | CheckableMethod) => void): void { function forAllMethods (task: string, fn: (method: Method | CheckableMethod) => Promise<void>): void {
forCheckableMethods(task, fn) forCheckableMethods(task, fn)
forCorrectMethods(task, fn) forCorrectMethods(task, fn)
} }
@ -78,7 +78,7 @@ function forAllMethods (task: string, fn: (method: Method | CheckableMethod) =>
forAllMethods('# Should be implemented', async (method) => { forAllMethods('# Should be implemented', async (method) => {
const request = new FetchRequest() const request = new FetchRequest()
triggerMethod(request, method) await triggerMethod(request, method)
expect(global.fetch).toBeCalledWith(PATH, expect.objectContaining({ expect(global.fetch).toBeCalledWith(PATH, expect.objectContaining({
method: method.replace('checkable', '').toUpperCase(), method: method.replace('checkable', '').toUpperCase(),
@ -89,7 +89,7 @@ describe('# Should implement prefix', () => {
forAllMethods('should implement global prefix', async (method) => { forAllMethods('should implement global prefix', async (method) => {
const request = new FetchRequest({ prefix: PREFIX }) const request = new FetchRequest({ prefix: PREFIX })
triggerMethod(request, method) await triggerMethod(request, method)
expect(global.fetch).toBeCalledWith(`${PREFIX}${PATH}`, expect.any(Object)) expect(global.fetch).toBeCalledWith(`${PREFIX}${PATH}`, expect.any(Object))
}) })
@ -97,7 +97,7 @@ describe('# Should implement prefix', () => {
forAllMethods('should implement local prefix', async (method) => { forAllMethods('should implement local prefix', async (method) => {
const request = new FetchRequest() const request = new FetchRequest()
triggerMethod(request, method, { prefix: SUB_PREFIX }) await triggerMethod(request, method, { prefix: SUB_PREFIX })
expect(global.fetch).toBeCalledWith(`${SUB_PREFIX}${PATH}`, expect.any(Object)) expect(global.fetch).toBeCalledWith(`${SUB_PREFIX}${PATH}`, expect.any(Object))
}) })
@ -105,7 +105,7 @@ describe('# Should implement prefix', () => {
forAllMethods('should implement global + local prefix', async (method) => { forAllMethods('should implement global + local prefix', async (method) => {
const request = new FetchRequest({ prefix: PREFIX }) const request = new FetchRequest({ prefix: PREFIX })
triggerMethod(request, method, { prefix: SUB_PREFIX }) await triggerMethod(request, method, { prefix: SUB_PREFIX })
expect(global.fetch).toBeCalledWith(`${SUB_PREFIX}${PATH}`, expect.any(Object)) expect(global.fetch).toBeCalledWith(`${SUB_PREFIX}${PATH}`, expect.any(Object))
}) })
@ -115,7 +115,7 @@ describe('# Should convert query object to query string in request url', () => {
forAllMethods('should implement global query', async (method) => { forAllMethods('should implement global query', async (method) => {
const request = new FetchRequest({ params: PARAMS }) const request = new FetchRequest({ params: PARAMS })
triggerMethod(request, method) await triggerMethod(request, method)
expect(global.fetch).toBeCalledWith(`${PATH}?${params2query(PARAMS)}`, expect.any(Object)) expect(global.fetch).toBeCalledWith(`${PATH}?${params2query(PARAMS)}`, expect.any(Object))
}) })
@ -123,7 +123,7 @@ describe('# Should convert query object to query string in request url', () => {
forAllMethods('should implement local query', async (method) => { forAllMethods('should implement local query', async (method) => {
const request = new FetchRequest() const request = new FetchRequest()
triggerMethod(request, method, { params: PARAMS }) await triggerMethod(request, method, { params: PARAMS })
expect(global.fetch).toBeCalledWith(`${PATH}?${params2query(PARAMS)}`, expect.any(Object)) expect(global.fetch).toBeCalledWith(`${PATH}?${params2query(PARAMS)}`, expect.any(Object))
}) })
@ -134,7 +134,7 @@ describe('# Should convert query object to query string in request url', () => {
const expectedOptions = { params: { q1: 'q11', q2: 'q2', q3: 'q3' } } const expectedOptions = { params: { q1: 'q11', q2: 'q2', q3: 'q3' } }
const request = new FetchRequest(options) const request = new FetchRequest(options)
triggerMethod(request, method, localOptions) await triggerMethod(request, method, localOptions)
expect(global.fetch).toBeCalledWith(`${PATH}?${params2query(expectedOptions.params)}`, expect.any(Object)) expect(global.fetch).toBeCalledWith(`${PATH}?${params2query(expectedOptions.params)}`, expect.any(Object))
}) })
@ -174,15 +174,15 @@ forCorrectMethods('# Should converted correct response body to json', async func
forCheckableMethods('# Should converted checkable response to Either<NetworkError, DATA_TYPE>', async function (method) { forCheckableMethods('# Should converted checkable response to Either<NetworkError, DATA_TYPE>', async function (method) {
const DATA = { foo: 'bar' } const DATA = { foo: 'bar' }
type DATA_TYPE = {foo: 'bar'} interface DATA_TYPE { foo: 'bar' }
mockFetch({ type: 'body', ...DATA }) mockFetch({ type: 'body', ...DATA })
const request = new FetchRequest() const request = new FetchRequest()
const result = await triggerMethod<DATA_TYPE>(request, method) const result = await triggerMethod<DATA_TYPE>(request, method)
const resultIsEither = isEither<NetworkError, DATA_TYPE>(result) const resultIsEither = isEither<unknown, DATA_TYPE>(result)
const resultIsOk = isEither<NetworkError, DATA_TYPE>(result) && result.isOk() const resultIsOk = isEither<unknown, DATA_TYPE>(result) && result.isOk()
const resultValue = isEither<NetworkError, DATA_TYPE>(result) && result.isOk() ? result.value : null const resultValue = isEither<unknown, DATA_TYPE>(result) && result.isOk() ? result.value : null
expect(resultIsEither).toBe(true) expect(resultIsEither).toBe(true)
expect(resultIsOk).toBe(true) expect(resultIsOk).toBe(true)
@ -216,9 +216,9 @@ forCheckableMethods('# Should return Either<NetworkError, DATA_TYPE> if checkabl
const request = new FetchRequest() const request = new FetchRequest()
const result = await triggerMethod(request, method) const result = await triggerMethod(request, method)
const resultIsEither = isEither<NetworkError, void>(result) const resultIsEither = isEither<NetworkError, unknown>(result)
const resultIsNotOk = isEither<NetworkError, void>(result) && result.isFail() const resultIsNotOk = isEither<NetworkError, unknown>(result) && result.isFail()
const resultValue = isEither<NetworkError, void>(result) && result.isFail() ? result.value : null const resultValue = isEither<NetworkError, unknown>(result) && result.isFail() ? result.value : null
expect(resultIsEither).toBe(true) expect(resultIsEither).toBe(true)
expect(resultIsNotOk).toBe(true) expect(resultIsNotOk).toBe(true)
@ -233,7 +233,7 @@ describe('# Authorization header', function () {
const request = new FetchRequest() const request = new FetchRequest()
request.setAuthorizationHeader(TOKEN) request.setAuthorizationHeader(TOKEN)
triggerMethod(request, method) await triggerMethod(request, method)
expect(global.fetch).toBeCalledWith(PATH, expect.objectContaining(OPTIONS)) expect(global.fetch).toBeCalledWith(PATH, expect.objectContaining(OPTIONS))
}) })

View File

@ -6,39 +6,39 @@ import { Either, fail, success } from './either'
import params2query from './params-to-query' import params2query from './params-to-query'
export interface FetchRequestOptions { export interface FetchRequestOptions {
prefix: string; prefix: string
headers: Record<string, string>; headers: Record<string, string>
params: Record<string, string | number | boolean>; params: Record<string, string | number | boolean>
} }
export default class FetchRequest { export default class FetchRequest {
private defaultOptions: FetchRequestOptions = { private readonly defaultOptions: FetchRequestOptions = {
prefix: '', prefix: '',
headers: {}, headers: {},
params: {}, params: {},
} }
private options: FetchRequestOptions private readonly options: FetchRequestOptions
constructor (options: Partial<FetchRequestOptions> = {}) { constructor (options: Partial<FetchRequestOptions> = {}) {
this.options = merge(this.defaultOptions, options) this.options = merge(this.defaultOptions, options)
} }
private generateFinalUrl = (url: string, options: Partial<FetchRequestOptions> = {}) => { private readonly generateFinalUrl = (url: string, options: Partial<FetchRequestOptions> = {}) => {
const prefix = options.prefix ?? this.options.prefix const prefix = options.prefix ?? this.options.prefix
const params = merge(this.options.params, options.params ?? {}) const params = merge(this.options.params, options.params ?? {})
let finalUrl = `${prefix}${url}` let finalUrl = `${prefix}${url}`
if (Object.keys(params).length) finalUrl += `?${params2query(params)}` if (Object.keys(params).length > 0) finalUrl += `?${params2query(params)}`
return finalUrl return finalUrl
} }
private generateFinalHeaders = (options: Partial<FetchRequestOptions> = {}) => { private readonly generateFinalHeaders = (options: Partial<FetchRequestOptions> = {}) => {
return merge(this.options.headers, options.headers ?? {}) return merge(this.options.headers, options.headers ?? {})
} }
private handleResponse = <T>(response: Response): Promise<Either<NetworkError, T>> => { private readonly handleResponse = <T>(response: Response): Promise<Either<NetworkError, T>> => {
if (response.ok) { if (response.ok) {
return response.json().then(json => success(json as T)) return response.json().then(json => success(json as T))
} }
@ -46,7 +46,7 @@ export default class FetchRequest {
return Promise.resolve(fail(new NetworkError(response))) return Promise.resolve(fail(new NetworkError(response)))
} }
private handleCorrectResponse = <T>(response: Response): Promise<T> => { private readonly handleCorrectResponse = <T>(response: Response): Promise<T> => {
if (response.ok) { if (response.ok) {
return response.json() return response.json()
} }
@ -55,9 +55,9 @@ export default class FetchRequest {
} }
private runFetch ({ method, url, data, options }: { private runFetch ({ method, url, data, options }: {
method: 'GET' | 'DELETE' | 'POST' | 'PUT' | 'PATCH', method: 'GET' | 'DELETE' | 'POST' | 'PUT' | 'PATCH'
url: string, url: string
data?: unknown, data?: unknown
options?: Partial<FetchRequestOptions> options?: Partial<FetchRequestOptions>
}) { }) {
const finalUrl = this.generateFinalUrl(url, options) const finalUrl = this.generateFinalUrl(url, options)
@ -65,7 +65,7 @@ export default class FetchRequest {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const fetchOptions: any = { method, headers } const fetchOptions: any = { method, headers }
if (data) fetchOptions.body = JSON.stringify(data) if (data !== undefined) fetchOptions.body = JSON.stringify(data)
return fetch(finalUrl, fetchOptions) return fetch(finalUrl, fetchOptions)
} }
@ -118,8 +118,7 @@ export default class FetchRequest {
} }
public setAuthorizationHeader (token: string): void { public setAuthorizationHeader (token: string): void {
if (!this.options.headers) this.options.headers = {} if (token !== '') this.options.headers.Authorization = `Token ${token}`
if (token) this.options.headers.Authorization = `Token ${token}`
} }
public deleteAuthorizationHeader (): void { public deleteAuthorizationHeader (): void {

View File

@ -1,6 +1,6 @@
function get<T> (key: string): T | null { function get<T> (key: string): T | null {
try { try {
const value = localStorage.getItem(key) || '' const value = localStorage.getItem(key) ?? ''
return JSON.parse(value) return JSON.parse(value)
} catch (e) { } catch (e) {
return null return null

View File

@ -3,9 +3,9 @@ interface FetchResponseBody {
} }
interface FetchResponseFull { interface FetchResponseFull {
type: 'full' type: 'full'
ok: boolean, ok: boolean
status: number, status: number
statusText:string statusText: string
json: () => Promise<unknown> json: () => Promise<unknown>
} }

View File

@ -1,8 +1,8 @@
interface WrapTestsProps <Item> { interface WrapTestsProps <Item> {
task: string task: string
list: Item[] list: Item[]
fn: (item: Item) => void, fn: (item: Item) => void
only?: boolean, only?: boolean
testName?: (item: Item, index: number) => string testName?: (item: Item, index: number) => string
} }
@ -11,7 +11,7 @@ function wrapTests<Item> ({ task, list, fn, testName, only = false }: WrapTestsP
descFn(task, () => { descFn(task, () => {
list.forEach((item, index) => { list.forEach((item, index) => {
const name = testName ? testName(item, index) : '' const name = testName !== undefined ? testName(item, index) : ''
it(name, () => fn(item)) it(name, () => fn(item))
}) })
}) })

148
yarn.lock
View File

@ -1151,13 +1151,13 @@
dependencies: dependencies:
"@types/yargs-parser" "*" "@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^4.6.1": "@typescript-eslint/eslint-plugin@^4.7.0":
version "4.6.1" version "4.7.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.6.1.tgz#99d77eb7a016fd5a5e749d2c44a7e4c317eb7da3" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.7.0.tgz#85c9bbda00c0cb604d3c241f7bc7fb171a2d3479"
integrity sha512-SNZyflefTMK2JyrPfFFzzoy2asLmZvZJ6+/L5cIqg4HfKGiW2Gr1Go1OyEVqne/U4QwmoasuMwppoBHWBWF2nA== integrity sha512-li9aiSVBBd7kU5VlQlT1AqP0uWGDK6JYKUQ9cVDnOg34VNnd9t4jr0Yqc/bKxJr/tDCPDaB4KzoSFN9fgVxe/Q==
dependencies: dependencies:
"@typescript-eslint/experimental-utils" "4.6.1" "@typescript-eslint/experimental-utils" "4.7.0"
"@typescript-eslint/scope-manager" "4.6.1" "@typescript-eslint/scope-manager" "4.7.0"
debug "^4.1.1" debug "^4.1.1"
functional-red-black-tree "^1.0.1" functional-red-black-tree "^1.0.1"
regexpp "^3.0.0" regexpp "^3.0.0"
@ -1175,15 +1175,15 @@
eslint-scope "^5.0.0" eslint-scope "^5.0.0"
eslint-utils "^2.0.0" eslint-utils "^2.0.0"
"@typescript-eslint/experimental-utils@4.6.1": "@typescript-eslint/experimental-utils@4.7.0":
version "4.6.1" version "4.7.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.6.1.tgz#a9c691dfd530a9570274fe68907c24c07a06c4aa" resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.7.0.tgz#8d1058c38bec3d3bbd9c898a1c32318d80faf3c5"
integrity sha512-qyPqCFWlHZXkEBoV56UxHSoXW2qnTr4JrWVXOh3soBP3q0o7p4pUEMfInDwIa0dB/ypdtm7gLOS0hg0a73ijfg== integrity sha512-cymzovXAiD4EF+YoHAB5Oh02MpnXjvyaOb+v+BdpY7lsJXZQN34oIETeUwVT2XfV9rSNpXaIcknDLfupO/tUoA==
dependencies: dependencies:
"@types/json-schema" "^7.0.3" "@types/json-schema" "^7.0.3"
"@typescript-eslint/scope-manager" "4.6.1" "@typescript-eslint/scope-manager" "4.7.0"
"@typescript-eslint/types" "4.6.1" "@typescript-eslint/types" "4.7.0"
"@typescript-eslint/typescript-estree" "4.6.1" "@typescript-eslint/typescript-estree" "4.7.0"
eslint-scope "^5.0.0" eslint-scope "^5.0.0"
eslint-utils "^2.0.0" eslint-utils "^2.0.0"
@ -1198,33 +1198,33 @@
"@typescript-eslint/typescript-estree" "3.10.1" "@typescript-eslint/typescript-estree" "3.10.1"
eslint-visitor-keys "^1.1.0" eslint-visitor-keys "^1.1.0"
"@typescript-eslint/parser@^4.6.1": "@typescript-eslint/parser@^4.0.0", "@typescript-eslint/parser@^4.7.0":
version "4.6.1" version "4.7.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.6.1.tgz#b801bff67b536ecc4a840ac9289ba2be57e02428" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.7.0.tgz#44bdab0f788b478178368baa65d3365fdc63da1c"
integrity sha512-lScKRPt1wM9UwyKkGKyQDqf0bh6jm8DQ5iN37urRIXDm16GEv+HGEmum2Fc423xlk5NUOkOpfTnKZc/tqKZkDQ== integrity sha512-+meGV8bMP1sJHBI2AFq1GeTwofcGiur8LoIr6v+rEmD9knyCqDlrQcFHR0KDDfldHIFDU/enZ53fla6ReF4wRw==
dependencies: dependencies:
"@typescript-eslint/scope-manager" "4.6.1" "@typescript-eslint/scope-manager" "4.7.0"
"@typescript-eslint/types" "4.6.1" "@typescript-eslint/types" "4.7.0"
"@typescript-eslint/typescript-estree" "4.6.1" "@typescript-eslint/typescript-estree" "4.7.0"
debug "^4.1.1" debug "^4.1.1"
"@typescript-eslint/scope-manager@4.6.1": "@typescript-eslint/scope-manager@4.7.0":
version "4.6.1" version "4.7.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.6.1.tgz#21872b91cbf7adfc7083f17b8041149148baf992" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.7.0.tgz#2115526085fb72723ccdc1eeae75dec7126220ed"
integrity sha512-f95+80r6VdINYscJY1KDUEDcxZ3prAWHulL4qRDfNVD0I5QAVSGqFkwHERDoLYJJWmEAkUMdQVvx7/c2Hp+Bjg== integrity sha512-ILITvqwDJYbcDCROj6+Ob0oCKNg3SH46iWcNcTIT9B5aiVssoTYkhKjxOMNzR1F7WSJkik4zmuqve5MdnA0DyA==
dependencies: dependencies:
"@typescript-eslint/types" "4.6.1" "@typescript-eslint/types" "4.7.0"
"@typescript-eslint/visitor-keys" "4.6.1" "@typescript-eslint/visitor-keys" "4.7.0"
"@typescript-eslint/types@3.10.1": "@typescript-eslint/types@3.10.1":
version "3.10.1" version "3.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-3.10.1.tgz#1d7463fa7c32d8a23ab508a803ca2fe26e758727"
integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ== integrity sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==
"@typescript-eslint/types@4.6.1": "@typescript-eslint/types@4.7.0":
version "4.6.1" version "4.7.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.6.1.tgz#d3ad7478f53f22e7339dc006ab61aac131231552" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.7.0.tgz#5e95ef5c740f43d942542b35811f87b62fccca69"
integrity sha512-k2ZCHhJ96YZyPIsykickez+OMHkz06xppVLfJ+DY90i532/Cx2Z+HiRMH8YZQo7a4zVd/TwNBuRCdXlGK4yo8w== integrity sha512-uLszFe0wExJc+I7q0Z/+BnP7wao/kzX0hB5vJn4LIgrfrMLgnB2UXoReV19lkJQS1a1mHWGGODSxnBx6JQC3Sg==
"@typescript-eslint/typescript-estree@3.10.1": "@typescript-eslint/typescript-estree@3.10.1":
version "3.10.1" version "3.10.1"
@ -1240,13 +1240,13 @@
semver "^7.3.2" semver "^7.3.2"
tsutils "^3.17.1" tsutils "^3.17.1"
"@typescript-eslint/typescript-estree@4.6.1": "@typescript-eslint/typescript-estree@4.7.0":
version "4.6.1" version "4.7.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.6.1.tgz#6025cce724329413f57e4959b2d676fceeca246f" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.7.0.tgz#539531167f05ba20eb0b6785567076679e29d393"
integrity sha512-/J/kxiyjQQKqEr5kuKLNQ1Finpfb8gf/NpbwqFFYEBjxOsZ621r9AqwS9UDRA1Rrr/eneX/YsbPAIhU2rFLjXQ== integrity sha512-5XZRQznD1MfUmxu1t8/j2Af4OxbA7EFU2rbo0No7meb46eHgGkSieFdfV6omiC/DGIBhH9H9gXn7okBbVOm8jw==
dependencies: dependencies:
"@typescript-eslint/types" "4.6.1" "@typescript-eslint/types" "4.7.0"
"@typescript-eslint/visitor-keys" "4.6.1" "@typescript-eslint/visitor-keys" "4.7.0"
debug "^4.1.1" debug "^4.1.1"
globby "^11.0.1" globby "^11.0.1"
is-glob "^4.0.1" is-glob "^4.0.1"
@ -1261,12 +1261,12 @@
dependencies: dependencies:
eslint-visitor-keys "^1.1.0" eslint-visitor-keys "^1.1.0"
"@typescript-eslint/visitor-keys@4.6.1": "@typescript-eslint/visitor-keys@4.7.0":
version "4.6.1" version "4.7.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.6.1.tgz#6b125883402d8939df7b54528d879e88f7ba3614" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.7.0.tgz#6783824f22acfc49e754970ed21b88ac03b80e6f"
integrity sha512-owABze4toX7QXwOLT3/D5a8NecZEjEWU1srqxENTfqsY3bwVnl3YYbOh6s1rp2wQKO9RTHFGjKes08FgE7SVMw== integrity sha512-aDJDWuCRsf1lXOtignlfiPODkzSxxop7D0rZ91L6ZuMlcMCSh0YyK+gAfo5zN/ih6WxMwhoXgJWC3cWQdaKC+A==
dependencies: dependencies:
"@typescript-eslint/types" "4.6.1" "@typescript-eslint/types" "4.7.0"
eslint-visitor-keys "^2.0.0" eslint-visitor-keys "^2.0.0"
"@vue/compiler-core@3.0.2": "@vue/compiler-core@3.0.2":
@ -3079,6 +3079,19 @@ escodegen@^1.14.1, escodegen@^1.9.1:
optionalDependencies: optionalDependencies:
source-map "~0.6.1" source-map "~0.6.1"
eslint-config-standard-with-typescript@^19.0.1:
version "19.0.1"
resolved "https://registry.yarnpkg.com/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-19.0.1.tgz#d486e08a82f6bf43a8e0ef1bc76088e26fe7a587"
integrity sha512-hAKj81+f4a+9lnvpHwZ4XSL672CbwSe5UJ7fTdL/RsQdqs4IjHudMETZuNQwwU7NlYpBTF9se7FRf5Pp7CVdag==
dependencies:
"@typescript-eslint/parser" "^4.0.0"
eslint-config-standard "^14.1.1"
eslint-config-standard@^14.1.1:
version "14.1.1"
resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.1.1.tgz#830a8e44e7aef7de67464979ad06b406026c56ea"
integrity sha512-Z9B+VR+JIXRxz21udPTL9HpFMyoMUEeX1G251EQ6e05WD9aPVtVBn09XUmZ259wCMlCDmYDSZG62Hhm+ZTJcUg==
eslint-config-standard@^16.0.1: eslint-config-standard@^16.0.1:
version "16.0.1" version "16.0.1"
resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-16.0.1.tgz#9a385eea27f96b7918cb53f07e01e9d10cc56401" resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-16.0.1.tgz#9a385eea27f96b7918cb53f07e01e9d10cc56401"
@ -3151,10 +3164,10 @@ eslint-plugin-promise@^4.2.1:
resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a"
integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==
eslint-plugin-standard@^4.0.2: eslint-plugin-standard@^4.1.0:
version "4.0.2" version "4.1.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.2.tgz#021211a9f077e63a6847e7bb9ab4247327ac8e0c" resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.1.0.tgz#0c3bf3a67e853f8bbbc580fb4945fbf16f41b7c5"
integrity sha512-nKptN8l7jksXkwFk++PhJB3cCDTcXOEyhISIN86Ue2feJ1LFyY3PrY3/xT2keXlJSY5bpmbiTG0f885/YKAvTA== integrity sha512-ZL7+QRixjTR6/528YNGyDotyffm5OQst/sGxKDwGb9Uqs4In5Egi4+jbobhqJoyoCM6/7v/1A5fhQ7ScMtDjaQ==
eslint-plugin-vue@^7.1.0: eslint-plugin-vue@^7.1.0:
version "7.1.0" version "7.1.0"
@ -3241,7 +3254,7 @@ eslint@^6.8.0:
text-table "^0.2.0" text-table "^0.2.0"
v8-compile-cache "^2.0.3" v8-compile-cache "^2.0.3"
eslint@^7.11.0: eslint@^7.11.0, eslint@^7.13.0:
version "7.13.0" version "7.13.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.13.0.tgz#7f180126c0dcdef327bfb54b211d7802decc08da" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.13.0.tgz#7f180126c0dcdef327bfb54b211d7802decc08da"
integrity sha512-uCORMuOO8tUzJmsdRtrvcGq5qposf7Rw0LwkTJkoDbOycVQtQjmnhZSuLQnozLE4TmAzlMVV45eCHmQ1OpDKUQ== integrity sha512-uCORMuOO8tUzJmsdRtrvcGq5qposf7Rw0LwkTJkoDbOycVQtQjmnhZSuLQnozLE4TmAzlMVV45eCHmQ1OpDKUQ==
@ -3284,49 +3297,6 @@ eslint@^7.11.0:
text-table "^0.2.0" text-table "^0.2.0"
v8-compile-cache "^2.0.3" v8-compile-cache "^2.0.3"
eslint@^7.12.1:
version "7.12.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.12.1.tgz#bd9a81fa67a6cfd51656cdb88812ce49ccec5801"
integrity sha512-HlMTEdr/LicJfN08LB3nM1rRYliDXOmfoO4vj39xN6BLpFzF00hbwBoqHk8UcJ2M/3nlARZWy/mslvGEuZFvsg==
dependencies:
"@babel/code-frame" "^7.0.0"
"@eslint/eslintrc" "^0.2.1"
ajv "^6.10.0"
chalk "^4.0.0"
cross-spawn "^7.0.2"
debug "^4.0.1"
doctrine "^3.0.0"
enquirer "^2.3.5"
eslint-scope "^5.1.1"
eslint-utils "^2.1.0"
eslint-visitor-keys "^2.0.0"
espree "^7.3.0"
esquery "^1.2.0"
esutils "^2.0.2"
file-entry-cache "^5.0.1"
functional-red-black-tree "^1.0.1"
glob-parent "^5.0.0"
globals "^12.1.0"
ignore "^4.0.6"
import-fresh "^3.0.0"
imurmurhash "^0.1.4"
is-glob "^4.0.0"
js-yaml "^3.13.1"
json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.4.1"
lodash "^4.17.19"
minimatch "^3.0.4"
natural-compare "^1.4.0"
optionator "^0.9.1"
progress "^2.0.0"
regexpp "^3.1.0"
semver "^7.2.1"
strip-ansi "^6.0.0"
strip-json-comments "^3.1.0"
table "^5.2.3"
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
espree@^6.1.2, espree@^6.2.1: espree@^6.1.2, espree@^6.2.1:
version "6.2.1" version "6.2.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"