From 37c3cd111702ea020a367480f3bf0cb83bdf7fdb Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Tue, 7 Nov 2017 16:28:35 -0800 Subject: [PATCH] Start moving emmet extension to strict mode (#37740) * Start moving emmet to strict mode First part of moving the emmet extension to strict mode TypeScript. This change focuses on adding annotations when things can be undefined and removing jsdoc type comments * Fix a few more errors * Fix compile errors * Tiny updates --- extensions/emmet/src/abbreviationActions.ts | 21 ++++--- extensions/emmet/src/balance.ts | 15 +++-- extensions/emmet/src/bufferStream.ts | 38 +++--------- .../emmet/src/defaultCompletionProvider.ts | 8 +-- extensions/emmet/src/editPoint.ts | 24 ++++---- .../emmet/src/evaluateMathExpression.ts | 4 +- extensions/emmet/src/imageSizeHelper.ts | 23 +++---- extensions/emmet/src/selectItemHTML.ts | 23 ++++--- extensions/emmet/src/selectItemStylesheet.ts | 8 +-- extensions/emmet/src/splitJoinTag.ts | 16 ++--- extensions/emmet/src/toggleComment.ts | 12 ++-- extensions/emmet/src/updateImageSize.ts | 55 ++++++----------- extensions/emmet/src/updateTag.ts | 8 +-- extensions/emmet/src/util.ts | 61 ++++++++----------- 14 files changed, 129 insertions(+), 187 deletions(-) diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts index 0d474f1c3f1..0224594a33e 100644 --- a/extensions/emmet/src/abbreviationActions.ts +++ b/extensions/emmet/src/abbreviationActions.ts @@ -17,8 +17,8 @@ interface ExpandAbbreviationInput { filter?: string; } -export function wrapWithAbbreviation(args) { - if (!validate(false)) { +export function wrapWithAbbreviation(args: any) { + if (!validate(false) || !vscode.window.activeTextEditor) { return; } @@ -50,8 +50,8 @@ export function wrapWithAbbreviation(args) { }); } -export function wrapIndividualLinesWithAbbreviation(args) { - if (!validate(false)) { +export function wrapIndividualLinesWithAbbreviation(args: any) { + if (!validate(false) || !vscode.window.activeTextEditor) { return; } @@ -88,7 +88,7 @@ export function wrapIndividualLinesWithAbbreviation(args) { } -export function expandEmmetAbbreviation(args): Thenable { +export function expandEmmetAbbreviation(args: any): Thenable { const syntax = getSyntaxFromArgs(args); if (!syntax || !validate()) { return fallbackTab(); @@ -179,7 +179,7 @@ export function expandEmmetAbbreviation(args): Thenable { }); } -function fallbackTab(): Thenable { +function fallbackTab(): Thenable | undefined { if (vscode.workspace.getConfiguration('emmet')['triggerExpansionOnTab'] === true) { return vscode.commands.executeCommand('tab'); } @@ -226,7 +226,8 @@ export function isValidLocationForEmmetAbbreviation(currentNode: Node, syntax: s const currentHtmlNode = currentNode; if (currentHtmlNode.close) { - return getInnerRange(currentHtmlNode).contains(position); + const innerRange = getInnerRange(currentHtmlNode); + return !!innerRange && innerRange.contains(position); } return false; @@ -247,7 +248,7 @@ function expandAbbreviationInRange(editor: vscode.TextEditor, expandAbbrList: Ex // Snippet to replace at multiple cursors are not the same // `editor.insertSnippet` will have to be called for each instance separately // We will not be able to maintain multiple cursors after snippet insertion - let insertPromises = []; + let insertPromises: Thenable[] = []; if (!insertSameSnippet) { expandAbbrList.forEach((expandAbbrInput: ExpandAbbreviationInput) => { let expandedText = expandAbbr(expandAbbrInput); @@ -278,7 +279,7 @@ function expandAbbreviationInRange(editor: vscode.TextEditor, expandAbbrList: Ex /** * Expands abbreviation as detailed in given input. */ -function expandAbbr(input: ExpandAbbreviationInput): string { +function expandAbbr(input: ExpandAbbreviationInput): string | undefined { const helper = getEmmetHelper(); const expandOptions = helper.getExpandOptions(input.syntax, getEmmetConfiguration(input.syntax), input.filter); @@ -322,7 +323,7 @@ function expandAbbr(input: ExpandAbbreviationInput): string { } -function getSyntaxFromArgs(args: any): string { +function getSyntaxFromArgs(args: any): string | undefined { let editor = vscode.window.activeTextEditor; if (!editor) { vscode.window.showInformationMessage('No editor is active.'); diff --git a/extensions/emmet/src/balance.ts b/extensions/emmet/src/balance.ts index 22474be22dd..2d4917994ae 100644 --- a/extensions/emmet/src/balance.ts +++ b/extensions/emmet/src/balance.ts @@ -16,11 +16,10 @@ export function balanceIn() { } function balance(out: boolean) { - let editor = vscode.window.activeTextEditor; - if (!validate(false)) { + if (!validate(false) || !vscode.window.activeTextEditor) { return; } - + const editor = vscode.window.activeTextEditor; let rootNode = parseDocument(editor.document); if (!rootNode) { return; @@ -30,7 +29,7 @@ function balance(out: boolean) { let newSelections: vscode.Selection[] = []; editor.selections.forEach(selection => { let range = getRangeFunction(editor.document, selection, rootNode); - newSelections.push(range ? range : selection); + newSelections.push(range); }); editor.selection = newSelections[0]; @@ -40,7 +39,7 @@ function balance(out: boolean) { function getRangeToBalanceOut(document: vscode.TextDocument, selection: vscode.Selection, rootNode: HtmlNode): vscode.Selection { let nodeToBalance = getNode(rootNode, selection.start); if (!nodeToBalance) { - return; + return selection; } if (!nodeToBalance.close) { return new vscode.Selection(nodeToBalance.start, nodeToBalance.end); @@ -55,13 +54,13 @@ function getRangeToBalanceOut(document: vscode.TextDocument, selection: vscode.S if (outerSelection.contains(selection) && !outerSelection.isEqual(selection)) { return outerSelection; } - return; + return selection; } function getRangeToBalanceIn(document: vscode.TextDocument, selection: vscode.Selection, rootNode: HtmlNode): vscode.Selection { let nodeToBalance = getNode(rootNode, selection.start, true); if (!nodeToBalance) { - return; + return selection; } if (selection.start.isEqual(nodeToBalance.start) @@ -71,7 +70,7 @@ function getRangeToBalanceIn(document: vscode.TextDocument, selection: vscode.Se } if (!nodeToBalance.firstChild) { - return; + return selection; } if (selection.start.isEqual(nodeToBalance.firstChild.start) diff --git a/extensions/emmet/src/bufferStream.ts b/extensions/emmet/src/bufferStream.ts index a7a6cd5a0d2..3ab9d9c2eab 100644 --- a/extensions/emmet/src/bufferStream.ts +++ b/extensions/emmet/src/bufferStream.ts @@ -43,20 +43,16 @@ export class DocumentStreamReader { /** * Creates a new stream instance which is limited to given range for given document - * @param {Position} start - * @param {Position} end - * @return {DocumentStreamReader} */ - limit(start, end) { + limit(start: Position, end: Position): DocumentStreamReader { return new DocumentStreamReader(this.document, start, new Range(start, end)); } /** * Returns the next character code in the stream without advancing it. * Will return NaN at the end of the file. - * @returns {Number} */ - peek() { + peek(): number { if (this.eof()) { return NaN; } @@ -67,9 +63,8 @@ export class DocumentStreamReader { /** * Returns the next character in the stream and advances it. * Also returns NaN when no more characters are available. - * @returns {Number} */ - next() { + next(): number { if (this.eof()) { return NaN; } @@ -95,9 +90,8 @@ export class DocumentStreamReader { /** * Backs up the stream n characters. Backing it up further than the * start of the current token will cause things to break, so be careful. - * @param {Number} n */ - backUp(n) { + backUp(n: number) { let row = this.pos.line; let column = this.pos.character; column -= (n || 1); @@ -117,28 +111,22 @@ export class DocumentStreamReader { /** * Get the string between the start of the current token and the * current stream position. - * @returns {String} */ - current() { + current(): string { return this.substring(this.start, this.pos); } /** * Returns contents for given range - * @param {Position} from - * @param {Position} to - * @return {String} */ - substring(from, to) { + substring(from: Position, to: Position): string { return this.document.getText(new Range(from, to)); } /** * Creates error object with current stream state - * @param {String} message - * @return {Error} */ - error(message) { + error(message: string): Error { const err = new Error(`${message} at row ${this.pos.line}, column ${this.pos.character}`); return err; @@ -146,10 +134,8 @@ export class DocumentStreamReader { /** * Returns line length of given row, including line ending - * @param {Number} row - * @return {Number} */ - _lineLength(row) { + _lineLength(row: number): number { if (row === this.document.lineCount - 1) { return this.document.lineAt(row).text.length; } @@ -161,10 +147,8 @@ export class DocumentStreamReader { * and returns a boolean. If the next character in the stream 'matches' * the given argument, it is consumed and returned. * Otherwise, `false` is returned. - * @param {Number|Function} match - * @returns {Boolean} */ - eat(match) { + eat(match: number | Function): boolean { const ch = this.peek(); const ok = typeof match === 'function' ? match(ch) : ch === match; @@ -178,10 +162,8 @@ export class DocumentStreamReader { /** * Repeatedly calls eat with the given argument, until it * fails. Returns true if any characters were eaten. - * @param {Object} match - * @returns {Boolean} */ - eatWhile(match) { + eatWhile(match: number | Function): boolean { const start = this.pos; while (!this.eof() && this.eat(match)) { } return !this.pos.isEqual(start); diff --git a/extensions/emmet/src/defaultCompletionProvider.ts b/extensions/emmet/src/defaultCompletionProvider.ts index 1c825fc21c3..ad6f548bd9f 100644 --- a/extensions/emmet/src/defaultCompletionProvider.ts +++ b/extensions/emmet/src/defaultCompletionProvider.ts @@ -12,7 +12,7 @@ const allowedMimeTypesInScriptTag = ['text/html', 'text/plain', 'text/x-template export class DefaultCompletionItemProvider implements vscode.CompletionItemProvider { - public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable { + public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Thenable | undefined { const mappedLanguages = getMappingForIncludedLanguages(); const emmetConfig = vscode.workspace.getConfiguration('emmet'); @@ -45,8 +45,8 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi if (abbreviation.startsWith('this.')) { noiseCheckPromise = Promise.resolve(true); } else { - noiseCheckPromise = vscode.commands.executeCommand('vscode.executeDocumentSymbolProvider', document.uri).then((symbols: vscode.SymbolInformation[]) => { - return symbols.find(x => abbreviation === x.name || (abbreviation.startsWith(x.name + '.') && !/>|\*|\+/.test(abbreviation))); + noiseCheckPromise = vscode.commands.executeCommand('vscode.executeDocumentSymbolProvider', document.uri).then((symbols: vscode.SymbolInformation[] | undefined) => { + return symbols && symbols.find(x => abbreviation === x.name || (abbreviation.startsWith(x.name + '.') && !/>|\*|\+/.test(abbreviation))); }); } } @@ -88,7 +88,7 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi * @param document vscode.Textdocument * @param position vscode.Position position of the abbreviation that needs to be expanded */ - private syntaxHelper(syntax: string, document: vscode.TextDocument, position: vscode.Position): string { + private syntaxHelper(syntax: string | undefined, document: vscode.TextDocument, position: vscode.Position): string | undefined { if (!syntax) { return syntax; } diff --git a/extensions/emmet/src/editPoint.ts b/extensions/emmet/src/editPoint.ts index 2028d3295b3..afd73a4ddcb 100644 --- a/extensions/emmet/src/editPoint.ts +++ b/extensions/emmet/src/editPoint.ts @@ -7,40 +7,42 @@ import * as vscode from 'vscode'; import { validate } from './util'; export function fetchEditPoint(direction: string): void { - let editor = vscode.window.activeTextEditor; - if (!validate()) { + if (!validate() || !vscode.window.activeTextEditor) { return; } + const editor = vscode.window.activeTextEditor; let newSelections: vscode.Selection[] = []; editor.selections.forEach(selection => { - let updatedSelection = direction === 'next' ? nextEditPoint(selection.anchor, editor) : prevEditPoint(selection.anchor, editor); - newSelections.push(updatedSelection ? updatedSelection : selection); + let updatedSelection = direction === 'next' ? nextEditPoint(selection, editor) : prevEditPoint(selection, editor); + newSelections.push(updatedSelection); }); editor.selections = newSelections; editor.revealRange(editor.selections[editor.selections.length - 1]); } -function nextEditPoint(position: vscode.Position, editor: vscode.TextEditor): vscode.Selection { - for (let lineNum = position.line; lineNum < editor.document.lineCount; lineNum++) { - let updatedSelection = findEditPoint(lineNum, editor, position, 'next'); +function nextEditPoint(selection: vscode.Selection, editor: vscode.TextEditor): vscode.Selection { + for (let lineNum = selection.anchor.line; lineNum < editor.document.lineCount; lineNum++) { + let updatedSelection = findEditPoint(lineNum, editor, selection.anchor, 'next'); if (updatedSelection) { return updatedSelection; } } + return selection; } -function prevEditPoint(position: vscode.Position, editor: vscode.TextEditor): vscode.Selection { - for (let lineNum = position.line; lineNum >= 0; lineNum--) { - let updatedSelection = findEditPoint(lineNum, editor, position, 'prev'); +function prevEditPoint(selection: vscode.Selection, editor: vscode.TextEditor): vscode.Selection { + for (let lineNum = selection.anchor.line; lineNum >= 0; lineNum--) { + let updatedSelection = findEditPoint(lineNum, editor, selection.anchor, 'prev'); if (updatedSelection) { return updatedSelection; } } + return selection; } -function findEditPoint(lineNum: number, editor: vscode.TextEditor, position: vscode.Position, direction: string): vscode.Selection { +function findEditPoint(lineNum: number, editor: vscode.TextEditor, position: vscode.Position, direction: string): vscode.Selection | undefined { let line = editor.document.lineAt(lineNum); let lineContent = line.text; diff --git a/extensions/emmet/src/evaluateMathExpression.ts b/extensions/emmet/src/evaluateMathExpression.ts index cf7b84d41cc..8a7de6ca423 100644 --- a/extensions/emmet/src/evaluateMathExpression.ts +++ b/extensions/emmet/src/evaluateMathExpression.ts @@ -10,11 +10,11 @@ import evaluate from '@emmetio/math-expression'; import { DocumentStreamReader } from './bufferStream'; export function evaluateMathExpression() { - let editor = vscode.window.activeTextEditor; - if (!editor) { + if (!vscode.window.activeTextEditor) { vscode.window.showInformationMessage('No editor is active'); return; } + const editor = vscode.window.activeTextEditor; const stream = new DocumentStreamReader(editor.document); editor.edit(editBuilder => { editor.selections.forEach(selection => { diff --git a/extensions/emmet/src/imageSizeHelper.ts b/extensions/emmet/src/imageSizeHelper.ts index 2c8f1de5945..1a88f35c110 100644 --- a/extensions/emmet/src/imageSizeHelper.ts +++ b/extensions/emmet/src/imageSizeHelper.ts @@ -19,20 +19,16 @@ const reUrl = /^https?:/; /** * Get size of given image file. Supports files from local filesystem, * as well as URLs - * @param {String} file Path to local file or URL - * @return {Promise} */ -export function getImageSize(file) { +export function getImageSize(file: string) { file = file.replace(/^file:\/\//, ''); return reUrl.test(file) ? getImageSizeFromURL(file) : getImageSizeFromFile(file); } /** * Get image size from file on local file system - * @param {String} file - * @return {Promise} */ -function getImageSizeFromFile(file) { +function getImageSizeFromFile(file: string) { return new Promise((resolve, reject) => { const isDataUrl = file.match(/^data:.+?;base64,/); @@ -46,7 +42,7 @@ function getImageSizeFromFile(file) { } } - sizeOf(file, (err, size) => { + sizeOf(file, (err: any, size: any) => { if (err) { reject(err); } else { @@ -58,15 +54,13 @@ function getImageSizeFromFile(file) { /** * Get image size from given remove URL - * @param {String} url - * @return {Promise} */ -function getImageSizeFromURL(url) { +function getImageSizeFromURL(urlStr: string) { return new Promise((resolve, reject) => { - url = parseUrl(url); + const url = parseUrl(urlStr); const getTransport = url.protocol === 'https:' ? https.get : http.get; - getTransport(url, resp => { + getTransport(url as any, resp => { const chunks = []; let bufSize = 0; @@ -102,11 +96,8 @@ function getImageSizeFromURL(url) { /** * Returns size object for given file name. If file name contains `@Nx` token, * the final dimentions will be downscaled by N - * @param {String} fileName - * @param {Object} size - * @return {Object} */ -function sizeForFileName(fileName, size) { +function sizeForFileName(fileName: string, size: any) { const m = fileName.match(/@(\d+)x\./); const scale = m ? +m[1] : 1; diff --git a/extensions/emmet/src/selectItemHTML.ts b/extensions/emmet/src/selectItemHTML.ts index 1b234e2f349..23647722bcc 100644 --- a/extensions/emmet/src/selectItemHTML.ts +++ b/extensions/emmet/src/selectItemHTML.ts @@ -7,9 +7,9 @@ import * as vscode from 'vscode'; import { getDeepestNode, findNextWord, findPrevWord, getNode } from './util'; import { HtmlNode } from 'EmmetNode'; -export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vscode.Position, editor: vscode.TextEditor, rootNode: HtmlNode): vscode.Selection { +export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vscode.Position, editor: vscode.TextEditor, rootNode: HtmlNode): vscode.Selection | undefined { let currentNode = getNode(rootNode, selectionEnd); - let nextNode: HtmlNode; + let nextNode: HtmlNode | undefined = undefined; if (!currentNode) { return; @@ -50,12 +50,12 @@ export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vsco } } - return getSelectionFromNode(nextNode, editor.document); + return nextNode && getSelectionFromNode(nextNode, editor.document); } -export function prevItemHTML(selectionStart: vscode.Position, selectionEnd: vscode.Position, editor: vscode.TextEditor, rootNode: HtmlNode): vscode.Selection { +export function prevItemHTML(selectionStart: vscode.Position, selectionEnd: vscode.Position, editor: vscode.TextEditor, rootNode: HtmlNode): vscode.Selection | undefined { let currentNode = getNode(rootNode, selectionStart); - let prevNode: HtmlNode; + let prevNode: HtmlNode | undefined = undefined; if (!currentNode) { return; @@ -68,7 +68,7 @@ export function prevItemHTML(selectionStart: vscode.Position, selectionEnd: vsco } else { // Select the child that appears just before the cursor and is not a comment prevNode = currentNode.firstChild; - let oldOption: HtmlNode; + let oldOption: HtmlNode | undefined = undefined; while (prevNode.nextSibling && selectionStart.isAfterOrEqual(prevNode.nextSibling.end)) { if (prevNode && prevNode.type !== 'comment') { oldOption = prevNode; @@ -94,20 +94,25 @@ export function prevItemHTML(selectionStart: vscode.Position, selectionEnd: vsco } + if (!prevNode) { + return undefined; + } + let attrSelection = getPrevAttribute(selectionStart, selectionEnd, editor.document, prevNode); return attrSelection ? attrSelection : getSelectionFromNode(prevNode, editor.document); } -function getSelectionFromNode(node: HtmlNode, document: vscode.TextDocument): vscode.Selection { +function getSelectionFromNode(node: HtmlNode, document: vscode.TextDocument): vscode.Selection | undefined { if (node && node.open) { let selectionStart = (node.open.start).translate(0, 1); let selectionEnd = selectionStart.translate(0, node.name.length); return new vscode.Selection(selectionStart, selectionEnd); } + return undefined; } -function getNextAttribute(selectionStart: vscode.Position, selectionEnd: vscode.Position, document: vscode.TextDocument, node: HtmlNode): vscode.Selection { +function getNextAttribute(selectionStart: vscode.Position, selectionEnd: vscode.Position, document: vscode.TextDocument, node: HtmlNode): vscode.Selection | undefined { if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') { return; @@ -158,7 +163,7 @@ function getNextAttribute(selectionStart: vscode.Position, selectionEnd: vscode. } } -function getPrevAttribute(selectionStart: vscode.Position, selectionEnd: vscode.Position, document: vscode.TextDocument, node: HtmlNode): vscode.Selection { +function getPrevAttribute(selectionStart: vscode.Position, selectionEnd: vscode.Position, document: vscode.TextDocument, node: HtmlNode): vscode.Selection | undefined { if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') { return; diff --git a/extensions/emmet/src/selectItemStylesheet.ts b/extensions/emmet/src/selectItemStylesheet.ts index 09f0d6103a9..367e0320837 100644 --- a/extensions/emmet/src/selectItemStylesheet.ts +++ b/extensions/emmet/src/selectItemStylesheet.ts @@ -7,7 +7,7 @@ import * as vscode from 'vscode'; import { getDeepestNode, findNextWord, findPrevWord, getNode } from './util'; import { Node, CssNode, Rule, Property } from 'EmmetNode'; -export function nextItemStylesheet(startOffset: vscode.Position, endOffset: vscode.Position, editor: vscode.TextEditor, rootNode: Node): vscode.Selection { +export function nextItemStylesheet(startOffset: vscode.Position, endOffset: vscode.Position, editor: vscode.TextEditor, rootNode: Node): vscode.Selection | undefined { let currentNode = getNode(rootNode, endOffset, true); if (!currentNode) { currentNode = rootNode; @@ -50,7 +50,7 @@ export function nextItemStylesheet(startOffset: vscode.Position, endOffset: vsco } -export function prevItemStylesheet(startOffset: vscode.Position, endOffset: vscode.Position, editor: vscode.TextEditor, rootNode: CssNode): vscode.Selection { +export function prevItemStylesheet(startOffset: vscode.Position, endOffset: vscode.Position, editor: vscode.TextEditor, rootNode: CssNode): vscode.Selection | undefined { let currentNode = getNode(rootNode, startOffset); if (!currentNode) { currentNode = rootNode; @@ -88,7 +88,7 @@ export function prevItemStylesheet(startOffset: vscode.Position, endOffset: vsco } -function getSelectionFromNode(node: Node, document: vscode.TextDocument): vscode.Selection { +function getSelectionFromNode(node: Node, document: vscode.TextDocument): vscode.Selection | undefined { if (!node) { return; } @@ -98,7 +98,7 @@ function getSelectionFromNode(node: Node, document: vscode.TextDocument): vscode } -function getSelectionFromProperty(node: Node, document: vscode.TextDocument, selectionStart: vscode.Position, selectionEnd: vscode.Position, selectFullValue: boolean, direction: string): vscode.Selection { +function getSelectionFromProperty(node: Node, document: vscode.TextDocument, selectionStart: vscode.Position, selectionEnd: vscode.Position, selectFullValue: boolean, direction: string): vscode.Selection | undefined { if (!node || node.type !== 'property') { return; } diff --git a/extensions/emmet/src/splitJoinTag.ts b/extensions/emmet/src/splitJoinTag.ts index 382bd1841b0..67a403bce29 100644 --- a/extensions/emmet/src/splitJoinTag.ts +++ b/extensions/emmet/src/splitJoinTag.ts @@ -8,11 +8,11 @@ import { HtmlNode } from 'EmmetNode'; import { getNode, parseDocument, validate } from './util'; export function splitJoinTag() { - let editor = vscode.window.activeTextEditor; - if (!validate(false)) { + if (!validate(false) || !vscode.window.activeTextEditor) { return; } + const editor = vscode.window.activeTextEditor; let rootNode = parseDocument(editor.document); if (!rootNode) { return; @@ -20,23 +20,19 @@ export function splitJoinTag() { return editor.edit(editBuilder => { editor.selections.reverse().forEach(selection => { - let textEdit = getRangesToReplace(editor.document, selection, rootNode); - if (textEdit) { + let nodeToUpdate = getNode(rootNode, selection.start); + if (nodeToUpdate) { + let textEdit = getRangesToReplace(editor.document, nodeToUpdate); editBuilder.replace(textEdit.range, textEdit.newText); } }); }); } -function getRangesToReplace(document: vscode.TextDocument, selection: vscode.Selection, rootNode: HtmlNode): vscode.TextEdit { - let nodeToUpdate = getNode(rootNode, selection.start); +function getRangesToReplace(document: vscode.TextDocument, nodeToUpdate: HtmlNode): vscode.TextEdit { let rangeToReplace: vscode.Range; let textToReplaceWith: string; - if (!nodeToUpdate) { - return; - } - if (!nodeToUpdate.close) { // Split Tag let nodeText = document.getText(new vscode.Range(nodeToUpdate.start, nodeToUpdate.end)); diff --git a/extensions/emmet/src/toggleComment.ts b/extensions/emmet/src/toggleComment.ts index 1140473881d..e15dbd0c984 100644 --- a/extensions/emmet/src/toggleComment.ts +++ b/extensions/emmet/src/toggleComment.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { getNodesInBetween, getNode, parseDocument, sameNodes, isStyleSheet } from './util'; +import { getNodesInBetween, getNode, parseDocument, sameNodes, isStyleSheet, validate } from './util'; import { Node, Stylesheet, Rule, HtmlNode } from 'EmmetNode'; import parseStylesheet from '@emmetio/css-parser'; import { DocumentStreamReader } from './bufferStream'; @@ -14,14 +14,12 @@ const endCommentStylesheet = '*/'; const startCommentHTML = ''; -export function toggleComment(): Thenable { - let editor = vscode.window.activeTextEditor; - if (!editor) { - vscode.window.showInformationMessage('No editor is active'); +export function toggleComment(): Thenable | undefined { + if (!validate() || !vscode.window.activeTextEditor) { return; } - - let toggleCommentInternal; + const editor = vscode.window.activeTextEditor; + let toggleCommentInternal: (document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node) => vscode.TextEdit[]; if (isStyleSheet(editor.document.languageId)) { toggleCommentInternal = toggleCommentStylesheet; diff --git a/extensions/emmet/src/updateImageSize.ts b/extensions/emmet/src/updateImageSize.ts index ea57e099e8a..06fbd45a15b 100644 --- a/extensions/emmet/src/updateImageSize.ts +++ b/extensions/emmet/src/updateImageSize.ts @@ -10,7 +10,7 @@ import { TextEditor, Range, Position, window, TextEdit } from 'vscode'; import * as path from 'path'; import { getImageSize } from './imageSizeHelper'; -import { parseDocument, getNode, iterateCSSToken, getCssPropertyFromRule, isStyleSheet } from './util'; +import { parseDocument, getNode, iterateCSSToken, getCssPropertyFromRule, isStyleSheet, validate } from './util'; import { HtmlNode, CssToken, HtmlToken, Attribute, Property } from 'EmmetNode'; import { locateFile } from './locateFile'; import parseStylesheet from '@emmetio/css-parser'; @@ -20,11 +20,10 @@ import { DocumentStreamReader } from './bufferStream'; * Updates size of context image in given editor */ export function updateImageSize() { - let editor = window.activeTextEditor; - if (!editor) { - window.showInformationMessage('No editor is active.'); + if (!validate() || !window.activeTextEditor) { return; } + const editor = window.activeTextEditor; let allUpdatesPromise = editor.selections.reverse().map(selection => { let position = selection.isReversed ? selection.active : selection.anchor; @@ -49,7 +48,7 @@ export function updateImageSize() { /** * Updates image size of context tag of HTML model */ -function updateImageSizeHTML(editor: TextEditor, position: Position): Promise { +function updateImageSizeHTML(editor: TextEditor, position: Position): Promise { const src = getImageSrcHTML(getImageHTMLNode(editor, position)); if (!src) { @@ -70,7 +69,7 @@ function updateImageSizeHTML(editor: TextEditor, position: Position): Promise { - let getPropertyInsiderStyleTag = (editor) => { + const getPropertyInsiderStyleTag = (editor: TextEditor): Property | undefined => { const rootNode = parseDocument(editor.document); const currentNode = getNode(rootNode, position); if (currentNode && currentNode.name === 'style' @@ -79,7 +78,7 @@ function updateImageSizeStyleTag(editor: TextEditor, position: Position): Promis let buffer = new DocumentStreamReader(editor.document, currentNode.open.end, new Range(currentNode.open.end, currentNode.close.start)); let rootNode = parseStylesheet(buffer); const node = getNode(rootNode, position); - return (node && node.type === 'property') ? node : null; + return (node && node.type === 'property') ? node : undefined; } }; @@ -93,7 +92,7 @@ function updateImageSizeCSSFile(editor: TextEditor, position: Position): Promise /** * Updates image size of context rule of stylesheet model */ -function updateImageSizeCSS(editor: TextEditor, position: Position, fetchNode: (editor, position) => Property): Promise { +function updateImageSizeCSS(editor: TextEditor, position: Position, fetchNode: (editor: TextEditor, position: Position) => Property | undefined): Promise { const src = getImageSrcCSS(fetchNode(editor, position), position); @@ -141,10 +140,8 @@ function getImageCSSNode(editor: TextEditor, position: Position): Property { /** * Returns image source from given node - * @param {HtmlNode} node - * @return {string} */ -function getImageSrcHTML(node: HtmlNode): string { +function getImageSrcHTML(node: HtmlNode): string | undefined { const srcAttr = getAttribute(node, 'src'); if (!srcAttr) { return; @@ -155,11 +152,8 @@ function getImageSrcHTML(node: HtmlNode): string { /** * Returns image source from given `url()` token - * @param {Property} node - * @param {Position} - * @return {string} */ -function getImageSrcCSS(node: Property, position: Position): string { +function getImageSrcCSS(node: Property | undefined, position: Position): string | undefined { if (!node) { return; } @@ -213,10 +207,6 @@ function updateHTMLTag(editor: TextEditor, node: HtmlNode, width: number, height /** * Updates size of given CSS rule - * @param {TextEditor} editor - * @param {Property} srcProp - * @param {number} width - * @param {number} height */ function updateCSSNode(editor: TextEditor, srcProp: Property, width: number, height: number): TextEdit[] { const rule = srcProp.parent; @@ -252,36 +242,28 @@ function updateCSSNode(editor: TextEditor, srcProp: Property, width: number, hei /** * Returns attribute object with `attrName` name from given HTML node - * @param {Node} node - * @param {String} attrName - * @return {Object} */ -function getAttribute(node, attrName): Attribute { +function getAttribute(node: HtmlNode, attrName: string): Attribute { attrName = attrName.toLowerCase(); - return node && node.open.attributes.find(attr => attr.name.value.toLowerCase() === attrName); + return node && (node.open as any).attributes.find(attr => attr.name.value.toLowerCase() === attrName); } /** * Returns quote character, used for value of given attribute. May return empty * string if attribute wasn’t quoted - * @param {TextEditor} editor - * @param {Object} attr - * @return {String} + */ -function getAttributeQuote(editor, attr) { +function getAttributeQuote(editor: TextEditor, attr: any): string { const range = new Range(attr.value ? attr.value.end : attr.end, attr.end); return range.isEmpty ? '' : editor.document.getText(range); } /** * Finds 'url' token for given `pos` point in given CSS property `node` - * @param {Node} node - * @param {Position} pos - * @return {Token} */ -function findUrlToken(node, pos: Position) { - for (let i = 0, il = node.parsedValue.length, url; i < il; i++) { - iterateCSSToken(node.parsedValue[i], (token: CssToken) => { +function findUrlToken(node: Property, pos: Position): CssToken | undefined { + for (let i = 0, il = (node as any).parsedValue.length, url; i < il; i++) { + iterateCSSToken((node as any).parsedValue[i], (token: CssToken) => { if (token.type === 'url' && token.start.isBeforeOrEqual(pos) && token.end.isAfterOrEqual(pos)) { url = token; return false; @@ -296,11 +278,8 @@ function findUrlToken(node, pos: Position) { /** * Returns a string that is used to delimit properties in current node’s rule - * @param {TextEditor} editor - * @param {Property} node - * @return {String} */ -function getPropertyDelimitor(editor: TextEditor, node: Property) { +function getPropertyDelimitor(editor: TextEditor, node: Property): string { let anchor; if (anchor = (node.previousSibling || node.parent.contentStartToken)) { return editor.document.getText(new Range(anchor.end, node.start)); diff --git a/extensions/emmet/src/updateTag.ts b/extensions/emmet/src/updateTag.ts index 66a23e074b6..b62f167d70a 100644 --- a/extensions/emmet/src/updateTag.ts +++ b/extensions/emmet/src/updateTag.ts @@ -7,17 +7,17 @@ import * as vscode from 'vscode'; import { HtmlNode } from 'EmmetNode'; import { getNode, parseDocument, validate } from './util'; -export function updateTag(tagName: string): Thenable { - let editor = vscode.window.activeTextEditor; - if (!validate(false)) { +export function updateTag(tagName: string): Thenable | undefined { + if (!validate(false) || !vscode.window.activeTextEditor) { return; } + let editor = vscode.window.activeTextEditor; let rootNode = parseDocument(editor.document); if (!rootNode) { return; } - let rangesToUpdate = []; + let rangesToUpdate: vscode.Range[] = []; editor.selections.reverse().forEach(selection => { rangesToUpdate = rangesToUpdate.concat(getRangesToUpdate(editor, selection, rootNode)); }); diff --git a/extensions/emmet/src/util.ts b/extensions/emmet/src/util.ts index 3903c73190d..450aa136143 100644 --- a/extensions/emmet/src/util.ts +++ b/extensions/emmet/src/util.ts @@ -6,12 +6,12 @@ import * as vscode from 'vscode'; import parse from '@emmetio/html-matcher'; import parseStylesheet from '@emmetio/css-parser'; -import { Node, HtmlNode, CssToken, Property } from 'EmmetNode'; +import { Node, HtmlNode, CssToken, Property, Rule } from 'EmmetNode'; import { DocumentStreamReader } from './bufferStream'; import * as path from 'path'; -let _emmetHelper; -let _currentExtensionsPath = undefined; +let _emmetHelper: any; +let _currentExtensionsPath: string | undefined = undefined; export function getEmmetHelper() { if (!_emmetHelper) { @@ -27,15 +27,15 @@ export function resolveUpdateExtensionsPath() { } let extensionsPath = vscode.workspace.getConfiguration('emmet')['extensionsPath']; if (extensionsPath && !path.isAbsolute(extensionsPath)) { - extensionsPath = path.join(vscode.workspace.rootPath, extensionsPath); + extensionsPath = path.join(vscode.workspace.rootPath || '', extensionsPath); } if (_currentExtensionsPath !== extensionsPath) { _currentExtensionsPath = extensionsPath; - _emmetHelper.updateExtensionsPath(_currentExtensionsPath).then(null, err => vscode.window.showErrorMessage(err)); + _emmetHelper.updateExtensionsPath(_currentExtensionsPath).then(null, (err: string) => vscode.window.showErrorMessage(err)); } } -export const LANGUAGE_MODES: Object = { +export const LANGUAGE_MODES: any = { 'html': ['!', '.', '}', ':', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], 'jade': ['!', '.', '}', ':', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], 'slim': ['!', '.', '}', ':', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'], @@ -62,7 +62,7 @@ export const MAPPED_MODES: Object = { 'php': 'html' }; -export function isStyleSheet(syntax): boolean { +export function isStyleSheet(syntax: string): boolean { let stylesheetSyntaxes = ['css', 'scss', 'sass', 'less', 'stylus']; return (stylesheetSyntaxes.indexOf(syntax) > -1); } @@ -80,7 +80,7 @@ export function validate(allowStylesheet: boolean = true): boolean { } export function getMappingForIncludedLanguages(): any { - let finalMappedModes = {}; + const finalMappedModes = Object.create(null); let includeLanguagesConfig = vscode.workspace.getConfiguration('emmet')['includeLanguages']; let includeLanguages = Object.assign({}, MAPPED_MODES, includeLanguagesConfig ? includeLanguagesConfig : {}); Object.keys(includeLanguages).forEach(syntax => { @@ -94,13 +94,13 @@ export function getMappingForIncludedLanguages(): any { /** * Get the corresponding emmet mode for given vscode language mode * Eg: jsx for typescriptreact/javascriptreact or pug for jade -* If the language is not supported by emmet or has been exlcuded via `exlcudeLanguages` setting, +* If the language is not supported by emmet or has been exlcuded via `exlcudeLanguages` setting, * then nothing is returned -* -* @param language +* +* @param language * @param exlcudedLanguages Array of language ids that user has chosen to exlcude for emmet */ -export function getEmmetMode(language: string, excludedLanguages: string[]): string { +export function getEmmetMode(language: string, excludedLanguages: string[]): string | undefined { if (!language || excludedLanguages.indexOf(language) > -1) { return; } @@ -120,34 +120,29 @@ export function getEmmetMode(language: string, excludedLanguages: string[]): str /** * Parses the given document using emmet parsing modules - * @param document */ -export function parseDocument(document: vscode.TextDocument, showError: boolean = true): Node { +export function parseDocument(document: vscode.TextDocument, showError: boolean = true): Node | undefined { let parseContent = isStyleSheet(document.languageId) ? parseStylesheet : parse; - let rootNode: Node; try { - rootNode = parseContent(new DocumentStreamReader(document)); + return parseContent(new DocumentStreamReader(document)); } catch (e) { if (showError) { vscode.window.showErrorMessage('Emmet: Failed to parse the file'); } } - return rootNode; + return undefined; } /** * Returns node corresponding to given position in the given root node - * @param root - * @param position - * @param includeNodeBoundary */ -export function getNode(root: Node, position: vscode.Position, includeNodeBoundary: boolean = false) { +export function getNode(root: Node | undefined, position: vscode.Position, includeNodeBoundary: boolean = false) { if (!root) { return null; } let currentNode = root.firstChild; - let foundNode: Node = null; + let foundNode: Node | null = null; while (currentNode) { const nodeStart: vscode.Position = currentNode.start; @@ -170,14 +165,14 @@ export function getNode(root: Node, position: vscode.Position, includeNodeBounda * Returns inner range of an html node. * @param currentNode */ -export function getInnerRange(currentNode: HtmlNode): vscode.Range { +export function getInnerRange(currentNode: HtmlNode): vscode.Range | undefined { if (!currentNode.close) { - return; + return undefined; } return new vscode.Range(currentNode.open.end, currentNode.close.start); } -export function getDeepestNode(node: Node): Node { +export function getDeepestNode(node: Node | undefined): Node | undefined { if (!node || !node.children || node.children.length === 0 || !node.children.find(x => x.type !== 'comment')) { return node; } @@ -186,6 +181,7 @@ export function getDeepestNode(node: Node): Node { return getDeepestNode(node.children[i]); } } + return undefined; } export function findNextWord(propertyValue: string, pos: number): [number, number] { @@ -342,10 +338,8 @@ export function getEmmetConfiguration(syntax: string) { /** * Itereates by each child, as well as nested child’ children, in their order * and invokes `fn` for each. If `fn` function returns `false`, iteration stops - * @param {Token} token - * @param {Function} fn */ -export function iterateCSSToken(token: CssToken, fn) { +export function iterateCSSToken(token: CssToken, fn: (x: any) => any) { for (let i = 0, il = token.size; i < il; i++) { if (fn(token.item(i)) === false || iterateCSSToken(token.item(i), fn) === false) { return false; @@ -355,21 +349,16 @@ export function iterateCSSToken(token: CssToken, fn) { /** * Returns `name` CSS property from given `rule` - * @param {Node} rule - * @param {String} name - * @return {Property} */ -export function getCssPropertyFromRule(rule, name): Property { - return rule.children.find(node => node.type === 'property' && node.name === name); +export function getCssPropertyFromRule(rule: Rule, name: string): Property | undefined { + return rule.children.find(node => node.type === 'property' && node.name === name) as Property; } /** * Returns css property under caret in given editor or `null` if such node cannot * be found - * @param {TextEditor} editor - * @return {Property} */ -export function getCssPropertyFromDocument(editor: vscode.TextEditor, position: vscode.Position): Property { +export function getCssPropertyFromDocument(editor: vscode.TextEditor, position: vscode.Position): Property | undefined { const rootNode = parseDocument(editor.document); const node = getNode(rootNode, position);