diff --git a/lib/adapters/apply-edit-adapter.ts b/lib/adapters/apply-edit-adapter.ts index 6d89ceb..695e938 100644 --- a/lib/adapters/apply-edit-adapter.ts +++ b/lib/adapters/apply-edit-adapter.ts @@ -40,7 +40,7 @@ export default class ApplyEditAdapter { }), ); - const checkpoints = []; + const checkpoints: Array<{ buffer: TextBuffer, checkpoint: number}> = []; try { for (let i = 0; i < editors.length; i++) { const editor = editors[i] as TextEditor; @@ -52,7 +52,7 @@ export default class ApplyEditAdapter { const buffer = editor.getBuffer(); const checkpoint = buffer.createCheckpoint(); checkpoints.push({buffer, checkpoint}); - let prevEdit = null; + let prevEdit: atomIde.TextEdit | null = null; for (const edit of edits) { ApplyEditAdapter.validateEdit(buffer, edit, prevEdit); buffer.setTextInRange(edit.oldRange, edit.newText); @@ -74,9 +74,13 @@ export default class ApplyEditAdapter { } // Private: Do some basic sanity checking on the edit ranges. - private static validateEdit(buffer: TextBuffer, edit: atomIde.TextEdit, prevEdit: atomIde.TextEdit | null): void { + private static validateEdit( + buffer: TextBuffer, + edit: atomIde.TextEdit, + prevEdit: atomIde.TextEdit | null, + ): void { const path = buffer.getPath() || ''; - if (prevEdit != null && edit.oldRange.end.compare(prevEdit.oldRange.start) > 0) { + if (prevEdit && edit.oldRange.end.compare(prevEdit.oldRange.start) > 0) { throw Error(`Found overlapping edit ranges in ${path}`); } const startRow = edit.oldRange.start.row; diff --git a/lib/adapters/autocomplete-adapter.ts b/lib/adapters/autocomplete-adapter.ts index e68a782..f823ba0 100644 --- a/lib/adapters/autocomplete-adapter.ts +++ b/lib/adapters/autocomplete-adapter.ts @@ -267,11 +267,11 @@ export default class AutocompleteAdapter { // * `editor` An Atom {TextEditor} used to obtain the necessary text replacement. // * `suggestion` An {atom$AutocompleteSuggestion} to set the replacementPrefix and text properties of. public static applyTextEditToSuggestion( - textEdit: TextEdit | null, + textEdit: TextEdit | undefined, editor: TextEditor, suggestion: AutocompleteSuggestion, ): void { - if (textEdit != null) { + if (textEdit) { suggestion.replacementPrefix = editor.getTextInBufferRange(Convert.lsRangeToAtomRange(textEdit.range)); suggestion.text = textEdit.newText; } @@ -296,7 +296,7 @@ export default class AutocompleteAdapter { // // Returns a {String} containing the AutoComplete+ suggestion type equivalent // to the given completion kind. - public static completionKindToSuggestionType(kind: number | null): string { + public static completionKindToSuggestionType(kind: number | undefined): string { switch (kind) { case CompletionItemKind.Constant: return 'constant'; diff --git a/lib/adapters/code-action-adapter.ts b/lib/adapters/code-action-adapter.ts index 330491d..43a5ec4 100644 --- a/lib/adapters/code-action-adapter.ts +++ b/lib/adapters/code-action-adapter.ts @@ -27,7 +27,7 @@ export default class CodeActionAdapter { public static async getCodeActions( connection: LanguageClientConnection, serverCapabilities: ServerCapabilities, - linterAdapter: LinterPushV2Adapter | null, + linterAdapter: LinterPushV2Adapter | undefined , editor: TextEditor, range: Range, diagnostics: atomIde.Diagnostic[], diff --git a/lib/adapters/datatip-adapter.ts b/lib/adapters/datatip-adapter.ts index 7ef8bd8..d6d1c26 100644 --- a/lib/adapters/datatip-adapter.ts +++ b/lib/adapters/datatip-adapter.ts @@ -88,5 +88,8 @@ export default class DatatipAdapter { value: markedString.value, }; } + + // Catch-all case + return { type: 'markdown', value: markedString.toString() }; } } diff --git a/lib/adapters/document-sync-adapter.ts b/lib/adapters/document-sync-adapter.ts index 05f066b..7208a68 100644 --- a/lib/adapters/document-sync-adapter.ts +++ b/lib/adapters/document-sync-adapter.ts @@ -64,7 +64,7 @@ export default class DocumentSyncAdapter { // indicating whether this adapter should care about the contents of the editor. constructor( connection: LanguageClientConnection, - documentSyncKind: TextDocumentSyncOptions | number | null, + documentSyncKind: TextDocumentSyncOptions | number | undefined, editorSelector: (editor: TextEditor) => boolean, ) { this._connection = connection; @@ -129,7 +129,7 @@ export default class DocumentSyncAdapter { ); } - public getEditorSyncAdapter(editor: TextEditor): TextEditorSyncAdapter | null { + public getEditorSyncAdapter(editor: TextEditor): TextEditorSyncAdapter | undefined { return this._editors.get(editor); } } diff --git a/lib/adapters/linter-push-v2-adapter.ts b/lib/adapters/linter-push-v2-adapter.ts index 1bfdeef..ab08724 100644 --- a/lib/adapters/linter-push-v2-adapter.ts +++ b/lib/adapters/linter-push-v2-adapter.ts @@ -113,7 +113,7 @@ export default class LinterPushV2Adapter { if (path != null) { const diagnosticCodes = this._diagnosticCodes.get(path); if (diagnosticCodes != null) { - return diagnosticCodes.get(getCodeKey(range, text)); + return diagnosticCodes.get(getCodeKey(range, text)) || null; } } return null; @@ -121,5 +121,5 @@ export default class LinterPushV2Adapter { } function getCodeKey(range: atom.Range, text: string): string { - return [].concat(...range.serialize(), text).join(','); + return ([] as any[]).concat(...range.serialize(), text).join(','); } diff --git a/lib/adapters/outline-view-adapter.ts b/lib/adapters/outline-view-adapter.ts index 3bffeb0..3f7bcc4 100644 --- a/lib/adapters/outline-view-adapter.ts +++ b/lib/adapters/outline-view-adapter.ts @@ -115,24 +115,27 @@ export default class OutlineViewAdapter { return null; } - let parent = null; + let parent: atomIde.OutlineTree | undefined; for (const candidate of candidates) { if ( candidate !== child && candidate.startPosition.isLessThanOrEqual(child.startPosition) && - (candidate.endPosition == null || candidate.endPosition.isGreaterThanOrEqual(child.endPosition)) + (candidate.endPosition === undefined || + (child.endPosition && candidate.endPosition.isGreaterThanOrEqual(child.endPosition))) ) { if ( - parent == null || + parent === undefined || (parent.startPosition.isLessThanOrEqual(candidate.startPosition) || - (parent.endPosition != null && parent.endPosition.isGreaterThanOrEqual(candidate.endPosition))) + (parent.endPosition != null && + candidate.endPosition && + parent.endPosition.isGreaterThanOrEqual(candidate.endPosition))) ) { parent = candidate; } } } - return parent; + return parent || null; } // Public: Convert an individual {SymbolInformation} from the language server diff --git a/lib/adapters/signature-help-adapter.ts b/lib/adapters/signature-help-adapter.ts index 95003a1..dfe9b67 100644 --- a/lib/adapters/signature-help-adapter.ts +++ b/lib/adapters/signature-help-adapter.ts @@ -31,8 +31,8 @@ export default class SignatureHelpAdapter { const {signatureHelpProvider} = this._capabilities; assert(signatureHelpProvider != null); - let triggerCharacters = null; - if (Array.isArray(signatureHelpProvider.triggerCharacters)) { + let triggerCharacters: Set | undefined; + if (signatureHelpProvider && Array.isArray(signatureHelpProvider.triggerCharacters)) { triggerCharacters = new Set(signatureHelpProvider.triggerCharacters); } diff --git a/lib/auto-languageclient.ts b/lib/auto-languageclient.ts index 27d8aea..b4299cb 100644 --- a/lib/auto-languageclient.ts +++ b/lib/auto-languageclient.ts @@ -4,13 +4,13 @@ import * as rpc from 'vscode-jsonrpc'; import * as path from 'path'; import * as atomIde from 'atom-ide'; import * as linter from 'atom-linter'; -import * as stream from 'stream'; import { Socket } from 'net'; -import { EventEmitter } from 'events'; import { ConsoleLogger, NullLogger, Logger } from './logger'; -import { ServerManager, ActiveServer } from './server-manager.js'; +import { LanguageServerProcess, ServerManager, ActiveServer } from './server-manager.js'; import Convert from './convert.js'; +export { LanguageServerProcess }; + import { AutocompleteDidInsert, AutocompleteProvider, @@ -39,21 +39,6 @@ import SignatureHelpAdapter from './adapters/signature-help-adapter'; export type ConnectionType = 'stdio' | 'socket' | 'ipc'; -// Public: Defines the minimum surface area for an object that resembles a -// ChildProcess. This is used so that language packages with alternative -// language server process hosting strategies can return something compatible -// with AutoLanguageClient.startServerProcess. -export interface LanguageServerProcess extends EventEmitter { - stdin: stream.Writable; - stdout: stream.Readable; - stderr: stream.Readable; - pid: number; - - kill(signal?: string): void; - on(event: 'error', listener: (err: Error) => void): this; - on(event: 'exit', listener: (code: number, signal: string) => void): this; -} - // Public: AutoLanguageClient provides a simple way to have all the supported // Atom-IDE services wired up entirely for you by just subclassing it and // implementing startServerProcess/getGrammarScopes/getLanguageName and @@ -62,7 +47,7 @@ export default class AutoLanguageClient { private _disposable = new CompositeDisposable(); private _serverManager: ServerManager; private _linterDelegate: linter.V2IndieDelegate; - private _signatureHelpRegistry: atomIde.SignatureHelpRegistry; + private _signatureHelpRegistry: atomIde.SignatureHelpRegistry | null; private _lastAutocompleteRequest: AutocompleteRequest; private _isDeactivating: boolean; diff --git a/lib/convert.ts b/lib/convert.ts index f9808d7..38744d6 100644 --- a/lib/convert.ts +++ b/lib/convert.ts @@ -159,7 +159,7 @@ export default class Convert { case 'deleted': return [{uri: Convert.pathToUri(fileEvent.path), type: ls.FileChangeType.Deleted}]; case 'renamed': { - const results = []; + const results: Array<{ uri: string, type: ls.FileChangeType }> = []; if (fileEvent.oldPath) { results.push({uri: Convert.pathToUri(fileEvent.oldPath || ''), type: ls.FileChangeType.Deleted}); } diff --git a/lib/download-file.ts b/lib/download-file.ts index a367249..e9f3950 100644 --- a/lib/download-file.ts +++ b/lib/download-file.ts @@ -31,7 +31,7 @@ export default (async function downloadFile( throw Error('No response body'); } - const finalLength = length || parseInt(response.headers.get('Content-Length' || '0'), 10); + const finalLength = length || parseInt(response.headers.get('Content-Length') || '0', 10); const reader = body.getReader(); const writer = fs.createWriteStream(targetFile); @@ -72,7 +72,7 @@ async function streamWithProgress( writer.write(Buffer.from(chunk)); if (progressCallback != null) { bytesDone += chunk.byteLength; - const percent: number = length === 0 ? null : Math.floor(bytesDone / length * 100); + const percent: number | undefined = length === 0 ? undefined : Math.floor(bytesDone / length * 100); progressCallback(bytesDone, percent); } } diff --git a/lib/server-manager.ts b/lib/server-manager.ts index 3fee2a6..a6ad187 100644 --- a/lib/server-manager.ts +++ b/lib/server-manager.ts @@ -1,20 +1,37 @@ import { Logger } from './logger'; +import { EventEmitter } from 'events'; import LinterPushV2Adapter from './adapters/linter-push-v2-adapter'; import DocumentSyncAdapter from './adapters/document-sync-adapter'; import SignatureHelpAdapter from './adapters/signature-help-adapter'; import * as path from 'path'; +import * as stream from 'stream'; import * as cp from 'child_process'; import * as ls from './languageclient'; import * as atomIde from 'atom-ide'; import Convert from './convert'; import { CompositeDisposable, ProjectFileEvent, TextEditor } from 'atom'; +// Public: Defines the minimum surface area for an object that resembles a +// ChildProcess. This is used so that language packages with alternative +// language server process hosting strategies can return something compatible +// with AutoLanguageClient.startServerProcess. +export interface LanguageServerProcess extends EventEmitter { + stdin: stream.Writable; + stdout: stream.Readable; + stderr: stream.Readable; + pid: number; + + kill(signal?: string): void; + on(event: 'error', listener: (err: Error) => void): this; + on(event: 'exit', listener: (code: number, signal: string) => void): this; +} + // The necessary elements for a server that has started or is starting. export interface ActiveServer { disposable: CompositeDisposable; projectPath: string; - process: cp.ChildProcess; + process: LanguageServerProcess; connection: ls.LanguageClientConnection; capabilities: ls.ServerCapabilities; linterPushV2?: LinterPushV2Adapter; @@ -269,7 +286,7 @@ export class ServerManager { if (filePath == null) { return null; } - return this._normalizedProjectPaths.find((d) => filePath.startsWith(d)); + return this._normalizedProjectPaths.find((d) => filePath.startsWith(d)) || null; } public updateNormalizedProjectPaths(): void { @@ -293,7 +310,7 @@ export class ServerManager { } for (const activeServer of this._activeServers) { - const changes = []; + const changes: ls.FileEvent[] = []; for (const fileEvent of fileEvents) { if (fileEvent.path.startsWith(activeServer.projectPath) && this._changeWatchedFileFilter(fileEvent.path)) { changes.push(Convert.atomFileEventToLSFileEvents(fileEvent)[0]); diff --git a/test/adapters/autocomplete-adapter.test.ts b/test/adapters/autocomplete-adapter.test.ts index ad27356..df6d61f 100644 --- a/test/adapters/autocomplete-adapter.test.ts +++ b/test/adapters/autocomplete-adapter.test.ts @@ -20,7 +20,7 @@ describe('AutoCompleteAdapter', () => { capabilities: {completionProvider: { }}, connection: new ls.LanguageClientConnection(createSpyConnection()), disposable: new CompositeDisposable(), - process: undefined, + process: undefined as any, projectPath: '/', }; } @@ -279,7 +279,7 @@ describe('AutoCompleteAdapter', () => { it('does not do anything if there is no textEdit', () => { const completionItem: AutocompleteSuggestion = {}; - AutoCompleteAdapter.applyTextEditToSuggestion(null, new TextEditor(), completionItem); + AutoCompleteAdapter.applyTextEditToSuggestion(undefined, new TextEditor(), completionItem); expect(completionItem).deep.equals({}); }); @@ -315,7 +315,7 @@ describe('AutoCompleteAdapter', () => { }); it('defaults to "value"', () => { - const result = AutoCompleteAdapter.completionKindToSuggestionType(null); + const result = AutoCompleteAdapter.completionKindToSuggestionType(undefined); expect(result).equals('value'); }); }); diff --git a/test/adapters/code-highlight-adapter.test.ts b/test/adapters/code-highlight-adapter.test.ts index 09f47d1..154776e 100644 --- a/test/adapters/code-highlight-adapter.test.ts +++ b/test/adapters/code-highlight-adapter.test.ts @@ -50,8 +50,10 @@ describe('CodeHighlightAdapter', () => { expect(highlightStub.called).to.be.true; invariant(result != null); - expect(result.length).to.equal(1); - expect(result[0].isEqual(new Range([0, 1], [0, 2]))).to.be.true; + if (result) { + expect(result.length).to.equal(1); + expect(result[0].isEqual(new Range([0, 1], [0, 2]))).to.be.true; + } }); it('throws if document highlights are not supported', async () => { diff --git a/test/adapters/datatip-adapter.test.ts b/test/adapters/datatip-adapter.test.ts index 89eebc0..54e5b62 100644 --- a/test/adapters/datatip-adapter.test.ts +++ b/test/adapters/datatip-adapter.test.ts @@ -48,21 +48,23 @@ describe('DatatipAdapter', () => { expect(datatip).to.be.ok; invariant(datatip != null); - expect(datatip.range.start.row).equal(0); - expect(datatip.range.start.column).equal(1); - expect(datatip.range.end.row).equal(0); - expect(datatip.range.end.column).equal(2); + if (datatip) { + expect(datatip.range.start.row).equal(0); + expect(datatip.range.start.column).equal(1); + expect(datatip.range.end.row).equal(0); + expect(datatip.range.end.column).equal(2); - expect(datatip.markedStrings).to.have.lengthOf(2); - expect(datatip.markedStrings[0]).eql({type: 'markdown', value: 'test'}); + expect(datatip.markedStrings).to.have.lengthOf(2); + expect(datatip.markedStrings[0]).eql({type: 'markdown', value: 'test'}); - const snippet = datatip.markedStrings[1]; - expect(snippet.type).equal('snippet'); - invariant(snippet.type === 'snippet'); - expect((snippet as any).grammar.scopeName).equal('text.plain.null-grammar'); - expect(snippet.value).equal('test snippet'); + const snippet = datatip.markedStrings[1]; + expect(snippet.type).equal('snippet'); + invariant(snippet.type === 'snippet'); + expect((snippet as any).grammar.scopeName).equal('text.plain.null-grammar'); + expect(snippet.value).equal('test snippet'); - expect(grammarSpy.calledWith('source.testlang')).to.be.true; + expect(grammarSpy.calledWith('source.testlang')).to.be.true; + } }); }); }); diff --git a/test/adapters/document-sync-adapter.test.ts b/test/adapters/document-sync-adapter.test.ts index b57aca9..916dbbb 100644 --- a/test/adapters/document-sync-adapter.test.ts +++ b/test/adapters/document-sync-adapter.test.ts @@ -49,7 +49,7 @@ describe('DocumentSyncAdapter', () => { describe('constructor', () => { function create(textDocumentSync) { - return new DocumentSyncAdapter(null, textDocumentSync, () => false); + return new DocumentSyncAdapter(null as any, textDocumentSync, () => false); } it('sets _documentSyncKind correctly Incremental for v2 capabilities', () => { diff --git a/test/adapters/outline-view-adapter.test.ts b/test/adapters/outline-view-adapter.test.ts index 8212eb8..b568560 100644 --- a/test/adapters/outline-view-adapter.test.ts +++ b/test/adapters/outline-view-adapter.test.ts @@ -121,11 +121,22 @@ describe('OutlineViewAdapter', () => { containerName: 'duplicate', }, ]; + const result = OutlineViewAdapter.createOutlineTrees(sourceItems); expect(result.length).to.equal(1); - expect(result[0].endPosition.row).to.equal(10); - expect(result[0].children.length).to.equal(1); - expect(result[0].children[0].endPosition.row).to.equal(7); + + const outline = result[0]; + expect(outline.endPosition).to.not.be.undefined; + if (outline.endPosition) { + expect(outline.endPosition.row).to.equal(10); + expect(outline.children.length).to.equal(1); + + const outlineChild = outline.children[0]; + expect(outlineChild.endPosition).to.not.be.undefined; + if (outlineChild.endPosition) { + expect(outlineChild.endPosition.row).to.equal(7); + } + } }); it('parents to the innnermost named container', () => { @@ -141,11 +152,29 @@ describe('OutlineViewAdapter', () => { ]; const result = OutlineViewAdapter.createOutlineTrees(sourceItems); expect(result.length).to.equal(1); - expect(result[0].endPosition.row).to.equal(10); - expect(result[0].children.length).to.equal(1); - expect(result[0].children[0].endPosition.row).to.equal(8); - expect(result[0].children[0].children.length).to.equal(1); - expect(result[0].children[0].children[0].endPosition.row).to.equal(5); + + const outline = result[0]; + expect(outline).to.not.be.undefined; + if (outline) { + expect(outline.endPosition).to.not.be.undefined; + if (outline.endPosition) { + expect(outline.endPosition.row).to.equal(10); + expect(outline.children.length).to.equal(1); + + const outlineChild = outline.children[0]; + expect(outlineChild.endPosition).to.not.be.undefined; + if (outlineChild.endPosition) { + expect(outlineChild.endPosition.row).to.equal(8); + expect(outlineChild.children.length).to.equal(1); + + const outlineGrandChild = outlineChild.children[0]; + expect(outlineGrandChild.endPosition).to.not.be.undefined; + if (outlineGrandChild.endPosition) { + expect(outlineGrandChild.endPosition.row).to.equal(5); + } + } + } + } }); }); @@ -156,12 +185,19 @@ describe('OutlineViewAdapter', () => { expect(result.icon).to.equal('type-class'); expect(result.representativeName).to.equal('Program'); expect(result.children).to.deep.equal([]); - expect(result.tokenizedText[0].kind).to.equal('type'); - expect(result.tokenizedText[0].value).to.equal('Program'); - expect(result.startPosition.row).to.equal(1); - expect(result.startPosition.column).to.equal(2); - expect(result.endPosition.row).to.equal(3); - expect(result.endPosition.column).to.equal(4); + expect(result.tokenizedText).to.not.be.undefined; + if (result.tokenizedText) { + const resultTokenixedText = result.tokenizedText[0]; + expect(result.tokenizedText[0].kind).to.equal('type'); + expect(result.tokenizedText[0].value).to.equal('Program'); + expect(result.startPosition.row).to.equal(1); + expect(result.startPosition.column).to.equal(2); + expect(result.endPosition).to.not.be.undefined; + if (result.endPosition) { + expect(result.endPosition.row).to.equal(3); + expect(result.endPosition.column).to.equal(4); + } + } }); }); }); diff --git a/tsconfig.json b/tsconfig.json index 95fc7ac..aca5daa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "declaration": true, "inlineSources": true, "inlineSourceMap": true, + "strictNullChecks": true, "baseUrl": "./", "paths": { "atom-ide": [ diff --git a/typings/atom-ide/index.d.ts b/typings/atom-ide/index.d.ts index d033c3c..07aefae 100644 --- a/typings/atom-ide/index.d.ts +++ b/typings/atom-ide/index.d.ts @@ -178,7 +178,7 @@ export interface CodeActionProvider { editor: TextEditor, range: Range, diagnostics: Diagnostic[], - ): Promise; + ): Promise; } export interface BusySignalOptions {