diff --git a/.eslintrc.json b/.eslintrc.json index 350c4751033..1a85f595bb2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -25,13 +25,18 @@ "no-sparse-arrays": "warn", "no-throw-literal": "warn", "no-unsafe-finally": "warn", - "no-unused-expressions": "off", "no-unused-labels": "warn", "no-var": "warn", "jsdoc/no-types": "warn", "semi": "off", "@typescript-eslint/semi": "warn", "@typescript-eslint/class-name-casing": "warn", + "code-no-unused-expressions": [ + "warn", + { + "allowTernary": true + } + ], "code-translation-remind": "warn", "code-no-nls-in-standalone-editor": "warn", "code-no-standalone-editor": "warn", diff --git a/build/lib/eslint/code-no-unused-expressions.js b/build/lib/eslint/code-no-unused-expressions.js new file mode 100644 index 00000000000..c7b17551311 --- /dev/null +++ b/build/lib/eslint/code-no-unused-expressions.js @@ -0,0 +1,145 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// FORKED FROM https://github.com/eslint/eslint/blob/b23ad0d789a909baf8d7c41a35bc53df932eaf30/lib/rules/no-unused-expressions.js +// and added support for `OptionalCallExpression`, see https://github.com/facebook/create-react-app/issues/8107 and https://github.com/eslint/eslint/issues/12642 + +/** + * @fileoverview Flag expressions in statement position that do not side effect + * @author Michael Ficarra + */ + +'use strict'; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'suggestion', + + docs: { + description: 'disallow unused expressions', + category: 'Best Practices', + recommended: false, + url: 'https://eslint.org/docs/rules/no-unused-expressions' + }, + + schema: [ + { + type: 'object', + properties: { + allowShortCircuit: { + type: 'boolean', + default: false + }, + allowTernary: { + type: 'boolean', + default: false + }, + allowTaggedTemplates: { + type: 'boolean', + default: false + } + }, + additionalProperties: false + } + ] + }, + + create(context) { + const config = context.options[0] || {}, + allowShortCircuit = config.allowShortCircuit || false, + allowTernary = config.allowTernary || false, + allowTaggedTemplates = config.allowTaggedTemplates || false; + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {ASTNode} node any node + * @returns {boolean} whether the given node structurally represents a directive + */ + function looksLikeDirective(node) { + return node.type === 'ExpressionStatement' && + node.expression.type === 'Literal' && typeof node.expression.value === 'string'; + } + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {Function} predicate ([a] -> Boolean) the function used to make the determination + * @param {a[]} list the input list + * @returns {a[]} the leading sequence of members in the given list that pass the given predicate + */ + function takeWhile(predicate, list) { + for (let i = 0; i < list.length; ++i) { + if (!predicate(list[i])) { + return list.slice(0, i); + } + } + return list.slice(); + } + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {ASTNode} node a Program or BlockStatement node + * @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body + */ + function directives(node) { + return takeWhile(looksLikeDirective, node.body); + } + + // eslint-disable-next-line jsdoc/require-description + /** + * @param {ASTNode} node any node + * @param {ASTNode[]} ancestors the given node's ancestors + * @returns {boolean} whether the given node is considered a directive in its current position + */ + function isDirective(node, ancestors) { + const parent = ancestors[ancestors.length - 1], + grandparent = ancestors[ancestors.length - 2]; + + return (parent.type === 'Program' || parent.type === 'BlockStatement' && + (/Function/u.test(grandparent.type))) && + directives(parent).indexOf(node) >= 0; + } + + /** + * Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags. + * @param {ASTNode} node any node + * @returns {boolean} whether the given node is a valid expression + */ + function isValidExpression(node) { + if (allowTernary) { + + // Recursive check for ternary and logical expressions + if (node.type === 'ConditionalExpression') { + return isValidExpression(node.consequent) && isValidExpression(node.alternate); + } + } + + if (allowShortCircuit) { + if (node.type === 'LogicalExpression') { + return isValidExpression(node.right); + } + } + + if (allowTaggedTemplates && node.type === 'TaggedTemplateExpression') { + return true; + } + + return /^(?:Assignment|OptionalCall|Call|New|Update|Yield|Await)Expression$/u.test(node.type) || + (node.type === 'UnaryExpression' && ['delete', 'void'].indexOf(node.operator) >= 0); + } + + return { + ExpressionStatement(node) { + if (!isValidExpression(node.expression) && !isDirective(node, context.getAncestors())) { + context.report({ node, message: 'Expected an assignment or function call and instead saw an expression.' }); + } + } + }; + + } +}; diff --git a/src/vs/platform/log/common/fileLogService.ts b/src/vs/platform/log/common/fileLogService.ts index 32493332bc1..20716554d04 100644 --- a/src/vs/platform/log/common/fileLogService.ts +++ b/src/vs/platform/log/common/fileLogService.ts @@ -173,7 +173,7 @@ export class FileLoggerService extends Disposable implements ILoggerService { getLogger(resource: URI): ILogger { let logger = this.loggers.get(resource.toString()); if (!logger) { - logger = new BufferLogService, this.logService.getLevel(); + logger = new BufferLogService(this.logService.getLevel()); this.loggers.set(resource.toString(), logger); whenProviderRegistered(resource, this.fileService).then(() => (logger).logger = this.instantiationService.createInstance(FileLogService, basename(resource), resource, this.logService.getLevel())); } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts index 7de229cf583..cebc296f377 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts @@ -180,7 +180,7 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa // Show message and keep function to hide in case the file gets saved/reverted const actions: INotificationActions = { primary: primaryActions, secondary: secondaryActions }; const handle = this.notificationService.notify({ severity: Severity.Error, message, actions }); - Event.once(handle.onDidClose)(() => { dispose(primaryActions), dispose(secondaryActions); }); + Event.once(handle.onDidClose)(() => { dispose(primaryActions); dispose(secondaryActions); }); this.messages.set(model.resource, handle); }