Initial move towards new ESLint config supporting TS

Co-authored-by: Sidney Keese <sidney@carbonfive.com>
This commit is contained in:
Chris Svenningsen 2020-08-31 17:09:28 -07:00 committed by Josh Perez
parent c2aa8eb82b
commit 5b1536cc02
22 changed files with 3300 additions and 597 deletions

View file

@ -27,3 +27,31 @@ test/blanket_mocha.js
# TypeScript generated files # TypeScript generated files
ts/**/*.js ts/**/*.js
**/*.d.ts
webpack.config.ts
# Temporarily ignored during TSLint transition
# JIRA: DESKTOP-304
ts/*.ts
ts/backbone/**
ts/build/**
ts/components/*.ts
ts/components/*.tsx
ts/components/conversation/**
ts/components/stickers/**
ts/notifications/**
ts/protobuf/**
ts/scripts/**
ts/services/**
ts/shims/**
ts/sql/**
ts/state/**
ts/storybook/**
ts/styleguide/**
ts/test/**
ts/textsecure/**
ts/types/**
ts/updater/**
ts/util/**
sticker-creator/**/*.ts
sticker-creator/**/*.tsx

View file

@ -1,7 +1,11 @@
// For reference: https://github.com/airbnb/javascript // For reference: https://github.com/airbnb/javascript
module.exports = { module.exports = {
root: true,
settings: { settings: {
react: {
version: 'detect',
},
'import/core-modules': ['electron'], 'import/core-modules': ['electron'],
}, },
@ -9,6 +13,35 @@ module.exports = {
plugins: ['mocha', 'more'], plugins: ['mocha', 'more'],
overrides: [
{
files: ['*.ts', '*.tsx'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2018,
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'airbnb-typescript-prettier',
],
},
{
files: ['**/*.stories.tsx'],
rules: {
'import/no-extraneous-dependencies': 'off',
'react/jsx-props-no-spreading': 'off',
},
},
],
rules: { rules: {
'comma-dangle': [ 'comma-dangle': [
'error', 'error',
@ -37,7 +70,19 @@ module.exports = {
'no-console': 'error', 'no-console': 'error',
// consistently place operators at end of line except ternaries // consistently place operators at end of line except ternaries
'operator-linebreak': 'error', 'operator-linebreak': [
'error',
'after',
{ overrides: { '?': 'ignore', ':': 'ignore' } },
],
// Temporarily turned off during transition from TSLint
// JIRA: DESKTOP-623
'import/order': 'off',
'no-else-return': 'off',
'no-async-promise-executor': 'off',
'prefer-object-spread': 'off',
strict: 'off',
quotes: [ quotes: [
'error', 'error',

View file

@ -1910,6 +1910,42 @@
}, },
"description": "Shown as a tooltip over the emoji tone buttons." "description": "Shown as a tooltip over the emoji tone buttons."
}, },
"EmojiPicker__button--recents": {
"message": "Recents",
"description": "Label for recents emoji picker button"
},
"EmojiPicker__button--emoji": {
"message": "Emoji",
"description": "Label for emoji emoji picker button"
},
"EmojiPicker__button--animal": {
"message": "Animal",
"description": "Label for animal emoji picker button"
},
"EmojiPicker__button--food": {
"message": "Food",
"description": "Label for food emoji picker button"
},
"EmojiPicker__button--activity": {
"message": "Activity",
"description": "Label for activity emoji picker button"
},
"EmojiPicker__button--travel": {
"message": "Travel",
"description": "Label for travel emoji picker button"
},
"EmojiPicker__button--object": {
"message": "Object",
"description": "Label for object emoji picker button"
},
"EmojiPicker__button--symbol": {
"message": "Symbol",
"description": "Label for symbol emoji picker button"
},
"EmojiPicker__button--flag": {
"message": "Flag",
"description": "Label for flag emoji picker button"
},
"confirmation-dialog--Cancel": { "confirmation-dialog--Cancel": {
"message": "Cancel", "message": "Cancel",
"description": "Appears on the cancel button in confirmation dialogs." "description": "Appears on the cancel button in confirmation dialogs."
@ -2783,5 +2819,9 @@
"example": "10/23/2023, 7:10 PM" "example": "10/23/2023, 7:10 PM"
} }
} }
},
"EmojiButton__label": {
"message": "Emoji",
"description": "Label for emoji button"
} }
} }

