style: enable no-non-null-assertion rule (#2723)

Co-authored-by: Peng Xiao <pengxiao@outlook.com>
(cherry picked from commit 18dc427bc3)
This commit is contained in:
LongYinan
2023-06-08 15:23:20 +08:00
committed by Alex Yang
parent d1505a6c94
commit cc1315ef12
16 changed files with 196 additions and 147 deletions
+1
View File
@@ -6,3 +6,4 @@ storybook-static
affine-out affine-out
_next _next
lib lib
.eslintrc.js
+18 -17
View File
@@ -1,3 +1,5 @@
const { readdirSync, statSync } = require('fs');
const createPattern = packageName => [ const createPattern = packageName => [
{ {
group: ['**/dist', '**/dist/**'], group: ['**/dist', '**/dist/**'],
@@ -21,22 +23,14 @@ const createPattern = packageName => [
}, },
]; ];
const allPackages = [ const pkgs = readdirSync('./packages').filter(pkg => {
'cli', return statSync(`./packages/${pkg}`).isDirectory();
'component', });
'debug', const apps = readdirSync('./apps').filter(pkg => {
'env', return statSync(`./apps/${pkg}`).isDirectory();
'graphql', });
'hooks',
'i18n', const allPackages = pkgs.concat(apps);
'jotai',
'native',
'plugin-infra',
'templates',
'theme',
'workspace',
'y-indexeddb',
];
/** /**
* @type {import('eslint').Linter.Config} * @type {import('eslint').Linter.Config}
@@ -67,6 +61,7 @@ const config = {
}, },
ecmaVersion: 'latest', ecmaVersion: 'latest',
sourceType: 'module', sourceType: 'module',
project: './tsconfig.eslint.json',
}, },
plugins: [ plugins: [
'react', 'react',
@@ -83,7 +78,7 @@ const config = {
'no-cond-assign': 'off', 'no-cond-assign': 'off',
'react/prop-types': 'off', 'react/prop-types': 'off',
'@typescript-eslint/consistent-type-imports': 'error', '@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-non-null-assertion': 'error',
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-unused-vars': [ '@typescript-eslint/no-unused-vars': [
@@ -136,6 +131,12 @@ const config = {
'@typescript-eslint/no-var-requires': 0, '@typescript-eslint/no-var-requires': 0,
}, },
}, },
{
files: ['**/__tests__/**/*', '**/*.stories.tsx'],
rules: {
'@typescript-eslint/no-non-null-assertion': 0,
},
},
...allPackages.map(pkg => ({ ...allPackages.map(pkg => ({
files: [`packages/${pkg}/src/**/*.ts`, `packages/${pkg}/src/**/*.tsx`], files: [`packages/${pkg}/src/**/*.ts`, `packages/${pkg}/src/**/*.tsx`],
rules: { rules: {
@@ -10,9 +10,7 @@ export abstract class BaseSQLiteAdapter {
db: SqliteConnection | null = null; db: SqliteConnection | null = null;
abstract role: string; abstract role: string;
constructor(public readonly path: string) { constructor(public readonly path: string) {}
logger.info(`[SQLiteAdapter]`, 'path:', path);
}
async connectIfNeeded() { async connectIfNeeded() {
if (!this.db) { if (!this.db) {
@@ -100,6 +100,7 @@ function getWorkspaceDB$(id: string) {
) )
); );
} }
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return db$Map.get(id)!; return db$Map.get(id)!;
} }
@@ -107,7 +107,6 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
return; return;
} }
this.firstConnected = true; this.firstConnected = true;
const { db } = this;
const onUpstreamUpdate = (update: Uint8Array, origin: YOrigin) => { const onUpstreamUpdate = (update: Uint8Array, origin: YOrigin) => {
if (origin === 'renderer') { if (origin === 'renderer') {
@@ -118,8 +117,8 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
const onSelfUpdate = (update: Uint8Array, origin: YOrigin) => { const onSelfUpdate = (update: Uint8Array, origin: YOrigin) => {
// for self update from upstream, we need to push it to external DB // for self update from upstream, we need to push it to external DB
if (origin === 'upstream') { if (origin === 'upstream' && this.db) {
this.addUpdateToUpdateQueue(db!, update); this.addUpdateToUpdateQueue(this.db, update);
} }
if (origin === 'self') { if (origin === 'self') {
@@ -67,7 +67,7 @@ export const registerUpdater = async () => {
// register events for checkForUpdatesAndNotify // register events for checkForUpdatesAndNotify
_autoUpdater.on('update-available', info => { _autoUpdater.on('update-available', info => {
if (allowAutoUpdate) { if (allowAutoUpdate) {
_autoUpdater!.downloadUpdate(); _autoUpdater?.downloadUpdate();
logger.info('Update available, downloading...', info); logger.info('Update available, downloading...', info);
} }
updaterSubjects.updateAvailable.next({ updaterSubjects.updateAvailable.next({
+2 -2
View File
@@ -14,7 +14,7 @@ export function getRequestResponseFromContext(context: ExecutionContext) {
}>(); }>();
return { return {
req: gqlContext.req, req: gqlContext.req,
res: gqlContext.req.res!, res: gqlContext.req.res,
}; };
} }
case 'http': { case 'http': {
@@ -37,7 +37,7 @@ export function getRequestResponseFromHost(host: ArgumentsHost) {
}>(); }>();
return { return {
req: gqlContext.req, req: gqlContext.req,
res: gqlContext.req.res!, res: gqlContext.req.res,
}; };
} }
case 'http': { case 'http': {
@@ -4,7 +4,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon, NewIcon, ResetIcon } from '@blocksuite/icons'; import { CloseIcon, NewIcon, ResetIcon } from '@blocksuite/icons';
import clsx from 'clsx'; import clsx from 'clsx';
import { atom, useAtomValue, useSetAtom } from 'jotai'; import { atom, useAtomValue, useSetAtom } from 'jotai';
import { startTransition } from 'react'; import { startTransition, useCallback } from 'react';
import * as styles from './index.css'; import * as styles from './index.css';
import { import {
@@ -50,53 +50,67 @@ export function AppUpdaterButton({ className, style }: AddPageButtonProps) {
const downloadProgress = useAtomValue(downloadProgressAtom); const downloadProgress = useAtomValue(downloadProgressAtom);
const setChangelogCheckAtom = useSetAtom(changelogCheckedAtom); const setChangelogCheckAtom = useSetAtom(changelogCheckedAtom);
const onDismissCurrentChangelog = () => { const onDismissCurrentChangelog = useCallback(() => {
if (!currentVersion) {
return;
}
startTransition(() => startTransition(() =>
setChangelogCheckAtom(mapping => { setChangelogCheckAtom(mapping => {
return { return {
...mapping, ...mapping,
[currentVersion!]: true, [currentVersion]: true,
}; };
}) })
); );
}; }, [currentVersion, setChangelogCheckAtom]);
const onClickUpdate = useCallback(() => {
if (updateReady) {
window.apis?.updater.quitAndInstall();
} else if (updateAvailable) {
if (updateAvailable.allowAutoUpdate) {
// wait for download to finish
} else {
window.open(
`https://github.com/toeverything/AFFiNE/releases/tag/v${currentVersion}`,
'_blank'
);
}
} else if (currentChangelogUnread) {
window.open(config.changelogUrl, '_blank');
onDismissCurrentChangelog();
} else {
throw new Unreachable();
}
}, [
currentChangelogUnread,
currentVersion,
onDismissCurrentChangelog,
updateAvailable,
updateReady,
]);
if (!updateAvailable && !currentChangelogUnread) { if (!updateAvailable && !currentChangelogUnread) {
return null; return null;
} }
const updateAvailableNode = updateAvailable
? updateAvailable.allowAutoUpdate
? renderUpdateAvailableAllowAutoUpdate()
: renderUpdateAvailableNotAllowAutoUpdate()
: null;
const whatsNew =
!updateAvailable && currentChangelogUnread ? renderWhatsNew() : null;
return ( return (
<button <button
style={style} style={style}
className={clsx([styles.root, className])} className={clsx([styles.root, className])}
data-has-update={updateAvailable ? 'true' : 'false'} data-has-update={updateAvailable ? 'true' : 'false'}
data-disabled={updateAvailable?.allowAutoUpdate && !updateReady} data-disabled={updateAvailable?.allowAutoUpdate && !updateReady}
onClick={() => { onClick={onClickUpdate}
if (updateReady) {
window.apis?.updater.quitAndInstall();
} else if (updateAvailable) {
if (updateAvailable.allowAutoUpdate) {
// wait for download to finish
} else {
window.open(
`https://github.com/toeverything/AFFiNE/releases/tag/v${currentVersion}`,
'_blank'
);
}
} else if (currentChangelogUnread) {
window.open(config.changelogUrl, '_blank');
onDismissCurrentChangelog();
} else {
throw new Unreachable();
}
}}
> >
{updateAvailable && {updateAvailableNode}
(updateAvailable.allowAutoUpdate {whatsNew}
? renderUpdateAvailableAllowAutoUpdate()
: renderUpdateAvailableNotAllowAutoUpdate())}
{!updateAvailable && currentChangelogUnread && renderWhatsNew()}
<div className={styles.particles} aria-hidden="true"></div> <div className={styles.particles} aria-hidden="true"></div>
<span className={styles.halo} aria-hidden="true"></span> <span className={styles.halo} aria-hidden="true"></span>
</button> </button>
@@ -8,7 +8,7 @@ import {
ExportToMarkdownIcon, ExportToMarkdownIcon,
ExportToPdfIcon, ExportToPdfIcon,
} from '@blocksuite/icons'; } from '@blocksuite/icons';
import { useRef } from 'react'; import { useCallback, useRef } from 'react';
import { Menu, MenuItem } from '../../..'; import { Menu, MenuItem } from '../../..';
import type { CommonMenuItemProps } from './types'; import type { CommonMenuItemProps } from './types';
@@ -18,35 +18,39 @@ const ExportToPdfMenuItem = ({
}: CommonMenuItemProps<{ type: 'pdf' }>) => { }: CommonMenuItemProps<{ type: 'pdf' }>) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const contentParserRef = useRef<ContentParser>(); const contentParserRef = useRef<ContentParser>();
return ( const { currentEditor } = globalThis;
<> const onClickDownloadPDF = useCallback(() => {
{globalThis.currentEditor!.mode === 'page' && ( if (!currentEditor) {
<MenuItem return;
data-testid="export-to-pdf" }
onClick={async () => { const contentParser =
if (!contentParserRef.current) { contentParserRef.current ??
contentParserRef.current = new ContentParser( (contentParserRef.current = new ContentParser(currentEditor.page));
globalThis.currentEditor!.page
); window.apis?.export
} .savePDFFileAs(
const result = await window.apis?.export.savePDFFileAs( (currentEditor.page.root as PageBlockModel).title.toString()
( )
globalThis.currentEditor!.page.root as PageBlockModel .then(result => {
).title.toString() if (result !== undefined) {
); return;
if (result !== undefined) { }
return; contentParser.exportPdf();
} onSelect?.({ type: 'pdf' });
contentParserRef.current.exportPdf(); });
onSelect?.({ type: 'pdf' }); }, [currentEditor, onSelect]);
}} if (currentEditor && currentEditor.mode === 'page') {
icon={<ExportToPdfIcon />} return (
> <MenuItem
{t['Export to PDF']()} data-testid="export-to-pdf"
</MenuItem> onClick={onClickDownloadPDF}
)} icon={<ExportToPdfIcon />}
</> >
); {t['Export to PDF']()}
</MenuItem>
);
}
return null;
}; };
const ExportToHtmlMenuItem = ({ const ExportToHtmlMenuItem = ({
@@ -54,19 +58,22 @@ const ExportToHtmlMenuItem = ({
}: CommonMenuItemProps<{ type: 'html' }>) => { }: CommonMenuItemProps<{ type: 'html' }>) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const contentParserRef = useRef<ContentParser>(); const contentParserRef = useRef<ContentParser>();
const { currentEditor } = globalThis;
const onClickExportHtml = useCallback(() => {
if (!currentEditor) {
return;
}
if (!contentParserRef.current) {
contentParserRef.current = new ContentParser(currentEditor.page);
}
contentParserRef.current.exportHtml();
onSelect?.({ type: 'html' });
}, [onSelect, currentEditor]);
return ( return (
<> <>
<MenuItem <MenuItem
data-testid="export-to-html" data-testid="export-to-html"
onClick={() => { onClick={onClickExportHtml}
if (!contentParserRef.current) {
contentParserRef.current = new ContentParser(
globalThis.currentEditor!.page
);
}
contentParserRef.current.exportHtml();
onSelect?.({ type: 'html' });
}}
icon={<ExportToHtmlIcon />} icon={<ExportToHtmlIcon />}
> >
{t['Export to HTML']()} {t['Export to HTML']()}
@@ -108,19 +115,22 @@ const ExportToMarkdownMenuItem = ({
}: CommonMenuItemProps<{ type: 'markdown' }>) => { }: CommonMenuItemProps<{ type: 'markdown' }>) => {
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const contentParserRef = useRef<ContentParser>(); const contentParserRef = useRef<ContentParser>();
const { currentEditor } = globalThis;
const onClickExportMarkdown = useCallback(() => {
if (!currentEditor) {
return;
}
if (!contentParserRef.current) {
contentParserRef.current = new ContentParser(currentEditor.page);
}
contentParserRef.current.exportMarkdown();
onSelect?.({ type: 'markdown' });
}, [onSelect, currentEditor]);
return ( return (
<> <>
<MenuItem <MenuItem
data-testid="export-to-markdown" data-testid="export-to-markdown"
onClick={() => { onClick={onClickExportMarkdown}
if (!contentParserRef.current) {
contentParserRef.current = new ContentParser(
globalThis.currentEditor!.page
);
}
contentParserRef.current.exportMarkdown();
onSelect?.({ type: 'markdown' });
}}
icon={<ExportToMarkdownIcon />} icon={<ExportToMarkdownIcon />}
> >
{t['Export to Markdown']()} {t['Export to Markdown']()}
@@ -12,7 +12,7 @@ import { useCallback, useState } from 'react';
import useCollapsed from './hooks/use-collapsed'; import useCollapsed from './hooks/use-collapsed';
import useSelectWithKeyboard from './hooks/use-select-with-keyboard'; import useSelectWithKeyboard from './hooks/use-select-with-keyboard';
import { TreeNode, TreeNodeWithDnd } from './tree-node'; import { TreeNode, TreeNodeWithDnd } from './tree-node';
import type { TreeViewProps } from './types'; import type { Node, TreeViewProps } from './types';
import { findNode } from './utils'; import { findNode } from './utils';
export const TreeView = <RenderProps,>({ export const TreeView = <RenderProps,>({
data, data,
@@ -58,6 +58,39 @@ export const TreeView = <RenderProps,>({
setDraggingId(e.active.id as string); setDraggingId(e.active.id as string);
}, []); }, []);
if (enableDnd) { if (enableDnd) {
const treeNodes = data.map((node, index) => (
<TreeNodeWithDnd
key={node.id}
index={index}
collapsedIds={collapsedIds}
setCollapsed={setCollapsed}
node={node}
selectedId={selectedId}
enableDnd={enableDnd}
disableCollapse={disableCollapse}
draggingId={draggingId}
{...otherProps}
/>
));
const draggingNode = (function () {
let draggingNode: Node<RenderProps> | undefined;
if (draggingId) {
draggingNode = findNode(draggingId, data);
}
if (draggingNode) {
return (
<TreeNode
node={draggingNode}
index={0}
allowDrop={false}
collapsedIds={collapsedIds}
setCollapsed={() => {}}
{...otherProps}
/>
);
}
return null;
})();
return ( return (
<DndContext <DndContext
sensors={sensors} sensors={sensors}
@@ -65,33 +98,8 @@ export const TreeView = <RenderProps,>({
onDragMove={onDragMove} onDragMove={onDragMove}
onDragEnd={onDragEnd} onDragEnd={onDragEnd}
> >
{data.map((node, index) => ( {treeNodes}
<TreeNodeWithDnd <DragOverlay>{draggingNode}</DragOverlay>
key={node.id}
index={index}
collapsedIds={collapsedIds}
setCollapsed={setCollapsed}
node={node}
selectedId={selectedId}
enableDnd={enableDnd}
disableCollapse={disableCollapse}
draggingId={draggingId}
{...otherProps}
/>
))}
<DragOverlay>
{draggingId && (
<TreeNode
node={findNode(draggingId, data)!}
index={0}
allowDrop={false}
collapsedIds={collapsedIds}
setCollapsed={() => {}}
{...otherProps}
/>
)}
</DragOverlay>
</DndContext> </DndContext>
); );
} }
@@ -46,14 +46,15 @@ export function useAffineListener(
if (!fnRef.current) { if (!fnRef.current) {
fnRef.current = listener; fnRef.current = listener;
} }
const ipcListener = fnRef.current ?? (fnRef.current = listener);
useEffect(() => { useEffect(() => {
if (once) { if (once) {
window.affine.ipcRenderer.once(channel, fnRef.current!); window.affine.ipcRenderer.once(channel, ipcListener);
} else { } else {
window.affine.ipcRenderer.on(channel, fnRef.current!); window.affine.ipcRenderer.on(channel, ipcListener);
} }
return () => { return () => {
window.affine.ipcRenderer.removeListener(channel, fnRef.current!); window.affine.ipcRenderer.removeListener(channel, ipcListener);
}; };
}, [channel, once]); }, [channel, once, ipcListener]);
} }
@@ -33,7 +33,7 @@ export const createBroadCastChannelProvider = (
case 'doc:diff': { case 'doc:diff': {
const [, diff, clientId] = event.data; const [, diff, clientId] = event.data;
const update = Y.encodeStateAsUpdate(doc, diff); const update = Y.encodeStateAsUpdate(doc, diff);
broadcastChannel!.postMessage(['doc:update', update, clientId]); broadcastChannel?.postMessage(['doc:update', update, clientId]);
break; break;
} }
case 'doc:update': { case 'doc:update': {
@@ -47,7 +47,7 @@ export const createBroadCastChannelProvider = (
const [, clientId] = event.data; const [, clientId] = event.data;
const clients = getClients(awareness); const clients = getClients(awareness);
const update = encodeAwarenessUpdate(awareness, clients); const update = encodeAwarenessUpdate(awareness, clients);
broadcastChannel!.postMessage(['awareness:update', update, clientId]); broadcastChannel?.postMessage(['awareness:update', update, clientId]);
break; break;
} }
case 'awareness:update': { case 'awareness:update': {
+3 -4
View File
@@ -162,8 +162,7 @@ const sqliteOrigin = Symbol('sqlite-provider-origin');
const createSQLiteProvider = ( const createSQLiteProvider = (
blockSuiteWorkspace: BlockSuiteWorkspace blockSuiteWorkspace: BlockSuiteWorkspace
): SQLiteProvider => { ): SQLiteProvider => {
const apis = window.apis!; const { apis, events } = window;
const events = window.events!;
// make sure it is being used in Electron with APIs // make sure it is being used in Electron with APIs
assertExists(apis); assertExists(apis);
assertExists(events); assertExists(events);
@@ -216,7 +215,7 @@ const createSQLiteProvider = (
const createSQLiteDBDownloadProvider = ( const createSQLiteDBDownloadProvider = (
blockSuiteWorkspace: BlockSuiteWorkspace blockSuiteWorkspace: BlockSuiteWorkspace
): SQLiteDBDownloadProvider => { ): SQLiteDBDownloadProvider => {
const apis = window.apis!; const { apis } = window;
let disconnected = false; let disconnected = false;
let _resolve: () => void; let _resolve: () => void;
@@ -273,7 +272,7 @@ const createSQLiteDBDownloadProvider = (
return; return;
} }
return window.apis?.db.addBlob( return apis?.db.addBlob(
blockSuiteWorkspace.id, blockSuiteWorkspace.id,
k, k,
new Uint8Array(await blob.arrayBuffer()) new Uint8Array(await blob.arrayBuffer())
+4 -3
View File
@@ -65,9 +65,10 @@ export function createEmptyBlockSuiteWorkspace(
const blobStorages: StoreOptions['blobStorages'] = []; const blobStorages: StoreOptions['blobStorages'] = [];
if (flavour === WorkspaceFlavour.AFFINE) { if (flavour === WorkspaceFlavour.AFFINE) {
blobStorages.push(id => if (config && config.workspaceApis) {
createAffineBlobStorage(id, config!.workspaceApis!) const workspaceApis = config.workspaceApis;
); blobStorages.push(id => createAffineBlobStorage(id, workspaceApis));
}
} else { } else {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
blobStorages.push(createIndexeddbStorage); blobStorages.push(createIndexeddbStorage);
+3 -3
View File
@@ -47,8 +47,8 @@ const handleEnter = ({
null null
>; >;
const vEditor = getVirgoByModel(blockRange.models[0]); const vEditor = getVirgoByModel(blockRange.models[0]);
const linkInfo = vEditor! const linkInfo = vEditor
.getDeltasByVRange({ ?.getDeltasByVRange({
index: blockRange.startOffset, index: blockRange.startOffset,
length: 0, length: 0,
}) })
@@ -70,7 +70,7 @@ const handleEnter = ({
currentBlockIndex + 1 currentBlockIndex + 1
); );
vEditor!.deleteText({ vEditor?.deleteText({
index, index,
length, length,
}); });
+16
View File
@@ -0,0 +1,16 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"allowJs": true
},
"include": ["."],
"exclude": [
"target",
"node_modules",
"dist",
"lib",
".coverage",
".yarn",
"test-results"
]
}