Skip to content

Commit 125ba4a

Browse files
committed
enable custom/forked no-unused-expressions rule
1 parent ca8a717 commit 125ba4a

4 files changed

Lines changed: 153 additions & 3 deletions

File tree

.eslintrc.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,18 @@
2525
"no-sparse-arrays": "warn",
2626
"no-throw-literal": "warn",
2727
"no-unsafe-finally": "warn",
28-
"no-unused-expressions": "off",
2928
"no-unused-labels": "warn",
3029
"no-var": "warn",
3130
"jsdoc/no-types": "warn",
3231
"semi": "off",
3332
"@typescript-eslint/semi": "warn",
3433
"@typescript-eslint/class-name-casing": "warn",
34+
"code-no-unused-expressions": [
35+
"warn",
36+
{
37+
"allowTernary": true
38+
}
39+
],
3540
"code-translation-remind": "warn",
3641
"code-no-nls-in-standalone-editor": "warn",
3742
"code-no-standalone-editor": "warn",
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
// FORKED FROM https://github.com/eslint/eslint/blob/b23ad0d789a909baf8d7c41a35bc53df932eaf30/lib/rules/no-unused-expressions.js
7+
// and added support for `OptionalCallExpression`, see https://github.com/facebook/create-react-app/issues/8107 and https://github.com/eslint/eslint/issues/12642
8+
9+
/**
10+
* @fileoverview Flag expressions in statement position that do not side effect
11+
* @author Michael Ficarra
12+
*/
13+
14+
'use strict';
15+
16+
//------------------------------------------------------------------------------
17+
// Rule Definition
18+
//------------------------------------------------------------------------------
19+
20+
module.exports = {
21+
meta: {
22+
type: 'suggestion',
23+
24+
docs: {
25+
description: 'disallow unused expressions',
26+
category: 'Best Practices',
27+
recommended: false,
28+
url: 'https://eslint.org/docs/rules/no-unused-expressions'
29+
},
30+
31+
schema: [
32+
{
33+
type: 'object',
34+
properties: {
35+
allowShortCircuit: {
36+
type: 'boolean',
37+
default: false
38+
},
39+
allowTernary: {
40+
type: 'boolean',
41+
default: false
42+
},
43+
allowTaggedTemplates: {
44+
type: 'boolean',
45+
default: false
46+
}
47+
},
48+
additionalProperties: false
49+
}
50+
]
51+
},
52+
53+
create(context) {
54+
const config = context.options[0] || {},
55+
allowShortCircuit = config.allowShortCircuit || false,
56+
allowTernary = config.allowTernary || false,
57+
allowTaggedTemplates = config.allowTaggedTemplates || false;
58+
59+
// eslint-disable-next-line jsdoc/require-description
60+
/**
61+
* @param {ASTNode} node any node
62+
* @returns {boolean} whether the given node structurally represents a directive
63+
*/
64+
function looksLikeDirective(node) {
65+
return node.type === 'ExpressionStatement' &&
66+
node.expression.type === 'Literal' && typeof node.expression.value === 'string';
67+
}
68+
69+
// eslint-disable-next-line jsdoc/require-description
70+
/**
71+
* @param {Function} predicate ([a] -> Boolean) the function used to make the determination
72+
* @param {a[]} list the input list
73+
* @returns {a[]} the leading sequence of members in the given list that pass the given predicate
74+
*/
75+
function takeWhile(predicate, list) {
76+
for (let i = 0; i < list.length; ++i) {
77+
if (!predicate(list[i])) {
78+
return list.slice(0, i);
79+
}
80+
}
81+
return list.slice();
82+
}
83+
84+
// eslint-disable-next-line jsdoc/require-description
85+
/**
86+
* @param {ASTNode} node a Program or BlockStatement node
87+
* @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body
88+
*/
89+
function directives(node) {
90+
return takeWhile(looksLikeDirective, node.body);
91+
}
92+
93+
// eslint-disable-next-line jsdoc/require-description
94+
/**
95+
* @param {ASTNode} node any node
96+
* @param {ASTNode[]} ancestors the given node's ancestors
97+
* @returns {boolean} whether the given node is considered a directive in its current position
98+
*/
99+
function isDirective(node, ancestors) {
100+
const parent = ancestors[ancestors.length - 1],
101+
grandparent = ancestors[ancestors.length - 2];
102+
103+
return (parent.type === 'Program' || parent.type === 'BlockStatement' &&
104+
(/Function/u.test(grandparent.type))) &&
105+
directives(parent).indexOf(node) >= 0;
106+
}
107+
108+
/**
109+
* Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags.
110+
* @param {ASTNode} node any node
111+
* @returns {boolean} whether the given node is a valid expression
112+
*/
113+
function isValidExpression(node) {
114+
if (allowTernary) {
115+
116+
// Recursive check for ternary and logical expressions
117+
if (node.type === 'ConditionalExpression') {
118+
return isValidExpression(node.consequent) && isValidExpression(node.alternate);
119+
}
120+
}
121+
122+
if (allowShortCircuit) {
123+
if (node.type === 'LogicalExpression') {
124+
return isValidExpression(node.right);
125+
}
126+
}
127+
128+
if (allowTaggedTemplates && node.type === 'TaggedTemplateExpression') {
129+
return true;
130+
}
131+
132+
return /^(?:Assignment|OptionalCall|Call|New|Update|Yield|Await)Expression$/u.test(node.type) ||
133+
(node.type === 'UnaryExpression' && ['delete', 'void'].indexOf(node.operator) >= 0);
134+
}
135+
136+
return {
137+
ExpressionStatement(node) {
138+
if (!isValidExpression(node.expression) && !isDirective(node, context.getAncestors())) {
139+
context.report({ node, message: 'Expected an assignment or function call and instead saw an expression.' });
140+
}
141+
}
142+
};
143+
144+
}
145+
};

src/vs/platform/log/common/fileLogService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ export class FileLoggerService extends Disposable implements ILoggerService {
173173
getLogger(resource: URI): ILogger {
174174
let logger = this.loggers.get(resource.toString());
175175
if (!logger) {
176-
logger = new BufferLogService, this.logService.getLevel();
176+
logger = new BufferLogService(this.logService.getLevel());
177177
this.loggers.set(resource.toString(), logger);
178178
whenProviderRegistered(resource, this.fileService).then(() => (<BufferLogService>logger).logger = this.instantiationService.createInstance(FileLogService, basename(resource), resource, this.logService.getLevel()));
179179
}

src/vs/workbench/contrib/files/browser/editors/textFileSaveErrorHandler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ export class TextFileSaveErrorHandler extends Disposable implements ISaveErrorHa
180180
// Show message and keep function to hide in case the file gets saved/reverted
181181
const actions: INotificationActions = { primary: primaryActions, secondary: secondaryActions };
182182
const handle = this.notificationService.notify({ severity: Severity.Error, message, actions });
183-
Event.once(handle.onDidClose)(() => { dispose(primaryActions), dispose(secondaryActions); });
183+
Event.once(handle.onDidClose)(() => { dispose(primaryActions); dispose(secondaryActions); });
184184
this.messages.set(model.resource, handle);
185185
}
186186

0 commit comments

Comments
 (0)