View file

@ -1,4 +1,3 @@
/* global exports, require */
/* eslint-disable strict */ /* eslint-disable strict */
const { Menu, clipboard } = require('electron'); const { Menu, clipboard } = require('electron');

6
js/modules/i18n.d.ts vendored Normal file
View file

@ -0,0 +1,6 @@
import { LocalizerType } from '../../ts/types/Util';
export const setup: (
language: string,
messages: Record<string, unknown>
) => LocalizerType;

View file

@ -239,7 +239,7 @@ function assembleChunks(chunkDescriptors) {
return concatenateBytes(...chunks); return concatenateBytes(...chunks);
} }
const ASCII_PATTERN = new RegExp('[\\u0000-\\u007F]', 'g'); const ASCII_PATTERN = new RegExp('[\\u0020-\\u007F]', 'g');
function isLinkSneaky(link) { function isLinkSneaky(link) {
// Any links which contain auth are considered sneaky // Any links which contain auth are considered sneaky

View file

@ -3,7 +3,6 @@
/* global drawAttention: false */ /* global drawAttention: false */
/* global i18n: false */ /* global i18n: false */
/* global Signal: false */
/* global storage: false */ /* global storage: false */
/* global Whisper: false */ /* global Whisper: false */
/* global _: false */ /* global _: false */

View file

@ -4,7 +4,6 @@
_, _,
ConversationController, ConversationController,
MessageController, MessageController,
window
*/ */
/* eslint-disable more/no-then */ /* eslint-disable more/no-then */

View file

@ -1,4 +1,4 @@
/* global mocha, chai, assert */ /* global chai */
mocha.setup('bdd'); mocha.setup('bdd');
window.assert = chai.assert; window.assert = chai.assert;

View file

@ -195,6 +195,8 @@
"@types/webpack": "4.39.0", "@types/webpack": "4.39.0",
"@types/webpack-dev-server": "3.1.7", "@types/webpack-dev-server": "3.1.7",
"@types/websocket": "1.0.0", "@types/websocket": "1.0.0",
"@typescript-eslint/eslint-plugin": "3.10.1",
"@typescript-eslint/parser": "3.10.1",
"arraybuffer-loader": "1.0.3", "arraybuffer-loader": "1.0.3",
"asar": "0.14.0", "asar": "0.14.0",
"babel-core": "7.0.0-bridge.0", "babel-core": "7.0.0-bridge.0",
@ -209,12 +211,13 @@
"electron-builder": "22.3.6", "electron-builder": "22.3.6",
"electron-mocha": "8.1.1", "electron-mocha": "8.1.1",
"electron-notarize": "0.1.1", "electron-notarize": "0.1.1",
"eslint": "4.18.2", "eslint": "7.7.0",
"eslint-config-airbnb-base": "12.1.0", "eslint-config-airbnb-typescript-prettier": "3.1.0",
"eslint-config-prettier": "2.9.0", "eslint-config-prettier": "6.11.0",
"eslint-plugin-import": "2.8.0", "eslint-plugin-import": "2.22.0",
"eslint-plugin-mocha": "4.12.1", "eslint-plugin-mocha": "8.0.0",
"eslint-plugin-more": "0.3.1", "eslint-plugin-more": "1.0.0",
"eslint-plugin-react": "7.20.6",
"file-loader": "4.2.0", "file-loader": "4.2.0",
"grunt": "1.0.1", "grunt": "1.0.1",
"grunt-cli": "1.2.0", "grunt-cli": "1.2.0",
@ -242,7 +245,7 @@
"style-loader": "1.0.0", "style-loader": "1.0.0",
"ts-loader": "4.1.0", "ts-loader": "4.1.0",
"ts-node": "8.3.0", "ts-node": "8.3.0",
"tslint": "5.13.0", "tslint": "6",
"tslint-microsoft-contrib": "6.2.0", "tslint-microsoft-contrib": "6.2.0",
"tslint-react": "3.6.0", "tslint-react": "3.6.0",
"typed-scss-modules": "0.0.11", "typed-scss-modules": "0.0.11",

View file

@ -1,4 +1,4 @@
/* global Signal, Whisper, assert, textsecure, _, libsignal */ /* global Signal, Whisper, textsecure, _, libsignal */
/* eslint-disable no-console */ /* eslint-disable no-console */

View file

@ -1,13 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import { Emoji, EmojiSizes, Props } from './Emoji';
// @ts-ignore
import { setup as setupI18n } from '../../js/modules/i18n';
// @ts-ignore
import enMessages from '../../_locales/en/messages.json';
import { storiesOf } from '@storybook/react'; import { storiesOf } from '@storybook/react';
import { boolean, select, text } from '@storybook/addon-knobs'; import { boolean, select, text } from '@storybook/addon-knobs';
import { Emoji, EmojiSizes, Props } from './Emoji';
const story = storiesOf('Components/Emoji/Emoji', module); const story = storiesOf('Components/Emoji/Emoji', module);

View file

@ -33,11 +33,13 @@ export const Emoji = React.memo(
}: Props, }: Props,
ref ref
) => { ) => {
const image = shortName let image = '';
? getImagePath(shortName, skinTone) if (shortName) {
: emoji image = getImagePath(shortName, skinTone);
? emojiToImage(emoji) } else if (emoji) {
: ''; image = emojiToImage(emoji) || '';
}
const backgroundStyle = inline const backgroundStyle = inline
? { backgroundImage: `url('${image}')` } ? { backgroundImage: `url('${image}')` }
: {}; : {};

View file

@ -1,12 +1,11 @@
import * as React from 'react'; import * as React from 'react';
// @ts-ignore
import { setup as setupI18n } from '../../../js/modules/i18n';
// @ts-ignore
import enMessages from '../../../_locales/en/messages.json';
import { storiesOf } from '@storybook/react'; import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import { setup as setupI18n } from '../../../js/modules/i18n';
import enMessages from '../../../_locales/en/messages.json';
import { EmojiButton } from './EmojiButton'; import { EmojiButton } from './EmojiButton';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);

