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

Co-authored-by: Peng Xiao <pengxiao@outlook.com>
This commit is contained in:
LongYinan 2023-06-08 15:23:20 +08:00 committed by GitHub
parent 1ad2e629ac
commit 18dc427bc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 196 additions and 147 deletions

View File

@ -6,3 +6,4 @@ storybook-static
affine-out
_next
lib
.eslintrc.js

View File

@ -1,3 +1,5 @@
const { readdirSync, statSync } = require('fs');
const createPattern = packageName => [
{
group: ['**/dist', '**/dist/**'],
@ -21,22 +23,14 @@ const createPattern = packageName => [
},
];
const allPackages = [
'cli',
'component',
'debug',
'env',
'graphql',
'hooks',
'i18n',
'jotai',
'native',
'plugin-infra',
'templates',
'theme',
'workspace',
'y-indexeddb',
];
const pkgs = readdirSync('./packages').filter(pkg => {
return statSync(`./packages/${pkg}`).isDirectory();
});
const apps = readdirSync('./apps').filter(pkg => {
return statSync(`./apps/${pkg}`).isDirectory();
});
const allPackages = pkgs.concat(apps);
/**
* @type {import('eslint').Linter.Config}
@ -67,6 +61,7 @@ const config = {
},
ecmaVersion: 'latest',
sourceType: 'module',
project: './tsconfig.eslint.json',
},
plugins: [
'react',
@ -83,7 +78,7 @@ const config = {
'no-cond-assign': 'off',
'react/prop-types': 'off',
'@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-empty-function': 'off',
'@typescript-eslint/no-unused-vars': [
@ -136,6 +131,12 @@ const config = {
'@typescript-eslint/no-var-requires': 0,
},
},
{
files: ['**/__tests__/**/*', '**/*.stories.tsx'],
rules: {
'@typescript-eslint/no-non-null-assertion': 0,
},
},
...allPackages.map(pkg => ({
files: [`packages/${pkg}/src/**/*.ts`, `packages/${pkg}/src/**/*.tsx`],
rules: {

View File

@ -10,9 +10,7 @@ export abstract class BaseSQLiteAdapter {
db: SqliteConnection | null = null;
abstract role: string;
constructor(public readonly path: string) {
logger.info(`[SQLiteAdapter]`, 'path:', path);
}
constructor(public readonly path: string) {}
async connectIfNeeded() {
if (!this.db) {

View File

@ -100,6 +100,7 @@ function getWorkspaceDB$(id: string) {
)
);
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return db$Map.get(id)!;
}

View File

@ -107,7 +107,6 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
return;
}
this.firstConnected = true;
const { db } = this;
const onUpstreamUpdate = (update: Uint8Array, origin: YOrigin) => {
if (origin === 'renderer') {
@ -118,8 +117,8 @@ export class SecondaryWorkspaceSQLiteDB extends BaseSQLiteAdapter {
const onSelfUpdate = (update: Uint8Array, origin: YOrigin) => {
// for self update from upstream, we need to push it to external DB
if (origin === 'upstream') {
this.addUpdateToUpdateQueue(db!, update);
if (origin === 'upstream' && this.db) {
this.addUpdateToUpdateQueue(this.db, update);
}
if (origin === 'self') {

View File

@ -67,7 +67,7 @@ export const registerUpdater = async () => {
// register events for checkForUpdatesAndNotify
_autoUpdater.on('update-available', info => {
if (allowAutoUpdate) {
_autoUpdater!.downloadUpdate();
_autoUpdater?.downloadUpdate();
logger.info('Update available, downloading...', info);
}
updaterSubjects.updateAvailable.next({

View File

@ -14,7 +14,7 @@ export function getRequestResponseFromContext(context: ExecutionContext) {
}>();
return {
req: gqlContext.req,
res: gqlContext.req.res!,
res: gqlContext.req.res,
};
}
case 'http': {
@ -37,7 +37,7 @@ export function getRequestResponseFromHost(host: ArgumentsHost) {
}>();
return {
req: gqlContext.req,
res: gqlContext.req.res!,
res: gqlContext.req.res,
};
}
case 'http': {

View File

@ -4,7 +4,7 @@ import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { CloseIcon, NewIcon, ResetIcon } from '@blocksuite/icons';
import clsx from 'clsx';
import { atom, useAtomValue, useSetAtom } from 'jotai';
import { startTransition } from 'react';
import { startTransition, useCallback } from 'react';
import * as styles from './index.css';
import {
@ -50,53 +50,67 @@ export function AppUpdaterButton({ className, style }: AddPageButtonProps) {
const downloadProgress = useAtomValue(downloadProgressAtom);
const setChangelogCheckAtom = useSetAtom(changelogCheckedAtom);
const onDismissCurrentChangelog = () => {
const onDismissCurrentChangelog = useCallback(() => {
if (!currentVersion) {
return;
}
startTransition(() =>
setChangelogCheckAtom(mapping => {
return {
...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) {
return null;
}
const updateAvailableNode = updateAvailable
? updateAvailable.allowAutoUpdate
? renderUpdateAvailableAllowAutoUpdate()
: renderUpdateAvailableNotAllowAutoUpdate()
: null;
const whatsNew =
!updateAvailable && currentChangelogUnread ? renderWhatsNew() : null;
return (
<button
style={style}
className={clsx([styles.root, className])}
data-has-update={updateAvailable ? 'true' : 'false'}
data-disabled={updateAvailable?.allowAutoUpdate && !updateReady}
onClick={() => {
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();
}
}}
onClick={onClickUpdate}
>
{updateAvailable &&
(updateAvailable.allowAutoUpdate
? renderUpdateAvailableAllowAutoUpdate()
: renderUpdateAvailableNotAllowAutoUpdate())}
{!updateAvailable && currentChangelogUnread && renderWhatsNew()}
{updateAvailableNode}
{whatsNew}
<div className={styles.particles} aria-hidden="true"></div>
<span className={styles.halo} aria-hidden="true"></span>
</button>

View File

@ -8,7 +8,7 @@ import {
ExportToMarkdownIcon,
ExportToPdfIcon,
} from '@blocksuite/icons';
import { useRef } from 'react';
import { useCallback, useRef } from 'react';
import { Menu, MenuItem } from '../../..';
import type { CommonMenuItemProps } from './types';
@ -18,35 +18,39 @@ const ExportToPdfMenuItem = ({
}: CommonMenuItemProps<{ type: 'pdf' }>) => {
const t = useAFFiNEI18N();
const contentParserRef = useRef<ContentParser>();
return (
<>
{globalThis.currentEditor!.mode === 'page' && (
<MenuItem
data-testid="export-to-pdf"
onClick={async () => {
if (!contentParserRef.current) {
contentParserRef.current = new ContentParser(
globalThis.currentEditor!.page
);
}
const result = await window.apis?.export.savePDFFileAs(
(
globalThis.currentEditor!.page.root as PageBlockModel
).title.toString()
);
if (result !== undefined) {
return;
}
contentParserRef.current.exportPdf();
onSelect?.({ type: 'pdf' });
}}
icon={<ExportToPdfIcon />}
>
{t['Export to PDF']()}
</MenuItem>
)}
</>
);
const { currentEditor } = globalThis;
const onClickDownloadPDF = useCallback(() => {
if (!currentEditor) {
return;
}
const contentParser =
contentParserRef.current ??
(contentParserRef.current = new ContentParser(currentEditor.page));
window.apis?.export
.savePDFFileAs(
(currentEditor.page.root as PageBlockModel).title.toString()
)
.then(result => {
if (result !== undefined) {
return;
}
contentParser.exportPdf();
onSelect?.({ type: 'pdf' });
});
}, [currentEditor, onSelect]);
if (currentEditor && currentEditor.mode === 'page') {
return (
<MenuItem
data-testid="export-to-pdf"
onClick={onClickDownloadPDF}
icon={<ExportToPdfIcon />}
>
{t['Export to PDF']()}
</MenuItem>
);
}
return null;
};
const ExportToHtmlMenuItem = ({
@ -54,19 +58,22 @@ const ExportToHtmlMenuItem = ({
}: CommonMenuItemProps<{ type: 'html' }>) => {
const t = useAFFiNEI18N();
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 (
<>
<MenuItem
data-testid="export-to-html"
onClick={() => {
if (!contentParserRef.current) {
contentParserRef.current = new ContentParser(
globalThis.currentEditor!.page
);
}
contentParserRef.current.exportHtml();
onSelect?.({ type: 'html' });
}}
onClick={onClickExportHtml}
icon={<ExportToHtmlIcon />}
>
{t['Export to HTML']()}
@ -108,19 +115,22 @@ const ExportToMarkdownMenuItem = ({
}: CommonMenuItemProps<{ type: 'markdown' }>) => {
const t = useAFFiNEI18N();
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 (
<>
<MenuItem
data-testid="export-to-markdown"
onClick={() => {
if (!contentParserRef.current) {
contentParserRef.current = new ContentParser(
globalThis.currentEditor!.page
);
}
contentParserRef.current.exportMarkdown();
onSelect?.({ type: 'markdown' });
}}
onClick={onClickExportMarkdown}
icon={<ExportToMarkdownIcon />}
>
{t['Export to Markdown']()}

View File

@ -12,7 +12,7 @@ import { useCallback, useState } from 'react';
import useCollapsed from './hooks/use-collapsed';
import useSelectWithKeyboard from './hooks/use-select-with-keyboard';
import { TreeNode, TreeNodeWithDnd } from './tree-node';
import type { TreeViewProps } from './types';
import type { Node, TreeViewProps } from './types';
import { findNode } from './utils';
export const TreeView = <RenderProps,>({
data,
@ -58,6 +58,39 @@ export const TreeView = <RenderProps,>({
setDraggingId(e.active.id as string);
}, []);
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 (
<DndContext
sensors={sensors}
@ -65,33 +98,8 @@ export const TreeView = <RenderProps,>({
onDragMove={onDragMove}
onDragEnd={onDragEnd}
>
{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}
/>
))}
<DragOverlay>
{draggingId && (
<TreeNode
node={findNode(draggingId, data)!}
index={0}
allowDrop={false}
collapsedIds={collapsedIds}
setCollapsed={() => {}}
{...otherProps}
/>
)}
</DragOverlay>
{treeNodes}
<DragOverlay>{draggingNode}</DragOverlay>
</DndContext>
);
}

View File

@ -46,14 +46,15 @@ export function useAffineListener(
if (!fnRef.current) {
fnRef.current = listener;
}
const ipcListener = fnRef.current ?? (fnRef.current = listener);
useEffect(() => {
if (once) {
window.affine.ipcRenderer.once(channel, fnRef.current!);
window.affine.ipcRenderer.once(channel, ipcListener);
} else {
window.affine.ipcRenderer.on(channel, fnRef.current!);
window.affine.ipcRenderer.on(channel, ipcListener);
}
return () => {
window.affine.ipcRenderer.removeListener(channel, fnRef.current!);
window.affine.ipcRenderer.removeListener(channel, ipcListener);
};
}, [channel, once]);
}, [channel, once, ipcListener]);
}

View File

@ -33,7 +33,7 @@ export const createBroadCastChannelProvider = (
case 'doc:diff': {
const [, diff, clientId] = event.data;
const update = Y.encodeStateAsUpdate(doc, diff);
broadcastChannel!.postMessage(['doc:update', update, clientId]);
broadcastChannel?.postMessage(['doc:update', update, clientId]);
break;
}
case 'doc:update': {
@ -47,7 +47,7 @@ export const createBroadCastChannelProvider = (
const [, clientId] = event.data;
const clients = getClients(awareness);
const update = encodeAwarenessUpdate(awareness, clients);
broadcastChannel!.postMessage(['awareness:update', update, clientId]);
broadcastChannel?.postMessage(['awareness:update', update, clientId]);
break;
}
case 'awareness:update': {

View File

@ -162,8 +162,7 @@ const sqliteOrigin = Symbol('sqlite-provider-origin');
const createSQLiteProvider = (
blockSuiteWorkspace: BlockSuiteWorkspace
): SQLiteProvider => {
const apis = window.apis!;
const events = window.events!;
const { apis, events } = window;
// make sure it is being used in Electron with APIs
assertExists(apis);
assertExists(events);
@ -216,7 +215,7 @@ const createSQLiteProvider = (
const createSQLiteDBDownloadProvider = (
blockSuiteWorkspace: BlockSuiteWorkspace
): SQLiteDBDownloadProvider => {
const apis = window.apis!;
const { apis } = window;
let disconnected = false;
let _resolve: () => void;
@ -273,7 +272,7 @@ const createSQLiteDBDownloadProvider = (
return;
}
return window.apis?.db.addBlob(
return apis?.db.addBlob(
blockSuiteWorkspace.id,
k,
new Uint8Array(await blob.arrayBuffer())

View File

@ -65,9 +65,10 @@ export function createEmptyBlockSuiteWorkspace(
const blobStorages: StoreOptions['blobStorages'] = [];
if (flavour === WorkspaceFlavour.AFFINE) {
blobStorages.push(id =>
createAffineBlobStorage(id, config!.workspaceApis!)
);
if (config && config.workspaceApis) {
const workspaceApis = config.workspaceApis;
blobStorages.push(id => createAffineBlobStorage(id, workspaceApis));
}
} else {
if (typeof window !== 'undefined') {
blobStorages.push(createIndexeddbStorage);

View File

@ -47,8 +47,8 @@ const handleEnter = ({
null
>;
const vEditor = getVirgoByModel(blockRange.models[0]);
const linkInfo = vEditor!
.getDeltasByVRange({
const linkInfo = vEditor
?.getDeltasByVRange({
index: blockRange.startOffset,
length: 0,
})
@ -70,7 +70,7 @@ const handleEnter = ({
currentBlockIndex + 1
);
vEditor!.deleteText({
vEditor?.deleteText({
index,
length,
});

16
tsconfig.eslint.json Normal file
View File

@ -0,0 +1,16 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"allowJs": true
},
"include": ["."],
"exclude": [
"target",
"node_modules",
"dist",
"lib",
".coverage",
".yarn",
"test-results"
]
}