Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: YASNCB (Yet Another Strict Null Checks Branch) #1703

Draft
wants to merge 73 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 69 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
aa2250b
Fix nulls/undefineds and improve repl info parser for params
corasaurus-hex Apr 30, 2022
a697c11
Stop using deprecated function and use our own instead
corasaurus-hex Apr 30, 2022
02d317d
Fix an undefined value in utilities
corasaurus-hex Apr 30, 2022
02960ca
Help typescript out with some type assertions in cursor-doc model
corasaurus-hex Apr 30, 2022
0f72124
Fix null checks for project-types
corasaurus-hex May 1, 2022
9426650
Fix null checks for completion provider
corasaurus-hex May 1, 2022
c73129e
Fix nulls in src/connector.ts
corasaurus-hex May 1, 2022
3dd6b85
Fix null in src/converters.ts
corasaurus-hex May 1, 2022
6e6640f
Fix nulls in cursor-doc indent and improve performance
corasaurus-hex May 1, 2022
5aad863
Fix nulls in clojure-lexer
corasaurus-hex May 1, 2022
0510888
Fix nulls in lexer
corasaurus-hex May 1, 2022
347e7eb
Fix nulls in paredit.ts
corasaurus-hex May 1, 2022
2527798
Fix nulls for _rangesForSexpsInList
corasaurus-hex May 1, 2022
be6765b
Fix remaining nulls for token-cursor.ts
corasaurus-hex May 1, 2022
46fa6ad
Fix more nulls in paredit
corasaurus-hex May 1, 2022
ff9f09d
Fix nulls in undo.ts
corasaurus-hex May 1, 2022
6e17118
Fix types for evaluating custom snippets
corasaurus-hex May 1, 2022
39c3208
Fix types in repl-session
corasaurus-hex May 1, 2022
e2622a2
Fix null checks for clojuredocs
corasaurus-hex May 1, 2022
7f77027
Fix debugger-related null checks
corasaurus-hex May 1, 2022
28cf0f3
Fix more null checks in the debugger
corasaurus-hex May 1, 2022
25af9b9
Another null check issue fixed, this time in doc-mirror
corasaurus-hex May 1, 2022
4043c42
Fix null check issues in src/evaluate.ts
corasaurus-hex May 1, 2022
74ab8f9
Fix some null check issues in extension tests
corasaurus-hex May 1, 2022
6a04743
Fix null check issues in src/extension.ts
corasaurus-hex May 1, 2022
b91edb1
Fix a ton of null check issues in extension.ts for highlighting
corasaurus-hex May 1, 2022
a58fbf1
Fix live-share.ts null check issues
corasaurus-hex May 1, 2022
30cc621
Fix a number of strict null check issues in lsp code
corasaurus-hex May 1, 2022
6e26d72
Fix null check issues in providers
corasaurus-hex May 1, 2022
387326f
Fix strict null check issue in repl-history.ts
corasaurus-hex May 1, 2022
c8c7ee7
Fix strict null check issue in when-contexts.ts
corasaurus-hex May 1, 2022
cb079b5
Fix strict null check issues for src/connector.ts
corasaurus-hex May 1, 2022
20d0f2c
Fix strict null check issues for src/evaluate.ts
corasaurus-hex May 1, 2022
1cddd85
Fix strict null check issues for src/nrepl/bencode.ts
corasaurus-hex May 1, 2022
49a0f0f
Fix strict null check issues for src/nrepl/connectSequence.ts
corasaurus-hex May 1, 2022
b9cdce3
Fix strict null check issues for src/nrepl/index.ts
corasaurus-hex May 1, 2022
d608036
Fix strict null check issues for src/nrepl/jack-in-terminal.ts
corasaurus-hex May 1, 2022
b33def4
Fix strict null check issues for src/nrepl/jack-in.ts
corasaurus-hex May 1, 2022
2dc2abf
Fix strict null check issues for src/nrepl/project-types.ts
corasaurus-hex May 1, 2022
407b589
Fix strict null check issues for src/nrepl/repl-start.ts
corasaurus-hex May 1, 2022
5e2ed4b
Fix strict null check issues for src/paredit/extension.ts
corasaurus-hex May 1, 2022
6ff6e9b
Fix strict null check issues for src/project-root.ts
corasaurus-hex May 1, 2022
e8c4cb1
Fix strict null check issues for src/providers/definition.ts
corasaurus-hex May 1, 2022
b6b8db4
Fix strict null check issues for src/providers/signature.ts
corasaurus-hex May 1, 2022
aea1267
Fix strict null check issues for src/refresh.ts
corasaurus-hex May 1, 2022
c6fd333
Fix strict null check issues for src/results-output/results-doc.ts
corasaurus-hex May 1, 2022
8425e1a
Fix strict null check issues for src/statusbar.ts
corasaurus-hex May 1, 2022
c3735e3
Fix strict null check issues for src/testRunner.ts
corasaurus-hex May 1, 2022
1cdcd92
Fix strict null check issues for src/util/cursor-get-text.ts
corasaurus-hex May 1, 2022
fcf14e0
Enable strict null checks for typescript
corasaurus-hex May 1, 2022
adfe62a
Run prettier formatter
corasaurus-hex May 1, 2022
b119164
Rename getSession => tryToGetSession and make another getSession
corasaurus-hex May 1, 2022
5da7c79
Restore whitespace
corasaurus-hex May 1, 2022
ac8f195
Remove assertions that are no longer needed due to new function
corasaurus-hex May 1, 2022
c1417db
No need to set to a variable before use
corasaurus-hex May 1, 2022
f310e64
More formatting
corasaurus-hex May 1, 2022
859a6bb
Minimize more changes to connector
corasaurus-hex May 1, 2022
7ac6f57
Whoops, use a record type not a map type
corasaurus-hex May 1, 2022
1e921d6
Ensure same behavior here
corasaurus-hex May 1, 2022
61437fe
Split project root local fns into tryToGet + get
corasaurus-hex May 1, 2022
d0cd19c
Move undefined check closer to where it is needed
corasaurus-hex May 1, 2022
0778866
Forgot a bang !
corasaurus-hex May 1, 2022
857054d
Swap out use of regex for much faster substring search
corasaurus-hex May 1, 2022
5106ee8
Use function instead of try + assert
corasaurus-hex May 1, 2022
18d7761
Compare against the right types
corasaurus-hex May 1, 2022
3d1aec3
Get rid of unused import
corasaurus-hex May 1, 2022
b83c26d
Move type checking functions out to an ns without vscode
corasaurus-hex May 1, 2022
13d7e82
Fix some null checks
corasaurus-hex May 1, 2022
bc7fadd
Fix the token cursor genericized function
corasaurus-hex May 1, 2022
cd0d893
Reformat the tsconfig
corasaurus-hex May 1, 2022
6414e32
Use prettier to format JSON files in the project
corasaurus-hex May 1, 2022
f5c1fbd
Debug lsp downloads
corasaurus-hex May 1, 2022
df16f82
Add some debugging so we can figure out why integration tests are
corasaurus-hex May 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { PrettyPrintingOptions } from './printer';
import { parseEdn } from '../out/cljs-lib/cljs-lib';
import * as state from './state';
import _ = require('lodash');
import { isDefined } from './utilities';
import { isDefined } from './type-checks';