View file

@ -105,12 +105,14 @@ export const EmojiButton = React.memo(
<Reference> <Reference>
{({ ref }) => ( {({ ref }) => (
<button <button
type="button"
ref={ref} ref={ref}
onClick={handleClickButton} onClick={handleClickButton}
className={classNames({ className={classNames({
'module-emoji-button__button': true, 'module-emoji-button__button': true,
'module-emoji-button__button--active': open, 'module-emoji-button__button--active': open,
})} })}
aria-label={i18n('EmojiButton__label')}
/> />
)} )}
</Reference> </Reference>

View file

@ -1,12 +1,11 @@
import * as React from 'react'; import * as React from 'react';
// @ts-ignore
import { setup as setupI18n } from '../../../js/modules/i18n';
// @ts-ignore
import enMessages from '../../../_locales/en/messages.json';
import { storiesOf } from '@storybook/react'; import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions'; import { action } from '@storybook/addon-actions';
import { setup as setupI18n } from '../../../js/modules/i18n';
import enMessages from '../../../_locales/en/messages.json';
import { EmojiPicker } from './EmojiPicker'; import { EmojiPicker } from './EmojiPicker';
const i18n = setupI18n('en', enMessages); const i18n = setupI18n('en', enMessages);

View file

@ -73,10 +73,7 @@ export const EmojiPicker = React.memo(
ref ref
) => { ) => {
const focusRef = React.useRef<HTMLButtonElement>(null); const focusRef = React.useRef<HTMLButtonElement>(null);
// Per design: memoize the initial recent emojis so the grid only updates after re-opening the picker. const [firstRecent] = React.useState(recentEmojis);
const firstRecent = React.useMemo(() => {
return recentEmojis;
}, []);
const [selectedCategory, setSelectedCategory] = React.useState( const [selectedCategory, setSelectedCategory] = React.useState(
categories[0] categories[0]
); );
@ -208,7 +205,7 @@ export const EmojiPicker = React.memo(
); );
return [...chunk(firstRecent, COL_COUNT), ...chunks]; return [...chunk(firstRecent, COL_COUNT), ...chunks];
}, [dataByCategory, categories, firstRecent, searchText]); }, [firstRecent, renderableCategories, searchText]);
const catRowEnds = React.useMemo(() => { const catRowEnds = React.useMemo(() => {
const rowEnds: Array<number> = [ const rowEnds: Array<number> = [
@ -223,13 +220,13 @@ export const EmojiPicker = React.memo(
}); });
return rowEnds; return rowEnds;
}, [categories, dataByCategory]); }, [firstRecent.length, renderableCategories]);
const catToRowOffsets = React.useMemo(() => { const catToRowOffsets = React.useMemo(() => {
const offsets = initial(catRowEnds).map(i => i + 1); const offsets = initial(catRowEnds).map(i => i + 1);
return zipObject(categories, [0, ...offsets]); return zipObject(categories, [0, ...offsets]);
}, [categories, catRowEnds]); }, [catRowEnds]);
const catOffsetEntries = React.useMemo( const catOffsetEntries = React.useMemo(
() => Object.entries(catToRowOffsets), () => Object.entries(catToRowOffsets),
@ -259,6 +256,7 @@ export const EmojiPicker = React.memo(
style={cellStyle} style={cellStyle}
> >
<button <button
type="button"
className="module-emoji-picker__button" className="module-emoji-picker__button"
onClick={handlePickEmoji} onClick={handlePickEmoji}
onKeyDown={handlePickEmoji} onKeyDown={handlePickEmoji}
@ -270,7 +268,7 @@ export const EmojiPicker = React.memo(
</div> </div>
) : null; ) : null;
}, },
[emojiGrid, selectedTone] [emojiGrid, handlePickEmoji, selectedTone]
); );
const getRowHeight = React.useCallback( const getRowHeight = React.useCallback(
@ -297,13 +295,14 @@ export const EmojiPicker = React.memo(
setSelectedCategory(cat); setSelectedCategory(cat);
}, 10), }, 10),
[catOffsetEntries, categories] [catOffsetEntries]
); );
return ( return (
<div className="module-emoji-picker" ref={ref} style={style}> <div className="module-emoji-picker" ref={ref} style={style}>
<header className="module-emoji-picker__header"> <header className="module-emoji-picker__header">
<button <button
type="button"
ref={focusRef} ref={focusRef}
onClick={handleToggleSearch} onClick={handleToggleSearch}
title={i18n('EmojiPicker--search-placeholder')} title={i18n('EmojiPicker--search-placeholder')}
@ -314,6 +313,7 @@ export const EmojiPicker = React.memo(
? 'module-emoji-picker__button--icon--close' ? 'module-emoji-picker__button--icon--close'
: 'module-emoji-picker__button--icon--search' : 'module-emoji-picker__button--icon--search'
)} )}
aria-label={i18n('EmojiPicker--search-placeholder')}
/> />
{searchMode ? ( {searchMode ? (
<div className="module-emoji-picker__header__search-field"> <div className="module-emoji-picker__header__search-field">
@ -328,6 +328,7 @@ export const EmojiPicker = React.memo(
categories.map(cat => categories.map(cat =>
cat === 'recents' && firstRecent.length === 0 ? null : ( cat === 'recents' && firstRecent.length === 0 ? null : (
<button <button
type="button"
key={cat} key={cat}
data-category={cat} data-category={cat}
title={cat} title={cat}
@ -340,6 +341,7 @@ export const EmojiPicker = React.memo(
? 'module-emoji-picker__button--selected' ? 'module-emoji-picker__button--selected'
: null : null
)} )}
aria-label={i18n(`EmojiPicker__button--${cat}`)}
/> />
) )
) )
@ -377,7 +379,7 @@ export const EmojiPicker = React.memo(
<Emoji <Emoji
shortName="slightly_frowning_face" shortName="slightly_frowning_face"
size={16} size={16}
inline={true} inline
style={{ marginLeft: '4px' }} style={{ marginLeft: '4px' }}
/> />
</div> </div>
@ -386,6 +388,7 @@ export const EmojiPicker = React.memo(
<footer className="module-emoji-picker__footer"> <footer className="module-emoji-picker__footer">
{[0, 1, 2, 3, 4, 5].map(tone => ( {[0, 1, 2, 3, 4, 5].map(tone => (
<button <button
type="button"
key={tone} key={tone}
data-tone={tone} data-tone={tone}
onClick={handlePickTone} onClick={handlePickTone}

View file

@ -1,4 +1,5 @@
// @ts-ignore: untyped json // Camelcase disabled due to emoji-datasource using snake_case
/* eslint-disable camelcase */
import untypedData from 'emoji-datasource'; import untypedData from 'emoji-datasource';
import emojiRegex from 'emoji-regex'; import emojiRegex from 'emoji-regex';
import { import {
@ -17,8 +18,6 @@ import Fuse from 'fuse.js';
import PQueue from 'p-queue'; import PQueue from 'p-queue';
import is from '@sindresorhus/is'; import is from '@sindresorhus/is';
export type ValuesOf<T extends Array<any>> = T[number];
export const skinTones = ['1F3FB', '1F3FC', '1F3FD', '1F3FE', '1F3FF']; export const skinTones = ['1F3FB', '1F3FC', '1F3FD', '1F3FE', '1F3FF'];
export type SkinToneKey = '1F3FB' | '1F3FC' | '1F3FD' | '1F3FE' | '1F3FF'; export type SkinToneKey = '1F3FB' | '1F3FC' | '1F3FD' | '1F3FE' | '1F3FF';
@ -82,7 +81,6 @@ const data = (untypedData as Array<EmojiData>)
: emoji : emoji
); );
// @ts-ignore
const ROOT_PATH = get( const ROOT_PATH = get(
// tslint:disable-next-line no-typeof-undefined // tslint:disable-next-line no-typeof-undefined
typeof window !== 'undefined' ? window : null, typeof window !== 'undefined' ? window : null,
@ -97,7 +95,7 @@ const makeImagePath = (src: string) => {
const imageQueue = new PQueue({ concurrency: 10 }); const imageQueue = new PQueue({ concurrency: 10 });
const images = new Set(); const images = new Set();
export const preloadImages = async () => { export const preloadImages = async (): Promise<void> => {
// Preload images // Preload images
const preload = async (src: string) => const preload = async (src: string) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
@ -110,7 +108,7 @@ export const preloadImages = async () => {
setTimeout(reject, 5000); setTimeout(reject, 5000);
}); });
// tslint:disable-next-line no-console // eslint-disable-next-line no-console
console.log('Preloading emoji images'); console.log('Preloading emoji images');
const start = Date.now(); const start = Date.now();
@ -129,7 +127,7 @@ export const preloadImages = async () => {
await imageQueue.onEmpty(); await imageQueue.onEmpty();
const end = Date.now(); const end = Date.now();
// tslint:disable-next-line no-console // eslint-disable-next-line no-console
console.log(`Done preloading emoji images in ${end - start}ms`); console.log(`Done preloading emoji images in ${end - start}ms`);
}; };
@ -222,7 +220,7 @@ const fuse = new Fuse(data, {
keys: ['name', 'short_name', 'short_names'], keys: ['name', 'short_name', 'short_names'],
}); });
export function search(query: string, count: number = 0) { export function search(query: string, count = 0): Array<EmojiData> {
const results = fuse.search(query.substr(0, 32)); const results = fuse.search(query.substr(0, 32));
if (count) { if (count) {
@ -237,11 +235,11 @@ const shortNames = new Set([
...compact<string>(flatMap(data, 'short_names')), ...compact<string>(flatMap(data, 'short_names')),
]); ]);
export function isShortName(name: string) { export function isShortName(name: string): boolean {
return shortNames.has(name); return shortNames.has(name);
} }
export function unifiedToEmoji(unified: string) { export function unifiedToEmoji(unified: string): string {
return unified return unified
.split('-') .split('-')
.map(c => String.fromCodePoint(parseInt(c, 16))) .map(c => String.fromCodePoint(parseInt(c, 16)))
@ -251,7 +249,7 @@ export function unifiedToEmoji(unified: string) {
export function convertShortName( export function convertShortName(
shortName: string, shortName: string,
skinTone: number | SkinToneKey = 0 skinTone: number | SkinToneKey = 0
) { ): string {
const base = dataByShortName[shortName]; const base = dataByShortName[shortName];
if (!base) { if (!base) {
@ -300,15 +298,17 @@ export function getSizeClass(str: string): SizeClassType {
if (emojiCount > 8) { if (emojiCount > 8) {
return ''; return '';
} else if (emojiCount > 6) {
return 'small';
} else if (emojiCount > 4) {
return 'medium';
} else if (emojiCount > 2) {
return 'large';
} else {
return 'jumbo';
} }
if (emojiCount > 6) {
return 'small';
}
if (emojiCount > 4) {
return 'medium';
}
if (emojiCount > 2) {
return 'large';
}
return 'jumbo';
} }
data.forEach(emoji => { data.forEach(emoji => {

File diff suppressed because it is too large Load diff

View file

@ -61,7 +61,7 @@
true, true,
{ {
"import-sources-order": "any", "import-sources-order": "any",
"named-imports-order": "case-insensitive" "named-imports-order": "case-insensitive-legacy"
} }
], ],
@ -174,5 +174,8 @@
// We use || and && shortcutting because we're javascript programmers // We use || and && shortcutting because we're javascript programmers
"strict-boolean-expressions": false "strict-boolean-expressions": false
}, },
"rulesDirectory": ["node_modules/tslint-microsoft-contrib"] "rulesDirectory": ["node_modules/tslint-microsoft-contrib"],
"linterOptions": {
"exclude": ["ts/components/emoji/**/*.ts"]
}
} }

View file

@ -1,7 +1,8 @@
import { resolve } from 'path';
// eslint-disable-next-line import/no-extraneous-dependencies
import { Configuration, EnvironmentPlugin } from 'webpack'; import { Configuration, EnvironmentPlugin } from 'webpack';
// tslint:disable-next-line no-require-imports // tslint:disable-next-line no-require-imports
import HtmlWebpackPlugin = require('html-webpack-plugin'); import HtmlWebpackPlugin = require('html-webpack-plugin');
import { resolve } from 'path';
const context = __dirname; const context = __dirname;
const { NODE_ENV: mode = 'development' } = process.env; const { NODE_ENV: mode = 'development' } = process.env;
@ -77,7 +78,6 @@ const stickerCreatorConfig: Configuration = {
}, },
}), }),
], ],
// @ts-ignore: this typing broke at some point
devServer: { devServer: {
port: 6380, port: 6380,
historyApiFallback: { historyApiFallback: {

1138
yarn.lock

File diff suppressed because it is too large Load diff