const REPL_FILE_EXT = 'calva-repl';
const KEYBINDINGS_ENABLED_CONFIG_KEY = 'calva.keybindingsEnabled';
Expand Down
79 changes: 47 additions & 32 deletions src/connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import * as clojureDocs from './clojuredocs';
import * as jszip from 'jszip';
import { addEdnConfig } from './config';
import { getJarContents } from './utilities';
import { assertIsDefined, isNonEmptyString } from './type-checks';

async function readJarContent(uri: string) {
try {
Expand All @@ -39,6 +40,7 @@ async function readJarContent(uri: string) {
}

async function readRuntimeConfigs() {
assertIsDefined(nClient, 'Expected there to be an nREPL client!');
const classpath = await nClient.session.classpath().catch((e) => {
console.error('readRuntimeConfigs:', e);
});
Expand All @@ -55,7 +57,7 @@ async function readRuntimeConfigs() {

// maybe we don't need to keep uri -> edn association, but it would make showing errors easier later
return files
.filter(([_, config]) => util.isNonEmptyString(config))
.filter(([_, config]) => isNonEmptyString(config))
.map(([_, config]) => addEdnConfig(config));
}
}
Expand Down Expand Up @@ -112,12 +114,9 @@ async function connectToHost(hostname: string, port: number, connectSequence: Re

if (connectSequence.afterCLJReplJackInCode) {
outputWindow.append(`\n; Evaluating 'afterCLJReplJackInCode'`);
await evaluateInOutputWindow(
connectSequence.afterCLJReplJackInCode,
'clj',
outputWindow.getNs(),
{}
);
const ns = outputWindow.getNs();
assertIsDefined(ns, 'Expected outputWindow to have a namespace!');
await evaluateInOutputWindow(connectSequence.afterCLJReplJackInCode, 'clj', ns, {});
}

outputWindow.appendPrompt();
Expand Down Expand Up @@ -371,15 +370,23 @@ function createCLJSReplType(
'cljsReplTypeHasBuilds',
cljsType.buildsRequired
);
let initCode = cljsType.connectCode,
build: string = null;
let initCode: typeof cljsType.connectCode | undefined = cljsType.connectCode,
build: string | null = null;
if (menuSelections && menuSelections.cljsDefaultBuild && useDefaultBuild) {
build = menuSelections.cljsDefaultBuild;
useDefaultBuild = false;
} else {
if (typeof initCode === 'object' || initCode.includes('%BUILD%')) {
const buildsForSelection = startedBuilds
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I stopped and wondered if we somehow consider the current selection here. Maybe name it builds? Or buildCandidates?.

? startedBuilds
: await figwheelOrShadowBuilds(cljsTypeName);
assertIsDefined(
buildsForSelection,
'Expected there to be figwheel or shadowcljs builds!'
);

build = await util.quickPickSingle({
values: startedBuilds ? startedBuilds : await figwheelOrShadowBuilds(cljsTypeName),
values: buildsForSelection,
placeHolder: 'Select which build to connect to',
saveAs: `${state.getProjectRootUri().toString()}/${cljsTypeName.replace(
' ',
Expand Down Expand Up @@ -415,12 +422,10 @@ function createCLJSReplType(
);
},
connected: (result, out, err) => {
if (cljsType.isConnectedRegExp) {
return (
[...out, result].find((x) => {
return x.search(cljsType.isConnectedRegExp) >= 0;
}) != undefined
);
const { isConnectedRegExp } = cljsType;

if (isConnectedRegExp) {
return [...out, result].find((x) => x.search(isConnectedRegExp) >= 0) !== undefined;
} else {
return true;
}
Expand All @@ -431,12 +436,15 @@ function createCLJSReplType(
replType.start = async (session, name, checkFn) => {
let startCode = cljsType.startCode;
if (!hasStarted) {
assertIsDefined(startCode, 'Expected startCode to be defined!');
if (startCode.includes('%BUILDS')) {
let builds: string[];
if (menuSelections && menuSelections.cljsLaunchBuilds) {
builds = menuSelections.cljsLaunchBuilds;
} else {
const allBuilds = await figwheelOrShadowBuilds(cljsTypeName);
assertIsDefined(allBuilds, 'Expected there to be figwheel or shadowcljs builds!');

builds =
allBuilds.length <= 1
? allBuilds
Expand Down Expand Up @@ -494,11 +502,11 @@ function createCLJSReplType(
}

replType.started = (result, out, err) => {
if (cljsType.isReadyToStartRegExp && !hasStarted) {
const { isReadyToStartRegExp } = cljsType;

if (isReadyToStartRegExp && !hasStarted) {
const started =
[...out, ...err].find((x) => {
return x.search(cljsType.isReadyToStartRegExp) >= 0;
}) != undefined;
[...out, ...err].find((x) => x.search(isReadyToStartRegExp) >= 0) !== undefined;
if (started) {
hasStarted = true;
}
Expand All @@ -512,7 +520,7 @@ function createCLJSReplType(
return replType;
}

async function makeCljsSessionClone(session, repl: ReplType, projectTypeName: string) {
async function makeCljsSessionClone(session, repl: ReplType, projectTypeName: string | undefined) {
outputWindow.append('; Creating cljs repl session...');
let newCljsSession = await session.clone();
newCljsSession.replType = 'cljs';
Expand All @@ -521,7 +529,8 @@ async function makeCljsSessionClone(session, repl: ReplType, projectTypeName: st
outputWindow.append(
'; The Calva Connection Log might have more connection progress information.'
);
if (repl.start != undefined) {
if (repl.start !== undefined) {
assertIsDefined(repl.started, "Expected repl to have a 'started' check function!");
if (await repl.start(newCljsSession, repl.name, repl.started)) {
state.analytics().logEvent('REPL', 'StartedCLJS', repl.name).send();
outputWindow.append('; Cljs builds started');
Expand All @@ -534,6 +543,9 @@ async function makeCljsSessionClone(session, repl: ReplType, projectTypeName: st
return [null, null];
}
}

assertIsDefined(repl.connect, 'Expected repl to have a connect function!');

if (await repl.connect(newCljsSession, repl.name, repl.connected)) {
state.analytics().logEvent('REPL', 'ConnectedCLJS', repl.name).send();
setStateValue('cljs', (cljsSession = newCljsSession));
Expand Down Expand Up @@ -588,7 +600,7 @@ async function promptForNreplUrlAndConnect(port, connectSequence: ReplConnectSeq
return true;
}

export let nClient: NReplClient;
export let nClient: NReplClient | undefined;
export let cljSession: NReplSession;
export let cljsSession: NReplSession;

Expand Down Expand Up @@ -637,7 +649,7 @@ export async function connect(
return true;
}

async function standaloneConnect(connectSequence: ReplConnectSequence) {
async function standaloneConnect(connectSequence: ReplConnectSequence | undefined) {
await outputWindow.initResultsDoc();
await outputWindow.openResultsDoc();

Expand Down Expand Up @@ -701,20 +713,21 @@ export default {
// the REPL client was connected.
nClient.close();
}

liveShareSupport.didDisconnectRepl();
nClient = undefined;
}

callback();
},
toggleCLJCSession: () => {
let newSession: NReplSession;
let newSession: NReplSession | undefined;

if (getStateValue('connected')) {
if (replSession.getSession('cljc') == replSession.getSession('cljs')) {
newSession = replSession.getSession('clj');
} else if (replSession.getSession('cljc') == replSession.getSession('clj')) {
newSession = replSession.getSession('cljs');
if (replSession.tryToGetSession('cljc') == replSession.tryToGetSession('cljs')) {
newSession = replSession.tryToGetSession('clj');
} else if (replSession.tryToGetSession('cljc') == replSession.tryToGetSession('clj')) {
newSession = replSession.tryToGetSession('cljs');
}
setStateValue('cljc', newSession);
if (outputWindow.isResultsDoc(util.getActiveTextEditor().document)) {
Expand All @@ -726,9 +739,11 @@ export default {
}
},
switchCljsBuild: async () => {
const cljSession = replSession.getSession('clj');
const cljsTypeName: string = state.extensionContext.workspaceState.get('selectedCljsTypeName'),
cljTypeName: string = state.extensionContext.workspaceState.get('selectedCljTypeName');
const cljSession = replSession.tryToGetSession('clj');
const cljsTypeName: string | undefined =
state.extensionContext.workspaceState.get('selectedCljsTypeName'),
cljTypeName: string | undefined =
state.extensionContext.workspaceState.get('selectedCljTypeName');
state.analytics().logEvent('REPL', 'switchCljsBuild', cljsTypeName).send();

const [session, build] = await makeCljsSessionClone(
Expand Down
3 changes: 2 additions & 1 deletion src/converters.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as vscode from 'vscode';
import * as calvaLib from '../out/cljs-lib/cljs-lib';
import { getActiveTextEditor } from './utilities';

type Js2CljsResult = {
result: string;
Expand All @@ -23,7 +24,7 @@ type Js2CljsInvalidResult = {
const isJs2CljsResult = (input: any): input is Js2CljsResult => input.result !== undefined;

export async function js2cljs() {
const editor = vscode.window.activeTextEditor;
const editor = getActiveTextEditor();
const selection = editor.selection;
const doc = editor.document;
const js = doc.getText(
Expand Down
4 changes: 2 additions & 2 deletions src/cursor-doc/clojure-lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function validPair(open: string, close: string): boolean {
}

export interface Token extends LexerToken {
state: ScannerState;
state: ScannerState | null;
}

// whitespace, excluding newlines
Expand Down Expand Up @@ -174,7 +174,7 @@ export class Scanner {
const tks: Token[] = [];
this.state = state;
let lex = (this.state.inString ? inString : toplevel).lex(line, this.maxLength);
let tk: LexerToken;
let tk: LexerToken | undefined;
do {
tk = lex.scan();
if (tk) {
Expand Down
23 changes: 16 additions & 7 deletions src/cursor-doc/indent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const indentRules: IndentRules = {
*/
export interface IndentInformation {
/** The first token in the expression (after the open paren/bracket etc.), as a raw string */
first: string;
first: string | null;

/** The indent immediately after the open paren/bracket etc */
startIndent: number;
Expand Down Expand Up @@ -61,6 +61,17 @@ export function collectIndents(
let lastIndent = 0;
const indents: IndentInformation[] = [];
const rules = config['cljfmt-options']['indents'];
const patterns = _.keys(rules);
const regexpPatterns = patterns.reduce((regexpMap, pattern) => {
const match = pattern.match(/^#"(.*)"$/);

if (match) {
regexpMap[pattern] = RegExp(match[1]);
}

return regexpMap;
}, {} as Record<string, RegExp>);
Copy link
Contributor Author

@corasaurus-hex corasaurus-hex May 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed to fix some nulls here anyways so I took the chance to put into place some of the optimizations I mentioned in that other issue.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is some behaviour you think could get affected, add some unit tests for that.


do {
if (!cursor.backwardSexp()) {
// this needs some work..
Expand Down Expand Up @@ -91,7 +102,10 @@ export function collectIndents(

const pattern =
isList &&
_.find(_.keys(rules), (pattern) => pattern === token || testCljRe(pattern, token));
patterns.find(
(pattern) =>
pattern === token || (regexpPatterns[pattern] && regexpPatterns[pattern].test(token))
);
const indentRule = pattern ? rules[pattern] : [];
indents.unshift({
first: token,
Expand Down Expand Up @@ -138,11 +152,6 @@ export function collectIndents(
return indents;
}

const testCljRe = (re, str) => {
const matches = re.match(/^#"(.*)"$/);
return matches && RegExp(matches[1]).test(str);
};

/** Returns the expected newline indent for the given position, in characters. */
export function getIndent(document: EditableModel, offset: number, config?: any): number {
if (!config) {
Expand Down
11 changes: 7 additions & 4 deletions src/cursor-doc/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* @module lexer
*/

import { assertIsDefined } from '../type-checks';

/**
* The base Token class. Contains the token type,
* the raw string of the token, and the offset into the input line.
Expand Down Expand Up @@ -36,8 +38,8 @@ export class Lexer {
constructor(public source: string, public rules: Rule[], private maxLength) {}

/** Returns the next token in this lexer, or null if at the end. If the match fails, throws an Error. */
scan(): Token {
let token = null,
scan(): Token | undefined {
let token: Token | undefined,
length = 0;
if (this.position < this.source.length) {
if (this.source !== undefined && this.source.length < this.maxLength) {
Expand All @@ -47,6 +49,7 @@ export class Lexer {
const x = rule.r.exec(this.source);
if (x && x[0].length > length && this.position + x[0].length == rule.r.lastIndex) {
token = rule.fn(this, x);
assertIsDefined(token, 'Expected token!');
token.offset = this.position;
token.raw = x[0];
length = x[0].length;
Expand All @@ -62,9 +65,9 @@ export class Lexer {
}
}
this.position += length;
if (token == null) {
if (token === undefined) {
if (this.position == this.source.length) {
return null;
return undefined;
}
throw new Error(
'Unexpected character at ' + this.position + ': ' + JSON.stringify(this.source)
Expand Down
12 changes: 6 additions & 6 deletions src/cursor-doc/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,12 @@ export class LineInputModel implements EditableModel {
}
return x;
})
.filter((x) => x !== null)
.filter((x) => x !== null) as number[]
Copy link
Contributor Author

@corasaurus-hex corasaurus-hex May 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript wasn't smart enough to know that filtering out nulls would make a (number|null)[] into a number[]. We know from the code this is the correct type so we can just assert it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Thanks for mention I learn from them. ❤️

);

this.insertedLines = new Set(
Array.from(this.insertedLines)
.map((x): [number, number] => {
.map((x) => {
const [a, b] = x;
if (a > start && a < start + deleted) {
return null;
Expand All @@ -170,12 +170,12 @@ export class LineInputModel implements EditableModel {
}
return [a, b];
})
.filter((x) => x !== null)
.filter((x) => x !== null) as [number, number][]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reasoning as above.

);

this.deletedLines = new Set(
Array.from(this.deletedLines)
.map((x): [number, number] => {
.map((x) => {
const [a, b] = x;
if (a > start && a < start + deleted) {
return null;
Expand All @@ -185,7 +185,7 @@ export class LineInputModel implements EditableModel {
}
return [a, b];
})
.filter((x) => x !== null)
.filter((x) => x !== null) as [number, number][]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same same.

);
}

Expand Down Expand Up @@ -224,7 +224,7 @@ export class LineInputModel implements EditableModel {
const seen = new Set<number>();
this.dirtyLines.sort();
while (this.dirtyLines.length) {
let nextIdx = this.dirtyLines.shift();
let nextIdx = this.dirtyLines.shift() as number;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, TypeScript isn't smart enough to figure out that the length check on the previous line means this will never be undefined and will always be a number.

if (seen.has(nextIdx)) {
continue;
} // already processed.
Expand Down
Loading